From a21f5f259ce9a889737c1803d17a6eb77f4214a3 Mon Sep 17 00:00:00 2001 From: Jonathan <34005131+JPPhoto@users.noreply.github.com> Date: Tue, 12 Sep 2023 16:31:35 -0500 Subject: [PATCH 01/31] Added crop option to ImagePasteInvocation (#4507) * Added crop option to ImagePasteInvocation ImagePasteInvocation extended the image with transparency when pasting outside of the base image's bounds. This introduces a new option to crop the resulting image back to the original base image. * Updated version for ImagePasteInvocation as 3.1.1 was released. --- invokeai/app/invocations/image.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/invokeai/app/invocations/image.py b/invokeai/app/invocations/image.py index dd7f927b6f..fda7561679 100644 --- a/invokeai/app/invocations/image.py +++ b/invokeai/app/invocations/image.py @@ -98,7 +98,7 @@ class ImageCropInvocation(BaseInvocation): ) -@invocation("img_paste", title="Paste Image", tags=["image", "paste"], category="image", version="1.0.0") +@invocation("img_paste", title="Paste Image", tags=["image", "paste"], category="image", version="1.0.1") class ImagePasteInvocation(BaseInvocation): """Pastes an image into another image.""" @@ -110,6 +110,7 @@ class ImagePasteInvocation(BaseInvocation): ) x: int = InputField(default=0, description="The left x coordinate at which to paste the image") y: int = InputField(default=0, description="The top y coordinate at which to paste the image") + crop: bool = InputField(default=False, description="Crop to base image dimensions") def invoke(self, context: InvocationContext) -> ImageOutput: base_image = context.services.images.get_pil_image(self.base_image.image_name) @@ -129,6 +130,10 @@ class ImagePasteInvocation(BaseInvocation): new_image.paste(base_image, (abs(min_x), abs(min_y))) new_image.paste(image, (max(0, self.x), max(0, self.y)), mask=mask) + if self.crop: + base_w, base_h = base_image.size + new_image = new_image.crop((abs(min_x), abs(min_y), abs(min_x) + base_w, abs(min_y) + base_h)) + image_dto = context.services.images.create( image=new_image, image_origin=ResourceOrigin.INTERNAL, From c68b55f8e6a73517954d679317d7b33f0f0d0100 Mon Sep 17 00:00:00 2001 From: Millun Atluri Date: Tue, 12 Sep 2023 18:07:54 +1000 Subject: [PATCH 02/31] Update latest tag format --- installer/create_installer.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer/create_installer.sh b/installer/create_installer.sh index 134c7313ca..4e0771fecc 100755 --- a/installer/create_installer.sh +++ b/installer/create_installer.sh @@ -14,7 +14,7 @@ fi VERSION=$(cd ..; python -c "from invokeai.version import __version__ as version; print(version)") PATCH="" VERSION="v${VERSION}${PATCH}" -LATEST_TAG="v3.0-latest" +LATEST_TAG="v3-latest" echo Building installer for version $VERSION echo "Be certain that you're in the 'installer' directory before continuing." From e9f5814c6d909e37f72228faed2f928a36a6f78d Mon Sep 17 00:00:00 2001 From: Millun Atluri Date: Tue, 12 Sep 2023 18:08:34 +1000 Subject: [PATCH 03/31] Update invokeai version to 3.1.1 --- invokeai/version/invokeai_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/version/invokeai_version.py b/invokeai/version/invokeai_version.py index 60bb910ed6..d539d50ceb 100644 --- a/invokeai/version/invokeai_version.py +++ b/invokeai/version/invokeai_version.py @@ -1 +1 @@ -__version__ = "3.1.1rc1" +__version__ = "3.1.1" From 005087a65226f0c0b240d6af520482c59317a8e2 Mon Sep 17 00:00:00 2001 From: dunkeroni Date: Tue, 5 Sep 2023 21:27:08 -0400 Subject: [PATCH 04/31] Added float math --- invokeai/app/invocations/math.py | 91 +++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 2 deletions(-) diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index 0bc8b7b950..e3da88e4bf 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -1,21 +1,32 @@ # Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654) import numpy as np +from typing import Literal -from invokeai.app.invocations.primitives import IntegerOutput +from invokeai.app.invocations.primitives import IntegerOutput, FloatOutput from .baseinvocation import BaseInvocation, FieldDescriptions, InputField, InvocationContext, invocation @invocation("add", title="Add Integers", tags=["math", "add"], category="math", version="1.0.0") class AddInvocation(BaseInvocation): - """Adds two numbers""" + """Adds two integer numbers""" a: int = InputField(default=0, description=FieldDescriptions.num_1) b: int = InputField(default=0, description=FieldDescriptions.num_2) def invoke(self, context: InvocationContext) -> IntegerOutput: return IntegerOutput(value=self.a + self.b) + +@invocation("add_float", title="Add Floats", tags=["math", "add", "float"], category="math", version="1.0.0") +class AddFloatInvocation(BaseInvocation): + """Adds two float numbers""" + + a: float = InputField(default=0, description=FieldDescriptions.num_1) + b: float = InputField(default=0, description=FieldDescriptions.num_2) + + def invoke(self, context: InvocationContext) -> FloatOutput: + return FloatOutput(value=self.a + self.b) @invocation("sub", title="Subtract Integers", tags=["math", "subtract"], category="math", version="1.0.0") @@ -27,6 +38,16 @@ class SubtractInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> IntegerOutput: return IntegerOutput(value=self.a - self.b) + +@invocation("sub_float", title="Subtract Floats", tags=["math", "subtract", "float"], category="math", version="1.0.0") +class SubtractFloatInvocation(BaseInvocation): + """Subtracts two float numbers""" + + a: float = InputField(default=0, description=FieldDescriptions.num_1) + b: float = InputField(default=0, description=FieldDescriptions.num_2) + + def invoke(self, context: InvocationContext) -> FloatOutput: + return FloatOutput(value=self.a - self.b) @invocation("mul", title="Multiply Integers", tags=["math", "multiply"], category="math", version="1.0.0") @@ -39,6 +60,16 @@ class MultiplyInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> IntegerOutput: return IntegerOutput(value=self.a * self.b) +@invocation("mul_float", title="Multiply Floats", tags=["math", "multiply", "float"], category="math", version="1.0.0") +class MultiplyFloatInvocation(BaseInvocation): + """Multiplies two float numbers""" + + a: float = InputField(default=0, description=FieldDescriptions.num_1) + b: float = InputField(default=0, description=FieldDescriptions.num_2) + + def invoke(self, context: InvocationContext) -> FloatOutput: + return FloatOutput(value=self.a * self.b) + @invocation("div", title="Divide Integers", tags=["math", "divide"], category="math", version="1.0.0") class DivideInvocation(BaseInvocation): @@ -50,6 +81,16 @@ class DivideInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> IntegerOutput: return IntegerOutput(value=int(self.a / self.b)) +@invocation("div_float", title="Divide Floats", tags=["math", "divide", "float"], category="math", version="1.0.0") +class DivideFloatInvocation(BaseInvocation): + """Divides two float numbers""" + + a: float = InputField(default=0, description=FieldDescriptions.num_1) + b: float = InputField(default=0, description=FieldDescriptions.num_2) + + def invoke(self, context: InvocationContext) -> FloatOutput: + return FloatOutput(value=self.a / self.b) + @invocation("rand_int", title="Random Integer", tags=["math", "random"], category="math", version="1.0.0") class RandomIntInvocation(BaseInvocation): @@ -60,3 +101,49 @@ class RandomIntInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> IntegerOutput: return IntegerOutput(value=np.random.randint(self.low, self.high)) + + +@invocation("float_to_int", title="Float to Integer", tags=["math", "convert"], category="math", version="1.0.0") +class FloatToIntInvocation(BaseInvocation): + """Converts a float to an integer.""" + + value: float = InputField(default=0, description="The float value") + method: Literal["Floor", "Ceiling"] = InputField(default="round", description="The method to use for conversion") + + def invoke(self, context: InvocationContext) -> IntegerOutput: + if self.method == "Floor": + return IntegerOutput(value=np.floor(self.value)) + else: + return IntegerOutput(value=np.ceil(self.value)) + + +@invocation("round", title="Round Float", tags=["math", "round"], category="math", version="1.0.0") +class RoundInvocation(BaseInvocation): + """Rounds a float to a specified number of decimal places.""" + + value: float = InputField(default=0, description="The float value") + decimals: int = InputField(default=0, description="The number of decimal places") + + def invoke(self, context: InvocationContext) -> FloatOutput: + return FloatOutput(value=round(self.value, self.decimals)) + + +@invocation("abs", title="Absolute Value", tags=["math", "abs"], category="math", version="1.0.0") +class AbsoluteValueInvocation(BaseInvocation): + """Returns the absolute value of a number.""" + + value: float = InputField(default=0, description="The float value") + + def invoke(self, context: InvocationContext) -> FloatOutput: + return FloatOutput(value=abs(self.value)) + + +@invocation("mod", title="Modulus", tags=["math", "modulus"], category="math", version="1.0.0") +class ModulusInvocation(BaseInvocation): + """Returns the modulus of two numbers.""" + + a: int = InputField(default=0, description=FieldDescriptions.num_1) + b: int = InputField(default=0, description=FieldDescriptions.num_2) + + def invoke(self, context: InvocationContext) -> IntegerOutput: + return IntegerOutput(value=self.a % self.b) \ No newline at end of file From 99ee47b79b2a651a640576985e7d5da7e1b7b5d2 Mon Sep 17 00:00:00 2001 From: dunkeroni Date: Wed, 6 Sep 2023 09:39:47 -0400 Subject: [PATCH 05/31] Added square root function --- invokeai/app/invocations/math.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index e3da88e4bf..fa10a20f8b 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -146,4 +146,14 @@ class ModulusInvocation(BaseInvocation): b: int = InputField(default=0, description=FieldDescriptions.num_2) def invoke(self, context: InvocationContext) -> IntegerOutput: - return IntegerOutput(value=self.a % self.b) \ No newline at end of file + return IntegerOutput(value=self.a % self.b) + + +@invocation("sqrt", title="Square Root", tags=["math", "sqrt"], category="math", version="1.0.0") +class SquareRootInvocation(BaseInvocation): + """Returns the square root of a number.""" + + value: float = InputField(default=0, ge=0, description="The float value") + + def invoke(self, context: InvocationContext) -> FloatOutput: + return FloatOutput(value=np.sqrt(self.value)) \ No newline at end of file From e542608534ddc2641c3711e1dfced51308a3602e Mon Sep 17 00:00:00 2001 From: dunkeroni Date: Wed, 6 Sep 2023 22:06:19 -0400 Subject: [PATCH 06/31] changed float_to_int to generalized round_multiple node --- invokeai/app/invocations/math.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index fa10a20f8b..474c75d3d2 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -103,21 +103,24 @@ class RandomIntInvocation(BaseInvocation): return IntegerOutput(value=np.random.randint(self.low, self.high)) -@invocation("float_to_int", title="Float to Integer", tags=["math", "convert"], category="math", version="1.0.0") -class FloatToIntInvocation(BaseInvocation): - """Converts a float to an integer.""" +@invocation("round_to_multiple", title="Round to Multiple", tags=["math", "round", "integer", "convert"], category="math", version="1.0.0") +class RoundToMultipleInvocation(BaseInvocation): + """Rounds a number to the nearest integer multiple.""" - value: float = InputField(default=0, description="The float value") - method: Literal["Floor", "Ceiling"] = InputField(default="round", description="The method to use for conversion") + value: float = InputField(default=0, description="The value to round") + multiple: int = InputField(default=1, ge=1, description="The multiple to round to") + method: Literal["Nearest", "Floor", "Ceiling"] = InputField(default="round", description="The method to use for rounding") def invoke(self, context: InvocationContext) -> IntegerOutput: - if self.method == "Floor": - return IntegerOutput(value=np.floor(self.value)) - else: - return IntegerOutput(value=np.ceil(self.value)) + if self.method == "Nearest": + return IntegerOutput(value=round(self.value / self.multiple) * self.multiple) + elif self.method == "Floor": + return IntegerOutput(value=np.floor(self.value / self.multiple) * self.multiple) + else: #self.method == "Ceiling" + return IntegerOutput(value=np.ceil(self.value / self.multiple) * self.multiple) -@invocation("round", title="Round Float", tags=["math", "round"], category="math", version="1.0.0") +@invocation("round_float", title="Round Float", tags=["math", "round"], category="math", version="1.0.0") class RoundInvocation(BaseInvocation): """Rounds a float to a specified number of decimal places.""" From dbde08f3d42d40a8d65defc1c34e6c3ca211e761 Mon Sep 17 00:00:00 2001 From: dunkeroni Date: Wed, 6 Sep 2023 22:43:14 -0400 Subject: [PATCH 07/31] Updated default value on round to multiple --- invokeai/app/invocations/math.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index 474c75d3d2..61c1dd3317 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -86,7 +86,7 @@ class DivideFloatInvocation(BaseInvocation): """Divides two float numbers""" a: float = InputField(default=0, description=FieldDescriptions.num_1) - b: float = InputField(default=0, description=FieldDescriptions.num_2) + b: float = InputField(default=1, description=FieldDescriptions.num_2) def invoke(self, context: InvocationContext) -> FloatOutput: return FloatOutput(value=self.a / self.b) @@ -109,7 +109,7 @@ class RoundToMultipleInvocation(BaseInvocation): value: float = InputField(default=0, description="The value to round") multiple: int = InputField(default=1, ge=1, description="The multiple to round to") - method: Literal["Nearest", "Floor", "Ceiling"] = InputField(default="round", description="The method to use for rounding") + method: Literal["Nearest", "Floor", "Ceiling"] = InputField(default="Nearest", description="The method to use for rounding") def invoke(self, context: InvocationContext) -> IntegerOutput: if self.method == "Nearest": From 79ca18127693d4932d2b48b3de1729bb2ba9fc5d Mon Sep 17 00:00:00 2001 From: dunkeroni Date: Wed, 6 Sep 2023 22:51:49 -0400 Subject: [PATCH 08/31] documentation update --- docs/nodes/defaultNodes.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/nodes/defaultNodes.md b/docs/nodes/defaultNodes.md index 8bc304216d..9b91db9348 100644 --- a/docs/nodes/defaultNodes.md +++ b/docs/nodes/defaultNodes.md @@ -5,6 +5,8 @@ The table below contains a list of the default nodes shipped with InvokeAI and t | Node | Function | |: ---------------------------------- | :--------------------------------------------------------------------------------------| |Add Integers | Adds two numbers| +|Add Floats | Adds two floating point numbers| +|Absolute Value | Returns the positive form of a float| |Boolean Primitive Collection | A collection of boolean primitive values| |Boolean Primitive | A boolean primitive value| |Canny Processor | Canny edge detection for ControlNet| @@ -20,6 +22,7 @@ The table below contains a list of the default nodes shipped with InvokeAI and t |OpenCV Inpaint | Simple inpaint using opencv.| |Denoise Latents | Denoises noisy latents to decodable images| |Divide Integers | Divides two numbers| +|Divide Floats | Divides two floating point numbers| |Dynamic Prompt | Parses a prompt using adieyal/dynamicprompts' random or combinatorial generator| |Upscale (RealESRGAN) | Upscales an image using RealESRGAN.| |Float Primitive Collection | A collection of float primitive values| @@ -42,6 +45,8 @@ The table below contains a list of the default nodes shipped with InvokeAI and t |Paste Image | Pastes an image into another image.| |ImageProcessor | Base class for invocations that preprocess images for ControlNet| |Resize Image | Resizes an image to specific dimensions| +|Round Float | Rounds a float to a specified number of decimal places| +|Round To Multiple | Converts a float to an integer| |Scale Image | Scales an image by a factor| |Image to Latents | Encodes an image into latents.| |Add Invisible Watermark | Add an invisible watermark to an image| @@ -65,7 +70,9 @@ The table below contains a list of the default nodes shipped with InvokeAI and t |Mediapipe Face Processor | Applies mediapipe face processing to image| |Midas (Depth) Processor | Applies Midas depth processing to image| |MLSD Processor | Applies MLSD processing to image| +|Modulus | Returns the remainder after integer division| |Multiply Integers | Multiplies two numbers| +|Multiply Floats | Multiplies two floating point numbers| |Noise | Generates latent noise.| |Normal BAE Processor | Applies NormalBae processing to image| |ONNX Latents to Image | Generates an image from latents.| @@ -92,6 +99,8 @@ The table below contains a list of the default nodes shipped with InvokeAI and t |String Primitive Collection | A collection of string primitive values| |String Primitive | A string primitive value| |Subtract Integers | Subtracts two numbers| +|Subtract Floats | Subtracts two floating point numbers| +|Square Root | Returns the square root of a float| |Tile Resample Processor | Tile resampler processor| |VAE Loader | Loads a VAE model, outputting a VaeLoaderOutput| |Zoe (Depth) Processor | Applies Zoe depth processing to image| \ No newline at end of file From 486b8506aab8cb828c69ea3149f6d872d3dd55fa Mon Sep 17 00:00:00 2001 From: dunkeroni Date: Sun, 10 Sep 2023 00:27:15 -0400 Subject: [PATCH 09/31] Combined nodes to Float and Int general maths --- invokeai/app/invocations/math.py | 197 +++++++++++++++++++++---------- 1 file changed, 135 insertions(+), 62 deletions(-) diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index 61c1dd3317..df5b5ec0d3 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -4,6 +4,7 @@ import numpy as np from typing import Literal from invokeai.app.invocations.primitives import IntegerOutput, FloatOutput +from pydantic import validator from .baseinvocation import BaseInvocation, FieldDescriptions, InputField, InvocationContext, invocation @@ -17,16 +18,6 @@ class AddInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> IntegerOutput: return IntegerOutput(value=self.a + self.b) - -@invocation("add_float", title="Add Floats", tags=["math", "add", "float"], category="math", version="1.0.0") -class AddFloatInvocation(BaseInvocation): - """Adds two float numbers""" - - a: float = InputField(default=0, description=FieldDescriptions.num_1) - b: float = InputField(default=0, description=FieldDescriptions.num_2) - - def invoke(self, context: InvocationContext) -> FloatOutput: - return FloatOutput(value=self.a + self.b) @invocation("sub", title="Subtract Integers", tags=["math", "subtract"], category="math", version="1.0.0") @@ -38,16 +29,6 @@ class SubtractInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> IntegerOutput: return IntegerOutput(value=self.a - self.b) - -@invocation("sub_float", title="Subtract Floats", tags=["math", "subtract", "float"], category="math", version="1.0.0") -class SubtractFloatInvocation(BaseInvocation): - """Subtracts two float numbers""" - - a: float = InputField(default=0, description=FieldDescriptions.num_1) - b: float = InputField(default=0, description=FieldDescriptions.num_2) - - def invoke(self, context: InvocationContext) -> FloatOutput: - return FloatOutput(value=self.a - self.b) @invocation("mul", title="Multiply Integers", tags=["math", "multiply"], category="math", version="1.0.0") @@ -60,16 +41,6 @@ class MultiplyInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> IntegerOutput: return IntegerOutput(value=self.a * self.b) -@invocation("mul_float", title="Multiply Floats", tags=["math", "multiply", "float"], category="math", version="1.0.0") -class MultiplyFloatInvocation(BaseInvocation): - """Multiplies two float numbers""" - - a: float = InputField(default=0, description=FieldDescriptions.num_1) - b: float = InputField(default=0, description=FieldDescriptions.num_2) - - def invoke(self, context: InvocationContext) -> FloatOutput: - return FloatOutput(value=self.a * self.b) - @invocation("div", title="Divide Integers", tags=["math", "divide"], category="math", version="1.0.0") class DivideInvocation(BaseInvocation): @@ -81,16 +52,6 @@ class DivideInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> IntegerOutput: return IntegerOutput(value=int(self.a / self.b)) -@invocation("div_float", title="Divide Floats", tags=["math", "divide", "float"], category="math", version="1.0.0") -class DivideFloatInvocation(BaseInvocation): - """Divides two float numbers""" - - a: float = InputField(default=0, description=FieldDescriptions.num_1) - b: float = InputField(default=1, description=FieldDescriptions.num_2) - - def invoke(self, context: InvocationContext) -> FloatOutput: - return FloatOutput(value=self.a / self.b) - @invocation("rand_int", title="Random Integer", tags=["math", "random"], category="math", version="1.0.0") class RandomIntInvocation(BaseInvocation): @@ -103,21 +64,23 @@ class RandomIntInvocation(BaseInvocation): return IntegerOutput(value=np.random.randint(self.low, self.high)) -@invocation("round_to_multiple", title="Round to Multiple", tags=["math", "round", "integer", "convert"], category="math", version="1.0.0") +@invocation("float_to_int", title="Float To Integer", tags=["math", "round", "integer", "float", "convert"], category="math", version="1.0.0") class RoundToMultipleInvocation(BaseInvocation): - """Rounds a number to the nearest integer multiple.""" + """Rounds a float number to (a multiple of) an integer.""" value: float = InputField(default=0, description="The value to round") - multiple: int = InputField(default=1, ge=1, description="The multiple to round to") - method: Literal["Nearest", "Floor", "Ceiling"] = InputField(default="Nearest", description="The method to use for rounding") + multiple: int = InputField(default=1, ge=1,title="Multiple of", description="The multiple to round to") + method: Literal["Nearest", "Floor", "Ceiling", "Truncate"] = InputField(default="Nearest", description="The method to use for rounding") def invoke(self, context: InvocationContext) -> IntegerOutput: if self.method == "Nearest": return IntegerOutput(value=round(self.value / self.multiple) * self.multiple) elif self.method == "Floor": return IntegerOutput(value=np.floor(self.value / self.multiple) * self.multiple) - else: #self.method == "Ceiling" + elif self.method == "Ceiling": return IntegerOutput(value=np.ceil(self.value / self.multiple) * self.multiple) + else: #self.method == "Truncate" + return IntegerOutput(value=int(self.value / self.multiple) * self.multiple) @invocation("round_float", title="Round Float", tags=["math", "round"], category="math", version="1.0.0") @@ -131,32 +94,142 @@ class RoundInvocation(BaseInvocation): return FloatOutput(value=round(self.value, self.decimals)) -@invocation("abs", title="Absolute Value", tags=["math", "abs"], category="math", version="1.0.0") -class AbsoluteValueInvocation(BaseInvocation): - """Returns the absolute value of a number.""" - - value: float = InputField(default=0, description="The float value") - - def invoke(self, context: InvocationContext) -> FloatOutput: - return FloatOutput(value=abs(self.value)) +INTEGER_OPERATIONS = Literal[ + "Add A+B", + "Subtract A-B", + "Multiply A*B", + "Divide A/B", + "Exponentiate A^B", + "Modulus A%B", + "Absolute Value of A", + "Minimum(A,B)", + "Maximum(A,B)" +] -@invocation("mod", title="Modulus", tags=["math", "modulus"], category="math", version="1.0.0") -class ModulusInvocation(BaseInvocation): - """Returns the modulus of two numbers.""" +@invocation( + "integer_math", + title="Integer Math", + tags=[ + "math", + "integer", + "add", + "subtract", + "multiply", + "divide", + "modulus", + "power", + "absolute value", + "min", + "max" + ], + category="math", + version="1.0.0" +) +class IntegerMathInvocation(BaseInvocation): + """Performs integer math.""" + operation: INTEGER_OPERATIONS = InputField(default="Add A+B", description="The operation to perform") a: int = InputField(default=0, description=FieldDescriptions.num_1) b: int = InputField(default=0, description=FieldDescriptions.num_2) + @validator("operation") + def no_divide_by_zero(cls, v, values): + if v == "Divide A/B" and values["b"] == 0: + raise ValueError("Cannot divide by zero") + elif v == "Modulus A%B" and values["b"] == 0: + raise ValueError("Cannot divide by zero") + elif v == "Exponentiate A^B" and values["b"] < 0: + raise ValueError("Result of exponentiation is not an integer") + return v + def invoke(self, context: InvocationContext) -> IntegerOutput: - return IntegerOutput(value=self.a % self.b) + #Python doesn't support switch statements until 3.10, but InvokeAI supports back to 3.9 + if self.operation == "Add A+B": + return IntegerOutput(value=self.a + self.b) + elif self.operation == "Subtract A-B": + return IntegerOutput(value=self.a - self.b) + elif self.operation == "Multiply A*B": + return IntegerOutput(value=self.a * self.b) + elif self.operation == "Divide A/B": + return IntegerOutput(value=int(self.a / self.b)) + elif self.operation == "Exponentiate A^B": + return IntegerOutput(value=self.a ** self.b) + elif self.operation == "Modulus A%B": + return IntegerOutput(value=self.a % self.b) + elif self.operation == "Absolute Value of A": + return IntegerOutput(value=abs(self.a)) + elif self.operation == "Minimum(A,B)": + return IntegerOutput(value=min(self.a, self.b)) + else: #self.operation == "Maximum(A,B)": + return IntegerOutput(value=max(self.a, self.b)) -@invocation("sqrt", title="Square Root", tags=["math", "sqrt"], category="math", version="1.0.0") -class SquareRootInvocation(BaseInvocation): - """Returns the square root of a number.""" +FLOAT_OPERATIONS = Literal[ + "Add A+B", + "Subtract A-B", + "Multiply A*B", + "Divide A/B", + "Exponentiate A^B", + "Absolute Value of A", + "Minimum(A,B)", + "Maximum(A,B)" +] - value: float = InputField(default=0, ge=0, description="The float value") + +@invocation( + "float_math", + title="Float Math", + tags=[ + "math", + "float", + "add", + "subtract", + "multiply", + "divide", + "power", + "root", + "absolute value", + "min", + "max" + ], + category="math", + version="1.0.0" +) +class FloatMathInvocation(BaseInvocation): + """Performs floating point math.""" + + operation: FLOAT_OPERATIONS = InputField(default="Add A+B", description="The operation to perform") + a: float = InputField(default=0, description=FieldDescriptions.num_1) + b: float = InputField(default=0, description=FieldDescriptions.num_2) + + @validator("operation") + def no_divide_by_zero(cls, v, values): + if v == "Divide A/B" and values["b"] == 0: + raise ValueError("Cannot divide by zero") + elif v == "Exponentiate A^B" and values["a"] == 0 and values["b"] < 0: + raise ValueError("Cannot raise zero to a negative power") + elif v == "Exponentiate A^B" and type(values["a"]**values["b"]) == complex: + raise ValueError("Root operation resulted in a complex number") + return v def invoke(self, context: InvocationContext) -> FloatOutput: - return FloatOutput(value=np.sqrt(self.value)) \ No newline at end of file + #Python doesn't support switch statements until 3.10, but InvokeAI supports back to 3.9 + if self.operation == "Add A+B": + return FloatOutput(value=self.a + self.b) + elif self.operation == "Subtract A-B": + return FloatOutput(value=self.a - self.b) + elif self.operation == "Multiply A*B": + return FloatOutput(value=self.a * self.b) + elif self.operation == "Divide A/B": + return FloatOutput(value=self.a / self.b) + elif self.operation == "Exponentiate A^B": + return FloatOutput(value=self.a ** self.b) + elif self.operation == "Square Root of A": + return FloatOutput(value=np.sqrt(self.a)) + elif self.operation == "Absolute Value of A": + return FloatOutput(value=abs(self.a)) + elif self.operation == "Minimum(A,B)": + return FloatOutput(value=min(self.a, self.b)) + else: #self.operation == "Maximum(A,B)": + return FloatOutput(value=max(self.a, self.b)) \ No newline at end of file From 244201b45dc9c7e3c21826488774410b5f1d5e99 Mon Sep 17 00:00:00 2001 From: dunkeroni Date: Sun, 10 Sep 2023 00:52:53 -0400 Subject: [PATCH 10/31] Cleanup documentation --- docs/nodes/defaultNodes.md | 9 ++------- invokeai/app/invocations/math.py | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/docs/nodes/defaultNodes.md b/docs/nodes/defaultNodes.md index 9b91db9348..33561677c7 100644 --- a/docs/nodes/defaultNodes.md +++ b/docs/nodes/defaultNodes.md @@ -5,8 +5,6 @@ The table below contains a list of the default nodes shipped with InvokeAI and t | Node | Function | |: ---------------------------------- | :--------------------------------------------------------------------------------------| |Add Integers | Adds two numbers| -|Add Floats | Adds two floating point numbers| -|Absolute Value | Returns the positive form of a float| |Boolean Primitive Collection | A collection of boolean primitive values| |Boolean Primitive | A boolean primitive value| |Canny Processor | Canny edge detection for ControlNet| @@ -22,9 +20,9 @@ The table below contains a list of the default nodes shipped with InvokeAI and t |OpenCV Inpaint | Simple inpaint using opencv.| |Denoise Latents | Denoises noisy latents to decodable images| |Divide Integers | Divides two numbers| -|Divide Floats | Divides two floating point numbers| |Dynamic Prompt | Parses a prompt using adieyal/dynamicprompts' random or combinatorial generator| |Upscale (RealESRGAN) | Upscales an image using RealESRGAN.| +|Float Math | Perform basic math operations on two floats| |Float Primitive Collection | A collection of float primitive values| |Float Primitive | A float primitive value| |Float Range | Creates a range| @@ -32,6 +30,7 @@ The table below contains a list of the default nodes shipped with InvokeAI and t |Blur Image | Blurs an image| |Extract Image Channel | Gets a channel from an image.| |Image Primitive Collection | A collection of image primitive values| +|Integer Math | Perform basic math operations on two integers| |Convert Image Mode | Converts an image to a different mode.| |Crop Image | Crops an image to a specified box. The box can be outside of the image.| |Image Hue Adjustment | Adjusts the Hue of an image.| @@ -70,9 +69,7 @@ The table below contains a list of the default nodes shipped with InvokeAI and t |Mediapipe Face Processor | Applies mediapipe face processing to image| |Midas (Depth) Processor | Applies Midas depth processing to image| |MLSD Processor | Applies MLSD processing to image| -|Modulus | Returns the remainder after integer division| |Multiply Integers | Multiplies two numbers| -|Multiply Floats | Multiplies two floating point numbers| |Noise | Generates latent noise.| |Normal BAE Processor | Applies NormalBae processing to image| |ONNX Latents to Image | Generates an image from latents.| @@ -99,8 +96,6 @@ The table below contains a list of the default nodes shipped with InvokeAI and t |String Primitive Collection | A collection of string primitive values| |String Primitive | A string primitive value| |Subtract Integers | Subtracts two numbers| -|Subtract Floats | Subtracts two floating point numbers| -|Square Root | Returns the square root of a float| |Tile Resample Processor | Tile resampler processor| |VAE Loader | Loads a VAE model, outputting a VaeLoaderOutput| |Zoe (Depth) Processor | Applies Zoe depth processing to image| \ No newline at end of file diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index df5b5ec0d3..31e7b1b257 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -11,7 +11,7 @@ from .baseinvocation import BaseInvocation, FieldDescriptions, InputField, Invoc @invocation("add", title="Add Integers", tags=["math", "add"], category="math", version="1.0.0") class AddInvocation(BaseInvocation): - """Adds two integer numbers""" + """Adds two numbers""" a: int = InputField(default=0, description=FieldDescriptions.num_1) b: int = InputField(default=0, description=FieldDescriptions.num_2) From 41f2eaa4de07869fff1e670ff29eefa8253cd223 Mon Sep 17 00:00:00 2001 From: dunkeroni Date: Sun, 10 Sep 2023 00:55:46 -0400 Subject: [PATCH 11/31] updated name references for Float To Integer --- docs/nodes/defaultNodes.md | 2 +- invokeai/app/invocations/math.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/nodes/defaultNodes.md b/docs/nodes/defaultNodes.md index 33561677c7..b022eeed14 100644 --- a/docs/nodes/defaultNodes.md +++ b/docs/nodes/defaultNodes.md @@ -45,7 +45,7 @@ The table below contains a list of the default nodes shipped with InvokeAI and t |ImageProcessor | Base class for invocations that preprocess images for ControlNet| |Resize Image | Resizes an image to specific dimensions| |Round Float | Rounds a float to a specified number of decimal places| -|Round To Multiple | Converts a float to an integer| +|Float to Integer | Converts a float to an integer. Optionally rounds to an even multiple of a input number.| |Scale Image | Scales an image by a factor| |Image to Latents | Encodes an image into latents.| |Add Invisible Watermark | Add an invisible watermark to an image| diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index 31e7b1b257..06d7d58d83 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -65,7 +65,7 @@ class RandomIntInvocation(BaseInvocation): @invocation("float_to_int", title="Float To Integer", tags=["math", "round", "integer", "float", "convert"], category="math", version="1.0.0") -class RoundToMultipleInvocation(BaseInvocation): +class FloatToIntegerInvocation(BaseInvocation): """Rounds a float number to (a multiple of) an integer.""" value: float = InputField(default=0, description="The value to round") From 93c55ebcf24408b9b61c77908290d46a9ffccc00 Mon Sep 17 00:00:00 2001 From: dunkeroni Date: Sun, 10 Sep 2023 02:17:53 -0400 Subject: [PATCH 12/31] fixed validator when operation is first input --- invokeai/app/invocations/math.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index 06d7d58d83..0b95ec01b7 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -133,13 +133,13 @@ class IntegerMathInvocation(BaseInvocation): a: int = InputField(default=0, description=FieldDescriptions.num_1) b: int = InputField(default=0, description=FieldDescriptions.num_2) - @validator("operation") - def no_divide_by_zero(cls, v, values): - if v == "Divide A/B" and values["b"] == 0: + @validator("b") + def no_unrepresentable_results(cls, v, values): + if values["operation"] == "Divide A/B" and v == 0: raise ValueError("Cannot divide by zero") - elif v == "Modulus A%B" and values["b"] == 0: + elif values["operation"] == "Modulus A%B" and v == 0: raise ValueError("Cannot divide by zero") - elif v == "Exponentiate A^B" and values["b"] < 0: + elif values["operation"] == "Exponentiate A^B" and v < 0: raise ValueError("Result of exponentiation is not an integer") return v @@ -203,13 +203,13 @@ class FloatMathInvocation(BaseInvocation): a: float = InputField(default=0, description=FieldDescriptions.num_1) b: float = InputField(default=0, description=FieldDescriptions.num_2) - @validator("operation") - def no_divide_by_zero(cls, v, values): - if v == "Divide A/B" and values["b"] == 0: + @validator("b") + def no_unrepresentable_results(cls, v, values): + if values["operation"] == "Divide A/B" and v == 0: raise ValueError("Cannot divide by zero") - elif v == "Exponentiate A^B" and values["a"] == 0 and values["b"] < 0: + elif values["operation"] == "Exponentiate A^B" and values["a"] == 0 and v < 0: raise ValueError("Cannot raise zero to a negative power") - elif v == "Exponentiate A^B" and type(values["a"]**values["b"]) == complex: + elif values["operation"] == "Exponentiate A^B" and type(values["a"]**v) == complex: raise ValueError("Root operation resulted in a complex number") return v From ec0f6e72480a35529048943efca8157ac6595b23 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 13 Sep 2023 15:49:19 +1000 Subject: [PATCH 13/31] chore: black --- invokeai/app/invocations/math.py | 60 +++++++++++++++----------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index 0b95ec01b7..0a45ed72d4 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -64,13 +64,21 @@ class RandomIntInvocation(BaseInvocation): return IntegerOutput(value=np.random.randint(self.low, self.high)) -@invocation("float_to_int", title="Float To Integer", tags=["math", "round", "integer", "float", "convert"], category="math", version="1.0.0") +@invocation( + "float_to_int", + title="Float To Integer", + tags=["math", "round", "integer", "float", "convert"], + category="math", + version="1.0.0", +) class FloatToIntegerInvocation(BaseInvocation): """Rounds a float number to (a multiple of) an integer.""" value: float = InputField(default=0, description="The value to round") - multiple: int = InputField(default=1, ge=1,title="Multiple of", description="The multiple to round to") - method: Literal["Nearest", "Floor", "Ceiling", "Truncate"] = InputField(default="Nearest", description="The method to use for rounding") + multiple: int = InputField(default=1, ge=1, title="Multiple of", description="The multiple to round to") + method: Literal["Nearest", "Floor", "Ceiling", "Truncate"] = InputField( + default="Nearest", description="The method to use for rounding" + ) def invoke(self, context: InvocationContext) -> IntegerOutput: if self.method == "Nearest": @@ -79,7 +87,7 @@ class FloatToIntegerInvocation(BaseInvocation): return IntegerOutput(value=np.floor(self.value / self.multiple) * self.multiple) elif self.method == "Ceiling": return IntegerOutput(value=np.ceil(self.value / self.multiple) * self.multiple) - else: #self.method == "Truncate" + else: # self.method == "Truncate" return IntegerOutput(value=int(self.value / self.multiple) * self.multiple) @@ -103,7 +111,7 @@ INTEGER_OPERATIONS = Literal[ "Modulus A%B", "Absolute Value of A", "Minimum(A,B)", - "Maximum(A,B)" + "Maximum(A,B)", ] @@ -121,10 +129,10 @@ INTEGER_OPERATIONS = Literal[ "power", "absolute value", "min", - "max" - ], - category="math", - version="1.0.0" + "max", + ], + category="math", + version="1.0.0", ) class IntegerMathInvocation(BaseInvocation): """Performs integer math.""" @@ -144,7 +152,7 @@ class IntegerMathInvocation(BaseInvocation): return v def invoke(self, context: InvocationContext) -> IntegerOutput: - #Python doesn't support switch statements until 3.10, but InvokeAI supports back to 3.9 + # Python doesn't support switch statements until 3.10, but InvokeAI supports back to 3.9 if self.operation == "Add A+B": return IntegerOutput(value=self.a + self.b) elif self.operation == "Subtract A-B": @@ -154,14 +162,14 @@ class IntegerMathInvocation(BaseInvocation): elif self.operation == "Divide A/B": return IntegerOutput(value=int(self.a / self.b)) elif self.operation == "Exponentiate A^B": - return IntegerOutput(value=self.a ** self.b) + return IntegerOutput(value=self.a**self.b) elif self.operation == "Modulus A%B": return IntegerOutput(value=self.a % self.b) elif self.operation == "Absolute Value of A": return IntegerOutput(value=abs(self.a)) elif self.operation == "Minimum(A,B)": return IntegerOutput(value=min(self.a, self.b)) - else: #self.operation == "Maximum(A,B)": + else: # self.operation == "Maximum(A,B)": return IntegerOutput(value=max(self.a, self.b)) @@ -173,28 +181,16 @@ FLOAT_OPERATIONS = Literal[ "Exponentiate A^B", "Absolute Value of A", "Minimum(A,B)", - "Maximum(A,B)" + "Maximum(A,B)", ] @invocation( "float_math", title="Float Math", - tags=[ - "math", - "float", - "add", - "subtract", - "multiply", - "divide", - "power", - "root", - "absolute value", - "min", - "max" - ], + tags=["math", "float", "add", "subtract", "multiply", "divide", "power", "root", "absolute value", "min", "max"], category="math", - version="1.0.0" + version="1.0.0", ) class FloatMathInvocation(BaseInvocation): """Performs floating point math.""" @@ -209,12 +205,12 @@ class FloatMathInvocation(BaseInvocation): raise ValueError("Cannot divide by zero") elif values["operation"] == "Exponentiate A^B" and values["a"] == 0 and v < 0: raise ValueError("Cannot raise zero to a negative power") - elif values["operation"] == "Exponentiate A^B" and type(values["a"]**v) == complex: + elif values["operation"] == "Exponentiate A^B" and type(values["a"] ** v) == complex: raise ValueError("Root operation resulted in a complex number") return v def invoke(self, context: InvocationContext) -> FloatOutput: - #Python doesn't support switch statements until 3.10, but InvokeAI supports back to 3.9 + # Python doesn't support switch statements until 3.10, but InvokeAI supports back to 3.9 if self.operation == "Add A+B": return FloatOutput(value=self.a + self.b) elif self.operation == "Subtract A-B": @@ -224,12 +220,12 @@ class FloatMathInvocation(BaseInvocation): elif self.operation == "Divide A/B": return FloatOutput(value=self.a / self.b) elif self.operation == "Exponentiate A^B": - return FloatOutput(value=self.a ** self.b) + return FloatOutput(value=self.a**self.b) elif self.operation == "Square Root of A": return FloatOutput(value=np.sqrt(self.a)) elif self.operation == "Absolute Value of A": return FloatOutput(value=abs(self.a)) elif self.operation == "Minimum(A,B)": return FloatOutput(value=min(self.a, self.b)) - else: #self.operation == "Maximum(A,B)": - return FloatOutput(value=max(self.a, self.b)) \ No newline at end of file + else: # self.operation == "Maximum(A,B)": + return FloatOutput(value=max(self.a, self.b)) From 57ebf735e60f1c2143086dc3eed120601ba8d61f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 13 Sep 2023 16:24:22 +1000 Subject: [PATCH 14/31] feat(nodes): add `InputField.ui_choice_labels: dict[str, str]` This maps values to labels for multiple-choice fields. This allows "enum" fields (i.e. `Literal["val1", "val2", ...]` fields) to use code-friendly string values for choices, but present this to the UI as human-friendly labels. --- invokeai/app/invocations/baseinvocation.py | 3 + .../fields/inputs/EnumInputField.tsx | 6 +- .../web/src/features/nodes/types/types.ts | 8 +- .../nodes/util/fieldTemplateBuilders.ts | 4 +- .../features/nodes/util/fieldValueBuilders.ts | 18 +- .../frontend/web/src/services/api/schema.d.ts | 211 ++++++++++++++++-- 6 files changed, 215 insertions(+), 35 deletions(-) diff --git a/invokeai/app/invocations/baseinvocation.py b/invokeai/app/invocations/baseinvocation.py index 715ee5ca6e..9fbd920dd2 100644 --- a/invokeai/app/invocations/baseinvocation.py +++ b/invokeai/app/invocations/baseinvocation.py @@ -198,6 +198,7 @@ class _InputField(BaseModel): ui_type: Optional[UIType] ui_component: Optional[UIComponent] ui_order: Optional[int] + ui_choice_labels: Optional[dict[str, str]] item_default: Optional[Any] @@ -246,6 +247,7 @@ def InputField( ui_component: Optional[UIComponent] = None, ui_hidden: bool = False, ui_order: Optional[int] = None, + ui_choice_labels: Optional[dict[str, str]] = None, item_default: Optional[Any] = None, **kwargs: Any, ) -> Any: @@ -312,6 +314,7 @@ def InputField( ui_hidden=ui_hidden, ui_order=ui_order, item_default=item_default, + ui_choice_labels=ui_choice_labels, **kwargs, ) diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/EnumInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/EnumInputField.tsx index 84ecf4842e..277020d847 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/EnumInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/EnumInputField.tsx @@ -35,7 +35,11 @@ const EnumInputFieldComponent = ( value={field.value} > {fieldTemplate.options.map((option) => ( - + ))} ); diff --git a/invokeai/frontend/web/src/features/nodes/types/types.ts b/invokeai/frontend/web/src/features/nodes/types/types.ts index 402ef4ac7a..b1ad6a7b96 100644 --- a/invokeai/frontend/web/src/features/nodes/types/types.ts +++ b/invokeai/frontend/web/src/features/nodes/types/types.ts @@ -286,7 +286,7 @@ export type BooleanPolymorphicInputFieldValue = z.infer< export const zEnumInputFieldValue = zInputFieldValueBase.extend({ type: z.literal('enum'), - value: z.union([z.string(), z.number()]).optional(), + value: z.string().optional(), }); export type EnumInputFieldValue = z.infer; @@ -822,10 +822,10 @@ export type ControlPolymorphicInputFieldTemplate = Omit< }; export type EnumInputFieldTemplate = InputFieldTemplateBase & { - default: string | number; + default: string; type: 'enum'; - enumType: 'string' | 'number'; - options: Array; + options: string[]; + labels?: { [key: string]: string }; }; export type MainModelInputFieldTemplate = InputFieldTemplateBase & { diff --git a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts index 20463f37f6..c5e6be75c0 100644 --- a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts +++ b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts @@ -656,8 +656,8 @@ const buildEnumInputFieldTemplate = ({ const template: EnumInputFieldTemplate = { ...baseField, type: 'enum', - enumType: (schemaObject.type as 'string' | 'number') ?? 'string', // TODO: dangerous? - options: options, + options, + ui_choice_labels: schemaObject.ui_choice_labels, default: schemaObject.default ?? options[0], }; diff --git a/invokeai/frontend/web/src/features/nodes/util/fieldValueBuilders.ts b/invokeai/frontend/web/src/features/nodes/util/fieldValueBuilders.ts index a3046feee7..3052d4e230 100644 --- a/invokeai/frontend/web/src/features/nodes/util/fieldValueBuilders.ts +++ b/invokeai/frontend/web/src/features/nodes/util/fieldValueBuilders.ts @@ -1,8 +1,7 @@ import { InputFieldTemplate, InputFieldValue } from '../types/types'; const FIELD_VALUE_FALLBACK_MAP = { - 'enum.number': 0, - 'enum.string': '', + enum: '', boolean: false, BooleanCollection: [], BooleanPolymorphic: false, @@ -62,19 +61,8 @@ export const buildInputFieldValue = ( fieldKind: 'input', } as InputFieldValue; - if (template.type === 'enum') { - if (template.enumType === 'number') { - fieldValue.value = - template.default ?? FIELD_VALUE_FALLBACK_MAP['enum.number']; - } - if (template.enumType === 'string') { - fieldValue.value = - template.default ?? FIELD_VALUE_FALLBACK_MAP['enum.string']; - } - } else { - fieldValue.value = - template.default ?? FIELD_VALUE_FALLBACK_MAP[template.type]; - } + fieldValue.value = + template.default ?? FIELD_VALUE_FALLBACK_MAP[template.type]; return fieldValue; }; diff --git a/invokeai/frontend/web/src/services/api/schema.d.ts b/invokeai/frontend/web/src/services/api/schema.d.ts index f4b87e15da..dfa292c643 100644 --- a/invokeai/frontend/web/src/services/api/schema.d.ts +++ b/invokeai/frontend/web/src/services/api/schema.d.ts @@ -1553,7 +1553,7 @@ export type components = { /** * App Version * @description The version of InvokeAI used to generate this image - * @default 3.1.0 + * @default 3.1.1rc1 */ app_version?: string; /** @@ -2216,6 +2216,53 @@ export type components = { */ type: "float_range"; }; + /** + * Float Math + * @description Performs floating point math. + */ + FloatMathInvocation: { + /** + * Id + * @description The id of this instance of an invocation. Must be unique among all instances of invocations. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this is an intermediate invocation. + * @default false + */ + is_intermediate?: boolean; + /** + * Workflow + * @description The workflow to save with the image + */ + workflow?: string; + /** + * Operation + * @description The operation to perform + * @default ADD + * @enum {string} + */ + operation?: "ADD" | "SUB" | "MUL" | "DIV" | "EXP" | "ABS" | "SQRT" | "MIN" | "MAX"; + /** + * A + * @description The first number + * @default 0 + */ + a?: number; + /** + * B + * @description The second number + * @default 0 + */ + b?: number; + /** + * Type + * @default float_math + * @enum {string} + */ + type: "float_math"; + }; /** * FloatOutput * @description Base class for nodes that output a single float @@ -2233,6 +2280,53 @@ export type components = { */ type: "float_output"; }; + /** + * Float To Integer + * @description Rounds a float number to (a multiple of) an integer. + */ + FloatToIntegerInvocation: { + /** + * Id + * @description The id of this instance of an invocation. Must be unique among all instances of invocations. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this is an intermediate invocation. + * @default false + */ + is_intermediate?: boolean; + /** + * Workflow + * @description The workflow to save with the image + */ + workflow?: string; + /** + * Value + * @description The value to round + * @default 0 + */ + value?: number; + /** + * Multiple of + * @description The multiple to round to + * @default 1 + */ + multiple?: number; + /** + * Method + * @description The method to use for rounding + * @default Nearest + * @enum {string} + */ + method?: "Nearest" | "Floor" | "Ceiling" | "Truncate"; + /** + * Type + * @default float_to_int + * @enum {string} + */ + type: "float_to_int"; + }; /** Graph */ Graph: { /** @@ -2245,7 +2339,7 @@ export type components = { * @description The nodes in this graph */ nodes?: { - [key: string]: components["schemas"]["BooleanInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["SDXLLoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ONNXPromptInvocation"] | components["schemas"]["ONNXTextToLatentsInvocation"] | components["schemas"]["ONNXLatentsToImageInvocation"] | components["schemas"]["OnnxModelLoaderInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"]; + [key: string]: components["schemas"]["BooleanInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["SDXLLoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["ONNXPromptInvocation"] | components["schemas"]["ONNXTextToLatentsInvocation"] | components["schemas"]["ONNXLatentsToImageInvocation"] | components["schemas"]["OnnxModelLoaderInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"]; }; /** * Edges @@ -2288,7 +2382,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: components["schemas"]["BooleanOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["SDXLLoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["MetadataAccumulatorOutput"] | components["schemas"]["ClipSkipInvocationOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["ONNXModelLoaderOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]; + [key: string]: components["schemas"]["BooleanOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["SDXLLoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["MetadataAccumulatorOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["ClipSkipInvocationOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["ONNXModelLoaderOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]; }; /** * Errors @@ -3684,6 +3778,53 @@ export type components = { */ type: "integer"; }; + /** + * Integer Math + * @description Performs integer math. + */ + IntegerMathInvocation: { + /** + * Id + * @description The id of this instance of an invocation. Must be unique among all instances of invocations. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this is an intermediate invocation. + * @default false + */ + is_intermediate?: boolean; + /** + * Workflow + * @description The workflow to save with the image + */ + workflow?: string; + /** + * Operation + * @description The operation to perform + * @default ADD + * @enum {string} + */ + operation?: "ADD" | "SUB" | "MUL" | "DIV" | "EXP" | "MOD" | "ABS" | "MIN" | "MAX"; + /** + * A + * @description The first number + * @default 0 + */ + a?: number; + /** + * B + * @description The second number + * @default 0 + */ + b?: number; + /** + * Type + * @default integer_math + * @enum {string} + */ + type: "integer_math"; + }; /** * IntegerOutput * @description Base class for nodes that output a single integer @@ -5805,6 +5946,46 @@ export type components = { * @enum {string} */ ResourceOrigin: "internal" | "external"; + /** + * Round Float + * @description Rounds a float to a specified number of decimal places. + */ + RoundInvocation: { + /** + * Id + * @description The id of this instance of an invocation. Must be unique among all instances of invocations. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this is an intermediate invocation. + * @default false + */ + is_intermediate?: boolean; + /** + * Workflow + * @description The workflow to save with the image + */ + workflow?: string; + /** + * Value + * @description The float value + * @default 0 + */ + value?: number; + /** + * Decimals + * @description The number of decimal places + * @default 0 + */ + decimals?: number; + /** + * Type + * @default round_float + * @enum {string} + */ + type: "round_float"; + }; /** * SDXL Prompt * @description Parse prompt using compel package to conditioning. @@ -7088,6 +7269,10 @@ export type components = { ui_component?: components["schemas"]["UIComponent"]; /** Ui Order */ ui_order?: number; + /** Ui Choice Labels */ + ui_choice_labels?: { + [key: string]: string; + }; /** Item Default */ item_default?: unknown; }; @@ -7106,11 +7291,11 @@ export type components = { ui_order?: number; }; /** - * StableDiffusionXLModelFormat + * StableDiffusion1ModelFormat * @description An enumeration. * @enum {string} */ - StableDiffusionXLModelFormat: "checkpoint" | "diffusers"; + StableDiffusion1ModelFormat: "checkpoint" | "diffusers"; /** * StableDiffusionOnnxModelFormat * @description An enumeration. @@ -7123,18 +7308,18 @@ export type components = { * @enum {string} */ ControlNetModelFormat: "checkpoint" | "diffusers"; + /** + * StableDiffusionXLModelFormat + * @description An enumeration. + * @enum {string} + */ + StableDiffusionXLModelFormat: "checkpoint" | "diffusers"; /** * StableDiffusion2ModelFormat * @description An enumeration. * @enum {string} */ StableDiffusion2ModelFormat: "checkpoint" | "diffusers"; - /** - * StableDiffusion1ModelFormat - * @description An enumeration. - * @enum {string} - */ - StableDiffusion1ModelFormat: "checkpoint" | "diffusers"; }; responses: never; parameters: never; @@ -7251,7 +7436,7 @@ export type operations = { }; requestBody: { content: { - "application/json": components["schemas"]["BooleanInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["SDXLLoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ONNXPromptInvocation"] | components["schemas"]["ONNXTextToLatentsInvocation"] | components["schemas"]["ONNXLatentsToImageInvocation"] | components["schemas"]["OnnxModelLoaderInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"]; + "application/json": components["schemas"]["BooleanInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["SDXLLoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["ONNXPromptInvocation"] | components["schemas"]["ONNXTextToLatentsInvocation"] | components["schemas"]["ONNXLatentsToImageInvocation"] | components["schemas"]["OnnxModelLoaderInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"]; }; }; responses: { @@ -7292,7 +7477,7 @@ export type operations = { }; requestBody: { content: { - "application/json": components["schemas"]["BooleanInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["SDXLLoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ONNXPromptInvocation"] | components["schemas"]["ONNXTextToLatentsInvocation"] | components["schemas"]["ONNXLatentsToImageInvocation"] | components["schemas"]["OnnxModelLoaderInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"]; + "application/json": components["schemas"]["BooleanInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["SDXLLoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["ONNXPromptInvocation"] | components["schemas"]["ONNXTextToLatentsInvocation"] | components["schemas"]["ONNXLatentsToImageInvocation"] | components["schemas"]["OnnxModelLoaderInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"]; }; }; responses: { From fb188ce63e523279e679c1e1334c06cba8ef65d6 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 13 Sep 2023 16:24:46 +1000 Subject: [PATCH 15/31] feat(nodes): update `float_math` and `integer_math` to use new `ui_choice_labels` --- invokeai/app/invocations/math.py | 117 +++++++++++++++++++------------ 1 file changed, 74 insertions(+), 43 deletions(-) diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index 0a45ed72d4..ddff9fb7c0 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -103,18 +103,31 @@ class RoundInvocation(BaseInvocation): INTEGER_OPERATIONS = Literal[ - "Add A+B", - "Subtract A-B", - "Multiply A*B", - "Divide A/B", - "Exponentiate A^B", - "Modulus A%B", - "Absolute Value of A", - "Minimum(A,B)", - "Maximum(A,B)", + "ADD", + "SUB", + "MUL", + "DIV", + "EXP", + "MOD", + "ABS", + "MIN", + "MAX", ] +INTEGER_OPERATIONS_LABELS = dict( + ADD="Add A+B", + SUB="Subtract A-B", + MUL="Multiply A*B", + DIV="Divide A/B", + EXP="Exponentiate A^B", + MOD="Modulus A%B", + ABS="Absolute Value of A", + MIN="Minimum(A,B)", + MAX="Maximum(A,B)", +) + + @invocation( "integer_math", title="Integer Math", @@ -137,54 +150,70 @@ INTEGER_OPERATIONS = Literal[ class IntegerMathInvocation(BaseInvocation): """Performs integer math.""" - operation: INTEGER_OPERATIONS = InputField(default="Add A+B", description="The operation to perform") + operation: INTEGER_OPERATIONS = InputField( + default="ADD", description="The operation to perform", ui_choice_labels=INTEGER_OPERATIONS_LABELS + ) a: int = InputField(default=0, description=FieldDescriptions.num_1) b: int = InputField(default=0, description=FieldDescriptions.num_2) @validator("b") def no_unrepresentable_results(cls, v, values): - if values["operation"] == "Divide A/B" and v == 0: + if values["operation"] == "DIV" and v == 0: raise ValueError("Cannot divide by zero") - elif values["operation"] == "Modulus A%B" and v == 0: + elif values["operation"] == "MOD" and v == 0: raise ValueError("Cannot divide by zero") - elif values["operation"] == "Exponentiate A^B" and v < 0: + elif values["operation"] == "EXP" and v < 0: raise ValueError("Result of exponentiation is not an integer") return v def invoke(self, context: InvocationContext) -> IntegerOutput: # Python doesn't support switch statements until 3.10, but InvokeAI supports back to 3.9 - if self.operation == "Add A+B": + if self.operation == "ADD": return IntegerOutput(value=self.a + self.b) - elif self.operation == "Subtract A-B": + elif self.operation == "SUB": return IntegerOutput(value=self.a - self.b) - elif self.operation == "Multiply A*B": + elif self.operation == "MUL": return IntegerOutput(value=self.a * self.b) - elif self.operation == "Divide A/B": + elif self.operation == "DIV": return IntegerOutput(value=int(self.a / self.b)) - elif self.operation == "Exponentiate A^B": + elif self.operation == "EXP": return IntegerOutput(value=self.a**self.b) - elif self.operation == "Modulus A%B": + elif self.operation == "MOD": return IntegerOutput(value=self.a % self.b) - elif self.operation == "Absolute Value of A": + elif self.operation == "ABS": return IntegerOutput(value=abs(self.a)) - elif self.operation == "Minimum(A,B)": + elif self.operation == "MIN": return IntegerOutput(value=min(self.a, self.b)) - else: # self.operation == "Maximum(A,B)": + else: # self.operation == "MAX": return IntegerOutput(value=max(self.a, self.b)) FLOAT_OPERATIONS = Literal[ - "Add A+B", - "Subtract A-B", - "Multiply A*B", - "Divide A/B", - "Exponentiate A^B", - "Absolute Value of A", - "Minimum(A,B)", - "Maximum(A,B)", + "ADD", + "SUB", + "MUL", + "DIV", + "EXP", + "ABS", + "SQRT", + "MIN", + "MAX", ] +FLOAT_OPERATIONS_LABELS = dict( + ADD="Add A+B", + SUB="Subtract A-B", + MUL="Multiply A*B", + DIV="Divide A/B", + EXP="Exponentiate A^B", + ABS="Absolute Value of A", + SQRT="Square Root of A", + MIN="Minimum(A,B)", + MAX="Maximum(A,B)", +) + + @invocation( "float_math", title="Float Math", @@ -195,37 +224,39 @@ FLOAT_OPERATIONS = Literal[ class FloatMathInvocation(BaseInvocation): """Performs floating point math.""" - operation: FLOAT_OPERATIONS = InputField(default="Add A+B", description="The operation to perform") + operation: FLOAT_OPERATIONS = InputField( + default="ADD", description="The operation to perform", ui_choice_labels=FLOAT_OPERATIONS_LABELS + ) a: float = InputField(default=0, description=FieldDescriptions.num_1) b: float = InputField(default=0, description=FieldDescriptions.num_2) @validator("b") def no_unrepresentable_results(cls, v, values): - if values["operation"] == "Divide A/B" and v == 0: + if values["operation"] == "DIV" and v == 0: raise ValueError("Cannot divide by zero") - elif values["operation"] == "Exponentiate A^B" and values["a"] == 0 and v < 0: + elif values["operation"] == "EXP" and values["a"] == 0 and v < 0: raise ValueError("Cannot raise zero to a negative power") - elif values["operation"] == "Exponentiate A^B" and type(values["a"] ** v) == complex: + elif values["operation"] == "EXP" and type(values["a"] ** v) == complex: raise ValueError("Root operation resulted in a complex number") return v def invoke(self, context: InvocationContext) -> FloatOutput: # Python doesn't support switch statements until 3.10, but InvokeAI supports back to 3.9 - if self.operation == "Add A+B": + if self.operation == "ADD": return FloatOutput(value=self.a + self.b) - elif self.operation == "Subtract A-B": + elif self.operation == "SUB": return FloatOutput(value=self.a - self.b) - elif self.operation == "Multiply A*B": + elif self.operation == "MUL": return FloatOutput(value=self.a * self.b) - elif self.operation == "Divide A/B": + elif self.operation == "DIV": return FloatOutput(value=self.a / self.b) - elif self.operation == "Exponentiate A^B": + elif self.operation == "EXP": return FloatOutput(value=self.a**self.b) - elif self.operation == "Square Root of A": + elif self.operation == "SQRT": return FloatOutput(value=np.sqrt(self.a)) - elif self.operation == "Absolute Value of A": + elif self.operation == "ABS": return FloatOutput(value=abs(self.a)) - elif self.operation == "Minimum(A,B)": + elif self.operation == "MIN": return FloatOutput(value=min(self.a, self.b)) - else: # self.operation == "Maximum(A,B)": + else: # self.operation == "MAX": return FloatOutput(value=max(self.a, self.b)) From a88f16b81cde422e97664a5b184f8d172c1795ad Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 13 Sep 2023 16:27:35 +1000 Subject: [PATCH 16/31] chore: isort --- invokeai/app/invocations/math.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index ddff9fb7c0..2047ca2bca 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -1,11 +1,12 @@ # Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654) -import numpy as np from typing import Literal -from invokeai.app.invocations.primitives import IntegerOutput, FloatOutput +import numpy as np from pydantic import validator +from invokeai.app.invocations.primitives import FloatOutput, IntegerOutput + from .baseinvocation import BaseInvocation, FieldDescriptions, InputField, InvocationContext, invocation From 30792cb259e0a5be1958f3187dbbc8286ddfd290 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 13 Sep 2023 16:29:16 +1000 Subject: [PATCH 17/31] chore: flake8 --- invokeai/app/invocations/math.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index 2047ca2bca..ac15146478 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -237,7 +237,7 @@ class FloatMathInvocation(BaseInvocation): raise ValueError("Cannot divide by zero") elif values["operation"] == "EXP" and values["a"] == 0 and v < 0: raise ValueError("Cannot raise zero to a negative power") - elif values["operation"] == "EXP" and type(values["a"] ** v) == complex: + elif values["operation"] == "EXP" and type(values["a"] ** v) is complex: raise ValueError("Root operation resulted in a complex number") return v From 8c63173b0c359977b2b9d22b6ebd520d7eecb7b7 Mon Sep 17 00:00:00 2001 From: mickr777 <115216705+mickr777@users.noreply.github.com> Date: Wed, 13 Sep 2023 17:31:34 +1000 Subject: [PATCH 18/31] Translation update (#4503) * Update Translations * Fix Prettier Issue * Fix Error in invokebutton.tsx * More Translations * few Fixes * More Translations * More Translations and lint Fixes * Update constants.ts Revert "Update constants.ts" --------- Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com> --- invokeai/frontend/web/public/locales/en.json | 1441 ++++++++++------- .../src/common/hooks/useIsReadyToInvoke.ts | 32 +- .../components/ChangeBoardModal.tsx | 16 +- .../controlNet/components/ControlNet.tsx | 26 +- .../components/ControlNetImagePreview.tsx | 8 +- .../ParamControlNetShouldAutoConfig.tsx | 6 +- .../imports/ControlNetCanvasImageImports.tsx | 10 +- .../parameters/ParamControlNetBeginEnd.tsx | 6 +- .../parameters/ParamControlNetControlMode.tsx | 18 +- .../parameters/ParamControlNetModel.tsx | 8 +- .../ParamControlNetProcessorSelect.tsx | 4 +- .../parameters/ParamControlNetResizeMode.tsx | 16 +- .../parameters/ParamControlNetWeight.tsx | 4 +- .../components/processors/CannyProcessor.tsx | 6 +- .../processors/ContentShuffleProcessor.tsx | 12 +- .../components/processors/HedProcessor.tsx | 8 +- .../processors/LineartAnimeProcessor.tsx | 6 +- .../processors/LineartProcessor.tsx | 8 +- .../processors/MediapipeFaceProcessor.tsx | 6 +- .../processors/MidasDepthProcessor.tsx | 6 +- .../processors/MlsdImageProcessor.tsx | 10 +- .../processors/NormalBaeProcessor.tsx | 6 +- .../processors/OpenposeProcessor.tsx | 8 +- .../components/processors/PidiProcessor.tsx | 10 +- .../features/controlNet/store/constants.ts | 108 +- .../components/ImageUsageMessage.tsx | 23 +- .../ParamDynamicPromptsCollapse.tsx | 4 +- .../ParamDynamicPromptsCombinatorial.tsx | 4 +- .../components/ParamDynamicPromptsEnabled.tsx | 4 +- .../ParamDynamicPromptsMaxPrompts.tsx | 4 +- .../components/AddEmbeddingButton.tsx | 6 +- .../components/ParamEmbeddingPopover.tsx | 10 +- .../components/Boards/BoardAutoAddSelect.tsx | 8 +- .../components/Boards/BoardContextMenu.tsx | 5 +- .../Boards/BoardsList/AddBoardButton.tsx | 12 +- .../Boards/BoardsList/BoardsSearch.tsx | 6 +- .../components/Boards/DeleteBoardModal.tsx | 4 +- .../CurrentImage/CurrentImagePreview.tsx | 5 +- .../components/ImageGrid/GalleryImage.tsx | 4 +- .../components/ImageGrid/GalleryImageGrid.tsx | 6 +- .../ImageMetadataViewer/DataViewer.tsx | 11 +- .../ImageMetadataActions.tsx | 44 +- .../ImageMetadataViewer.tsx | 20 +- .../features/nodes/components/NodeEditor.tsx | 4 +- .../flow/AddNodePopover/AddNodePopover.tsx | 81 +- .../nodes/Invocation/InvocationNodeNotes.tsx | 27 +- .../InvocationNodeStatusIndicator.tsx | 15 +- .../flow/nodes/Invocation/NotesTextarea.tsx | 4 +- .../Invocation/fields/EditableFieldTitle.tsx | 12 +- .../Invocation/fields/FieldContextMenu.tsx | 6 +- .../Invocation/fields/FieldTooltipContent.tsx | 8 +- .../Invocation/fields/LinearViewField.tsx | 7 +- .../fields/inputs/LoRAModelInputField.tsx | 8 +- .../fields/inputs/MainModelInputField.tsx | 7 +- .../fields/inputs/RefinerModelInputField.tsx | 8 +- .../fields/inputs/SDXLMainModelInputField.tsx | 8 +- .../flow/nodes/common/NodeTitle.tsx | 12 +- .../BottomLeftPanel/NodeOpacitySlider.tsx | 4 +- .../flow/panels/TopLeftPanel/TopLeftPanel.tsx | 7 +- .../TopRightPanel/WorkflowEditorSettings.tsx | 29 +- .../inspector/InspectorDetailsTab.tsx | 6 +- .../inspector/InspectorOutputsTab.tsx | 12 +- .../inspector/InspectorTemplateTab.tsx | 8 +- .../sidePanel/workflow/WorkflowGeneralTab.tsx | 25 +- .../sidePanel/workflow/WorkflowJSONTab.tsx | 4 +- .../sidePanel/workflow/WorkflowLinearTab.tsx | 4 +- .../nodes/hooks/useLoadWorkflowFromFile.tsx | 10 +- .../util/makeIsConnectionValidSelector.ts | 24 +- .../Advanced/ParamAdvancedCollapse.tsx | 5 +- .../Parameters/Noise/ParamCpuNoise.tsx | 4 +- .../Parameters/Noise/ParamNoiseToggle.tsx | 4 +- .../ProcessButtons/CancelButton.tsx | 2 +- .../ProcessButtons/InvokeButton.tsx | 13 +- .../sdxl/components/ParamSDXLConcatButton.tsx | 6 +- .../ParamSDXLImg2ImgDenoisingStrength.tsx | 2 +- .../ParamSDXLNegativeStyleConditioning.tsx | 4 +- .../ParamSDXLPositiveStyleConditioning.tsx | 4 +- .../components/ParamSDXLRefinerCollapse.tsx | 4 +- .../SDXLRefiner/ParamSDXLRefinerCFGScale.tsx | 4 +- .../ParamSDXLRefinerModelSelect.tsx | 12 +- ...ParamSDXLRefinerNegativeAestheticScore.tsx | 4 +- ...ParamSDXLRefinerPositiveAestheticScore.tsx | 4 +- .../SDXLRefiner/ParamSDXLRefinerScheduler.tsx | 2 +- .../SDXLRefiner/ParamSDXLRefinerStart.tsx | 4 +- .../SDXLRefiner/ParamSDXLRefinerSteps.tsx | 4 +- .../SDXLRefiner/ParamUseSDXLRefiner.tsx | 4 +- .../AddModelsPanel/AdvancedAddCheckpoint.tsx | 20 +- .../AddModelsPanel/AdvancedAddDiffusers.tsx | 18 +- .../AddModelsPanel/AdvancedAddModels.tsx | 5 +- .../AddModelsPanel/FoundModelsList.tsx | 10 +- .../AddModelsPanel/ScanAdvancedAddModels.tsx | 7 +- .../AddModelsPanel/SimpleAddModels.tsx | 8 +- 92 files changed, 1502 insertions(+), 961 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 125554fc40..c1983c6a53 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1,40 +1,66 @@ { "accessibility": { - "modelSelect": "Model Select", - "invokeProgressBar": "Invoke progress bar", - "reset": "Reset", - "uploadImage": "Upload Image", - "previousImage": "Previous Image", - "nextImage": "Next Image", - "useThisParameter": "Use this parameter", "copyMetadataJson": "Copy metadata JSON", "exitViewer": "Exit Viewer", - "zoomIn": "Zoom In", - "zoomOut": "Zoom Out", - "rotateCounterClockwise": "Rotate Counter-Clockwise", - "rotateClockwise": "Rotate Clockwise", "flipHorizontally": "Flip Horizontally", "flipVertically": "Flip Vertically", + "invokeProgressBar": "Invoke progress bar", + "menu": "Menu", + "modelSelect": "Model Select", "modifyConfig": "Modify Config", - "toggleAutoscroll": "Toggle autoscroll", - "toggleLogViewer": "Toggle Log Viewer", + "nextImage": "Next Image", + "previousImage": "Previous Image", + "reset": "Reset", + "rotateClockwise": "Rotate Clockwise", + "rotateCounterClockwise": "Rotate Counter-Clockwise", "showGallery": "Show Gallery", "showOptionsPanel": "Show Side Panel", - "menu": "Menu" + "toggleAutoscroll": "Toggle autoscroll", + "toggleLogViewer": "Toggle Log Viewer", + "uploadImage": "Upload Image", + "useThisParameter": "Use this parameter", + "zoomIn": "Zoom In", + "zoomOut": "Zoom Out" + }, + "boards": { + "addBoard": "Add Board", + "autoAddBoard": "Auto-Add Board", + "bottomMessage": "Deleting this board and its images will reset any features currently using them.", + "cancel": "Cancel", + "changeBoard": "Change Board", + "clearSearch": "Clear Search", + "loading": "Loading...", + "menuItemAutoAdd": "Auto-add to this Board", + "move": "Move", + "myBoard": "My Board", + "noMatching": "No matching Boards", + "searchBoard": "Search Boards...", + "selectBoard": "Select a Board", + "topMessage": "This board contains images used in the following features:", + "uncategorized": "Uncategorized" }, "common": { + "accept": "Accept", + "advanced": "Advanced", + "areYouSure": "Are you sure?", + "back": "Back", + "batch": "Batch Manager", + "cancel": "Cancel", + "close": "Close", "communityLabel": "Community", - "hotkeysLabel": "Hotkeys", + "controlNet": "Controlnet", "darkMode": "Dark Mode", - "lightMode": "Light Mode", - "languagePickerLabel": "Language", - "reportBugLabel": "Report Bug", - "githubLabel": "Github", "discordLabel": "Discord", - "settingsLabel": "Settings", + "dontAskMeAgain": "Don't ask me again", + "generate": "Generate", + "githubLabel": "Github", + "hotkeysLabel": "Hotkeys", + "imagePrompt": "Image Prompt", + "img2img": "Image To Image", "langArabic": "العربية", - "langEnglish": "English", + "langBrPortuguese": "Português do Brasil", "langDutch": "Nederlands", + "langEnglish": "English", "langFrench": "Français", "langGerman": "Deutsch", "langHebrew": "עברית", @@ -43,377 +69,426 @@ "langKorean": "한국어", "langPolish": "Polski", "langPortuguese": "Português", - "langBrPortuguese": "Português do Brasil", "langRussian": "Русский", "langSimplifiedChinese": "简体中文", - "langUkranian": "Украї́нська", "langSpanish": "Español", - "txt2img": "Text To Image", - "img2img": "Image To Image", - "unifiedCanvas": "Unified Canvas", + "languagePickerLabel": "Language", + "langUkranian": "Украї́нська", + "lightMode": "Light Mode", "linear": "Linear", - "nodes": "Workflow Editor", - "batch": "Batch Manager", + "load": "Load", + "loading": "Loading", + "loadingInvokeAI": "Loading Invoke AI", "modelManager": "Model Manager", - "postprocessing": "Post Processing", + "nodeEditor": "Node Editor", + "nodes": "Workflow Editor", "nodesDesc": "A node based system for the generation of images is under development currently. Stay tuned for updates about this amazing feature.", - "postProcessing": "Post Processing", + "openInNewTab": "Open in New Tab", "postProcessDesc1": "Invoke AI offers a wide variety of post processing features. Image Upscaling and Face Restoration are already available in the WebUI. You can access them from the Advanced Options menu of the Text To Image and Image To Image tabs. You can also process images directly, using the image action buttons above the current image display or in the viewer.", "postProcessDesc2": "A dedicated UI will be released soon to facilitate more advanced post processing workflows.", "postProcessDesc3": "The Invoke AI Command Line Interface offers various other features including Embiggen.", - "training": "Training", - "trainingDesc1": "A dedicated workflow for training your own embeddings and checkpoints using Textual Inversion and Dreambooth from the web interface.", - "trainingDesc2": "InvokeAI already supports training custom embeddourings using Textual Inversion using the main script.", - "upload": "Upload", - "close": "Close", - "cancel": "Cancel", - "accept": "Accept", - "load": "Load", - "back": "Back", + "postprocessing": "Post Processing", + "postProcessing": "Post Processing", + "random": "Random", + "reportBugLabel": "Report Bug", + "settingsLabel": "Settings", "statusConnected": "Connected", + "statusConvertingModel": "Converting Model", "statusDisconnected": "Disconnected", "statusError": "Error", - "statusPreparing": "Preparing", - "statusProcessingCanceled": "Processing Canceled", - "statusProcessingComplete": "Processing Complete", "statusGenerating": "Generating", - "statusGeneratingTextToImage": "Generating Text To Image", "statusGeneratingImageToImage": "Generating Image To Image", "statusGeneratingInpainting": "Generating Inpainting", "statusGeneratingOutpainting": "Generating Outpainting", + "statusGeneratingTextToImage": "Generating Text To Image", "statusGenerationComplete": "Generation Complete", "statusIterationComplete": "Iteration Complete", - "statusSavingImage": "Saving Image", + "statusLoadingModel": "Loading Model", + "statusMergedModels": "Models Merged", + "statusMergingModels": "Merging Models", + "statusModelChanged": "Model Changed", + "statusModelConverted": "Model Converted", + "statusPreparing": "Preparing", + "statusProcessingCanceled": "Processing Canceled", + "statusProcessingComplete": "Processing Complete", "statusRestoringFaces": "Restoring Faces", - "statusRestoringFacesGFPGAN": "Restoring Faces (GFPGAN)", "statusRestoringFacesCodeFormer": "Restoring Faces (CodeFormer)", + "statusRestoringFacesGFPGAN": "Restoring Faces (GFPGAN)", + "statusSavingImage": "Saving Image", "statusUpscaling": "Upscaling", "statusUpscalingESRGAN": "Upscaling (ESRGAN)", - "statusLoadingModel": "Loading Model", - "statusModelChanged": "Model Changed", - "statusConvertingModel": "Converting Model", - "statusModelConverted": "Model Converted", - "statusMergingModels": "Merging Models", - "statusMergedModels": "Models Merged", - "loading": "Loading", - "loadingInvokeAI": "Loading Invoke AI", - "random": "Random", - "generate": "Generate", - "openInNewTab": "Open in New Tab", - "dontAskMeAgain": "Don't ask me again", - "areYouSure": "Are you sure?", - "imagePrompt": "Image Prompt" + "training": "Training", + "trainingDesc1": "A dedicated workflow for training your own embeddings and checkpoints using Textual Inversion and Dreambooth from the web interface.", + "trainingDesc2": "InvokeAI already supports training custom embeddourings using Textual Inversion using the main script.", + "txt2img": "Text To Image", + "unifiedCanvas": "Unified Canvas", + "upload": "Upload" + }, + "controlnet": { + "amult": "a_mult", + "autoConfigure": "Auto configure processor", + "balanced": "Balanced", + "beginEndStepPercent": "Begin / End Step Percentage", + "bgth": "bg_th", + "canny": "Canny", + "cannyDescription": "Canny edge detection", + "coarse": "Coarse", + "contentShuffle": "Content Shuffle", + "contentShuffleDescription": "Shuffles the content in an image", + "control": "Control", + "controlMode": "Control Mode", + "crop": "Crop", + "delete": "Delete", + "depthMidas": "Depth (Midas)", + "depthMidasDescription": "Depth map generation using Midas", + "depthZoe": "Depth (Zoe)", + "depthZoeDescription": "Depth map generation using Zoe", + "detectResolution": "Detect Resolution", + "duplicate": "Duplicate", + "enableControlnet": "Enable ControlNet", + "f": "F", + "fill": "Fill", + "h": "H", + "handAndFace": "Hand and Face", + "hed": "HED", + "hedDescription": "Holistically-Nested Edge Detection", + "hideAdvanced": "Hide Advanced", + "highThreshold": "High Threshold", + "imageResolution": "Image Resolution", + "importImageFromCanvas": "Import Image From Canvas", + "importMaskFromCanvas": "Import Mask From Canvas", + "incompatibleBaseModel": "Incompatible base model:", + "lineart": "Lineart", + "lineartAnime": "Lineart Anime", + "lineartAnimeDescription": "Anime-style lineart processing", + "lineartDescription": "Converts image to lineart", + "lowThreshold": "Low Threshold", + "maxFaces": "Max Faces", + "mediapipeFace": "Mediapipe Face", + "mediapipeFaceDescription": "Face detection using Mediapipe", + "megaControl": "Mega Control", + "minConfidence": "Min Confidence", + "mlsd": "M-LSD", + "mlsdDescription": "Minimalist Line Segment Detector", + "none": "None", + "noneDescription": "No processing applied", + "normalBae": "Normal BAE", + "normalBaeDescription": "Normal BAE processing", + "openPose": "Openpose", + "openPoseDescription": "Human pose estimation using Openpose", + "pidi": "PIDI", + "pidiDescription": "PIDI image processing", + "processor": "Processor", + "prompt": "Prompt", + "resetControlImage": "Reset Control Image", + "resize": "Resize", + "resizeMode": "Resize Mode", + "safe": "Safe", + "saveControlImage": "Save Control Image", + "scribble": "scribble", + "selectModel": "Select a model", + "setControlImageDimensions": "Set Control Image Dimensions To W/H", + "showAdvanced": "Show Advanced", + "toggleControlNet": "Toggle this ControlNet", + "w": "W", + "weight": "Weight" + }, + "embedding": { + "addEmbedding": "Add Embedding", + "incompatibleModel": "Incompatible base model:", + "noMatchingEmbedding": "No matching Embeddings" }, "gallery": { - "generations": "Generations", - "showGenerations": "Show Generations", - "uploads": "Uploads", - "showUploads": "Show Uploads", - "galleryImageSize": "Image Size", - "galleryImageResetSize": "Reset Size", - "gallerySettings": "Gallery Settings", - "maintainAspectRatio": "Maintain Aspect Ratio", - "autoSwitchNewImages": "Auto-Switch to New Images", - "singleColumnLayout": "Single Column Layout", "allImagesLoaded": "All Images Loaded", - "loadMore": "Load More", - "noImagesInGallery": "No Images to Display", + "assets": "Assets", + "autoAssignBoardOnClick": "Auto-Assign Board on Click", + "autoSwitchNewImages": "Auto-Switch to New Images", + "copy": "Copy", + "currentlyInUse": "This image is currently in use in the following features:", "deleteImage": "Delete Image", "deleteImageBin": "Deleted images will be sent to your operating system's Bin.", "deleteImagePermanent": "Deleted images cannot be restored.", + "download": "Download", + "featuresWillReset": "If you delete this image, those features will immediately be reset.", + "galleryImageResetSize": "Reset Size", + "galleryImageSize": "Image Size", + "gallerySettings": "Gallery Settings", + "generations": "Generations", "images": "Images", - "assets": "Assets", - "autoAssignBoardOnClick": "Auto-Assign Board on Click" + "loading": "Loading", + "loadMore": "Load More", + "maintainAspectRatio": "Maintain Aspect Ratio", + "noImagesInGallery": "No Images to Display", + "setCurrentImage": "Set as Current Image", + "showGenerations": "Show Generations", + "showUploads": "Show Uploads", + "singleColumnLayout": "Single Column Layout", + "unableToLoad": "Unable to load Gallery", + "uploads": "Uploads" }, "hotkeys": { - "keyboardShortcuts": "Keyboard Shortcuts", - "appHotkeys": "App Hotkeys", - "generalHotkeys": "General Hotkeys", - "galleryHotkeys": "Gallery Hotkeys", - "unifiedCanvasHotkeys": "Unified Canvas Hotkeys", - "nodesHotkeys": "Nodes Hotkeys", - "invoke": { - "title": "Invoke", - "desc": "Generate an image" - }, - "cancel": { - "title": "Cancel", - "desc": "Cancel image generation" - }, - "focusPrompt": { - "title": "Focus Prompt", - "desc": "Focus the prompt input area" - }, - "toggleOptions": { - "title": "Toggle Options", - "desc": "Open and close the options panel" - }, - "pinOptions": { - "title": "Pin Options", - "desc": "Pin the options panel" - }, - "toggleViewer": { - "title": "Toggle Viewer", - "desc": "Open and close Image Viewer" - }, - "toggleGallery": { - "title": "Toggle Gallery", - "desc": "Open and close the gallery drawer" - }, - "maximizeWorkSpace": { - "title": "Maximize Workspace", - "desc": "Close panels and maximize work area" - }, - "changeTabs": { - "title": "Change Tabs", - "desc": "Switch to another workspace" - }, - "consoleToggle": { - "title": "Console Toggle", - "desc": "Open and close console" - }, - "setPrompt": { - "title": "Set Prompt", - "desc": "Use the prompt of the current image" - }, - "setSeed": { - "title": "Set Seed", - "desc": "Use the seed of the current image" - }, - "setParameters": { - "title": "Set Parameters", - "desc": "Use all parameters of the current image" - }, - "restoreFaces": { - "title": "Restore Faces", - "desc": "Restore the current image" - }, - "upscale": { - "title": "Upscale", - "desc": "Upscale the current image" - }, - "showInfo": { - "title": "Show Info", - "desc": "Show metadata info of the current image" - }, - "sendToImageToImage": { - "title": "Send To Image To Image", - "desc": "Send current image to Image to Image" - }, - "deleteImage": { - "title": "Delete Image", - "desc": "Delete the current image" - }, - "closePanels": { - "title": "Close Panels", - "desc": "Closes open panels" - }, - "previousImage": { - "title": "Previous Image", - "desc": "Display the previous image in gallery" - }, - "nextImage": { - "title": "Next Image", - "desc": "Display the next image in gallery" - }, - "toggleGalleryPin": { - "title": "Toggle Gallery Pin", - "desc": "Pins and unpins the gallery to the UI" - }, - "increaseGalleryThumbSize": { - "title": "Increase Gallery Image Size", - "desc": "Increases gallery thumbnails size" - }, - "decreaseGalleryThumbSize": { - "title": "Decrease Gallery Image Size", - "desc": "Decreases gallery thumbnails size" - }, - "selectBrush": { - "title": "Select Brush", - "desc": "Selects the canvas brush" - }, - "selectEraser": { - "title": "Select Eraser", - "desc": "Selects the canvas eraser" - }, - "decreaseBrushSize": { - "title": "Decrease Brush Size", - "desc": "Decreases the size of the canvas brush/eraser" - }, - "increaseBrushSize": { - "title": "Increase Brush Size", - "desc": "Increases the size of the canvas brush/eraser" - }, - "decreaseBrushOpacity": { - "title": "Decrease Brush Opacity", - "desc": "Decreases the opacity of the canvas brush" - }, - "increaseBrushOpacity": { - "title": "Increase Brush Opacity", - "desc": "Increases the opacity of the canvas brush" - }, - "moveTool": { - "title": "Move Tool", - "desc": "Allows canvas navigation" - }, - "fillBoundingBox": { - "title": "Fill Bounding Box", - "desc": "Fills the bounding box with brush color" - }, - "eraseBoundingBox": { - "title": "Erase Bounding Box", - "desc": "Erases the bounding box area" - }, - "colorPicker": { - "title": "Select Color Picker", - "desc": "Selects the canvas color picker" - }, - "toggleSnap": { - "title": "Toggle Snap", - "desc": "Toggles Snap to Grid" - }, - "quickToggleMove": { - "title": "Quick Toggle Move", - "desc": "Temporarily toggles Move mode" - }, - "toggleLayer": { - "title": "Toggle Layer", - "desc": "Toggles mask/base layer selection" - }, - "clearMask": { - "title": "Clear Mask", - "desc": "Clear the entire mask" - }, - "hideMask": { - "title": "Hide Mask", - "desc": "Hide and unhide mask" - }, - "showHideBoundingBox": { - "title": "Show/Hide Bounding Box", - "desc": "Toggle visibility of bounding box" - }, - "mergeVisible": { - "title": "Merge Visible", - "desc": "Merge all visible layers of canvas" - }, - "saveToGallery": { - "title": "Save To Gallery", - "desc": "Save current canvas to gallery" - }, - "copyToClipboard": { - "title": "Copy to Clipboard", - "desc": "Copy current canvas to clipboard" - }, - "downloadImage": { - "title": "Download Image", - "desc": "Download current canvas" - }, - "undoStroke": { - "title": "Undo Stroke", - "desc": "Undo a brush stroke" - }, - "redoStroke": { - "title": "Redo Stroke", - "desc": "Redo a brush stroke" - }, - "resetView": { - "title": "Reset View", - "desc": "Reset Canvas View" - }, - "previousStagingImage": { - "title": "Previous Staging Image", - "desc": "Previous Staging Area Image" - }, - "nextStagingImage": { - "title": "Next Staging Image", - "desc": "Next Staging Area Image" - }, "acceptStagingImage": { - "title": "Accept Staging Image", - "desc": "Accept Current Staging Area Image" + "desc": "Accept Current Staging Area Image", + "title": "Accept Staging Image" }, "addNodes": { - "title": "Add Nodes", - "desc": "Opens the add node menu" + "desc": "Opens the add node menu", + "title": "Add Nodes" + }, + "appHotkeys": "App Hotkeys", + "cancel": { + "desc": "Cancel image generation", + "title": "Cancel" + }, + "changeTabs": { + "desc": "Switch to another workspace", + "title": "Change Tabs" + }, + "clearMask": { + "desc": "Clear the entire mask", + "title": "Clear Mask" + }, + "closePanels": { + "desc": "Closes open panels", + "title": "Close Panels" + }, + "colorPicker": { + "desc": "Selects the canvas color picker", + "title": "Select Color Picker" + }, + "consoleToggle": { + "desc": "Open and close console", + "title": "Console Toggle" + }, + "copyToClipboard": { + "desc": "Copy current canvas to clipboard", + "title": "Copy to Clipboard" + }, + "decreaseBrushOpacity": { + "desc": "Decreases the opacity of the canvas brush", + "title": "Decrease Brush Opacity" + }, + "decreaseBrushSize": { + "desc": "Decreases the size of the canvas brush/eraser", + "title": "Decrease Brush Size" + }, + "decreaseGalleryThumbSize": { + "desc": "Decreases gallery thumbnails size", + "title": "Decrease Gallery Image Size" + }, + "deleteImage": { + "desc": "Delete the current image", + "title": "Delete Image" + }, + "downloadImage": { + "desc": "Download current canvas", + "title": "Download Image" + }, + "eraseBoundingBox": { + "desc": "Erases the bounding box area", + "title": "Erase Bounding Box" + }, + "fillBoundingBox": { + "desc": "Fills the bounding box with brush color", + "title": "Fill Bounding Box" + }, + "focusPrompt": { + "desc": "Focus the prompt input area", + "title": "Focus Prompt" + }, + "galleryHotkeys": "Gallery Hotkeys", + "generalHotkeys": "General Hotkeys", + "hideMask": { + "desc": "Hide and unhide mask", + "title": "Hide Mask" + }, + "increaseBrushOpacity": { + "desc": "Increases the opacity of the canvas brush", + "title": "Increase Brush Opacity" + }, + "increaseBrushSize": { + "desc": "Increases the size of the canvas brush/eraser", + "title": "Increase Brush Size" + }, + "increaseGalleryThumbSize": { + "desc": "Increases gallery thumbnails size", + "title": "Increase Gallery Image Size" + }, + "invoke": { + "desc": "Generate an image", + "title": "Invoke" + }, + "keyboardShortcuts": "Keyboard Shortcuts", + "maximizeWorkSpace": { + "desc": "Close panels and maximize work area", + "title": "Maximize Workspace" + }, + "mergeVisible": { + "desc": "Merge all visible layers of canvas", + "title": "Merge Visible" + }, + "moveTool": { + "desc": "Allows canvas navigation", + "title": "Move Tool" + }, + "nextImage": { + "desc": "Display the next image in gallery", + "title": "Next Image" + }, + "nextStagingImage": { + "desc": "Next Staging Area Image", + "title": "Next Staging Image" + }, + "nodesHotkeys": "Nodes Hotkeys", + "pinOptions": { + "desc": "Pin the options panel", + "title": "Pin Options" + }, + "previousImage": { + "desc": "Display the previous image in gallery", + "title": "Previous Image" + }, + "previousStagingImage": { + "desc": "Previous Staging Area Image", + "title": "Previous Staging Image" + }, + "quickToggleMove": { + "desc": "Temporarily toggles Move mode", + "title": "Quick Toggle Move" + }, + "redoStroke": { + "desc": "Redo a brush stroke", + "title": "Redo Stroke" + }, + "resetView": { + "desc": "Reset Canvas View", + "title": "Reset View" + }, + "restoreFaces": { + "desc": "Restore the current image", + "title": "Restore Faces" + }, + "saveToGallery": { + "desc": "Save current canvas to gallery", + "title": "Save To Gallery" + }, + "selectBrush": { + "desc": "Selects the canvas brush", + "title": "Select Brush" + }, + "selectEraser": { + "desc": "Selects the canvas eraser", + "title": "Select Eraser" + }, + "sendToImageToImage": { + "desc": "Send current image to Image to Image", + "title": "Send To Image To Image" + }, + "setParameters": { + "desc": "Use all parameters of the current image", + "title": "Set Parameters" + }, + "setPrompt": { + "desc": "Use the prompt of the current image", + "title": "Set Prompt" + }, + "setSeed": { + "desc": "Use the seed of the current image", + "title": "Set Seed" + }, + "showHideBoundingBox": { + "desc": "Toggle visibility of bounding box", + "title": "Show/Hide Bounding Box" + }, + "showInfo": { + "desc": "Show metadata info of the current image", + "title": "Show Info" + }, + "toggleGallery": { + "desc": "Open and close the gallery drawer", + "title": "Toggle Gallery" + }, + "toggleGalleryPin": { + "desc": "Pins and unpins the gallery to the UI", + "title": "Toggle Gallery Pin" + }, + "toggleLayer": { + "desc": "Toggles mask/base layer selection", + "title": "Toggle Layer" + }, + "toggleOptions": { + "desc": "Open and close the options panel", + "title": "Toggle Options" + }, + "toggleSnap": { + "desc": "Toggles Snap to Grid", + "title": "Toggle Snap" + }, + "toggleViewer": { + "desc": "Open and close Image Viewer", + "title": "Toggle Viewer" + }, + "undoStroke": { + "desc": "Undo a brush stroke", + "title": "Undo Stroke" + }, + "unifiedCanvasHotkeys": "Unified Canvas Hotkeys", + "upscale": { + "desc": "Upscale the current image", + "title": "Upscale" } }, - "modelManager": { - "modelManager": "Model Manager", + "metadata": { + "cfgScale": "CFG scale", + "createdBy": "Created By", + "fit": "Image to image fit", + "generationMode": "Generation Mode", + "height": "Height", + "hiresFix": "High Resolution Optimization", + "imageDetails": "Image Details", + "initImage": "Initial image", + "metadata": "Metadata", "model": "Model", - "vae": "VAE", - "allModels": "All Models", - "checkpointModels": "Checkpoints", - "diffusersModels": "Diffusers", - "loraModels": "LoRAs", - "safetensorModels": "SafeTensors", - "onnxModels": "Onnx", - "oliveModels": "Olives", - "modelAdded": "Model Added", - "modelUpdated": "Model Updated", - "modelUpdateFailed": "Model Update Failed", - "modelEntryDeleted": "Model Entry Deleted", - "cannotUseSpaces": "Cannot Use Spaces", + "negativePrompt": "Negative Prompt", + "noImageDetails": "No image details found", + "noMetaData": "No metadata found", + "perlin": "Perlin Noise", + "positivePrompt": "Positive Prompt", + "scheduler": "Scheduler", + "seamless": "Seamless", + "seed": "Seed", + "steps": "Steps", + "strength": "Image to image strength", + "Threshold": "Noise Threshold", + "variations": "Seed-weight pairs", + "width": "Width", + "workflow": "Workflow" + }, + "modelManager": { + "active": "active", + "addCheckpointModel": "Add Checkpoint / Safetensor Model", + "addDifference": "Add Difference", + "addDiffuserModel": "Add Diffusers", + "addManually": "Add Manually", + "addModel": "Add Model", "addNew": "Add New", "addNewModel": "Add New Model", - "addCheckpointModel": "Add Checkpoint / Safetensor Model", - "addDiffuserModel": "Add Diffusers", - "scanForModels": "Scan For Models", - "addManually": "Add Manually", - "manual": "Manual", + "addSelected": "Add Selected", + "advanced": "Advanced", + "allModels": "All Models", + "alpha": "Alpha", + "availableModels": "Available Models", "baseModel": "Base Model", - "name": "Name", - "nameValidationMsg": "Enter a name for your model", - "description": "Description", - "descriptionValidationMsg": "Add a description for your model", + "cached": "cached", + "cannotUseSpaces": "Cannot Use Spaces", + "checkpointFolder": "Checkpoint Folder", + "checkpointModels": "Checkpoints", + "clearCheckpointFolder": "Clear Checkpoint Folder", + "closeAdvanced": "Close Advanced", "config": "Config", "configValidationMsg": "Path to the config file of your model.", - "modelLocation": "Model Location", - "modelLocationValidationMsg": "Path to where your model is located locally.", - "repo_id": "Repo ID", - "repoIDValidationMsg": "Online repository of your model", - "vaeLocation": "VAE Location", - "vaeLocationValidationMsg": "Path to where your VAE is located.", - "variant": "Variant", - "vaeRepoID": "VAE Repo ID", - "vaeRepoIDValidationMsg": "Online repository of your VAE", - "width": "Width", - "widthValidationMsg": "Default width of your model.", - "height": "Height", - "heightValidationMsg": "Default height of your model.", - "addModel": "Add Model", - "updateModel": "Update Model", - "availableModels": "Available Models", - "search": "Search", - "load": "Load", - "active": "active", - "notLoaded": "not loaded", - "cached": "cached", - "checkpointFolder": "Checkpoint Folder", - "clearCheckpointFolder": "Clear Checkpoint Folder", - "findModels": "Find Models", - "scanAgain": "Scan Again", - "modelsFound": "Models Found", - "selectFolder": "Select Folder", - "selected": "Selected", - "selectAll": "Select All", - "deselectAll": "Deselect All", - "showExisting": "Show Existing", - "addSelected": "Add Selected", - "modelExists": "Model Exists", - "selectAndAdd": "Select and Add Models Listed Below", - "noModelsFound": "No Models Found", - "delete": "Delete", - "deleteModel": "Delete Model", - "deleteConfig": "Delete Config", - "deleteMsg1": "Are you sure you want to delete this model from InvokeAI?", - "modelDeleted": "Model Deleted", - "modelDeleteFailed": "Failed to delete model", - "deleteMsg2": "This WILL delete the model from disk if it is in the InvokeAI root folder. If you are using a custom location, then the model WILL NOT be deleted from disk.", - "formMessageDiffusersModelLocation": "Diffusers Model Location", - "formMessageDiffusersModelLocationDesc": "Please enter at least one.", - "formMessageDiffusersVAELocation": "VAE Location", - "formMessageDiffusersVAELocationDesc": "If not provided, InvokeAI will look for the VAE file inside the model location given above.", "convert": "Convert", + "convertingModelBegin": "Converting Model. Please wait.", "convertToDiffusers": "Convert To Diffusers", "convertToDiffusersHelpText1": "This model will be converted to the 🧨 Diffusers format.", "convertToDiffusersHelpText2": "This process will replace your Model Manager entry with the Diffusers version of the same model.", @@ -422,318 +497,492 @@ "convertToDiffusersHelpText5": "Please make sure you have enough disk space. Models generally vary between 2GB-7GB in size.", "convertToDiffusersHelpText6": "Do you wish to convert this model?", "convertToDiffusersSaveLocation": "Save Location", - "noCustomLocationProvided": "No Custom Location Provided", - "convertingModelBegin": "Converting Model. Please wait.", - "v1": "v1", - "v2_base": "v2 (512px)", - "v2_768": "v2 (768px)", - "inpainting": "v1 Inpainting", - "customConfig": "Custom Config", - "pathToCustomConfig": "Path To Custom Config", - "statusConverting": "Converting", - "modelConverted": "Model Converted", - "modelConversionFailed": "Model Conversion Failed", - "sameFolder": "Same folder", - "invokeRoot": "InvokeAI folder", "custom": "Custom", + "customConfig": "Custom Config", + "customConfigFileLocation": "Custom Config File Location", "customSaveLocation": "Custom Save Location", - "merge": "Merge", - "modelsMerged": "Models Merged", - "modelsMergeFailed": "Model Merge Failed", - "mergeModels": "Merge Models", - "modelOne": "Model 1", - "modelTwo": "Model 2", - "modelThree": "Model 3", - "mergedModelName": "Merged Model Name", - "alpha": "Alpha", - "interpolationType": "Interpolation Type", - "mergedModelSaveLocation": "Save Location", - "mergedModelCustomSaveLocation": "Custom Path", - "invokeAIFolder": "Invoke AI Folder", + "delete": "Delete", + "deleteConfig": "Delete Config", + "deleteModel": "Delete Model", + "deleteMsg1": "Are you sure you want to delete this model from InvokeAI?", + "deleteMsg2": "This WILL delete the model from disk if it is in the InvokeAI root folder. If you are using a custom location, then the model WILL NOT be deleted from disk.", + "description": "Description", + "descriptionValidationMsg": "Add a description for your model", + "deselectAll": "Deselect All", + "diffusersModels": "Diffusers", + "findModels": "Find Models", + "formMessageDiffusersModelLocation": "Diffusers Model Location", + "formMessageDiffusersModelLocationDesc": "Please enter at least one.", + "formMessageDiffusersVAELocation": "VAE Location", + "formMessageDiffusersVAELocationDesc": "If not provided, InvokeAI will look for the VAE file inside the model location given above.", + "height": "Height", + "heightValidationMsg": "Default height of your model.", "ignoreMismatch": "Ignore Mismatches Between Selected Models", + "importModels": "Import Models", + "inpainting": "v1 Inpainting", + "interpolationType": "Interpolation Type", + "inverseSigmoid": "Inverse Sigmoid", + "invokeAIFolder": "Invoke AI Folder", + "invokeRoot": "InvokeAI folder", + "load": "Load", + "loraModels": "LoRAs", + "manual": "Manual", + "merge": "Merge", + "mergedModelCustomSaveLocation": "Custom Path", + "mergedModelName": "Merged Model Name", + "mergedModelSaveLocation": "Save Location", + "mergeModels": "Merge Models", + "model": "Model", + "modelAdded": "Model Added", + "modelConversionFailed": "Model Conversion Failed", + "modelConverted": "Model Converted", + "modelDeleted": "Model Deleted", + "modelDeleteFailed": "Failed to delete model", + "modelEntryDeleted": "Model Entry Deleted", + "modelExists": "Model Exists", + "modelLocation": "Model Location", + "modelLocationValidationMsg": "Provide the path to a local folder where your Diffusers Model is stored", + "modelManager": "Model Manager", + "modelMergeAlphaHelp": "Alpha controls blend strength for the models. Lower alpha values lead to lower influence of the second model.", "modelMergeHeaderHelp1": "You can merge up to three different models to create a blend that suits your needs.", "modelMergeHeaderHelp2": "Only Diffusers are available for merging. If you want to merge a checkpoint model, please convert it to Diffusers first.", - "modelMergeAlphaHelp": "Alpha controls blend strength for the models. Lower alpha values lead to lower influence of the second model.", "modelMergeInterpAddDifferenceHelp": "In this mode, Model 3 is first subtracted from Model 2. The resulting version is blended with Model 1 with the alpha rate set above.", - "inverseSigmoid": "Inverse Sigmoid", - "sigmoid": "Sigmoid", - "weightedSum": "Weighted Sum", + "modelOne": "Model 1", + "modelsFound": "Models Found", + "modelsMerged": "Models Merged", + "modelsMergeFailed": "Model Merge Failed", + "modelsSynced": "Models Synced", + "modelSyncFailed": "Model Sync Failed", + "modelThree": "Model 3", + "modelTwo": "Model 2", + "modelType": "Model Type", + "modelUpdated": "Model Updated", + "modelUpdateFailed": "Model Update Failed", + "name": "Name", + "nameValidationMsg": "Enter a name for your model", + "noCustomLocationProvided": "No Custom Location Provided", + "noModels": "No Models Found", + "noModelsFound": "No Models Found", "none": "none", - "addDifference": "Add Difference", + "notLoaded": "not loaded", + "oliveModels": "Olives", + "onnxModels": "Onnx", + "pathToCustomConfig": "Path To Custom Config", "pickModelType": "Pick Model Type", + "predictionType": "Prediction Type (for Stable Diffusion 2.x Models only)", + "quickAdd": "Quick Add", + "repo_id": "Repo ID", + "repoIDValidationMsg": "Online repository of your model", + "safetensorModels": "SafeTensors", + "sameFolder": "Same folder", + "scanAgain": "Scan Again", + "scanForModels": "Scan For Models", + "search": "Search", + "selectAll": "Select All", + "selectAndAdd": "Select and Add Models Listed Below", + "selected": "Selected", + "selectFolder": "Select Folder", "selectModel": "Select Model", - "importModels": "Import Models", "settings": "Settings", + "showExisting": "Show Existing", + "sigmoid": "Sigmoid", + "simpleModelDesc": "Provide a path to a local Diffusers model, local checkpoint / safetensors model a HuggingFace Repo ID, or a checkpoint/diffusers model URL.", + "statusConverting": "Converting", "syncModels": "Sync Models", "syncModelsDesc": "If your models are out of sync with the backend, you can refresh them up using this option. This is generally handy in cases where you manually update your models.yaml file or add models to the InvokeAI root folder after the application has booted.", - "modelsSynced": "Models Synced", - "modelSyncFailed": "Model Sync Failed" + "updateModel": "Update Model", + "useCustomConfig": "Use Custom Config", + "v1": "v1", + "v2_768": "v2 (768px)", + "v2_base": "v2 (512px)", + "vae": "VAE", + "vaeLocation": "VAE Location", + "vaeLocationValidationMsg": "Path to where your VAE is located.", + "vaeRepoID": "VAE Repo ID", + "vaeRepoIDValidationMsg": "Online repository of your VAE", + "variant": "Variant", + "weightedSum": "Weighted Sum", + "width": "Width", + "widthValidationMsg": "Default width of your model." + }, + "models": { + "loading": "loading", + "noLoRAsAvailable": "No LoRAs available", + "noMatchingLoRAs": "No matching LoRAs", + "noMatchingModels": "No matching Models", + "noModelsAvailable": "No Modelss available", + "selectLoRA": "Select a LoRA", + "selectModel": "Select a Model" + }, + "nodes": { + "addNode": "Add Node", + "addNodeToolTip": "Add Node (Shift+A, Space)", + "animatedEdges": "Animated Edges", + "animatedEdgesHelp": "Animate selected edges and edges connected to selected nodes", + "cannotConnectInputToInput": "Cannot connect input to input", + "cannotConnectOutputToOutput": "Cannot connect output to output", + "cannotConnectToSelf": "Cannot connect to self", + "colorCodeEdges": "Color-Code Edges", + "colorCodeEdgesHelp": "Color-code edges according to their connected fields", + "connectionWouldCreateCycle": "Connection would create a cycle", + "currentImage": "Current Image", + "currentImageDescription": "Displays the current image in the Node Editor", + "downloadWorkflow": "Download Workflow JSON", + "fieldTypesMustMatch": "Field types must match", + "fitViewportNodes": "Fit View", + "fullyContainNodes": "Fully Contain Nodes to Select", + "fullyContainNodesHelp": "Nodes must be fully inside the selection box to be selected", + "hideGraphNodes": "Hide Graph Overlay", + "hideLegendNodes": "Hide Field Type Legend", + "hideMinimapnodes": "Hide MiniMap", + "inputMayOnlyHaveOneConnection": "Input may only have one connection", + "loadingNodes": "Loading Nodes...", + "loadWorkflow": "Load Workflow", + "noConnectionData": "No connection data", + "noConnectionInProgress": "No connection in progress", + "nodeOutputs": "Node Outputs", + "nodeSearch": "Search for nodes", + "nodeTemplate": "Node Template", + "noFieldsLinearview": "No fields added to Linear View", + "noFieldType": "No field type", + "noMatchingNodes": "No matching nodes", + "noNodeSelected": "No node selected", + "noOpacity": "Node Opacity", + "noOutputRecorded": "No outputs recorded", + "notes": "Notes", + "notesDescription": "Add notes about your workflow", + "pickOne": "Pick One", + "problemSettingTitle": "Problem Setting Title", + "reloadNodeTemplates": "Reload Node Templates", + "removeLinearView": "Remove from Linear View", + "resetWorkflow": "Reset Workflow", + "resetWorkflowDesc": "Are you sure you want to reset this workflow?", + "resetWorkflowDesc2": "Resetting the workflow will clear all nodes, edges and workflow details.", + "showGraphNodes": "Show Graph Overlay", + "showLegendNodes": "Show Field Type Legend", + "showMinimapnodes": "Show MiniMap", + "snapToGrid": "Snap to Grid", + "snapToGridHelp": "Snap nodes to grid when moved", + "unableToLoadWorkflow": "Unable to Validate Workflow", + "unableToValidateWorkflow": "Unable to Validate Workflow", + "unknownField": "Unknown Field", + "unkownInvocation": "Unknown Invocation type", + "validateConnections": "Validate Connections and Graph", + "validateConnectionsHelp": "Prevent invalid connections from being made, and invalid graphs from being invoked", + "workflow": "Workflow", + "workflowAuthor": "Author", + "workflowContact": "Contact", + "workflowDescription": "Short Description", + "workflowName": "Name", + "workflowNotes": "Notes", + "workflowSettings": "Workflow Editor Settings", + "workflowTags": "Tags", + "workflowValidation": "Workflow Validation Error", + "workflowVersion": "Version", + "zoomInNodes": "Zoom In", + "zoomOutNodes": "Zoom Out", + "executionStateError": "Error", + "executionStateCompleted": "Completed", + "executionStateInProgress": "In Progress", + "versionUnknown": " Version Unknown", + "unknownNode": "Unknown Node", + "version": "Version", + "updateApp": "Update App", + "unknownTemplate": "Unknown Template" }, "parameters": { - "general": "General", - "images": "Images", - "steps": "Steps", - "cfgScale": "CFG Scale", - "width": "Width", - "height": "Height", - "scheduler": "Scheduler", - "seed": "Seed", - "boundingBoxWidth": "Bounding Box Width", + "aspectRatio": "Ratio", + "boundingBoxHeader": "Bounding Box", "boundingBoxHeight": "Bounding Box Height", - "imageToImage": "Image to Image", - "randomizeSeed": "Randomize Seed", - "shuffle": "Shuffle Seed", - "noiseThreshold": "Noise Threshold", - "perlinNoise": "Perlin Noise", - "noiseSettings": "Noise", - "variations": "Variations", - "variationAmount": "Variation Amount", - "seedWeights": "Seed Weights", - "faceRestoration": "Face Restoration", - "restoreFaces": "Restore Faces", - "type": "Type", - "strength": "Strength", - "upscaling": "Upscaling", - "upscale": "Upscale", - "upscaleImage": "Upscale Image", + "boundingBoxWidth": "Bounding Box Width", + "cancel": { + "cancel": "Cancel", + "immediate": "Cancel immediately", + "isScheduled": "Canceling", + "schedule": "Cancel after current iteration", + "setType": "Set cancel type" + }, + "cfgScale": "CFG Scale", + "clipSkip": "CLIP Skip", + "closeViewer": "Close Viewer", + "codeformerFidelity": "Fidelity", + "coherenceMode": "Mode", + "coherencePassHeader": "Coherence Pass", + "coherenceSteps": "Steps", + "coherenceStrength": "Strength", + "compositingSettingsHeader": "Compositing Settings", + "controlNetControlMode": "Control Mode", + "copyImage": "Copy Image", + "copyImageToLink": "Copy Image To Link", "denoisingStrength": "Denoising Strength", - "scale": "Scale", - "otherOptions": "Other Options", - "seamlessTiling": "Seamless Tiling", - "seamlessXAxis": "X Axis", - "seamlessYAxis": "Y Axis", + "downloadImage": "Download Image", + "enableNoiseSettings": "Enable Noise Settings", + "faceRestoration": "Face Restoration", + "general": "General", + "height": "Height", + "hidePreview": "Hide Preview", "hiresOptim": "High Res Optimization", "hiresStrength": "High Res Strength", + "hSymmetryStep": "H Symmetry Step", "imageFit": "Fit Initial Image To Output Size", - "codeformerFidelity": "Fidelity", - "compositingSettingsHeader": "Compositing Settings", + "images": "Images", + "imageToImage": "Image to Image", + "img2imgStrength": "Image To Image Strength", + "infillMethod": "Infill Method", + "infillScalingHeader": "Infill and Scaling", + "info": "Info", + "initialImage": "Initial Image", + "invoke": { + "addingImagesTo": "Adding images to", + "invoke": "Invoke", + "missingFieldTemplate": "Missing field template", + "missingInputForField": "{{nodeLabel}} -> {{fieldLabel}} missing input", + "missingNodeTemplate": "Missing node template", + "noControlImageForControlNet": "ControlNet {{index}} has no control image", + "noInitialImageSelected": "No initial image selected", + "noModelForControlNet": "ControlNet {{index}} has no model selected.", + "noModelSelected": "No model selected", + "noNodesInGraph": "No nodes in graph", + "readyToInvoke": "Ready to Invoke", + "systemBusy": "System busy", + "systemDisconnected": "System disconnected", + "unableToInvoke": "Unable to Invoke" + }, "maskAdjustmentsHeader": "Mask Adjustments", "maskBlur": "Blur", "maskBlurMethod": "Blur Method", - "coherencePassHeader": "Coherence Pass", - "coherenceMode": "Mode", - "coherenceSteps": "Steps", - "coherenceStrength": "Strength", - "seamLowThreshold": "Low", - "seamHighThreshold": "High", - "scaleBeforeProcessing": "Scale Before Processing", - "scaledWidth": "Scaled W", - "scaledHeight": "Scaled H", - "infillMethod": "Infill Method", - "tileSize": "Tile Size", - "patchmatchDownScaleSize": "Downscale", - "boundingBoxHeader": "Bounding Box", - "seamCorrectionHeader": "Seam Correction", - "infillScalingHeader": "Infill and Scaling", - "img2imgStrength": "Image To Image Strength", - "toggleLoopback": "Toggle Loopback", - "symmetry": "Symmetry", - "hSymmetryStep": "H Symmetry Step", - "vSymmetryStep": "V Symmetry Step", - "invoke": "Invoke", - "cancel": { - "immediate": "Cancel immediately", - "schedule": "Cancel after current iteration", - "isScheduled": "Canceling", - "setType": "Set cancel type" - }, - "positivePromptPlaceholder": "Positive Prompt", "negativePromptPlaceholder": "Negative Prompt", + "noiseSettings": "Noise", + "noiseThreshold": "Noise Threshold", + "openInViewer": "Open In Viewer", + "otherOptions": "Other Options", + "patchmatchDownScaleSize": "Downscale", + "perlinNoise": "Perlin Noise", + "positivePromptPlaceholder": "Positive Prompt", + "randomizeSeed": "Randomize Seed", + "restoreFaces": "Restore Faces", + "scale": "Scale", + "scaleBeforeProcessing": "Scale Before Processing", + "scaledHeight": "Scaled H", + "scaledWidth": "Scaled W", + "scheduler": "Scheduler", + "seamCorrectionHeader": "Seam Correction", + "seamHighThreshold": "High", + "seamlessTiling": "Seamless Tiling", + "seamlessXAxis": "X Axis", + "seamlessYAxis": "Y Axis", + "seamLowThreshold": "Low", + "seed": "Seed", + "seedWeights": "Seed Weights", "sendTo": "Send to", "sendToImg2Img": "Send to Image to Image", "sendToUnifiedCanvas": "Send To Unified Canvas", - "copyImage": "Copy Image", - "copyImageToLink": "Copy Image To Link", - "downloadImage": "Download Image", - "openInViewer": "Open In Viewer", - "closeViewer": "Close Viewer", + "showOptionsPanel": "Show Options Panel", + "showPreview": "Show Preview", + "shuffle": "Shuffle Seed", + "steps": "Steps", + "strength": "Strength", + "symmetry": "Symmetry", + "tileSize": "Tile Size", + "toggleLoopback": "Toggle Loopback", + "type": "Type", + "upscale": "Upscale", + "upscaleImage": "Upscale Image", + "upscaling": "Upscaling", + "useAll": "Use All", + "useCpuNoise": "Use CPU Noise", + "useInitImg": "Use Initial Image", "usePrompt": "Use Prompt", "useSeed": "Use Seed", - "useAll": "Use All", - "useInitImg": "Use Initial Image", - "info": "Info", - "initialImage": "Initial Image", - "showOptionsPanel": "Show Options Panel", - "hidePreview": "Hide Preview", - "showPreview": "Show Preview", - "controlNetControlMode": "Control Mode", - "clipSkip": "CLIP Skip", - "aspectRatio": "Ratio" + "variationAmount": "Variation Amount", + "variations": "Variations", + "vSymmetryStep": "V Symmetry Step", + "width": "Width" + }, + "prompt": { + "combinatorial": "Combinatorial Generation", + "dynamicPrompts": "Dynamic Prompts", + "enableDynamicPrompts": "Enable Dynamic Prompts", + "maxPrompts": "Max Prompts" + }, + "sdxl": { + "cfgScale": "CFG Scale", + "concatPromptStyle": "Concatenate Prompt & Style", + "denoisingStrength": "Denoising Strength", + "loading": "Loading...", + "negAestheticScore": "Negative Aesthetic Score", + "negStylePrompt": "Negative Style Prompt", + "noModelsAvailable": "No models available", + "posAestheticScore": "Positive Aesthetic Score", + "posStylePrompt": "Positive Style Prompt", + "refiner": "Refiner", + "refinermodel": "Refiner Model", + "refinerStart": "Refiner Start", + "scheduler": "Scheduler", + "selectAModel": "Select a model", + "steps": "Steps", + "useRefiner": "Use Refiner" }, "settings": { - "models": "Models", - "displayInProgress": "Display Progress Images", - "saveSteps": "Save images every n steps", - "confirmOnDelete": "Confirm On Delete", - "displayHelpIcons": "Display Help Icons", "alternateCanvasLayout": "Alternate Canvas Layout", - "enableNodesEditor": "Enable Nodes Editor", - "enableImageDebugging": "Enable Image Debugging", - "useSlidersForAll": "Use Sliders For All Options", - "showProgressInViewer": "Show Progress Images in Viewer", "antialiasProgressImages": "Antialias Progress Images", "autoChangeDimensions": "Update W/H To Model Defaults On Change", + "beta": "Beta", + "confirmOnDelete": "Confirm On Delete", + "consoleLogLevel": "Log Level", + "developer": "Developer", + "displayHelpIcons": "Display Help Icons", + "displayInProgress": "Display Progress Images", + "enableImageDebugging": "Enable Image Debugging", + "enableNodesEditor": "Enable Nodes Editor", + "experimental": "Experimental", + "favoriteSchedulers": "Favorite Schedulers", + "favoriteSchedulersPlaceholder": "No schedulers favorited", + "general": "General", + "generation": "Generation", + "models": "Models", + "resetComplete": "Web UI has been reset.", "resetWebUI": "Reset Web UI", "resetWebUIDesc1": "Resetting the web UI only resets the browser's local cache of your images and remembered settings. It does not delete any images from disk.", "resetWebUIDesc2": "If images aren't showing up in the gallery or something else isn't working, please try resetting before submitting an issue on GitHub.", - "resetComplete": "Web UI has been reset.", - "consoleLogLevel": "Log Level", + "saveSteps": "Save images every n steps", "shouldLogToConsole": "Console Logging", - "developer": "Developer", - "general": "General", - "generation": "Generation", - "ui": "User Interface", - "favoriteSchedulers": "Favorite Schedulers", - "favoriteSchedulersPlaceholder": "No schedulers favorited", "showAdvancedOptions": "Show Advanced Options", - "experimental": "Experimental", - "beta": "Beta" + "showProgressInViewer": "Show Progress Images in Viewer", + "ui": "User Interface", + "useSlidersForAll": "Use Sliders For All Options" }, "toast": { - "serverError": "Server Error", - "disconnected": "Disconnected from Server", - "connected": "Connected to Server", "canceled": "Processing Canceled", - "tempFoldersEmptied": "Temp Folder Emptied", - "uploadFailed": "Upload failed", - "uploadFailedUnableToLoadDesc": "Unable to load file", - "uploadFailedInvalidUploadDesc": "Must be single PNG or JPEG image", + "canvasMerged": "Canvas Merged", + "connected": "Connected to Server", + "disconnected": "Disconnected from Server", "downloadImageStarted": "Image Download Started", + "faceRestoreFailed": "Face Restoration Failed", "imageCopied": "Image Copied", - "problemCopyingImage": "Unable to Copy Image", "imageLinkCopied": "Image Link Copied", - "problemCopyingImageLink": "Unable to Copy Image Link", "imageNotLoaded": "No Image Loaded", "imageNotLoadedDesc": "Could not find image", "imageSavedToGallery": "Image Saved to Gallery", - "canvasMerged": "Canvas Merged", - "sentToImageToImage": "Sent To Image To Image", - "sentToUnifiedCanvas": "Sent to Unified Canvas", - "parameterSet": "Parameter set", - "parameterNotSet": "Parameter not set", - "parametersSet": "Parameters Set", - "parametersNotSet": "Parameters Not Set", - "parametersNotSetDesc": "No metadata found for this image.", - "parametersFailed": "Problem loading parameters", - "parametersFailedDesc": "Unable to load init image.", - "seedSet": "Seed Set", - "seedNotSet": "Seed Not Set", - "seedNotSetDesc": "Could not find seed for this image.", - "promptSet": "Prompt Set", - "promptNotSet": "Prompt Not Set", - "promptNotSetDesc": "Could not find prompt for this image.", - "upscalingFailed": "Upscaling Failed", - "faceRestoreFailed": "Face Restoration Failed", - "metadataLoadFailed": "Failed to load metadata", - "initialImageSet": "Initial Image Set", "initialImageNotSet": "Initial Image Not Set", "initialImageNotSetDesc": "Could not load initial image", - "nodesSaved": "Nodes Saved", + "initialImageSet": "Initial Image Set", + "metadataLoadFailed": "Failed to load metadata", + "modelAdded": "Model Added: {{modelName}}", + "modelAddedSimple": "Model Added", + "modelAddFailed": "Model Add Failed", + "nodesBrokenConnections": "Cannot load. Some connections are broken.", + "nodesCleared": "Nodes Cleared", + "nodesCorruptedGraph": "Cannot load. Graph seems to be corrupted.", "nodesLoaded": "Nodes Loaded", + "nodesLoadedFailed": "Failed To Load Nodes", "nodesNotValidGraph": "Not a valid InvokeAI Node Graph", "nodesNotValidJSON": "Not a valid JSON", - "nodesCorruptedGraph": "Cannot load. Graph seems to be corrupted.", + "nodesSaved": "Nodes Saved", "nodesUnrecognizedTypes": "Cannot load. Graph has unrecognized types", - "nodesBrokenConnections": "Cannot load. Some connections are broken.", - "nodesLoadedFailed": "Failed To Load Nodes", - "nodesCleared": "Nodes Cleared" + "parameterNotSet": "Parameter not set", + "parameterSet": "Parameter set", + "parametersFailed": "Problem loading parameters", + "parametersFailedDesc": "Unable to load init image.", + "parametersNotSet": "Parameters Not Set", + "parametersNotSetDesc": "No metadata found for this image.", + "parametersSet": "Parameters Set", + "problemCopyingImage": "Unable to Copy Image", + "problemCopyingImageLink": "Unable to Copy Image Link", + "promptNotSet": "Prompt Not Set", + "promptNotSetDesc": "Could not find prompt for this image.", + "promptSet": "Prompt Set", + "seedNotSet": "Seed Not Set", + "seedNotSetDesc": "Could not find seed for this image.", + "seedSet": "Seed Set", + "sentToImageToImage": "Sent To Image To Image", + "sentToUnifiedCanvas": "Sent to Unified Canvas", + "serverError": "Server Error", + "tempFoldersEmptied": "Temp Folder Emptied", + "uploadFailed": "Upload failed", + "uploadFailedInvalidUploadDesc": "Must be single PNG or JPEG image", + "uploadFailedUnableToLoadDesc": "Unable to load file", + "upscalingFailed": "Upscaling Failed" }, "tooltip": { "feature": { - "prompt": "This is the prompt field. Prompt includes generation objects and stylistic terms. You can add weight (token importance) in the prompt as well, but CLI commands and parameters will not work.", - "gallery": "Gallery displays generations from the outputs folder as they're created. Settings are stored within files and accesed by context menu.", - "other": "These options will enable alternative processing modes for Invoke. 'Seamless tiling' will create repeating patterns in the output. 'High resolution' is generation in two steps with img2img: use this setting when you want a larger and more coherent image without artifacts. It will take longer than usual txt2img.", - "seed": "Seed value affects the initial noise from which the image is formed. You can use the already existing seeds from previous images. 'Noise Threshold' is used to mitigate artifacts at high CFG values (try the 0-10 range), and Perlin to add Perlin noise during generation: both serve to add variation to your outputs.", - "variations": "Try a variation with a value between 0.1 and 1.0 to change the result for a given seed. Interesting variations of the seed are between 0.1 and 0.3.", - "upscale": "Use ESRGAN to enlarge the image immediately after generation.", - "faceCorrection": "Face correction with GFPGAN or Codeformer: the algorithm detects faces in the image and corrects any defects. High value will change the image more, resulting in more attractive faces. Codeformer with a higher fidelity preserves the original image at the expense of stronger face correction.", - "imageToImage": "Image to Image loads any image as initial, which is then used to generate a new one along with the prompt. The higher the value, the more the result image will change. Values from 0.0 to 1.0 are possible, the recommended range is .25-.75", "boundingBox": "The bounding box is the same as the Width and Height settings for Text to Image or Image to Image. Only the area in the box will be processed.", + "faceCorrection": "Face correction with GFPGAN or Codeformer: the algorithm detects faces in the image and corrects any defects. High value will change the image more, resulting in more attractive faces. Codeformer with a higher fidelity preserves the original image at the expense of stronger face correction.", + "gallery": "Gallery displays generations from the outputs folder as they're created. Settings are stored within files and accesed by context menu.", + "imageToImage": "Image to Image loads any image as initial, which is then used to generate a new one along with the prompt. The higher the value, the more the result image will change. Values from 0.0 to 1.0 are possible, the recommended range is .25-.75", + "infillAndScaling": "Manage infill methods (used on masked or erased areas of the canvas) and scaling (useful for small bounding box sizes).", + "other": "These options will enable alternative processing modes for Invoke. 'Seamless tiling' will create repeating patterns in the output. 'High resolution' is generation in two steps with img2img: use this setting when you want a larger and more coherent image without artifacts. It will take longer than usual txt2img.", + "prompt": "This is the prompt field. Prompt includes generation objects and stylistic terms. You can add weight (token importance) in the prompt as well, but CLI commands and parameters will not work.", "seamCorrection": "Controls the handling of visible seams that occur between generated images on the canvas.", - "infillAndScaling": "Manage infill methods (used on masked or erased areas of the canvas) and scaling (useful for small bounding box sizes)." + "seed": "Seed value affects the initial noise from which the image is formed. You can use the already existing seeds from previous images. 'Noise Threshold' is used to mitigate artifacts at high CFG values (try the 0-10 range), and Perlin to add Perlin noise during generation: both serve to add variation to your outputs.", + "upscale": "Use ESRGAN to enlarge the image immediately after generation.", + "variations": "Try a variation with a value between 0.1 and 1.0 to change the result for a given seed. Interesting variations of the seed are between 0.1 and 0.3." } }, + "ui": { + "hideProgressImages": "Hide Progress Images", + "lockRatio": "Lock Ratio", + "showProgressImages": "Show Progress Images", + "swapSizes": "Swap Sizes" + }, "unifiedCanvas": { - "layer": "Layer", - "base": "Base", - "mask": "Mask", - "maskingOptions": "Masking Options", - "enableMask": "Enable Mask", - "preserveMaskedArea": "Preserve Masked Area", - "clearMask": "Clear Mask", - "brush": "Brush", - "eraser": "Eraser", - "fillBoundingBox": "Fill Bounding Box", - "eraseBoundingBox": "Erase Bounding Box", - "colorPicker": "Color Picker", - "brushOptions": "Brush Options", - "brushSize": "Size", - "move": "Move", - "resetView": "Reset View", - "mergeVisible": "Merge Visible", - "saveToGallery": "Save To Gallery", - "copyToClipboard": "Copy to Clipboard", - "downloadAsImage": "Download As Image", - "undo": "Undo", - "redo": "Redo", - "clearCanvas": "Clear Canvas", - "canvasSettings": "Canvas Settings", - "showIntermediates": "Show Intermediates", - "showGrid": "Show Grid", - "snapToGrid": "Snap to Grid", - "darkenOutsideSelection": "Darken Outside Selection", - "autoSaveToGallery": "Auto Save to Gallery", - "saveBoxRegionOnly": "Save Box Region Only", - "limitStrokesToBox": "Limit Strokes to Box", - "showCanvasDebugInfo": "Show Additional Canvas Info", - "clearCanvasHistory": "Clear Canvas History", - "clearHistory": "Clear History", - "clearCanvasHistoryMessage": "Clearing the canvas history leaves your current canvas intact, but irreversibly clears the undo and redo history.", - "clearCanvasHistoryConfirm": "Are you sure you want to clear the canvas history?", - "emptyTempImageFolder": "Empty Temp Image Folder", - "emptyFolder": "Empty Folder", - "emptyTempImagesFolderMessage": "Emptying the temp image folder also fully resets the Unified Canvas. This includes all undo/redo history, images in the staging area, and the canvas base layer.", - "emptyTempImagesFolderConfirm": "Are you sure you want to empty the temp folder?", - "activeLayer": "Active Layer", - "canvasScale": "Canvas Scale", - "boundingBox": "Bounding Box", - "scaledBoundingBox": "Scaled Bounding Box", - "boundingBoxPosition": "Bounding Box Position", - "canvasDimensions": "Canvas Dimensions", - "canvasPosition": "Canvas Position", - "cursorPosition": "Cursor Position", - "previous": "Previous", - "next": "Next", "accept": "Accept", - "showHide": "Show/Hide", - "discardAll": "Discard All", + "activeLayer": "Active Layer", + "antialiasing": "Antialiasing", + "autoSaveToGallery": "Auto Save to Gallery", + "base": "Base", "betaClear": "Clear", "betaDarkenOutside": "Darken Outside", "betaLimitToBox": "Limit To Box", "betaPreserveMasked": "Preserve Masked", - "antialiasing": "Antialiasing" - }, - "ui": { - "showProgressImages": "Show Progress Images", - "hideProgressImages": "Hide Progress Images", - "swapSizes": "Swap Sizes", - "lockRatio": "Lock Ratio" - }, - "nodes": { - "reloadNodeTemplates": "Reload Node Templates", - "downloadWorkflow": "Download Workflow JSON", - "loadWorkflow": "Load Workflow", - "resetWorkflow": "Reset Workflow", - "resetWorkflowDesc": "Are you sure you want to reset this workflow?", - "resetWorkflowDesc2": "Resetting the workflow will clear all nodes, edges and workflow details.", - "zoomInNodes": "Zoom In", - "zoomOutNodes": "Zoom Out", - "fitViewportNodes": "Fit View", - "hideGraphNodes": "Hide Graph Overlay", - "showGraphNodes": "Show Graph Overlay", - "hideLegendNodes": "Hide Field Type Legend", - "showLegendNodes": "Show Field Type Legend", - "hideMinimapnodes": "Hide MiniMap", - "showMinimapnodes": "Show MiniMap" + "boundingBox": "Bounding Box", + "boundingBoxPosition": "Bounding Box Position", + "brush": "Brush", + "brushOptions": "Brush Options", + "brushSize": "Size", + "canvasDimensions": "Canvas Dimensions", + "canvasPosition": "Canvas Position", + "canvasScale": "Canvas Scale", + "canvasSettings": "Canvas Settings", + "clearCanvas": "Clear Canvas", + "clearCanvasHistory": "Clear Canvas History", + "clearCanvasHistoryConfirm": "Are you sure you want to clear the canvas history?", + "clearCanvasHistoryMessage": "Clearing the canvas history leaves your current canvas intact, but irreversibly clears the undo and redo history.", + "clearHistory": "Clear History", + "clearMask": "Clear Mask", + "colorPicker": "Color Picker", + "copyToClipboard": "Copy to Clipboard", + "cursorPosition": "Cursor Position", + "darkenOutsideSelection": "Darken Outside Selection", + "discardAll": "Discard All", + "downloadAsImage": "Download As Image", + "emptyFolder": "Empty Folder", + "emptyTempImageFolder": "Empty Temp Image Folder", + "emptyTempImagesFolderConfirm": "Are you sure you want to empty the temp folder?", + "emptyTempImagesFolderMessage": "Emptying the temp image folder also fully resets the Unified Canvas. This includes all undo/redo history, images in the staging area, and the canvas base layer.", + "enableMask": "Enable Mask", + "eraseBoundingBox": "Erase Bounding Box", + "eraser": "Eraser", + "fillBoundingBox": "Fill Bounding Box", + "layer": "Layer", + "limitStrokesToBox": "Limit Strokes to Box", + "mask": "Mask", + "maskingOptions": "Masking Options", + "mergeVisible": "Merge Visible", + "move": "Move", + "next": "Next", + "preserveMaskedArea": "Preserve Masked Area", + "previous": "Previous", + "redo": "Redo", + "resetView": "Reset View", + "saveBoxRegionOnly": "Save Box Region Only", + "saveToGallery": "Save To Gallery", + "scaledBoundingBox": "Scaled Bounding Box", + "showCanvasDebugInfo": "Show Additional Canvas Info", + "showGrid": "Show Grid", + "showHide": "Show/Hide", + "showIntermediates": "Show Intermediates", + "snapToGrid": "Snap to Grid", + "undo": "Undo" } } diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts index 8eaabeeedf..dd21afe459 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts @@ -6,6 +6,7 @@ import { isInvocationNode } from 'features/nodes/types/types'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { forEach, map } from 'lodash-es'; import { getConnectedEdges } from 'reactflow'; +import i18n from 'i18next'; const selector = createSelector( [stateSelector, activeTabNameSelector], @@ -19,22 +20,22 @@ const selector = createSelector( // Cannot generate if already processing an image if (isProcessing) { - reasons.push('System busy'); + reasons.push(i18n.t('parameters.invoke.systemBusy')); } // Cannot generate if not connected if (!isConnected) { - reasons.push('System disconnected'); + reasons.push(i18n.t('parameters.invoke.systemDisconnected')); } if (activeTabName === 'img2img' && !initialImage) { - reasons.push('No initial image selected'); + reasons.push(i18n.t('parameters.invoke.noInitialImageSelected')); } if (activeTabName === 'nodes') { if (nodes.shouldValidateGraph) { if (!nodes.nodes.length) { - reasons.push('No nodes in graph'); + reasons.push(i18n.t('parameters.invoke.noNodesInGraph')); } nodes.nodes.forEach((node) => { @@ -46,7 +47,7 @@ const selector = createSelector( if (!nodeTemplate) { // Node type not found - reasons.push('Missing node template'); + reasons.push(i18n.t('parameters.invoke.missingNodeTemplate')); return; } @@ -60,7 +61,7 @@ const selector = createSelector( ); if (!fieldTemplate) { - reasons.push('Missing field template'); + reasons.push(i18n.t('parameters.invoke.missingFieldTemplate')); return; } @@ -70,9 +71,10 @@ const selector = createSelector( !hasConnection ) { reasons.push( - `${node.data.label || nodeTemplate.title} -> ${ - field.label || fieldTemplate.title - } missing input` + i18n.t('parameters.invoke.missingInputForField', { + nodeLabel: node.data.label || nodeTemplate.title, + fieldLabel: field.label || fieldTemplate.title, + }) ); return; } @@ -81,7 +83,7 @@ const selector = createSelector( } } else { if (!model) { - reasons.push('No model selected'); + reasons.push(i18n.t('parameters.invoke.noModelSelected')); } if (state.controlNet.isEnabled) { @@ -90,7 +92,9 @@ const selector = createSelector( return; } if (!controlNet.model) { - reasons.push(`ControlNet ${i + 1} has no model selected.`); + reasons.push( + i18n.t('parameters.invoke.noModelForControlNet', { index: i + 1 }) + ); } if ( @@ -98,7 +102,11 @@ const selector = createSelector( (!controlNet.processedControlImage && controlNet.processorType !== 'none') ) { - reasons.push(`ControlNet ${i + 1} has no control image`); + reasons.push( + i18n.t('parameters.invoke.noControlImageForControlNet', { + index: i + 1, + }) + ); } }); } diff --git a/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx b/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx index 2443fa6081..6bdf434d52 100644 --- a/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx +++ b/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx @@ -21,6 +21,7 @@ import { useRemoveImagesFromBoardMutation, } from 'services/api/endpoints/images'; import { changeBoardReset, isModalOpenChanged } from '../store/slice'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( [stateSelector], @@ -42,10 +43,11 @@ const ChangeBoardModal = () => { const { imagesToChange, isModalOpen } = useAppSelector(selector); const [addImagesToBoard] = useAddImagesToBoardMutation(); const [removeImagesFromBoard] = useRemoveImagesFromBoardMutation(); + const { t } = useTranslation(); const data = useMemo(() => { const data: { label: string; value: string }[] = [ - { label: 'Uncategorized', value: 'none' }, + { label: t('boards.uncategorized'), value: 'none' }, ]; (boards ?? []).forEach((board) => data.push({ @@ -55,7 +57,7 @@ const ChangeBoardModal = () => { ); return data; - }, [boards]); + }, [boards, t]); const handleClose = useCallback(() => { dispatch(changeBoardReset()); @@ -97,7 +99,7 @@ const ChangeBoardModal = () => { - Change Board + {t('boards.changeBoard')} @@ -107,7 +109,9 @@ const ChangeBoardModal = () => { {`${imagesToChange.length > 1 ? 's' : ''}`} to board: setSelectedBoard(v)} value={selectedBoard} @@ -117,10 +121,10 @@ const ChangeBoardModal = () => { - Cancel + {t('boards.cancel')} - Move + {t('boards.move')} diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx index 1f70542494..d40a234495 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx @@ -28,6 +28,7 @@ import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd'; import ParamControlNetControlMode from './parameters/ParamControlNetControlMode'; import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect'; import ParamControlNetResizeMode from './parameters/ParamControlNetResizeMode'; +import { useTranslation } from 'react-i18next'; type ControlNetProps = { controlNet: ControlNetConfig; @@ -37,6 +38,7 @@ const ControlNet = (props: ControlNetProps) => { const { controlNet } = props; const { controlNetId } = controlNet; const dispatch = useAppDispatch(); + const { t } = useTranslation(); const activeTabName = useAppSelector(activeTabNameSelector); @@ -95,8 +97,8 @@ const ControlNet = (props: ControlNetProps) => { > @@ -117,23 +119,31 @@ const ControlNet = (props: ControlNetProps) => { )} } /> } /> { } = controlNet; const dispatch = useAppDispatch(); + const { t } = useTranslation(); const { pendingControlImages, autoAddBoardId } = useAppSelector(selector); const activeTabName = useAppSelector(activeTabNameSelector); @@ -208,18 +210,18 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => { : undefined} - tooltip="Reset Control Image" + tooltip={t('controlnet.resetControlImage')} /> : undefined} - tooltip="Save Control Image" + tooltip={t('controlnet.saveControlImage')} styleOverrides={{ marginTop: 6 }} /> : undefined} - tooltip="Set Control Image Dimensions To W/H" + tooltip={t('controlnet.setControlImageDimensions')} styleOverrides={{ marginTop: 12 }} /> diff --git a/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx b/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx index 0e044d4575..76f1cb9dfe 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx @@ -6,6 +6,7 @@ import { } from 'features/controlNet/store/controlNetSlice'; import { selectIsBusy } from 'features/system/store/systemSelectors'; import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; type Props = { controlNet: ControlNetConfig; @@ -15,6 +16,7 @@ const ParamControlNetShouldAutoConfig = (props: Props) => { const { controlNetId, isEnabled, shouldAutoConfig } = props.controlNet; const dispatch = useAppDispatch(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleShouldAutoConfigChanged = useCallback(() => { dispatch(controlNetAutoConfigToggled({ controlNetId })); @@ -22,8 +24,8 @@ const ParamControlNetShouldAutoConfig = (props: Props) => { return ( { const { controlNet } = props; const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleImportImageFromCanvas = useCallback(() => { dispatch(canvasImageToControlNet({ controlNet })); @@ -36,15 +38,15 @@ const ControlNetCanvasImageImports = ( } - tooltip="Import Image From Canvas" - aria-label="Import Image From Canvas" + tooltip={t('controlnet.importImageFromCanvas')} + aria-label={t('controlnet.importImageFromCanvas')} onClick={handleImportImageFromCanvas} /> } - tooltip="Import Mask From Canvas" - aria-label="Import Mask From Canvas" + tooltip={t('controlnet.importMaskFromCanvas')} + aria-label={t('controlnet.importMaskFromCanvas')} onClick={handleImportMaskFromCanvas} /> diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx index 1219239e5d..f34c863cff 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx @@ -16,6 +16,7 @@ import { controlNetEndStepPctChanged, } from 'features/controlNet/store/controlNetSlice'; import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; type Props = { controlNet: ControlNetConfig; @@ -27,6 +28,7 @@ const ParamControlNetBeginEnd = (props: Props) => { const { beginStepPct, endStepPct, isEnabled, controlNetId } = props.controlNet; const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleStepPctChanged = useCallback( (v: number[]) => { @@ -48,10 +50,10 @@ const ParamControlNetBeginEnd = (props: Props) => { return ( - Begin / End Step Percentage + {t('controlnet.beginEndStepPercent')} { @@ -34,7 +36,7 @@ export default function ParamControlNetControlMode( return ( { const isBusy = useAppSelector(selectIsBusy); const { mainModel } = useAppSelector(selector); + const { t } = useTranslation(); const { data: controlNetModels } = useGetControlNetModelsQuery(); @@ -58,13 +60,13 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => { group: MODEL_TYPE_MAP[model.base_model], disabled, tooltip: disabled - ? `Incompatible base model: ${model.base_model}` + ? `${t('controlnet.incompatibleBaseModel')} ${model.base_model}` : undefined, }); }); return data; - }, [controlNetModels, mainModel?.base_model]); + }, [controlNetModels, mainModel?.base_model, t]); // grab the full model entity from the RTK Query cache const selectedModel = useMemo( @@ -105,7 +107,7 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => { error={ !selectedModel || mainModel?.base_model !== selectedModel.base_model } - placeholder="Select a model" + placeholder={t('controlnet.selectModel')} value={selectedModel?.id ?? null} onChange={handleModelChanged} disabled={isBusy || !isEnabled} diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx index 190b1bc012..a357547403 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx @@ -15,6 +15,7 @@ import { controlNetProcessorTypeChanged, } from '../../store/controlNetSlice'; import { ControlNetProcessorType } from '../../store/types'; +import { useTranslation } from 'react-i18next'; type ParamControlNetProcessorSelectProps = { controlNet: ControlNetConfig; @@ -57,6 +58,7 @@ const ParamControlNetProcessorSelect = ( const { controlNetId, isEnabled, processorNode } = props.controlNet; const isBusy = useAppSelector(selectIsBusy); const controlNetProcessors = useAppSelector(selector); + const { t } = useTranslation(); const handleProcessorTypeChanged = useCallback( (v: string | null) => { @@ -72,7 +74,7 @@ const ParamControlNetProcessorSelect = ( return ( { @@ -33,7 +35,7 @@ export default function ParamControlNetResizeMode( return ( { const { weight, isEnabled, controlNetId } = props.controlNet; const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleWeightChanged = useCallback( (weight: number) => { dispatch(controlNetWeightChanged({ controlNetId, weight })); @@ -23,7 +25,7 @@ const ParamControlNetWeight = (props: ParamControlNetWeightProps) => { return ( { const { low_threshold, high_threshold } = processorNode; const isBusy = useAppSelector(selectIsBusy); const processorChanged = useProcessorNodeChanged(); + const { t } = useTranslation(); const handleLowThresholdChanged = useCallback( (v: number) => { @@ -52,7 +54,7 @@ const CannyProcessor = (props: CannyProcessorProps) => { { /> { const { image_resolution, detect_resolution, w, h, f } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -90,7 +92,7 @@ const ContentShuffleProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { } = props; const isBusy = useAppSelector(selectIsBusy); const processorChanged = useProcessorNodeChanged(); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -62,7 +64,7 @@ const HedPreprocessor = (props: HedProcessorProps) => { return ( { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { const { image_resolution, detect_resolution } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -51,7 +53,7 @@ const LineartAnimeProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { const { image_resolution, detect_resolution, coarse } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -59,7 +61,7 @@ const LineartProcessor = (props: LineartProcessorProps) => { return ( { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { const { max_faces, min_confidence } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleMaxFacesChanged = useCallback( (v: number) => { @@ -47,7 +49,7 @@ const MediapipeFaceProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { const { a_mult, bg_th } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleAMultChanged = useCallback( (v: number) => { @@ -47,7 +49,7 @@ const MidasDepthProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { const { image_resolution, detect_resolution, thr_d, thr_v } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -73,7 +75,7 @@ const MlsdImageProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { const { image_resolution, detect_resolution } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -51,7 +53,7 @@ const NormalBaeProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { const { image_resolution, detect_resolution, hand_and_face } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -59,7 +61,7 @@ const OpenposeProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { const { image_resolution, detect_resolution, scribble, safe } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -66,7 +68,7 @@ const PidiProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> ; - /** * A dict of ControlNet processors, including: * - type @@ -25,16 +25,24 @@ type ControlNetProcessorsDict = Record< export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { none: { type: 'none', - label: 'none', - description: '', + get label() { + return i18n.t('controlnet.none'); + }, + get description() { + return i18n.t('controlnet.noneDescription'); + }, default: { type: 'none', }, }, canny_image_processor: { type: 'canny_image_processor', - label: 'Canny', - description: '', + get label() { + return i18n.t('controlnet.canny'); + }, + get description() { + return i18n.t('controlnet.cannyDescription'); + }, default: { id: 'canny_image_processor', type: 'canny_image_processor', @@ -44,8 +52,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, content_shuffle_image_processor: { type: 'content_shuffle_image_processor', - label: 'Content Shuffle', - description: '', + get label() { + return i18n.t('controlnet.contentShuffle'); + }, + get description() { + return i18n.t('controlnet.contentShuffleDescription'); + }, default: { id: 'content_shuffle_image_processor', type: 'content_shuffle_image_processor', @@ -58,8 +70,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, hed_image_processor: { type: 'hed_image_processor', - label: 'HED', - description: '', + get label() { + return i18n.t('controlnet.hed'); + }, + get description() { + return i18n.t('controlnet.hedDescription'); + }, default: { id: 'hed_image_processor', type: 'hed_image_processor', @@ -70,8 +86,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, lineart_anime_image_processor: { type: 'lineart_anime_image_processor', - label: 'Lineart Anime', - description: '', + get label() { + return i18n.t('controlnet.lineartAnime'); + }, + get description() { + return i18n.t('controlnet.lineartAnimeDescription'); + }, default: { id: 'lineart_anime_image_processor', type: 'lineart_anime_image_processor', @@ -81,8 +101,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, lineart_image_processor: { type: 'lineart_image_processor', - label: 'Lineart', - description: '', + get label() { + return i18n.t('controlnet.lineart'); + }, + get description() { + return i18n.t('controlnet.lineartDescription'); + }, default: { id: 'lineart_image_processor', type: 'lineart_image_processor', @@ -93,8 +117,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, mediapipe_face_processor: { type: 'mediapipe_face_processor', - label: 'Mediapipe Face', - description: '', + get label() { + return i18n.t('controlnet.mediapipeFace'); + }, + get description() { + return i18n.t('controlnet.mediapipeFaceDescription'); + }, default: { id: 'mediapipe_face_processor', type: 'mediapipe_face_processor', @@ -104,8 +132,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, midas_depth_image_processor: { type: 'midas_depth_image_processor', - label: 'Depth (Midas)', - description: '', + get label() { + return i18n.t('controlnet.depthMidas'); + }, + get description() { + return i18n.t('controlnet.depthMidasDescription'); + }, default: { id: 'midas_depth_image_processor', type: 'midas_depth_image_processor', @@ -115,8 +147,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, mlsd_image_processor: { type: 'mlsd_image_processor', - label: 'M-LSD', - description: '', + get label() { + return i18n.t('controlnet.mlsd'); + }, + get description() { + return i18n.t('controlnet.mlsdDescription'); + }, default: { id: 'mlsd_image_processor', type: 'mlsd_image_processor', @@ -128,8 +164,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, normalbae_image_processor: { type: 'normalbae_image_processor', - label: 'Normal BAE', - description: '', + get label() { + return i18n.t('controlnet.normalBae'); + }, + get description() { + return i18n.t('controlnet.normalBaeDescription'); + }, default: { id: 'normalbae_image_processor', type: 'normalbae_image_processor', @@ -139,8 +179,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, openpose_image_processor: { type: 'openpose_image_processor', - label: 'Openpose', - description: '', + get label() { + return i18n.t('controlnet.openPose'); + }, + get description() { + return i18n.t('controlnet.openPoseDescription'); + }, default: { id: 'openpose_image_processor', type: 'openpose_image_processor', @@ -151,8 +195,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, pidi_image_processor: { type: 'pidi_image_processor', - label: 'PIDI', - description: '', + get label() { + return i18n.t('controlnet.pidi'); + }, + get description() { + return i18n.t('controlnet.pidiDescription'); + }, default: { id: 'pidi_image_processor', type: 'pidi_image_processor', @@ -164,8 +212,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, zoe_depth_image_processor: { type: 'zoe_depth_image_processor', - label: 'Depth (Zoe)', - description: '', + get label() { + return i18n.t('controlnet.depthZoe'); + }, + get description() { + return i18n.t('controlnet.depthZoeDescription'); + }, default: { id: 'zoe_depth_image_processor', type: 'zoe_depth_image_processor', @@ -186,4 +238,6 @@ export const CONTROLNET_MODEL_DEFAULT_PROCESSORS: { shuffle: 'content_shuffle_image_processor', openpose: 'openpose_image_processor', mediapipe: 'mediapipe_face_processor', + pidi: 'pidi_image_processor', + zoe: 'zoe_depth_image_processor', }; diff --git a/invokeai/frontend/web/src/features/deleteImageModal/components/ImageUsageMessage.tsx b/invokeai/frontend/web/src/features/deleteImageModal/components/ImageUsageMessage.tsx index 3ed7f3f05f..de1782b439 100644 --- a/invokeai/frontend/web/src/features/deleteImageModal/components/ImageUsageMessage.tsx +++ b/invokeai/frontend/web/src/features/deleteImageModal/components/ImageUsageMessage.tsx @@ -2,16 +2,19 @@ import { ListItem, Text, UnorderedList } from '@chakra-ui/react'; import { some } from 'lodash-es'; import { memo } from 'react'; import { ImageUsage } from '../store/types'; +import { useTranslation } from 'react-i18next'; + type Props = { imageUsage?: ImageUsage; topMessage?: string; bottomMessage?: string; }; const ImageUsageMessage = (props: Props) => { + const { t } = useTranslation(); const { imageUsage, - topMessage = 'This image is currently in use in the following features:', - bottomMessage = 'If you delete this image, those features will immediately be reset.', + topMessage = t('gallery.currentlyInUse'), + bottomMessage = t('gallery.featuresWillReset'), } = props; if (!imageUsage) { @@ -26,10 +29,18 @@ const ImageUsageMessage = (props: Props) => { <> {topMessage} - {imageUsage.isInitialImage && Image to Image} - {imageUsage.isCanvasImage && Unified Canvas} - {imageUsage.isControlNetImage && ControlNet} - {imageUsage.isNodesImage && Node Editor} + {imageUsage.isInitialImage && ( + {t('common.img2img')} + )} + {imageUsage.isCanvasImage && ( + {t('common.unifiedCanvas')} + )} + {imageUsage.isControlNetImage && ( + {t('common.controlNet')} + )} + {imageUsage.isNodesImage && ( + {t('common.nodeEditor')} + )} {bottomMessage} diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx index 70aadcd4ce..b9fd655a43 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx @@ -9,6 +9,7 @@ import { useFeatureStatus } from '../../system/hooks/useFeatureStatus'; import ParamDynamicPromptsCombinatorial from './ParamDynamicPromptsCombinatorial'; import ParamDynamicPromptsToggle from './ParamDynamicPromptsEnabled'; import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -22,6 +23,7 @@ const selector = createSelector( const ParamDynamicPromptsCollapse = () => { const { activeLabel } = useAppSelector(selector); + const { t } = useTranslation(); const isDynamicPromptingEnabled = useFeatureStatus('dynamicPrompting').isFeatureEnabled; @@ -31,7 +33,7 @@ const ParamDynamicPromptsCollapse = () => { } return ( - + diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx index c028a5d55c..406dc8e216 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx @@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISwitch from 'common/components/IAISwitch'; import { memo, useCallback } from 'react'; import { combinatorialToggled } from '../store/dynamicPromptsSlice'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -19,6 +20,7 @@ const selector = createSelector( const ParamDynamicPromptsCombinatorial = () => { const { combinatorial, isDisabled } = useAppSelector(selector); const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleChange = useCallback(() => { dispatch(combinatorialToggled()); @@ -27,7 +29,7 @@ const ParamDynamicPromptsCombinatorial = () => { return ( diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsEnabled.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsEnabled.tsx index 1b31147937..a1d16b5361 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsEnabled.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsEnabled.tsx @@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISwitch from 'common/components/IAISwitch'; import { memo, useCallback } from 'react'; import { isEnabledToggled } from '../store/dynamicPromptsSlice'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -19,6 +20,7 @@ const selector = createSelector( const ParamDynamicPromptsToggle = () => { const dispatch = useAppDispatch(); const { isEnabled } = useAppSelector(selector); + const { t } = useTranslation(); const handleToggleIsEnabled = useCallback(() => { dispatch(isEnabledToggled()); @@ -26,7 +28,7 @@ const ParamDynamicPromptsToggle = () => { return ( diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx index f374f1cb15..158fab91d9 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx @@ -8,6 +8,7 @@ import { maxPromptsChanged, maxPromptsReset, } from '../store/dynamicPromptsSlice'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -31,6 +32,7 @@ const ParamDynamicPromptsMaxPrompts = () => { const { maxPrompts, min, sliderMax, inputMax, isDisabled } = useAppSelector(selector); const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleChange = useCallback( (v: number) => { @@ -45,7 +47,7 @@ const ParamDynamicPromptsMaxPrompts = () => { return ( void; @@ -8,11 +9,12 @@ type Props = { const AddEmbeddingButton = (props: Props) => { const { onClick } = props; + const { t } = useTranslation(); return ( } sx={{ p: 2, diff --git a/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx b/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx index 93daaf946f..164fd01a1f 100644 --- a/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx +++ b/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx @@ -16,6 +16,7 @@ import { forEach } from 'lodash-es'; import { PropsWithChildren, memo, useCallback, useMemo, useRef } from 'react'; import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models'; import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; +import { useTranslation } from 'react-i18next'; type Props = PropsWithChildren & { onSelect: (v: string) => void; @@ -27,6 +28,7 @@ const ParamEmbeddingPopover = (props: Props) => { const { onSelect, isOpen, onClose, children } = props; const { data: embeddingQueryData } = useGetTextualInversionModelsQuery(); const inputRef = useRef(null); + const { t } = useTranslation(); const currentMainModel = useAppSelector( (state: RootState) => state.generation.model @@ -52,7 +54,7 @@ const ParamEmbeddingPopover = (props: Props) => { group: MODEL_TYPE_MAP[embedding.base_model], disabled, tooltip: disabled - ? `Incompatible base model: ${embedding.base_model}` + ? `${t('embedding.incompatibleModel')} ${embedding.base_model}` : undefined, }); }); @@ -63,7 +65,7 @@ const ParamEmbeddingPopover = (props: Props) => { ); return data.sort((a, b) => (a.disabled && !b.disabled ? 1 : -1)); - }, [embeddingQueryData, currentMainModel?.base_model]); + }, [embeddingQueryData, currentMainModel?.base_model, t]); const handleChange = useCallback( (v: string | null) => { @@ -118,10 +120,10 @@ const ParamEmbeddingPopover = (props: Props) => { { const dispatch = useAppDispatch(); + const { t } = useTranslation(); const { autoAddBoardId, autoAssignBoardOnClick, isProcessing } = useAppSelector(selector); const inputRef = useRef(null); @@ -63,13 +65,13 @@ const BoardAutoAddSelect = () => { return ( diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx index c711ad892b..e5eb92028d 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx @@ -16,6 +16,7 @@ import { menuListMotionProps } from 'theme/components/menu'; import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems'; import NoBoardContextMenuItems from './NoBoardContextMenuItems'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { useTranslation } from 'react-i18next'; type Props = { board?: BoardDTO; @@ -59,6 +60,8 @@ const BoardContextMenu = ({ e.preventDefault(); }, []); + const { t } = useTranslation(); + return ( menuProps={{ size: 'sm', isLazy: true }} @@ -78,7 +81,7 @@ const BoardContextMenu = ({ isDisabled={isAutoAdd || isProcessing || autoAssignBoardOnClick} onClick={handleSetAutoAdd} > - Auto-add to this Board + {t('boards.menuItemAutoAdd')} {!board && } {board && ( diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx index ebd08e94d5..96739f4c84 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx @@ -2,22 +2,22 @@ import IAIIconButton from 'common/components/IAIIconButton'; import { memo, useCallback } from 'react'; import { FaPlus } from 'react-icons/fa'; import { useCreateBoardMutation } from 'services/api/endpoints/boards'; - -const DEFAULT_BOARD_NAME = 'My Board'; +import { useTranslation } from 'react-i18next'; const AddBoardButton = () => { + const { t } = useTranslation(); const [createBoard, { isLoading }] = useCreateBoardMutation(); - + const DEFAULT_BOARD_NAME = t('boards.myBoard'); const handleCreateBoard = useCallback(() => { createBoard(DEFAULT_BOARD_NAME); - }, [createBoard]); + }, [createBoard, DEFAULT_BOARD_NAME]); return ( } isLoading={isLoading} - tooltip="Add Board" - aria-label="Add Board" + tooltip={t('boards.addBoard')} + aria-label={t('boards.addBoard')} onClick={handleCreateBoard} size="sm" /> diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx index d7db96a938..2d2a85597c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx @@ -18,6 +18,7 @@ import { useEffect, useRef, } from 'react'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( [stateSelector], @@ -32,6 +33,7 @@ const BoardsSearch = () => { const dispatch = useAppDispatch(); const { boardSearchText } = useAppSelector(selector); const inputRef = useRef(null); + const { t } = useTranslation(); const handleBoardSearch = useCallback( (searchTerm: string) => { @@ -73,7 +75,7 @@ const BoardsSearch = () => { { onClick={clearBoardSearch} size="xs" variant="ghost" - aria-label="Clear Search" + aria-label={t('boards.clearSearch')} opacity={0.5} icon={} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx index f09cf131e2..2c54f06cec 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx @@ -132,8 +132,8 @@ const DeleteBoardModal = (props: Props) => { ) : ( )} Deleted boards cannot be restored. diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImagePreview.tsx index 2576c8e9e3..b16820a38f 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImagePreview.tsx @@ -19,6 +19,7 @@ import { FaImage } from 'react-icons/fa'; import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import ImageMetadataViewer from '../ImageMetadataViewer/ImageMetadataViewer'; import NextPrevImageButtons from '../NextPrevImageButtons'; +import { useTranslation } from 'react-i18next'; export const imagesSelector = createSelector( [stateSelector, selectLastSelectedImage], @@ -117,6 +118,8 @@ const CurrentImagePreview = () => { const timeoutId = useRef(0); + const { t } = useTranslation(); + const handleMouseOver = useCallback(() => { setShouldShowNextPrevButtons(true); window.clearTimeout(timeoutId.current); @@ -164,7 +167,7 @@ const CurrentImagePreview = () => { isUploadDisabled={true} fitContainer useThumbailFallback - dropLabel="Set as Current Image" + dropLabel={t('gallery.setCurrentImage')} noContentFallback={ } diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx index 35d6cc3361..fe9d891b23 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx @@ -18,6 +18,7 @@ import { useUnstarImagesMutation, } from 'services/api/endpoints/images'; import IAIDndImageIcon from '../../../../common/components/IAIDndImageIcon'; +import { useTranslation } from 'react-i18next'; interface HoverableImageProps { imageName: string; @@ -28,6 +29,7 @@ const GalleryImage = (props: HoverableImageProps) => { const { imageName } = props; const { currentData: imageDTO } = useGetImageDTOQuery(imageName); const shift = useAppSelector((state) => state.hotkeys.shift); + const { t } = useTranslation(); const { handleClick, isSelected, selection, selectionCount } = useMultiselect(imageDTO); @@ -136,7 +138,7 @@ const GalleryImage = (props: HoverableImageProps) => { } - tooltip="Delete" + tooltip={t('gallery.deleteImage')} styleOverrides={{ bottom: 2, top: 'auto', diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx index bacd5c38ad..dc41a2ef2a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx @@ -95,7 +95,7 @@ const GalleryImageGrid = () => { justifyContent: 'center', }} > - + ); } @@ -140,7 +140,7 @@ const GalleryImageGrid = () => { onClick={handleLoadMoreImages} isDisabled={!areMoreAvailable} isLoading={isFetching} - loadingText="Loading" + loadingText={t('gallery.loading')} flexShrink={0} > {`Load More (${currentData.ids.length} of ${currentViewTotal})`} @@ -153,7 +153,7 @@ const GalleryImageGrid = () => { return ( diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx index 2267bf15c2..ed7df88e3a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx @@ -3,6 +3,7 @@ import { isString } from 'lodash-es'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import { memo, useCallback, useMemo } from 'react'; import { FaCopy, FaDownload } from 'react-icons/fa'; +import { useTranslation } from 'react-i18next'; type Props = { label: string; @@ -33,6 +34,8 @@ const DataViewer = (props: Props) => { a.remove(); }, [dataString, label, fileName]); + const { t } = useTranslation(); + return ( { {withDownload && ( - + } variant="ghost" opacity={0.7} @@ -84,9 +87,9 @@ const DataViewer = (props: Props) => { )} {withCopy && ( - + } variant="ghost" opacity={0.7} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx index e82b03360e..c1124477e2 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx @@ -2,6 +2,7 @@ import { CoreMetadata } from 'features/nodes/types/types'; import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters'; import { memo, useCallback } from 'react'; import ImageMetadataItem from './ImageMetadataItem'; +import { useTranslation } from 'react-i18next'; type Props = { metadata?: CoreMetadata; @@ -10,6 +11,8 @@ type Props = { const ImageMetadataActions = (props: Props) => { const { metadata } = props; + const { t } = useTranslation(); + const { recallPositivePrompt, recallNegativePrompt, @@ -70,17 +73,20 @@ const ImageMetadataActions = (props: Props) => { return ( <> {metadata.created_by && ( - + )} {metadata.generation_mode && ( )} {metadata.positive_prompt && ( { )} {metadata.negative_prompt && ( { )} {metadata.seed !== undefined && metadata.seed !== null && ( @@ -105,63 +111,63 @@ const ImageMetadataActions = (props: Props) => { metadata.model !== null && metadata.model.model_name && ( )} {metadata.width && ( )} {metadata.height && ( )} {/* {metadata.threshold !== undefined && ( dispatch(setThreshold(Number(metadata.threshold)))} /> )} {metadata.perlin !== undefined && ( dispatch(setPerlin(Number(metadata.perlin)))} /> )} */} {metadata.scheduler && ( )} {metadata.steps && ( )} {metadata.cfg_scale !== undefined && metadata.cfg_scale !== null && ( )} {/* {metadata.variations && metadata.variations.length > 0 && ( dispatch(setSeamless(metadata.seamless))} /> )} {metadata.hires_fix && ( dispatch(setHiresFix(metadata.hires_fix))} /> @@ -187,7 +193,7 @@ const ImageMetadataActions = (props: Props) => { {/* {init_image_path && ( dispatch(setInitialImage(init_image_path))} @@ -195,14 +201,14 @@ const ImageMetadataActions = (props: Props) => { )} */} {metadata.strength && ( )} {/* {metadata.fit && ( dispatch(setShouldFitToWidthHeight(metadata.fit))} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx index bec5125657..5be0b08700 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx @@ -17,6 +17,7 @@ import DataViewer from './DataViewer'; import ImageMetadataActions from './ImageMetadataActions'; import { useAppSelector } from '../../../../app/store/storeHooks'; import { configSelector } from '../../../system/store/configSelectors'; +import { useTranslation } from 'react-i18next'; type ImageMetadataViewerProps = { image: ImageDTO; @@ -28,6 +29,7 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => { // useHotkeys('esc', () => { // dispatch(setShouldShowImageDetails(false)); // }); + const { t } = useTranslation(); const { shouldFetchMetadataFromApi } = useAppSelector(configSelector); @@ -70,31 +72,31 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => { sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }} > - Metadata - Image Details - Workflow + {t('metadata.metadata')} + {t('metadata.imageDetails')} + {t('metadata.workflow')} {metadata ? ( - + ) : ( - + )} {image ? ( - + ) : ( - + )} {workflow ? ( - + ) : ( - + )} diff --git a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx index 4cefdbb20b..3675dd9af9 100644 --- a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx @@ -12,9 +12,11 @@ import TopCenterPanel from './flow/panels/TopCenterPanel/TopCenterPanel'; import TopRightPanel from './flow/panels/TopRightPanel/TopRightPanel'; import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel'; import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel'; +import { useTranslation } from 'react-i18next'; const NodeEditor = () => { const isReady = useAppSelector((state) => state.nodes.isReady); + const { t } = useTranslation(); return ( { }} > diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx index 83f7482177..4433adf4ab 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx @@ -24,6 +24,7 @@ import { HotkeyCallback } from 'react-hotkeys-hook/dist/types'; import 'reactflow/dist/style.css'; import { AnyInvocationType } from 'services/events/types'; import { AddNodePopoverSelectItem } from './AddNodePopoverSelectItem'; +import { useTranslation } from 'react-i18next'; type NodeTemplate = { label: string; @@ -48,43 +49,45 @@ const filter = (value: string, item: NodeTemplate) => { ); }; -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'], - }); - - data.sort((a, b) => a.label.localeCompare(b.label)); - - return { data }; - }, - defaultSelectorOptions -); - const AddNodePopover = () => { const dispatch = useAppDispatch(); const buildInvocation = useBuildNodeData(); const toaster = useAppToaster(); + const { t } = useTranslation(); + + 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: t('nodes.currentImage'), + value: 'current_image', + description: t('nodes.currentImageDescription'), + tags: ['progress'], + }); + + data.push({ + label: t('nodes.notes'), + value: 'notes', + description: t('nodes.notesDescription'), + tags: ['notes'], + }); + + data.sort((a, b) => a.label.localeCompare(b.label)); + + return { data, t }; + }, + defaultSelectorOptions + ); + const { data } = useAppSelector(selector); const isOpen = useAppSelector((state) => state.nodes.isAddNodePopoverOpen); const inputRef = useRef(null); @@ -92,18 +95,20 @@ const AddNodePopover = () => { const addNode = useCallback( (nodeType: AnyInvocationType) => { const invocation = buildInvocation(nodeType); - if (!invocation) { + const errorMessage = t('nodes.unknownInvocation', { + nodeType: nodeType, + }); toaster({ status: 'error', - title: `Unknown Invocation type ${nodeType}`, + title: errorMessage, }); return; } dispatch(nodeAdded(invocation)); }, - [dispatch, buildInvocation, toaster] + [dispatch, buildInvocation, toaster, t] ); const handleChange = useCallback( @@ -179,11 +184,11 @@ const AddNodePopover = () => { { const label = useNodeLabel(nodeId); const title = useNodeTemplateTitle(nodeId); const doVersionsMatch = useDoNodeVersionsMatch(nodeId); + const { t } = useTranslation(); return ( <> @@ -65,7 +67,7 @@ const InvocationNodeNotes = ({ nodeId }: Props) => { - {label || title || 'Unknown Node'} + {label || title || t('nodes.unknownNode')} @@ -82,6 +84,7 @@ export default memo(InvocationNodeNotes); const TooltipContent = memo(({ nodeId }: { nodeId: string }) => { const data = useNodeData(nodeId); const nodeTemplate = useNodeTemplate(nodeId); + const { t } = useTranslation(); const title = useMemo(() => { if (data?.label && nodeTemplate?.title) { @@ -96,8 +99,8 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => { return nodeTemplate.title; } - return 'Unknown Node'; - }, [data, nodeTemplate]); + return t('nodes.unknownNode'); + }, [data, nodeTemplate, t]); const versionComponent = useMemo(() => { if (!isInvocationNodeData(data) || !nodeTemplate) { @@ -107,7 +110,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => { if (!data.version) { return ( - Version unknown + {t('nodes.versionUnknown')} ); } @@ -115,7 +118,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => { if (!nodeTemplate.version) { return ( - Version {data.version} (unknown template) + {t('nodes.version')} {data.version} ({t('nodes.unknownTemplate')}) ); } @@ -123,7 +126,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => { if (compare(data.version, nodeTemplate.version, '<')) { return ( - Version {data.version} (update node) + {t('nodes.version')} {data.version} ({t('nodes.updateNode')}) ); } @@ -131,16 +134,20 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => { if (compare(data.version, nodeTemplate.version, '>')) { return ( - Version {data.version} (update app) + {t('nodes.version')} {data.version} ({t('nodes.updateApp')}) ); } - return Version {data.version}; - }, [data, nodeTemplate]); + return ( + + {t('nodes.version')} {data.version} + + ); + }, [data, nodeTemplate, t]); if (!isInvocationNodeData(data)) { - return Unknown Node; + return {t('nodes.unknownNode')}; } return ( diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeStatusIndicator.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeStatusIndicator.tsx index 6e1da90ad8..28f6e08a4e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeStatusIndicator.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeStatusIndicator.tsx @@ -14,6 +14,7 @@ import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants'; import { NodeExecutionState, NodeStatus } from 'features/nodes/types/types'; import { memo, useMemo } from 'react'; import { FaCheck, FaEllipsisH, FaExclamation } from 'react-icons/fa'; +import { useTranslation } from 'react-i18next'; type Props = { nodeId: string; @@ -72,10 +73,10 @@ type TooltipLabelProps = { const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => { const { status, progress, progressImage } = nodeExecutionState; + const { t } = useTranslation(); if (status === NodeStatus.PENDING) { return Pending; } - if (status === NodeStatus.IN_PROGRESS) { if (progressImage) { return ( @@ -97,18 +98,22 @@ const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => { } if (progress !== null) { - return In Progress ({Math.round(progress * 100)}%); + return ( + + {t('nodes.executionStateInProgress')} ({Math.round(progress * 100)}%) + + ); } - return In Progress; + return {t('nodes.executionStateInProgress')}; } if (status === NodeStatus.COMPLETED) { - return Completed; + return {t('nodes.executionStateCompleted')}; } if (status === NodeStatus.FAILED) { - return nodeExecutionState.error; + return {t('nodes.executionStateError')}; } return null; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/NotesTextarea.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/NotesTextarea.tsx index 68967096f9..5e85f5ba3c 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/NotesTextarea.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/NotesTextarea.tsx @@ -5,10 +5,12 @@ import { useNodeData } from 'features/nodes/hooks/useNodeData'; import { nodeNotesChanged } from 'features/nodes/store/nodesSlice'; import { isInvocationNodeData } from 'features/nodes/types/types'; import { ChangeEvent, memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; const NotesTextarea = ({ nodeId }: { nodeId: string }) => { const dispatch = useAppDispatch(); const data = useNodeData(nodeId); + const { t } = useTranslation(); const handleNotesChanged = useCallback( (e: ChangeEvent) => { dispatch(nodeNotesChanged({ nodeId, notes: e.target.value })); @@ -20,7 +22,7 @@ const NotesTextarea = ({ nodeId }: { nodeId: string }) => { } return ( - Notes + {t('nodes.notes')} { } = props; const label = useFieldLabel(nodeId, fieldName); const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind); + const { t } = useTranslation(); const dispatch = useAppDispatch(); const [localTitle, setLocalTitle] = useState( - label || fieldTemplateTitle || 'Unknown Field' + label || fieldTemplateTitle || t('nodes.unknownFeild') ); const handleSubmit = useCallback( @@ -44,10 +46,10 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => { if (newTitle && (newTitle === label || newTitle === fieldTemplateTitle)) { return; } - setLocalTitle(newTitle || fieldTemplateTitle || 'Unknown Field'); + setLocalTitle(newTitle || fieldTemplateTitle || t('nodes.unknownField')); dispatch(fieldLabelChanged({ nodeId, fieldName, label: newTitle })); }, - [label, fieldTemplateTitle, dispatch, nodeId, fieldName] + [label, fieldTemplateTitle, dispatch, nodeId, fieldName, t] ); const handleChange = useCallback((newTitle: string) => { @@ -56,8 +58,8 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => { useEffect(() => { // Another component may change the title; sync local title with global state - setLocalTitle(label || fieldTemplateTitle || 'Unknown Field'); - }, [label, fieldTemplateTitle]); + setLocalTitle(label || fieldTemplateTitle || t('nodes.unknownField')); + }, [label, fieldTemplateTitle, t]); return ( { const label = useFieldLabel(nodeId, fieldName); const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind); const input = useFieldInputKind(nodeId, fieldName); + const { t } = useTranslation(); const skipEvent = useCallback((e: MouseEvent) => { e.preventDefault(); @@ -119,7 +121,9 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => { motionProps={menuListMotionProps} onContextMenu={skipEvent} > - + {menuItems} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent.tsx index 1a47e81aa3..be66214a59 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent.tsx @@ -8,6 +8,7 @@ import { } from 'features/nodes/types/types'; import { startCase } from 'lodash-es'; import { memo, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; interface Props { nodeId: string; @@ -19,6 +20,7 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => { const field = useFieldData(nodeId, fieldName); const fieldTemplate = useFieldTemplate(nodeId, fieldName, kind); const isInputTemplate = isInputFieldTemplate(fieldTemplate); + const { t } = useTranslation(); const fieldTitle = useMemo(() => { if (isInputFieldValue(field)) { if (field.label && fieldTemplate?.title) { @@ -33,11 +35,11 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => { return fieldTemplate.title; } - return 'Unknown Field'; + return t('nodes.unknownField'); } else { - return fieldTemplate?.title || 'Unknown Field'; + return fieldTemplate?.title || t('nodes.unknownField'); } - }, [field, fieldTemplate]); + }, [field, fieldTemplate, t]); return ( diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/LinearViewField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/LinearViewField.tsx index e3983560a8..a9416380d4 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/LinearViewField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/LinearViewField.tsx @@ -17,6 +17,7 @@ import { FaInfoCircle, FaTrash } from 'react-icons/fa'; import EditableFieldTitle from './EditableFieldTitle'; import FieldTooltipContent from './FieldTooltipContent'; import InputFieldRenderer from './InputFieldRenderer'; +import { useTranslation } from 'react-i18next'; type Props = { nodeId: string; @@ -27,7 +28,7 @@ const LinearViewField = ({ nodeId, fieldName }: Props) => { const dispatch = useAppDispatch(); const { isMouseOverNode, handleMouseOut, handleMouseOver } = useMouseOverNode(nodeId); - + const { t } = useTranslation(); const handleRemoveField = useCallback(() => { dispatch(workflowExposedFieldRemoved({ nodeId, fieldName })); }, [dispatch, fieldName, nodeId]); @@ -75,8 +76,8 @@ const LinearViewField = ({ nodeId, fieldName }: Props) => { { if (!loraModels) { @@ -92,9 +94,11 @@ const LoRAModelInputFieldComponent = ( 0 ? 'Select a LoRA' : 'No LoRAs available'} + placeholder={ + data.length > 0 ? t('models.selectLoRA') : t('models.noLoRAsAvailable') + } data={data} - nothingFound="No matching LoRAs" + nothingFound={t('models.noMatchingLoRAs')} itemComponent={IAIMantineSelectItemWithTooltip} disabled={data.length === 0} filter={(value, item: SelectItem) => diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/MainModelInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/MainModelInputField.tsx index f89177576c..08483986e3 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/MainModelInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/MainModelInputField.tsx @@ -19,6 +19,7 @@ import { useGetMainModelsQuery, useGetOnnxModelsQuery, } from 'services/api/endpoints/models'; +import { useTranslation } from 'react-i18next'; const MainModelInputFieldComponent = ( props: FieldComponentProps< @@ -29,7 +30,7 @@ const MainModelInputFieldComponent = ( const { nodeId, field } = props; const dispatch = useAppDispatch(); const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled; - + const { t } = useTranslation(); const { data: onnxModels, isLoading: isLoadingOnnxModels } = useGetOnnxModelsQuery(NON_SDXL_MAIN_MODELS); const { data: mainModels, isLoading: isLoadingMainModels } = @@ -127,7 +128,9 @@ const MainModelInputFieldComponent = ( tooltip={selectedModel?.description} value={selectedModel?.id} placeholder={ - data.length > 0 ? 'Select a model' : 'No models available' + data.length > 0 + ? t('models.selectModel') + : t('models.noModelsAvailable') } data={data} error={!selectedModel} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/RefinerModelInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/RefinerModelInputField.tsx index edad33d342..19f2c5ac8e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/RefinerModelInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/RefinerModelInputField.tsx @@ -89,7 +89,7 @@ const RefinerModelInputFieldComponent = ( return isLoading ? ( @@ -99,7 +99,11 @@ const RefinerModelInputFieldComponent = ( className="nowheel nodrag" tooltip={selectedModel?.description} value={selectedModel?.id} - placeholder={data.length > 0 ? 'Select a model' : 'No models available'} + placeholder={ + data.length > 0 + ? t('models.selectModel') + : t('models.noModelsAvailable') + } data={data} error={!selectedModel} disabled={data.length === 0} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/SDXLMainModelInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/SDXLMainModelInputField.tsx index ffb4d8d412..89cb8d5150 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/SDXLMainModelInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/SDXLMainModelInputField.tsx @@ -116,7 +116,7 @@ const ModelInputFieldComponent = ( return isLoading ? ( @@ -126,7 +126,11 @@ const ModelInputFieldComponent = ( className="nowheel nodrag" tooltip={selectedModel?.description} value={selectedModel?.id} - placeholder={data.length > 0 ? 'Select a model' : 'No models available'} + placeholder={ + data.length > 0 + ? t('models.selectModel') + : t('models.noModelsAvailable') + } data={data} error={!selectedModel} disabled={data.length === 0} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx index 283e5d115d..e31ac19be0 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx @@ -12,6 +12,7 @@ import { useNodeTemplateTitle } from 'features/nodes/hooks/useNodeTemplateTitle' import { nodeLabelChanged } from 'features/nodes/store/nodesSlice'; import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants'; import { MouseEvent, memo, useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; type Props = { nodeId: string; @@ -22,16 +23,17 @@ const NodeTitle = ({ nodeId, title }: Props) => { const dispatch = useAppDispatch(); const label = useNodeLabel(nodeId); const templateTitle = useNodeTemplateTitle(nodeId); + const { t } = useTranslation(); const [localTitle, setLocalTitle] = useState(''); const handleSubmit = useCallback( async (newTitle: string) => { dispatch(nodeLabelChanged({ nodeId, label: newTitle })); setLocalTitle( - newTitle || title || templateTitle || 'Problem Setting Title' + label || title || templateTitle || t('nodes.problemSettingTitle') ); }, - [dispatch, nodeId, title, templateTitle] + [dispatch, nodeId, title, templateTitle, label, t] ); const handleChange = useCallback((newTitle: string) => { @@ -40,8 +42,10 @@ const NodeTitle = ({ nodeId, title }: Props) => { useEffect(() => { // Another component may change the title; sync local title with global state - setLocalTitle(label || title || templateTitle || 'Problem Setting Title'); - }, [label, templateTitle, title]); + setLocalTitle( + label || title || templateTitle || t('nodes.problemSettingTitle') + ); + }, [label, templateTitle, title, t]); return ( state.nodes.nodeOpacity); + const { t } = useTranslation(); const handleChange = useCallback( (v: number) => { @@ -23,7 +25,7 @@ export default function NodeOpacitySlider() { return ( { const dispatch = useAppDispatch(); - + const { t } = useTranslation(); const handleOpenAddNodePopover = useCallback(() => { dispatch(addNodePopoverOpened()); }, [dispatch]); @@ -15,8 +16,8 @@ const TopLeftPanel = () => { return ( } onClick={handleOpenAddNodePopover} /> diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings.tsx index c423750cd8..b822b2abb9 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings.tsx @@ -29,6 +29,7 @@ import { ChangeEvent, memo, useCallback } from 'react'; import { FaCog } from 'react-icons/fa'; import { SelectionMode } from 'reactflow'; import ReloadNodeTemplatesButton from '../TopCenterPanel/ReloadSchemaButton'; +import { useTranslation } from 'react-i18next'; const formLabelProps: FormLabelProps = { fontWeight: 600, @@ -101,12 +102,14 @@ const WorkflowEditorSettings = forwardRef((_, ref) => { [dispatch] ); + const { t } = useTranslation(); + return ( <> } onClick={onOpen} /> @@ -114,7 +117,7 @@ const WorkflowEditorSettings = forwardRef((_, ref) => { - Workflow Editor Settings + {t('nodes.workflowSettings')} { formLabelProps={formLabelProps} onChange={handleChangeShouldAnimate} isChecked={shouldAnimateEdges} - label="Animated Edges" - helperText="Animate selected edges and edges connected to selected nodes" + label={t('nodes.animatedEdges')} + helperText={t('nodes.animatedEdgesHelp')} /> Advanced @@ -162,8 +165,8 @@ const WorkflowEditorSettings = forwardRef((_, ref) => { formLabelProps={formLabelProps} isChecked={shouldValidateGraph} onChange={handleChangeShouldValidate} - label="Validate Connections and Graph" - helperText="Prevent invalid connections from being made, and invalid graphs from being invoked" + label={t('nodes.validateConnections')} + helperText={t('nodes.validateConnectionsHelp')} /> diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx index d626a92021..ffc260b95a 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx @@ -9,6 +9,7 @@ import { memo } from 'react'; import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea'; import NodeTitle from '../../flow/nodes/common/NodeTitle'; import ScrollableContent from '../ScrollableContent'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -34,9 +35,12 @@ const selector = createSelector( const InspectorDetailsTab = () => { const { data, template } = useAppSelector(selector); + const { t } = useTranslation(); if (!template || !data) { - return ; + return ( + + ); } return ; diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx index f6b229f997..b6a194051e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx @@ -11,6 +11,7 @@ import { ImageOutput } from 'services/api/types'; import { AnyResult } from 'services/events/types'; import ScrollableContent from '../ScrollableContent'; import ImageOutputPreview from './outputs/ImageOutputPreview'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -40,13 +41,18 @@ const selector = createSelector( const InspectorOutputsTab = () => { const { node, template, nes } = useAppSelector(selector); + const { t } = useTranslation(); if (!node || !nes || !isInvocationNode(node)) { - return ; + return ( + + ); } if (nes.outputs.length === 0) { - return ; + return ( + + ); } return ( @@ -77,7 +83,7 @@ const InspectorOutputsTab = () => { /> )) ) : ( - + )} diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorTemplateTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorTemplateTab.tsx index 525b58b1cb..b5e358239a 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorTemplateTab.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorTemplateTab.tsx @@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer'; import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -29,12 +30,15 @@ const selector = createSelector( const NodeTemplateInspector = () => { const { template } = useAppSelector(selector); + const { t } = useTranslation(); if (!template) { - return ; + return ( + + ); } - return ; + return ; }; export default memo(NodeTemplateInspector); diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowGeneralTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowGeneralTab.tsx index e36675b71f..ad1070096e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowGeneralTab.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowGeneralTab.tsx @@ -16,6 +16,7 @@ import { } from 'features/nodes/store/nodesSlice'; import { ChangeEvent, memo, useCallback } from 'react'; import ScrollableContent from '../ScrollableContent'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -85,6 +86,8 @@ const WorkflowGeneralTab = () => { [dispatch] ); + const { t } = useTranslation(); + return ( { }} > - + - + - Short Description + {t('nodes.workflowDescription')} { /> - Notes + {t('nodes.workflowNotes')} { const workflow = useWorkflow(); + const { t } = useTranslation(); return ( { h: 'full', }} > - + ); }; diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLinearTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLinearTab.tsx index d1cecefbff..cf9e11d9d6 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLinearTab.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLinearTab.tsx @@ -7,6 +7,7 @@ import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { memo } from 'react'; import LinearViewField from '../../flow/nodes/Invocation/fields/LinearViewField'; import ScrollableContent from '../ScrollableContent'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -20,6 +21,7 @@ const selector = createSelector( const WorkflowLinearTab = () => { const { fields } = useAppSelector(selector); + const { t } = useTranslation(); return ( { )) ) : ( )} diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useLoadWorkflowFromFile.tsx b/invokeai/frontend/web/src/features/nodes/hooks/useLoadWorkflowFromFile.tsx index 7f015ac5eb..890fa7a72d 100644 --- a/invokeai/frontend/web/src/features/nodes/hooks/useLoadWorkflowFromFile.tsx +++ b/invokeai/frontend/web/src/features/nodes/hooks/useLoadWorkflowFromFile.tsx @@ -9,10 +9,12 @@ import { memo, useCallback } from 'react'; import { ZodError } from 'zod'; import { fromZodError, fromZodIssue } from 'zod-validation-error'; import { workflowLoadRequested } from '../store/actions'; +import { useTranslation } from 'react-i18next'; export const useLoadWorkflowFromFile = () => { const dispatch = useAppDispatch(); const logger = useLogger('nodes'); + const { t } = useTranslation(); const loadWorkflowFromFile = useCallback( (file: File | null) => { if (!file) { @@ -28,7 +30,7 @@ export const useLoadWorkflowFromFile = () => { if (!result.success) { const { message } = fromZodError(result.error, { - prefix: 'Workflow Validation Error', + prefix: t('nodes.workflowValidation'), }); logger.error({ error: parseify(result.error) }, message); @@ -36,7 +38,7 @@ export const useLoadWorkflowFromFile = () => { dispatch( addToast( makeToast({ - title: 'Unable to Validate Workflow', + title: t('nodes.unableToValidateWorkflow'), status: 'error', duration: 5000, }) @@ -54,7 +56,7 @@ export const useLoadWorkflowFromFile = () => { dispatch( addToast( makeToast({ - title: 'Unable to Load Workflow', + title: t('nodes.unableToLoadWorkflow'), status: 'error', }) ) @@ -64,7 +66,7 @@ export const useLoadWorkflowFromFile = () => { reader.readAsText(file); }, - [dispatch, logger] + [dispatch, logger, t] ); return loadWorkflowFromFile; diff --git a/invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts b/invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts index 5cb6d557e8..ac157bb476 100644 --- a/invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts +++ b/invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts @@ -9,6 +9,7 @@ import { } from 'features/nodes/types/constants'; import { FieldType } from 'features/nodes/types/types'; import { HandleType } from 'reactflow'; +import i18n from 'i18next'; /** * NOTE: The logic here must be duplicated in `invokeai/frontend/web/src/features/nodes/hooks/useIsValidConnection.ts` @@ -20,17 +21,17 @@ export const makeConnectionErrorSelector = ( fieldName: string, handleType: HandleType, fieldType?: FieldType -) => - createSelector(stateSelector, (state) => { +) => { + return createSelector(stateSelector, (state) => { if (!fieldType) { - return 'No field type'; + return i18n.t('nodes.noFieldType'); } const { currentConnectionFieldType, connectionStartParams, nodes, edges } = state.nodes; if (!connectionStartParams || !currentConnectionFieldType) { - return 'No connection in progress'; + return i18n.t('nodes.noConnectionInProgress'); } const { @@ -40,7 +41,7 @@ export const makeConnectionErrorSelector = ( } = connectionStartParams; if (!connectionHandleType || !connectionNodeId || !connectionFieldName) { - return 'No connection data'; + return i18n.t('nodes.noConnectionData'); } const targetType = @@ -49,14 +50,14 @@ export const makeConnectionErrorSelector = ( handleType === 'source' ? fieldType : currentConnectionFieldType; if (nodeId === connectionNodeId) { - return 'Cannot connect to self'; + return i18n.t('nodes.cannotConnectToSelf'); } if (handleType === connectionHandleType) { if (handleType === 'source') { - return 'Cannot connect output to output'; + return i18n.t('nodes.cannotConnectOutputToOutput'); } - return 'Cannot connect input to input'; + return i18n.t('nodes.cannotConnectInputToInput'); } if ( @@ -66,7 +67,7 @@ export const makeConnectionErrorSelector = ( // except CollectionItem inputs can have multiples targetType !== 'CollectionItem' ) { - return 'Input may only have one connection'; + return i18n.t('nodes.inputMayOnlyHaveOneConnection'); } /** @@ -125,7 +126,7 @@ export const makeConnectionErrorSelector = ( isIntToFloat ) ) { - return 'Field types must match'; + return i18n.t('nodes.fieldTypesMustMatch'); } } @@ -137,8 +138,9 @@ export const makeConnectionErrorSelector = ( ); if (!isGraphAcyclic) { - return 'Connection would create a cycle'; + return i18n.t('nodes.connectionWouldCreateCycle'); } return null; }); +}; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx index bca1402571..2d461f7bb4 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx @@ -5,6 +5,7 @@ import { useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAICollapse from 'common/components/IAICollapse'; import ParamClipSkip from './ParamClipSkip'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -22,13 +23,13 @@ export default function ParamAdvancedCollapse() { const shouldShowAdvancedOptions = useAppSelector( (state: RootState) => state.generation.shouldShowAdvancedOptions ); - + const { t } = useTranslation(); if (!shouldShowAdvancedOptions) { return null; } return ( - + diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamCpuNoise.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamCpuNoise.tsx index 45fd7fcf57..f10c3dd1a5 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamCpuNoise.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamCpuNoise.tsx @@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISwitch from 'common/components/IAISwitch'; import { shouldUseCpuNoiseChanged } from 'features/parameters/store/generationSlice'; import { ChangeEvent } from 'react'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -21,6 +22,7 @@ const selector = createSelector( export const ParamCpuNoiseToggle = () => { const dispatch = useAppDispatch(); const { isDisabled, shouldUseCpuNoise } = useAppSelector(selector); + const { t } = useTranslation(); const handleChange = (e: ChangeEvent) => dispatch(shouldUseCpuNoiseChanged(e.target.checked)); @@ -28,7 +30,7 @@ export const ParamCpuNoiseToggle = () => { return ( diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseToggle.tsx index c1c2fb5119..4ec6b0163b 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseToggle.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseToggle.tsx @@ -3,9 +3,11 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAISwitch from 'common/components/IAISwitch'; import { setShouldUseNoiseSettings } from 'features/parameters/store/generationSlice'; import { ChangeEvent } from 'react'; +import { useTranslation } from 'react-i18next'; export const ParamNoiseToggle = () => { const dispatch = useAppDispatch(); + const { t } = useTranslation(); const shouldUseNoiseSettings = useAppSelector( (state: RootState) => state.generation.shouldUseNoiseSettings @@ -16,7 +18,7 @@ export const ParamNoiseToggle = () => { return ( diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx index e7bd36b931..6a80ccb52f 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx @@ -146,7 +146,7 @@ const CancelButton = (props: Props) => { id="cancel-button" {...rest} > - Cancel + {t('parameters.cancel.cancel')} )} diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx index c70f2528bb..0b81fceed7 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx @@ -76,7 +76,7 @@ export default function InvokeButton(props: InvokeButton) { )} {asIconButton ? ( } isDisabled={!isReady} @@ -96,7 +96,7 @@ export default function InvokeButton(props: InvokeButton) { ) : ( } - aria-label={t('parameters.invoke')} + aria-label={t('parameters.invoke.invoke')} type="submit" data-progress={isProcessing} isDisabled={!isReady} @@ -105,7 +105,7 @@ export default function InvokeButton(props: InvokeButton) { id="invoke-button" leftIcon={isProcessing ? undefined : } isLoading={isProcessing} - loadingText={t('parameters.invoke')} + loadingText={t('parameters.invoke.invoke')} sx={{ w: 'full', flexGrow: 1, @@ -138,11 +138,14 @@ export const InvokeButtonTooltipContent = memo(() => { const { isReady, reasons } = useIsReadyToInvoke(); const { autoAddBoardId } = useAppSelector(tooltipSelector); const autoAddBoardName = useBoardName(autoAddBoardId); + const { t } = useTranslation(); return ( - {isReady ? 'Ready to Invoke' : 'Unable to Invoke'} + {isReady + ? t('parameters.invoke.readyToInvoke') + : t('parameters.invoke.unableToInvoke')} {reasons.length > 0 && ( @@ -159,7 +162,7 @@ export const InvokeButtonTooltipContent = memo(() => { _dark={{ borderColor: 'base.900' }} /> - Adding images to{' '} + {t('parameters.invoke.addingImagesTo')}{' '} {autoAddBoardName || 'Uncategorized'} diff --git a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLConcatButton.tsx b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLConcatButton.tsx index 057ee6fe78..94c4a3aeb0 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLConcatButton.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLConcatButton.tsx @@ -3,6 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIIconButton from 'common/components/IAIIconButton'; import { FaLink } from 'react-icons/fa'; import { setShouldConcatSDXLStylePrompt } from '../store/sdxlSlice'; +import { useTranslation } from 'react-i18next'; export default function ParamSDXLConcatButton() { const shouldConcatSDXLStylePrompt = useAppSelector( @@ -10,6 +11,7 @@ export default function ParamSDXLConcatButton() { ); const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleShouldConcatPromptChange = () => { dispatch(setShouldConcatSDXLStylePrompt(!shouldConcatSDXLStylePrompt)); @@ -17,8 +19,8 @@ export default function ParamSDXLConcatButton() { return ( { return ( { const isReady = useIsReadyToInvoke(); const promptRef = useRef(null); const { isOpen, onClose, onOpen } = useDisclosure(); + const { t } = useTranslation(); const { prompt, activeTabName, shouldConcatSDXLStylePrompt } = useAppSelector(promptInputSelector); @@ -143,7 +145,7 @@ const ParamSDXLNegativeStyleConditioning = () => { name="prompt" ref={promptRef} value={prompt} - placeholder="Negative Style Prompt" + placeholder={t('sdxl.negStylePrompt')} onChange={handleChangePrompt} onKeyDown={handleKeyDown} resize="vertical" diff --git a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLPositiveStyleConditioning.tsx b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLPositiveStyleConditioning.tsx index 8ff2f9f19e..b193e3adbd 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLPositiveStyleConditioning.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLPositiveStyleConditioning.tsx @@ -6,6 +6,7 @@ import { ChangeEvent, KeyboardEvent, memo, useCallback, useRef } from 'react'; import { createSelector } from '@reduxjs/toolkit'; import { clampSymmetrySteps } from 'features/parameters/store/generationSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; +import { useTranslation } from 'react-i18next'; import { userInvoked } from 'app/store/actions'; import IAITextarea from 'common/components/IAITextarea'; @@ -45,6 +46,7 @@ const ParamSDXLPositiveStyleConditioning = () => { const isReady = useIsReadyToInvoke(); const promptRef = useRef(null); const { isOpen, onClose, onOpen } = useDisclosure(); + const { t } = useTranslation(); const { prompt, activeTabName, shouldConcatSDXLStylePrompt } = useAppSelector(promptInputSelector); @@ -143,7 +145,7 @@ const ParamSDXLPositiveStyleConditioning = () => { name="prompt" ref={promptRef} value={prompt} - placeholder="Positive Style Prompt" + placeholder={t('sdxl.posStylePrompt')} onChange={handleChangePrompt} onKeyDown={handleKeyDown} resize="vertical" diff --git a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLRefinerCollapse.tsx b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLRefinerCollapse.tsx index fcf4d0bcd3..6ac3034834 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLRefinerCollapse.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLRefinerCollapse.tsx @@ -13,6 +13,7 @@ import ParamSDXLRefinerScheduler from './SDXLRefiner/ParamSDXLRefinerScheduler'; import ParamSDXLRefinerStart from './SDXLRefiner/ParamSDXLRefinerStart'; import ParamSDXLRefinerSteps from './SDXLRefiner/ParamSDXLRefinerSteps'; import ParamUseSDXLRefiner from './SDXLRefiner/ParamUseSDXLRefiner'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -29,9 +30,10 @@ const selector = createSelector( const ParamSDXLRefinerCollapse = () => { const { activeLabel, shouldUseSliders } = useAppSelector(selector); + const { t } = useTranslation(); return ( - + diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerCFGScale.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerCFGScale.tsx index dd678ac0f7..fb16c01676 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerCFGScale.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerCFGScale.tsx @@ -43,7 +43,7 @@ const ParamSDXLRefinerCFGScale = () => { return shouldUseSliders ? ( { /> ) : ( { const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled; const { model } = useAppSelector(selector); + const { t } = useTranslation(); const { data: refinerModels, isLoading } = useGetMainModelsQuery(REFINER_BASE_MODELS); @@ -81,8 +83,8 @@ const ParamSDXLRefinerModelSelect = () => { return isLoading ? ( @@ -90,9 +92,11 @@ const ParamSDXLRefinerModelSelect = () => { 0 ? 'Select a model' : 'No models available'} + placeholder={ + data.length > 0 ? t('sdxl.selectAModel') : t('sdxl.noModelsAvailable') + } data={data} error={data.length === 0} disabled={data.length === 0} diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerNegativeAestheticScore.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerNegativeAestheticScore.tsx index 4dad3f519a..bc08d64e9f 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerNegativeAestheticScore.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerNegativeAestheticScore.tsx @@ -6,6 +6,7 @@ import IAISlider from 'common/components/IAISlider'; import { setRefinerNegativeAestheticScore } from 'features/sdxl/store/sdxlSlice'; import { memo, useCallback } from 'react'; import { useIsRefinerAvailable } from 'services/api/hooks/useIsRefinerAvailable'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( [stateSelector], @@ -27,6 +28,7 @@ const ParamSDXLRefinerNegativeAestheticScore = () => { const isRefinerAvailable = useIsRefinerAvailable(); const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleChange = useCallback( (v: number) => dispatch(setRefinerNegativeAestheticScore(v)), @@ -40,7 +42,7 @@ const ParamSDXLRefinerNegativeAestheticScore = () => { return ( { const isRefinerAvailable = useIsRefinerAvailable(); const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleChange = useCallback( (v: number) => dispatch(setRefinerPositiveAestheticScore(v)), @@ -40,7 +42,7 @@ const ParamSDXLRefinerPositiveAestheticScore = () => { return ( { return ( { (v: number) => dispatch(setRefinerStart(v)), [dispatch] ); + const { t } = useTranslation(); const handleReset = useCallback( () => dispatch(setRefinerStart(0.8)), @@ -34,7 +36,7 @@ const ParamSDXLRefinerStart = () => { return ( { return shouldUseSliders ? ( { /> ) : ( ) => { dispatch(setShouldUseSDXLRefiner(e.target.checked)); @@ -19,7 +21,7 @@ export default function ParamUseSDXLRefiner() { return ( { @@ -114,14 +117,15 @@ export default function AdvancedAddCheckpoint( }} /> @@ -134,14 +138,14 @@ export default function AdvancedAddCheckpoint( ) : ( )} setUseCustomConfig(!useCustomConfig)} - label="Use Custom Config" + label={t('modelManager.useCustomConfig')} /> {t('modelManager.addModel')} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx index 181a6f4234..7c22b4b2ac 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx @@ -47,7 +47,9 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) { dispatch( addToast( makeToast({ - title: `Model Added: ${values.model_name}`, + title: t('modelManager.modelAdded', { + modelName: values.model_name, + }), status: 'success', }) ) @@ -63,7 +65,7 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) { dispatch( addToast( makeToast({ - title: 'Model Add Failed', + title: t('toast.modelAddFailed'), status: 'error', }) ) @@ -82,16 +84,17 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) { { if (advancedAddDiffusersForm.values['model_name'] === '') { @@ -106,14 +109,15 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) { }} /> diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx index a2a12b7f00..b78d0466c0 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx @@ -4,6 +4,7 @@ import IAIMantineSelect from 'common/components/IAIMantineSelect'; import { useState } from 'react'; import AdvancedAddCheckpoint from './AdvancedAddCheckpoint'; import AdvancedAddDiffusers from './AdvancedAddDiffusers'; +import { useTranslation } from 'react-i18next'; export const advancedAddModeData: SelectItem[] = [ { label: 'Diffusers', value: 'diffusers' }, @@ -16,10 +17,12 @@ export default function AdvancedAddModels() { const [advancedAddMode, setAdvancedAddMode] = useState('diffusers'); + const { t } = useTranslation(); + return ( { diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx index a44a747438..a2e6158515 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx @@ -77,7 +77,7 @@ export default function FoundModelsList() { dispatch( addToast( makeToast({ - title: 'Failed To Add Model', + title: t('toast.modelAddFailed'), status: 'error', }) ) @@ -85,7 +85,7 @@ export default function FoundModelsList() { } }); }, - [dispatch, importMainModel] + [dispatch, importMainModel, t] ); const handleSearchFilter = useCallback((e: ChangeEvent) => { @@ -137,13 +137,13 @@ export default function FoundModelsList() { onClick={quickAddHandler} isLoading={isLoading} > - Quick Add + {t('modelManager.quickAdd')} dispatch(setAdvancedAddScanModel(model))} isLoading={isLoading} > - Advanced + {t('modelManager.advanced')} ) : ( @@ -189,7 +189,7 @@ export default function FoundModelsList() { }, }} > - No Models Found + {t('modelManager.noModels')} ); } diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx index 997341279d..615c76b71e 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx @@ -10,12 +10,15 @@ import { setAdvancedAddScanModel } from '../../store/modelManagerSlice'; import AdvancedAddCheckpoint from './AdvancedAddCheckpoint'; import AdvancedAddDiffusers from './AdvancedAddDiffusers'; import { ManualAddMode, advancedAddModeData } from './AdvancedAddModels'; +import { useTranslation } from 'react-i18next'; export default function ScanAdvancedAddModels() { const advancedAddScanModel = useAppSelector( (state: RootState) => state.modelmanager.advancedAddScanModel ); + const { t } = useTranslation(); + const [advancedAddMode, setAdvancedAddMode] = useState('diffusers'); @@ -64,13 +67,13 @@ export default function ScanAdvancedAddModels() { } - aria-label="Close Advanced" + aria-label={t('modelManager.closeAdvanced')} onClick={() => dispatch(setAdvancedAddScanModel(null))} size="sm" /> { diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx index cb0ce627a0..fb1a5167a6 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx @@ -55,7 +55,7 @@ export default function SimpleAddModels() { dispatch( addToast( makeToast({ - title: 'Model Added', + title: t('toast.modelAddSimple'), status: 'success', }) ) @@ -84,13 +84,13 @@ export default function SimpleAddModels() { > Date: Wed, 13 Sep 2023 09:06:38 +0100 Subject: [PATCH 19/31] Prompts from file support nodes (#3964) * New classes to support the PromptsFromFileInvocation Class - PromptPosNegOutput - PromptSplitNegInvocation - PromptJoinInvocation - PromptReplaceInvocation * - Added PromptsToFileInvocation, - PromptSplitNegInvocation - now counts the bracket depth so ensures it cout the numbr of open and close brackets match. - checks for escaped [ ] so ignores them if escaped e.g \[ - PromptReplaceInvocation - now has a user regex. and no regex in made caseinsesitive * Update prompt.py created class PromptsToFileInvocationOutput and use it in PromptsToFileInvocation instead of BaseInvocationOutput * Update prompt.py * Added schema_extra title and tags for PromptReplaceInvocation, PromptJoinInvocation, PromptSplitNegInvocation and PromptsToFileInvocation * Added PTFileds Collect and Expand * update to nodes v1 * added ui_type to file_path for PromptToFile * update params for the primitive types used, remove the ui_type filepath, promptsToFile now only accepts collections until a fix is available * updated the parameters for the StringOutput primitive * moved the prompt tools nodes out of the prompt.py into prompt_tools.py * more rework for v1 * added github link * updated to use "@invocation" * updated tags * Adde new nodes PromptStrength and PromptStrengthsCombine * chore: black * feat(nodes): add version to prompt nodes * renamed nodes from prompt related to string related. Also moved them into a strings.py file. Also moved and renamed the PromptsFromFileInvocation from prompt.py to strings.py. The PTfileds still remain in the Prompt_tool.py for now. * added , version="1.0.0" to the invocations * removed the PTField related nodes and the prompt-tools.py file all new nodes now live in the * formatted prompt.py and strings.py with Black and fixed silly mistake in the new StringSplitInvocation * - Revert Prompt.py back to original - Update strings.py to be only StringJoin, StringJoinThre, StringReplace, StringSplitNeg, StringSplit * applied isort to imports * fix(nodes): typos in `strings.py` --------- Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Co-authored-by: Millun Atluri --- invokeai/app/invocations/strings.py | 139 ++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 invokeai/app/invocations/strings.py diff --git a/invokeai/app/invocations/strings.py b/invokeai/app/invocations/strings.py new file mode 100644 index 0000000000..3466206b37 --- /dev/null +++ b/invokeai/app/invocations/strings.py @@ -0,0 +1,139 @@ +# 2023 skunkworxdark (https://github.com/skunkworxdark) + +import re + +from .baseinvocation import ( + BaseInvocation, + BaseInvocationOutput, + InputField, + InvocationContext, + OutputField, + UIComponent, + invocation, + invocation_output, +) +from .primitives import StringOutput + + +@invocation_output("string_pos_neg_output") +class StringPosNegOutput(BaseInvocationOutput): + """Base class for invocations that output a positive and negative string""" + + positive_string: str = OutputField(description="Positive string") + negative_string: str = OutputField(description="Negative string") + + +@invocation( + "string_split_neg", + title="String Split Negative", + tags=["string", "split", "negative"], + category="string", + version="1.0.0", +) +class StringSplitNegInvocation(BaseInvocation): + """Splits string into two strings, inside [] goes into negative string everthing else goes into positive string. Each [ and ] character is replaced with a space""" + + string: str = InputField(default="", description="String to split", ui_component=UIComponent.Textarea) + + def invoke(self, context: InvocationContext) -> StringPosNegOutput: + p_string = "" + n_string = "" + brackets_depth = 0 + escaped = False + + for char in self.string or "": + if char == "[" and not escaped: + n_string += " " + brackets_depth += 1 + elif char == "]" and not escaped: + brackets_depth -= 1 + char = " " + elif brackets_depth > 0: + n_string += char + else: + p_string += char + + # keep track of the escape char but only if it isn't escaped already + if char == "\\" and not escaped: + escaped = True + else: + escaped = False + + return StringPosNegOutput(positive_string=p_string, negative_string=n_string) + + +@invocation_output("string_2_output") +class String2Output(BaseInvocationOutput): + """Base class for invocations that output two strings""" + + string_1: str = OutputField(description="string 1") + string_2: str = OutputField(description="string 2") + + +@invocation("string_split", title="String Split", tags=["string", "split"], category="string", version="1.0.0") +class StringSplitInvocation(BaseInvocation): + """Splits string into two strings, based on the first occurance of the delimiter. The delimiter will be removed from the string""" + + string: str = InputField(default="", description="String to split", ui_component=UIComponent.Textarea) + delimiter: str = InputField( + default="", description="Delimiter to spilt with. blank will split on the first whitespace" + ) + + def invoke(self, context: InvocationContext) -> String2Output: + result = self.string.split(self.delimiter, 1) + if len(result) == 2: + part1, part2 = result + else: + part1 = result[0] + part2 = "" + + return String2Output(string_1=part1, string_2=part2) + + +@invocation("string_join", title="String Join", tags=["string", "join"], category="string", version="1.0.0") +class StringJoinInvocation(BaseInvocation): + """Joins string left to string right""" + + string_left: str = InputField(default="", description="String Left", ui_component=UIComponent.Textarea) + string_right: str = InputField(default="", description="String Right", ui_component=UIComponent.Textarea) + + def invoke(self, context: InvocationContext) -> StringOutput: + return StringOutput(value=((self.string_left or "") + (self.string_right or ""))) + + +@invocation("string_join_three", title="String Join Three", tags=["string", "join"], category="string", version="1.0.0") +class StringJoinThreeInvocation(BaseInvocation): + """Joins string left to string middle to string right""" + + string_left: str = InputField(default="", description="String Left", ui_component=UIComponent.Textarea) + string_middle: str = InputField(default="", description="String Middle", ui_component=UIComponent.Textarea) + string_right: str = InputField(default="", description="String Right", ui_component=UIComponent.Textarea) + + def invoke(self, context: InvocationContext) -> StringOutput: + return StringOutput(value=((self.string_left or "") + (self.string_middle or "") + (self.string_right or ""))) + + +@invocation( + "string_replace", title="String Replace", tags=["string", "replace", "regex"], category="string", version="1.0.0" +) +class StringReplaceInvocation(BaseInvocation): + """Replaces the search string with the replace string""" + + string: str = InputField(default="", description="String to work on", ui_component=UIComponent.Textarea) + search_string: str = InputField(default="", description="String to search for", ui_component=UIComponent.Textarea) + replace_string: str = InputField( + default="", description="String to replace the search", ui_component=UIComponent.Textarea + ) + use_regex: bool = InputField( + default=False, description="Use search string as a regex expression (non regex is case insensitive)" + ) + + def invoke(self, context: InvocationContext) -> StringOutput: + pattern = self.search_string or "" + new_string = self.string or "" + if len(pattern) > 0: + if not self.use_regex: + # None regex so make case insensitve + pattern = "(?i)" + re.escape(pattern) + new_string = re.sub(pattern, (self.replace_string or ""), new_string) + return StringOutput(value=new_string) From 0f0366f1f31d22b3b48b4e9f94c62f20f3b29177 Mon Sep 17 00:00:00 2001 From: skunkworxdark Date: Wed, 13 Sep 2023 09:26:41 +0100 Subject: [PATCH 20/31] Update collections.py (#4513) * Update collections.py RangeOfSizeInvocation was not taking step into account when generating the end point of the range * - updated the node description to refelect this mod - added a gt=0 constraint to ensure only a positive size of the range - moved the + 1 to be on the size. To ensure the range is the requested size in cases where the step is negative - formatted with Black * Removed +1 from the range calculation --------- Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com> --- invokeai/app/invocations/collections.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/invokeai/app/invocations/collections.py b/invokeai/app/invocations/collections.py index 2814a9c3ca..702eb99831 100644 --- a/invokeai/app/invocations/collections.py +++ b/invokeai/app/invocations/collections.py @@ -38,14 +38,16 @@ class RangeInvocation(BaseInvocation): version="1.0.0", ) class RangeOfSizeInvocation(BaseInvocation): - """Creates a range from start to start + size with step""" + """Creates a range from start to start + (size * step) incremented by step""" start: int = InputField(default=0, description="The start of the range") - size: int = InputField(default=1, description="The number of values") + size: int = InputField(default=1, gt=0, description="The number of values") step: int = InputField(default=1, description="The step of the range") def invoke(self, context: InvocationContext) -> IntegerCollectionOutput: - return IntegerCollectionOutput(collection=list(range(self.start, self.start + self.size, self.step))) + return IntegerCollectionOutput( + collection=list(range(self.start, self.start + (self.step * self.size), self.step)) + ) @invocation( From d989c7fa34d0f4981862e0decef62577fe978a6e Mon Sep 17 00:00:00 2001 From: Mary Hipp Rogers Date: Wed, 13 Sep 2023 16:48:10 -0400 Subject: [PATCH 21/31] add option for custom star ui (#4530) Co-authored-by: Mary Hipp --- .../frontend/web/src/app/components/App.tsx | 10 +++++++++ .../web/src/app/components/InvokeAIUI.tsx | 4 ++++ .../MultipleSelectionMenuItems.tsx | 13 ++++++++---- .../SingleSelectionMenuItems.tsx | 16 ++++++++++---- .../components/ImageGrid/GalleryImage.tsx | 21 +++++++++++++++---- .../web/src/features/ui/store/uiSlice.ts | 7 ++++++- .../web/src/features/ui/store/uiTypes.ts | 13 ++++++++++++ 7 files changed, 71 insertions(+), 13 deletions(-) diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index a70ed03fda..aa1919abfb 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -18,6 +18,8 @@ import { usePreselectedImage } from '../../features/parameters/hooks/usePreselec import AppErrorBoundaryFallback from './AppErrorBoundaryFallback'; import GlobalHotkeys from './GlobalHotkeys'; import Toaster from './Toaster'; +import { CustomStarUi } from '../../features/ui/store/uiTypes'; +import { setCustomStarUi } from '../../features/ui/store/uiSlice'; const DEFAULT_CONFIG = {}; @@ -28,12 +30,14 @@ interface Props { imageName: string; action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters'; }; + customStarUi?: CustomStarUi; } const App = ({ config = DEFAULT_CONFIG, headerComponent, selectedImage, + customStarUi, }: Props) => { const language = useAppSelector(languageSelector); @@ -57,6 +61,12 @@ const App = ({ } }, [dispatch, config, logger]); + useEffect(() => { + if (customStarUi) { + dispatch(setCustomStarUi(customStarUi)); + } + }, [customStarUi, dispatch]); + useEffect(() => { dispatch(appStarted()); }, [dispatch]); diff --git a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx index 322ee3fd2b..36636b3ca9 100644 --- a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx +++ b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx @@ -15,6 +15,7 @@ import { socketMiddleware } from 'services/events/middleware'; import Loading from '../../common/components/Loading/Loading'; import '../../i18n'; import AppDndContext from '../../features/dnd/components/AppDndContext'; +import { CustomStarUi } from '../../features/ui/store/uiTypes'; const App = lazy(() => import('./App')); const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider')); @@ -30,6 +31,7 @@ interface Props extends PropsWithChildren { imageName: string; action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters'; }; + customStarUi?: CustomStarUi; } const InvokeAIUI = ({ @@ -40,6 +42,7 @@ const InvokeAIUI = ({ middleware, projectId, selectedImage, + customStarUi, }: Props) => { useEffect(() => { // configure API client token @@ -90,6 +93,7 @@ const InvokeAIUI = ({ config={config} headerComponent={headerComponent} selectedImage={selectedImage} + customStarUi={customStarUi} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx index bf2b344b4c..e2a9013aac 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx @@ -12,10 +12,12 @@ import { useStarImagesMutation, useUnstarImagesMutation, } from '../../../../services/api/endpoints/images'; +import { uiSelector } from '../../../ui/store/uiSelectors'; const MultipleSelectionMenuItems = () => { const dispatch = useAppDispatch(); const selection = useAppSelector((state) => state.gallery.selection); + const { customStarUi } = useAppSelector(uiSelector); const [starImages] = useStarImagesMutation(); const [unstarImages] = useUnstarImagesMutation(); @@ -49,15 +51,18 @@ const MultipleSelectionMenuItems = () => { <> {areAllStarred && ( } + icon={customStarUi ? customStarUi.on.icon : } onClickCapture={handleUnstarSelection} > - Unstar All + {customStarUi ? customStarUi.off.text : `Unstar All`} )} {(areAllUnstarred || (!areAllStarred && !areAllUnstarred)) && ( - } onClickCapture={handleStarSelection}> - Star All + } + onClickCapture={handleStarSelection} + > + {customStarUi ? customStarUi.on.text : `Star All`} )} } onClickCapture={handleChangeBoard}> diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx index 46c84e85ce..68c5c2e1ae 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx @@ -35,6 +35,7 @@ import { ImageDTO } from 'services/api/types'; import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions'; import { workflowLoadRequested } from 'features/nodes/store/actions'; import { configSelector } from '../../../system/store/configSelectors'; +import { uiSelector } from '../../../ui/store/uiSelectors'; type SingleSelectionMenuItemsProps = { imageDTO: ImageDTO; @@ -50,6 +51,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled; const { shouldFetchMetadataFromApi } = useAppSelector(configSelector); + const { customStarUi } = useAppSelector(uiSelector); const { metadata, workflow, isLoading } = useGetImageMetadataFromFileQuery( { image: imageDTO, shouldFetchMetadataFromApi }, @@ -225,12 +227,18 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { Change Board {imageDTO.starred ? ( - } onClickCapture={handleUnstarImage}> - Unstar Image + } + onClickCapture={handleUnstarImage} + > + {customStarUi ? customStarUi.off.text : `Unstar Image`} ) : ( - } onClickCapture={handleStarImage}> - Star Image + } + onClickCapture={handleStarImage} + > + {customStarUi ? customStarUi.on.text : `Star Image`} )} { const { handleClick, isSelected, selection, selectionCount } = useMultiselect(imageDTO); + const { customStarUi } = useAppSelector(uiSelector); + const handleDelete = useCallback( (e: MouseEvent) => { e.stopPropagation(); @@ -91,12 +94,22 @@ const GalleryImage = (props: HoverableImageProps) => { const starIcon = useMemo(() => { if (imageDTO?.starred) { - return ; + return customStarUi ? customStarUi.on.icon : ; } if (!imageDTO?.starred && isHovered) { - return ; + return customStarUi ? customStarUi.off.icon : ; } - }, [imageDTO?.starred, isHovered]); + }, [imageDTO?.starred, isHovered, customStarUi]); + + const starTooltip = useMemo(() => { + if (imageDTO?.starred) { + return customStarUi ? customStarUi.off.text : 'Unstar'; + } + if (!imageDTO?.starred) { + return customStarUi ? customStarUi.on.text : 'Star'; + } + return ''; + }, [imageDTO?.starred, customStarUi]); if (!imageDTO) { return ; @@ -131,7 +144,7 @@ const GalleryImage = (props: HoverableImageProps) => { {isHovered && shift && ( diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index 82c9ef4e77..33edc6308f 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -4,7 +4,7 @@ import { initialImageChanged } from 'features/parameters/store/generationSlice'; import { SchedulerParam } from 'features/parameters/types/parameterSchemas'; import { setActiveTabReducer } from './extraReducers'; import { InvokeTabName } from './tabMap'; -import { UIState } from './uiTypes'; +import { CustomStarUi, UIState } from './uiTypes'; export const initialUIState: UIState = { activeTab: 0, @@ -19,6 +19,7 @@ export const initialUIState: UIState = { favoriteSchedulers: [], globalContextMenuCloseTrigger: 0, panels: {}, + customStarUi: undefined, }; export const uiSlice = createSlice({ @@ -70,6 +71,9 @@ export const uiSlice = createSlice({ ) => { state.panels[action.payload.name] = action.payload.value; }, + setCustomStarUi: (state, action: PayloadAction) => { + state.customStarUi = action.payload; + }, }, extraReducers(builder) { builder.addCase(initialImageChanged, (state) => { @@ -91,6 +95,7 @@ export const { setShouldAutoChangeDimensions, contextMenusClosed, panelsChanged, + setCustomStarUi, } = uiSlice.actions; export default uiSlice.reducer; diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts index 41a359a651..8f7b1999a4 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts @@ -1,3 +1,4 @@ +import { MenuItemProps } from '@chakra-ui/react'; import { SchedulerParam } from 'features/parameters/types/parameterSchemas'; export type Coordinates = { @@ -12,6 +13,17 @@ export type Dimensions = { export type Rect = Coordinates & Dimensions; +export type CustomStarUi = { + on: { + icon: MenuItemProps['icon']; + text: string; + }; + off: { + icon: MenuItemProps['icon']; + text: string; + }; +}; + export interface UIState { activeTab: number; shouldShowImageDetails: boolean; @@ -25,4 +37,5 @@ export interface UIState { favoriteSchedulers: SchedulerParam[]; globalContextMenuCloseTrigger: number; panels: Record; + customStarUi?: CustomStarUi; } From fab055995e2a53638b18431c3a8bf4b1c777f704 Mon Sep 17 00:00:00 2001 From: Ryan <36354352+gogurtenjoyer@users.noreply.github.com> Date: Sat, 9 Sep 2023 22:54:20 -0400 Subject: [PATCH 22/31] Add empty_cache() for MPS hardware. --- invokeai/app/invocations/latent.py | 7 +++++++ invokeai/backend/model_management/model_cache.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index b764b3b336..78f8f624f2 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -6,6 +6,7 @@ from typing import List, Literal, Optional, Union import einops import numpy as np import torch +from torch import mps import torchvision.transforms as T from diffusers.image_processor import VaeImageProcessor from diffusers.models.attention_processor import ( @@ -541,6 +542,7 @@ class DenoiseLatentsInvocation(BaseInvocation): # https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699 result_latents = result_latents.to("cpu") torch.cuda.empty_cache() + mps.empty_cache() name = f"{context.graph_execution_state_id}__{self.id}" context.services.latents.save(name, result_latents) @@ -612,6 +614,7 @@ class LatentsToImageInvocation(BaseInvocation): # clear memory as vae decode can request a lot torch.cuda.empty_cache() + mps.empty_cache() with torch.inference_mode(): # copied from diffusers pipeline @@ -624,6 +627,7 @@ class LatentsToImageInvocation(BaseInvocation): image = VaeImageProcessor.numpy_to_pil(np_image)[0] torch.cuda.empty_cache() + mps.empty_cache() image_dto = context.services.images.create( image=image, @@ -683,6 +687,7 @@ class ResizeLatentsInvocation(BaseInvocation): # https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699 resized_latents = resized_latents.to("cpu") torch.cuda.empty_cache() + mps.empty_cache() name = f"{context.graph_execution_state_id}__{self.id}" # context.services.latents.set(name, resized_latents) @@ -719,6 +724,7 @@ class ScaleLatentsInvocation(BaseInvocation): # https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699 resized_latents = resized_latents.to("cpu") torch.cuda.empty_cache() + mps.empty_cache() name = f"{context.graph_execution_state_id}__{self.id}" # context.services.latents.set(name, resized_latents) @@ -875,6 +881,7 @@ class BlendLatentsInvocation(BaseInvocation): # https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699 blended_latents = blended_latents.to("cpu") torch.cuda.empty_cache() + mps.empty_cache() name = f"{context.graph_execution_state_id}__{self.id}" # context.services.latents.set(name, resized_latents) diff --git a/invokeai/backend/model_management/model_cache.py b/invokeai/backend/model_management/model_cache.py index 6d0f36ad8c..2db46e9f64 100644 --- a/invokeai/backend/model_management/model_cache.py +++ b/invokeai/backend/model_management/model_cache.py @@ -26,6 +26,7 @@ from pathlib import Path from typing import Any, Dict, Optional, Type, Union, types import torch +from torch import mps import invokeai.backend.util.logging as logger @@ -406,6 +407,7 @@ class ModelCache(object): gc.collect() torch.cuda.empty_cache() + mps.empty_cache() self.logger.debug(f"After unloading: cached_models={len(self._cached_models)}") @@ -426,6 +428,7 @@ class ModelCache(object): gc.collect() torch.cuda.empty_cache() + mps.empty_cache() def _local_model_hash(self, model_path: Union[str, Path]) -> str: sha = hashlib.sha256() From b7296000e4a2146e5e83d53e9a39391d68f4ab2a Mon Sep 17 00:00:00 2001 From: Ryan <36354352+gogurtenjoyer@users.noreply.github.com> Date: Mon, 11 Sep 2023 00:44:43 -0400 Subject: [PATCH 23/31] made MPS calls conditional on MPS actually being the chosen device with backend available --- invokeai/app/invocations/latent.py | 22 +++++++++++++------ .../backend/model_management/model_cache.py | 10 +++++++-- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 78f8f624f2..385ddc5df8 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -6,7 +6,6 @@ from typing import List, Literal, Optional, Union import einops import numpy as np import torch -from torch import mps import torchvision.transforms as T from diffusers.image_processor import VaeImageProcessor from diffusers.models.attention_processor import ( @@ -64,6 +63,9 @@ from .compel import ConditioningField from .controlnet_image_processors import ControlField from .model import ModelInfo, UNetField, VaeField +if choose_torch_device() == torch.device("mps"): + from torch import mps + DEFAULT_PRECISION = choose_precision(choose_torch_device()) @@ -542,7 +544,8 @@ class DenoiseLatentsInvocation(BaseInvocation): # https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699 result_latents = result_latents.to("cpu") torch.cuda.empty_cache() - mps.empty_cache() + if choose_torch_device() == torch.device("mps"): + mps.empty_cache() name = f"{context.graph_execution_state_id}__{self.id}" context.services.latents.save(name, result_latents) @@ -614,7 +617,8 @@ class LatentsToImageInvocation(BaseInvocation): # clear memory as vae decode can request a lot torch.cuda.empty_cache() - mps.empty_cache() + if choose_torch_device() == torch.device("mps"): + mps.empty_cache() with torch.inference_mode(): # copied from diffusers pipeline @@ -627,7 +631,8 @@ class LatentsToImageInvocation(BaseInvocation): image = VaeImageProcessor.numpy_to_pil(np_image)[0] torch.cuda.empty_cache() - mps.empty_cache() + if choose_torch_device() == torch.device("mps"): + mps.empty_cache() image_dto = context.services.images.create( image=image, @@ -687,7 +692,8 @@ class ResizeLatentsInvocation(BaseInvocation): # https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699 resized_latents = resized_latents.to("cpu") torch.cuda.empty_cache() - mps.empty_cache() + if device == torch.device("mps"): + mps.empty_cache() name = f"{context.graph_execution_state_id}__{self.id}" # context.services.latents.set(name, resized_latents) @@ -724,7 +730,8 @@ class ScaleLatentsInvocation(BaseInvocation): # https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699 resized_latents = resized_latents.to("cpu") torch.cuda.empty_cache() - mps.empty_cache() + if device == torch.device("mps"): + mps.empty_cache() name = f"{context.graph_execution_state_id}__{self.id}" # context.services.latents.set(name, resized_latents) @@ -881,7 +888,8 @@ class BlendLatentsInvocation(BaseInvocation): # https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699 blended_latents = blended_latents.to("cpu") torch.cuda.empty_cache() - mps.empty_cache() + if device == torch.device("mps"): + mps.empty_cache() name = f"{context.graph_execution_state_id}__{self.id}" # context.services.latents.set(name, resized_latents) diff --git a/invokeai/backend/model_management/model_cache.py b/invokeai/backend/model_management/model_cache.py index 2db46e9f64..d2850e21ec 100644 --- a/invokeai/backend/model_management/model_cache.py +++ b/invokeai/backend/model_management/model_cache.py @@ -30,8 +30,12 @@ from torch import mps import invokeai.backend.util.logging as logger +from ..util.devices import choose_torch_device from .models import BaseModelType, ModelBase, ModelType, SubModelType +if choose_torch_device() == torch.device("mps"): + from torch import mps + # Maximum size of the cache, in gigs # Default is roughly enough to hold three fp16 diffusers models in RAM simultaneously DEFAULT_MAX_CACHE_SIZE = 6.0 @@ -407,7 +411,8 @@ class ModelCache(object): gc.collect() torch.cuda.empty_cache() - mps.empty_cache() + if choose_torch_device() == torch.device("mps"): + mps.empty_cache() self.logger.debug(f"After unloading: cached_models={len(self._cached_models)}") @@ -428,7 +433,8 @@ class ModelCache(object): gc.collect() torch.cuda.empty_cache() - mps.empty_cache() + if choose_torch_device() == torch.device("mps"): + mps.empty_cache() def _local_model_hash(self, model_path: Union[str, Path]) -> str: sha = hashlib.sha256() From 2f5e923008673550ca6fa7db43662ba52aa4abd0 Mon Sep 17 00:00:00 2001 From: Ryan <36354352+gogurtenjoyer@users.noreply.github.com> Date: Mon, 11 Sep 2023 00:50:10 -0400 Subject: [PATCH 24/31] Removed duplicate import in model_cache.py --- invokeai/backend/model_management/model_cache.py | 1 - 1 file changed, 1 deletion(-) diff --git a/invokeai/backend/model_management/model_cache.py b/invokeai/backend/model_management/model_cache.py index d2850e21ec..8c015441b7 100644 --- a/invokeai/backend/model_management/model_cache.py +++ b/invokeai/backend/model_management/model_cache.py @@ -26,7 +26,6 @@ from pathlib import Path from typing import Any, Dict, Optional, Type, Union, types import torch -from torch import mps import invokeai.backend.util.logging as logger From f6738d647e163793adcfbb5e0857fa6376be9f2c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 14 Sep 2023 09:02:54 +1000 Subject: [PATCH 25/31] fix(ui): store customStarUI outside redux JSX is not serializable, so it cannot be in redux. Non-serializable global state may be put into `nanostores`. - Use `nanostores` for `customStarUI` - Use `nanostores` for `headerComponent` - Re-enable the serializable & immutable check redux middlewares --- .../frontend/web/src/app/components/App.tsx | 23 ++++---------- .../web/src/app/components/InvokeAIUI.tsx | 30 ++++++++++++++----- .../src/app/store/nanostores/customStarUI.ts | 14 +++++++++ .../app/store/nanostores/headerComponent.ts | 4 +++ .../web/src/app/store/nanostores/index.ts | 3 ++ invokeai/frontend/web/src/app/store/store.ts | 5 +--- .../MultipleSelectionMenuItems.tsx | 5 ++-- .../SingleSelectionMenuItems.tsx | 9 +++--- .../components/ImageGrid/GalleryImage.tsx | 7 +++-- .../web/src/features/ui/store/uiSlice.ts | 7 +---- .../web/src/features/ui/store/uiTypes.ts | 13 -------- 11 files changed, 64 insertions(+), 56 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/nanostores/customStarUI.ts create mode 100644 invokeai/frontend/web/src/app/store/nanostores/headerComponent.ts create mode 100644 invokeai/frontend/web/src/app/store/nanostores/index.ts diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index aa1919abfb..8c033440e3 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -12,33 +12,26 @@ import { languageSelector } from 'features/system/store/systemSelectors'; import InvokeTabs from 'features/ui/components/InvokeTabs'; import i18n from 'i18n'; import { size } from 'lodash-es'; -import { ReactNode, memo, useCallback, useEffect } from 'react'; +import { memo, useCallback, useEffect } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { usePreselectedImage } from '../../features/parameters/hooks/usePreselectedImage'; import AppErrorBoundaryFallback from './AppErrorBoundaryFallback'; import GlobalHotkeys from './GlobalHotkeys'; import Toaster from './Toaster'; -import { CustomStarUi } from '../../features/ui/store/uiTypes'; -import { setCustomStarUi } from '../../features/ui/store/uiSlice'; +import { useStore } from '@nanostores/react'; +import { $headerComponent } from 'app/store/nanostores/headerComponent'; const DEFAULT_CONFIG = {}; interface Props { config?: PartialAppConfig; - headerComponent?: ReactNode; selectedImage?: { imageName: string; action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters'; }; - customStarUi?: CustomStarUi; } -const App = ({ - config = DEFAULT_CONFIG, - headerComponent, - selectedImage, - customStarUi, -}: Props) => { +const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => { const language = useAppSelector(languageSelector); const logger = useLogger('system'); @@ -61,12 +54,6 @@ const App = ({ } }, [dispatch, config, logger]); - useEffect(() => { - if (customStarUi) { - dispatch(setCustomStarUi(customStarUi)); - } - }, [customStarUi, dispatch]); - useEffect(() => { dispatch(appStarted()); }, [dispatch]); @@ -75,6 +62,8 @@ const App = ({ handlePreselectedImage(selectedImage); }, [handlePreselectedImage, selectedImage]); + const headerComponent = useStore($headerComponent); + return ( import('./App')); const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider')); @@ -83,18 +84,33 @@ const InvokeAIUI = ({ }; }, [apiUrl, token, middleware, projectId]); + useEffect(() => { + if (customStarUi) { + $customStarUI.set(customStarUi); + } + + return () => { + $customStarUI.set(undefined); + }; + }, [customStarUi]); + + useEffect(() => { + if (headerComponent) { + $headerComponent.set(headerComponent); + } + + return () => { + $headerComponent.set(undefined); + }; + }, [headerComponent]); + return ( }> - + diff --git a/invokeai/frontend/web/src/app/store/nanostores/customStarUI.ts b/invokeai/frontend/web/src/app/store/nanostores/customStarUI.ts new file mode 100644 index 0000000000..0459c2f31f --- /dev/null +++ b/invokeai/frontend/web/src/app/store/nanostores/customStarUI.ts @@ -0,0 +1,14 @@ +import { MenuItemProps } from '@chakra-ui/react'; +import { atom } from 'nanostores'; + +export type CustomStarUi = { + on: { + icon: MenuItemProps['icon']; + text: string; + }; + off: { + icon: MenuItemProps['icon']; + text: string; + }; +}; +export const $customStarUI = atom(undefined); diff --git a/invokeai/frontend/web/src/app/store/nanostores/headerComponent.ts b/invokeai/frontend/web/src/app/store/nanostores/headerComponent.ts new file mode 100644 index 0000000000..90a4775ff9 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/nanostores/headerComponent.ts @@ -0,0 +1,4 @@ +import { atom } from 'nanostores'; +import { ReactNode } from 'react'; + +export const $headerComponent = atom(undefined); diff --git a/invokeai/frontend/web/src/app/store/nanostores/index.ts b/invokeai/frontend/web/src/app/store/nanostores/index.ts new file mode 100644 index 0000000000..ae43ed3035 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/nanostores/index.ts @@ -0,0 +1,3 @@ +/** + * For non-serializable data that needs to be available throughout the app, or when redux is not appropriate, use nanostores. + */ diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index ce2a21c6e7..29caa69cbe 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -86,10 +86,7 @@ export const store = configureStore({ .concat(autoBatchEnhancer()); }, middleware: (getDefaultMiddleware) => - getDefaultMiddleware({ - immutableCheck: false, - serializableCheck: false, - }) + getDefaultMiddleware() .concat(api.middleware) .concat(dynamicMiddlewares) .prepend(listenerMiddleware.middleware), diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx index e2a9013aac..29b45761ee 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx @@ -1,4 +1,6 @@ import { MenuItem } from '@chakra-ui/react'; +import { useStore } from '@nanostores/react'; +import { $customStarUI } from 'app/store/nanostores/customStarUI'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { imagesToChangeSelected, @@ -12,12 +14,11 @@ import { useStarImagesMutation, useUnstarImagesMutation, } from '../../../../services/api/endpoints/images'; -import { uiSelector } from '../../../ui/store/uiSelectors'; const MultipleSelectionMenuItems = () => { const dispatch = useAppDispatch(); const selection = useAppSelector((state) => state.gallery.selection); - const { customStarUi } = useAppSelector(uiSelector); + const customStarUi = useStore($customStarUI); const [starImages] = useStarImagesMutation(); const [unstarImages] = useUnstarImagesMutation(); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx index 68c5c2e1ae..e5b9d94578 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx @@ -1,5 +1,7 @@ import { Flex, MenuItem, Spinner } from '@chakra-ui/react'; +import { useStore } from '@nanostores/react'; import { useAppToaster } from 'app/components/Toaster'; +import { $customStarUI } from 'app/store/nanostores/customStarUI'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { @@ -7,6 +9,7 @@ import { isModalOpenChanged, } from 'features/changeBoardModal/store/slice'; import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice'; +import { workflowLoadRequested } from 'features/nodes/store/actions'; import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters'; import { initialImageSelected } from 'features/parameters/store/actions'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; @@ -32,10 +35,8 @@ import { useUnstarImagesMutation, } from 'services/api/endpoints/images'; import { ImageDTO } from 'services/api/types'; -import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions'; -import { workflowLoadRequested } from 'features/nodes/store/actions'; import { configSelector } from '../../../system/store/configSelectors'; -import { uiSelector } from '../../../ui/store/uiSelectors'; +import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions'; type SingleSelectionMenuItemsProps = { imageDTO: ImageDTO; @@ -51,7 +52,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled; const { shouldFetchMetadataFromApi } = useAppSelector(configSelector); - const { customStarUi } = useAppSelector(uiSelector); + const customStarUi = useStore($customStarUI); const { metadata, workflow, isLoading } = useGetImageMetadataFromFileQuery( { image: imageDTO, shouldFetchMetadataFromApi }, diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx index 6c34bc31b8..af01eeaea8 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx @@ -1,4 +1,6 @@ import { Box, Flex } from '@chakra-ui/react'; +import { useStore } from '@nanostores/react'; +import { $customStarUI } from 'app/store/nanostores/customStarUI'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDndImage from 'common/components/IAIDndImage'; import IAIFillSkeleton from 'common/components/IAIFillSkeleton'; @@ -10,6 +12,7 @@ import { } from 'features/dnd/types'; import { useMultiselect } from 'features/gallery/hooks/useMultiselect'; import { MouseEvent, memo, useCallback, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { FaTrash } from 'react-icons/fa'; import { MdStar, MdStarBorder } from 'react-icons/md'; import { @@ -18,8 +21,6 @@ import { useUnstarImagesMutation, } from 'services/api/endpoints/images'; import IAIDndImageIcon from '../../../../common/components/IAIDndImageIcon'; -import { uiSelector } from '../../../ui/store/uiSelectors'; -import { useTranslation } from 'react-i18next'; interface HoverableImageProps { imageName: string; @@ -35,7 +36,7 @@ const GalleryImage = (props: HoverableImageProps) => { const { handleClick, isSelected, selection, selectionCount } = useMultiselect(imageDTO); - const { customStarUi } = useAppSelector(uiSelector); + const customStarUi = useStore($customStarUI); const handleDelete = useCallback( (e: MouseEvent) => { diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index 33edc6308f..82c9ef4e77 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -4,7 +4,7 @@ import { initialImageChanged } from 'features/parameters/store/generationSlice'; import { SchedulerParam } from 'features/parameters/types/parameterSchemas'; import { setActiveTabReducer } from './extraReducers'; import { InvokeTabName } from './tabMap'; -import { CustomStarUi, UIState } from './uiTypes'; +import { UIState } from './uiTypes'; export const initialUIState: UIState = { activeTab: 0, @@ -19,7 +19,6 @@ export const initialUIState: UIState = { favoriteSchedulers: [], globalContextMenuCloseTrigger: 0, panels: {}, - customStarUi: undefined, }; export const uiSlice = createSlice({ @@ -71,9 +70,6 @@ export const uiSlice = createSlice({ ) => { state.panels[action.payload.name] = action.payload.value; }, - setCustomStarUi: (state, action: PayloadAction) => { - state.customStarUi = action.payload; - }, }, extraReducers(builder) { builder.addCase(initialImageChanged, (state) => { @@ -95,7 +91,6 @@ export const { setShouldAutoChangeDimensions, contextMenusClosed, panelsChanged, - setCustomStarUi, } = uiSlice.actions; export default uiSlice.reducer; diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts index 8f7b1999a4..41a359a651 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts @@ -1,4 +1,3 @@ -import { MenuItemProps } from '@chakra-ui/react'; import { SchedulerParam } from 'features/parameters/types/parameterSchemas'; export type Coordinates = { @@ -13,17 +12,6 @@ export type Dimensions = { export type Rect = Coordinates & Dimensions; -export type CustomStarUi = { - on: { - icon: MenuItemProps['icon']; - text: string; - }; - off: { - icon: MenuItemProps['icon']; - text: string; - }; -}; - export interface UIState { activeTab: number; shouldShowImageDetails: boolean; @@ -37,5 +25,4 @@ export interface UIState { favoriteSchedulers: SchedulerParam[]; globalContextMenuCloseTrigger: number; panels: Record; - customStarUi?: CustomStarUi; } From 0f93991087004a73c8b2d5b45599c6b60cc509fd Mon Sep 17 00:00:00 2001 From: Jonathan <34005131+JPPhoto@users.noreply.github.com> Date: Thu, 14 Sep 2023 07:56:17 -0500 Subject: [PATCH 26/31] Remove multiple of 8 requirement for ImageResizeInvocation (#4538) Testing required the width and height to be multiples of 8. This is no longer needed. --- invokeai/app/invocations/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/app/invocations/image.py b/invokeai/app/invocations/image.py index fda7561679..11e5fc90a0 100644 --- a/invokeai/app/invocations/image.py +++ b/invokeai/app/invocations/image.py @@ -335,8 +335,8 @@ class ImageResizeInvocation(BaseInvocation): """Resizes an image to specific dimensions""" image: ImageField = InputField(description="The image to resize") - width: int = InputField(default=512, ge=64, multiple_of=8, description="The width to resize to (px)") - height: int = InputField(default=512, ge=64, multiple_of=8, description="The height to resize to (px)") + width: int = InputField(default=512, gt=0, description="The width to resize to (px)") + height: int = InputField(default=512, gt=0, description="The height to resize to (px)") resample_mode: PIL_RESAMPLING_MODES = InputField(default="bicubic", description="The resampling mode") metadata: Optional[CoreMetadata] = InputField( default=None, description=FieldDescriptions.core_metadata, ui_hidden=True From 34a09cb4ca3b3fe05a4569a627e98161a2bea066 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 15 Sep 2023 10:17:21 +1000 Subject: [PATCH 27/31] fix(ui): fix send to canvas crash A few weeks back, we changed how the canvas scales in response to changes in window/panel size. This introduced a bug where if we the user hadn't already clicked the canvas tab once to initialize the stage elements, the stage's dimensions were zero, then the calculation of the stage's scale ends up zero, then something is divided by that zero and Konva dies. This is only a problem on Chromium browsers - somehow Firefox handles it gracefully. Now, when calculating the stage scale, never return a 0 - if it's a zero, return 1 instead. This is enough to fix the crash, but the image ends up centered on the top-left corner of the stage (the origin of the canvas). Because the canvas elements are not initialized at this point (we haven't switched tabs yet), the stage dimensions fall back to (0,0). This means the center of the stage is also (0,0) - so the image is centered on (0,0), the top-left corner of the stage. To fix this, we need to ensure we: - Change to the canvas tab before actually setting the image, so the stage elements are able to initialize - Use `flushSync` to flush DOM updates for this tab change so we actually have DOM elements to work with - Update the stage dimensions once on first load of it (so in the effect that sets up the resize observer, we update the stage dimensions) The result now is the expected behaviour - images sent to canvas do not crash and end up in the center of the canvas. --- .../web/src/features/canvas/components/IAICanvas.tsx | 2 ++ .../frontend/web/src/features/canvas/util/calculateScale.ts | 2 +- .../components/ImageContextMenu/SingleSelectionMenuItems.tsx | 5 ++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvas.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvas.tsx index 4f9e47282d..4a67898942 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvas.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvas.tsx @@ -154,6 +154,8 @@ const IAICanvas = () => { resizeObserver.observe(containerRef.current); + dispatch(canvasResized(containerRef.current.getBoundingClientRect())); + return () => { resizeObserver.disconnect(); }; diff --git a/invokeai/frontend/web/src/features/canvas/util/calculateScale.ts b/invokeai/frontend/web/src/features/canvas/util/calculateScale.ts index 954c36869c..255cb2850b 100644 --- a/invokeai/frontend/web/src/features/canvas/util/calculateScale.ts +++ b/invokeai/frontend/web/src/features/canvas/util/calculateScale.ts @@ -8,7 +8,7 @@ const calculateScale = ( const scaleX = (containerWidth * padding) / contentWidth; const scaleY = (containerHeight * padding) / contentHeight; const scaleFit = Math.min(1, Math.min(scaleX, scaleY)); - return scaleFit; + return scaleFit ? scaleFit : 1; }; export default calculateScale; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx index e5b9d94578..c50b0c13dd 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx @@ -37,6 +37,7 @@ import { import { ImageDTO } from 'services/api/types'; import { configSelector } from '../../../system/store/configSelectors'; import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions'; +import { flushSync } from 'react-dom'; type SingleSelectionMenuItemsProps = { imageDTO: ImageDTO; @@ -115,8 +116,10 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { const handleSendToCanvas = useCallback(() => { dispatch(sentImageToCanvas()); + flushSync(() => { + dispatch(setActiveTab('unifiedCanvas')); + }); dispatch(setInitialCanvasImage(imageDTO)); - dispatch(setActiveTab('unifiedCanvas')); toaster({ title: t('toast.sentToUnifiedCanvas'), From 704e016f058e3e1108e8e269e246c75c777864d1 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:33:57 +1000 Subject: [PATCH 28/31] feat(ui): disable immutable redux check The immutable and serializable checks for redux can cause substantial performance issues. The immutable check in particular is pretty heavy. It's only run in dev mode, but this and really slow down the already-slower performance of dev mode. The most important one for us is serializable, which has far less of a performance impact. The immutable check is largely redundant because we use immer-backed RTK for everything and immer gives us confidence there. Disable the immutable check, leaving serializable in. --- invokeai/frontend/web/src/app/store/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 29caa69cbe..f84f3dd9c7 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -86,7 +86,7 @@ export const store = configureStore({ .concat(autoBatchEnhancer()); }, middleware: (getDefaultMiddleware) => - getDefaultMiddleware() + getDefaultMiddleware({ immutableCheck: false }) .concat(api.middleware) .concat(dynamicMiddlewares) .prepend(listenerMiddleware.middleware), From 5a42774fbe098b1efdac79654c02b0826644f023 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 15 Sep 2023 12:04:38 +1000 Subject: [PATCH 29/31] Update FEATURE_REQUEST.yml Added some verbiage about making feature requests singular and focused. Updated the placeholder to something more Invoke-y. --- .github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml index c7e7f4bc87..6d43d447f4 100644 --- a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml +++ b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml @@ -34,12 +34,9 @@ body: id: whatisexpected attributes: label: What should this feature add? - description: Please try to explain the functionality this feature should add + description: Explain the functionality this feature should add. Feature requests should be for single features. Please create multiple requests if you want to request multiple features. placeholder: | - Instead of one huge text field, it would be nice to have forms for bug-reports, feature-requests, ... - Great benefits with automatic labeling, assigning and other functionalitys not available in that form - via old-fashioned markdown-templates. I would also love to see the use of a moderator bot 🤖 like - https://github.com/marketplace/actions/issue-moderator-with-commands to auto close old issues and other things + I'd like a button that creates an image of banana sushi every time I press it. Each image should be different. There should be a toggle next to the button that enables strawberry mode, in which the images are of strawberry sushi instead. validations: required: true From 604fc006b15d9c3f3e5cedbc232e2b4907147f48 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 15 Sep 2023 12:24:53 +1000 Subject: [PATCH 30/31] fix(ui): construct openapi url from window.location.origin --- invokeai/frontend/web/src/services/api/thunks/schema.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/services/api/thunks/schema.ts b/invokeai/frontend/web/src/services/api/thunks/schema.ts index 7dc0bdc331..c209469e02 100644 --- a/invokeai/frontend/web/src/services/api/thunks/schema.ts +++ b/invokeai/frontend/web/src/services/api/thunks/schema.ts @@ -26,7 +26,8 @@ export const receivedOpenAPISchema = createAsyncThunk( 'nodes/receivedOpenAPISchema', async (_, { rejectWithValue }) => { try { - const response = await fetch(`openapi.json`); + const url = [window.location.origin, 'openapi.json'].join('/'); + const response = await fetch(url); const openAPISchema = await response.json(); const schemaJSON = JSON.parse( From ff3150a818245c6dc83ba742045f18d5242a7c0d Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Wed, 13 Sep 2023 21:37:08 +0300 Subject: [PATCH 31/31] Update lora hotfix to new diffusers version(scale argument added) --- invokeai/backend/util/hotfixes.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/invokeai/backend/util/hotfixes.py b/invokeai/backend/util/hotfixes.py index 983d0b7601..852d640161 100644 --- a/invokeai/backend/util/hotfixes.py +++ b/invokeai/backend/util/hotfixes.py @@ -772,11 +772,13 @@ diffusers.models.controlnet.ControlNetModel = ControlNetModel # NOTE: with this patch, torch.compile crashes on 2.0 torch(already fixed in nightly) # https://github.com/huggingface/diffusers/pull/4315 # https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/lora.py#L96C18-L96C18 -def new_LoRACompatibleConv_forward(self, x): +def new_LoRACompatibleConv_forward(self, hidden_states, scale: float = 1.0): if self.lora_layer is None: - return super(diffusers.models.lora.LoRACompatibleConv, self).forward(x) + return super(diffusers.models.lora.LoRACompatibleConv, self).forward(hidden_states) else: - return super(diffusers.models.lora.LoRACompatibleConv, self).forward(x) + self.lora_layer(x) + return super(diffusers.models.lora.LoRACompatibleConv, self).forward(hidden_states) + ( + scale * self.lora_layer(hidden_states) + ) diffusers.models.lora.LoRACompatibleConv.forward = new_LoRACompatibleConv_forward