mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into maryhipp/informational-popover
This commit is contained in:
commit
7bf7c16a5d
7
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml
vendored
7
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml
vendored
@ -34,12 +34,9 @@ body:
|
|||||||
id: whatisexpected
|
id: whatisexpected
|
||||||
attributes:
|
attributes:
|
||||||
label: What should this feature add?
|
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: |
|
placeholder: |
|
||||||
Instead of one huge text field, it would be nice to have forms for bug-reports, feature-requests, ...
|
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.
|
||||||
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
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ The table below contains a list of the default nodes shipped with InvokeAI and t
|
|||||||
|Divide Integers | Divides two numbers|
|
|Divide Integers | Divides two numbers|
|
||||||
|Dynamic Prompt | Parses a prompt using adieyal/dynamicprompts' random or combinatorial generator|
|
|Dynamic Prompt | Parses a prompt using adieyal/dynamicprompts' random or combinatorial generator|
|
||||||
|Upscale (RealESRGAN) | Upscales an image using RealESRGAN.|
|
|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 Collection | A collection of float primitive values|
|
||||||
|Float Primitive | A float primitive value|
|
|Float Primitive | A float primitive value|
|
||||||
|Float Range | Creates a range|
|
|Float Range | Creates a range|
|
||||||
@ -29,6 +30,7 @@ The table below contains a list of the default nodes shipped with InvokeAI and t
|
|||||||
|Blur Image | Blurs an image|
|
|Blur Image | Blurs an image|
|
||||||
|Extract Image Channel | Gets a channel from an image.|
|
|Extract Image Channel | Gets a channel from an image.|
|
||||||
|Image Primitive Collection | A collection of image primitive values|
|
|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.|
|
|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.|
|
|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.|
|
|Image Hue Adjustment | Adjusts the Hue of an image.|
|
||||||
@ -42,6 +44,8 @@ The table below contains a list of the default nodes shipped with InvokeAI and t
|
|||||||
|Paste Image | Pastes an image into another image.|
|
|Paste Image | Pastes an image into another image.|
|
||||||
|ImageProcessor | Base class for invocations that preprocess images for ControlNet|
|
|ImageProcessor | Base class for invocations that preprocess images for ControlNet|
|
||||||
|Resize Image | Resizes an image to specific dimensions|
|
|Resize Image | Resizes an image to specific dimensions|
|
||||||
|
|Round Float | Rounds a float to a specified number of decimal places|
|
||||||
|
|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|
|
|Scale Image | Scales an image by a factor|
|
||||||
|Image to Latents | Encodes an image into latents.|
|
|Image to Latents | Encodes an image into latents.|
|
||||||
|Add Invisible Watermark | Add an invisible watermark to an image|
|
|Add Invisible Watermark | Add an invisible watermark to an image|
|
||||||
|
@ -14,7 +14,7 @@ fi
|
|||||||
VERSION=$(cd ..; python -c "from invokeai.version import __version__ as version; print(version)")
|
VERSION=$(cd ..; python -c "from invokeai.version import __version__ as version; print(version)")
|
||||||
PATCH=""
|
PATCH=""
|
||||||
VERSION="v${VERSION}${PATCH}"
|
VERSION="v${VERSION}${PATCH}"
|
||||||
LATEST_TAG="v3.0-latest"
|
LATEST_TAG="v3-latest"
|
||||||
|
|
||||||
echo Building installer for version $VERSION
|
echo Building installer for version $VERSION
|
||||||
echo "Be certain that you're in the 'installer' directory before continuing."
|
echo "Be certain that you're in the 'installer' directory before continuing."
|
||||||
|
@ -198,6 +198,7 @@ class _InputField(BaseModel):
|
|||||||
ui_type: Optional[UIType]
|
ui_type: Optional[UIType]
|
||||||
ui_component: Optional[UIComponent]
|
ui_component: Optional[UIComponent]
|
||||||
ui_order: Optional[int]
|
ui_order: Optional[int]
|
||||||
|
ui_choice_labels: Optional[dict[str, str]]
|
||||||
item_default: Optional[Any]
|
item_default: Optional[Any]
|
||||||
|
|
||||||
|
|
||||||
@ -246,6 +247,7 @@ def InputField(
|
|||||||
ui_component: Optional[UIComponent] = None,
|
ui_component: Optional[UIComponent] = None,
|
||||||
ui_hidden: bool = False,
|
ui_hidden: bool = False,
|
||||||
ui_order: Optional[int] = None,
|
ui_order: Optional[int] = None,
|
||||||
|
ui_choice_labels: Optional[dict[str, str]] = None,
|
||||||
item_default: Optional[Any] = None,
|
item_default: Optional[Any] = None,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
@ -312,6 +314,7 @@ def InputField(
|
|||||||
ui_hidden=ui_hidden,
|
ui_hidden=ui_hidden,
|
||||||
ui_order=ui_order,
|
ui_order=ui_order,
|
||||||
item_default=item_default,
|
item_default=item_default,
|
||||||
|
ui_choice_labels=ui_choice_labels,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,14 +38,16 @@ class RangeInvocation(BaseInvocation):
|
|||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
)
|
)
|
||||||
class RangeOfSizeInvocation(BaseInvocation):
|
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")
|
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")
|
step: int = InputField(default=1, description="The step of the range")
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> IntegerCollectionOutput:
|
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(
|
@invocation(
|
||||||
|
@ -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):
|
class ImagePasteInvocation(BaseInvocation):
|
||||||
"""Pastes an image into another image."""
|
"""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")
|
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")
|
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:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
base_image = context.services.images.get_pil_image(self.base_image.image_name)
|
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(base_image, (abs(min_x), abs(min_y)))
|
||||||
new_image.paste(image, (max(0, self.x), max(0, self.y)), mask=mask)
|
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_dto = context.services.images.create(
|
||||||
image=new_image,
|
image=new_image,
|
||||||
image_origin=ResourceOrigin.INTERNAL,
|
image_origin=ResourceOrigin.INTERNAL,
|
||||||
@ -330,8 +335,8 @@ class ImageResizeInvocation(BaseInvocation):
|
|||||||
"""Resizes an image to specific dimensions"""
|
"""Resizes an image to specific dimensions"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to resize")
|
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)")
|
width: int = InputField(default=512, gt=0, description="The width to resize to (px)")
|
||||||
height: int = InputField(default=512, ge=64, multiple_of=8, description="The height 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")
|
resample_mode: PIL_RESAMPLING_MODES = InputField(default="bicubic", description="The resampling mode")
|
||||||
metadata: Optional[CoreMetadata] = InputField(
|
metadata: Optional[CoreMetadata] = InputField(
|
||||||
default=None, description=FieldDescriptions.core_metadata, ui_hidden=True
|
default=None, description=FieldDescriptions.core_metadata, ui_hidden=True
|
||||||
|
@ -63,6 +63,9 @@ from .compel import ConditioningField
|
|||||||
from .controlnet_image_processors import ControlField
|
from .controlnet_image_processors import ControlField
|
||||||
from .model import ModelInfo, UNetField, VaeField
|
from .model import ModelInfo, UNetField, VaeField
|
||||||
|
|
||||||
|
if choose_torch_device() == torch.device("mps"):
|
||||||
|
from torch import mps
|
||||||
|
|
||||||
DEFAULT_PRECISION = choose_precision(choose_torch_device())
|
DEFAULT_PRECISION = choose_precision(choose_torch_device())
|
||||||
|
|
||||||
|
|
||||||
@ -541,6 +544,8 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
|||||||
# https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699
|
# https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699
|
||||||
result_latents = result_latents.to("cpu")
|
result_latents = result_latents.to("cpu")
|
||||||
torch.cuda.empty_cache()
|
torch.cuda.empty_cache()
|
||||||
|
if choose_torch_device() == torch.device("mps"):
|
||||||
|
mps.empty_cache()
|
||||||
|
|
||||||
name = f"{context.graph_execution_state_id}__{self.id}"
|
name = f"{context.graph_execution_state_id}__{self.id}"
|
||||||
context.services.latents.save(name, result_latents)
|
context.services.latents.save(name, result_latents)
|
||||||
@ -612,6 +617,8 @@ class LatentsToImageInvocation(BaseInvocation):
|
|||||||
|
|
||||||
# clear memory as vae decode can request a lot
|
# clear memory as vae decode can request a lot
|
||||||
torch.cuda.empty_cache()
|
torch.cuda.empty_cache()
|
||||||
|
if choose_torch_device() == torch.device("mps"):
|
||||||
|
mps.empty_cache()
|
||||||
|
|
||||||
with torch.inference_mode():
|
with torch.inference_mode():
|
||||||
# copied from diffusers pipeline
|
# copied from diffusers pipeline
|
||||||
@ -624,6 +631,8 @@ class LatentsToImageInvocation(BaseInvocation):
|
|||||||
image = VaeImageProcessor.numpy_to_pil(np_image)[0]
|
image = VaeImageProcessor.numpy_to_pil(np_image)[0]
|
||||||
|
|
||||||
torch.cuda.empty_cache()
|
torch.cuda.empty_cache()
|
||||||
|
if choose_torch_device() == torch.device("mps"):
|
||||||
|
mps.empty_cache()
|
||||||
|
|
||||||
image_dto = context.services.images.create(
|
image_dto = context.services.images.create(
|
||||||
image=image,
|
image=image,
|
||||||
@ -683,6 +692,8 @@ class ResizeLatentsInvocation(BaseInvocation):
|
|||||||
# https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699
|
# https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699
|
||||||
resized_latents = resized_latents.to("cpu")
|
resized_latents = resized_latents.to("cpu")
|
||||||
torch.cuda.empty_cache()
|
torch.cuda.empty_cache()
|
||||||
|
if device == torch.device("mps"):
|
||||||
|
mps.empty_cache()
|
||||||
|
|
||||||
name = f"{context.graph_execution_state_id}__{self.id}"
|
name = f"{context.graph_execution_state_id}__{self.id}"
|
||||||
# context.services.latents.set(name, resized_latents)
|
# context.services.latents.set(name, resized_latents)
|
||||||
@ -719,6 +730,8 @@ class ScaleLatentsInvocation(BaseInvocation):
|
|||||||
# https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699
|
# https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699
|
||||||
resized_latents = resized_latents.to("cpu")
|
resized_latents = resized_latents.to("cpu")
|
||||||
torch.cuda.empty_cache()
|
torch.cuda.empty_cache()
|
||||||
|
if device == torch.device("mps"):
|
||||||
|
mps.empty_cache()
|
||||||
|
|
||||||
name = f"{context.graph_execution_state_id}__{self.id}"
|
name = f"{context.graph_execution_state_id}__{self.id}"
|
||||||
# context.services.latents.set(name, resized_latents)
|
# context.services.latents.set(name, resized_latents)
|
||||||
@ -875,6 +888,8 @@ class BlendLatentsInvocation(BaseInvocation):
|
|||||||
# https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699
|
# https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699
|
||||||
blended_latents = blended_latents.to("cpu")
|
blended_latents = blended_latents.to("cpu")
|
||||||
torch.cuda.empty_cache()
|
torch.cuda.empty_cache()
|
||||||
|
if device == torch.device("mps"):
|
||||||
|
mps.empty_cache()
|
||||||
|
|
||||||
name = f"{context.graph_execution_state_id}__{self.id}"
|
name = f"{context.graph_execution_state_id}__{self.id}"
|
||||||
# context.services.latents.set(name, resized_latents)
|
# context.services.latents.set(name, resized_latents)
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
# Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654)
|
# Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654)
|
||||||
|
|
||||||
import numpy as np
|
from typing import Literal
|
||||||
|
|
||||||
from invokeai.app.invocations.primitives import IntegerOutput
|
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 .baseinvocation import BaseInvocation, FieldDescriptions, InputField, InvocationContext, invocation
|
||||||
|
|
||||||
@ -60,3 +63,201 @@ class RandomIntInvocation(BaseInvocation):
|
|||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
||||||
return IntegerOutput(value=np.random.randint(self.low, self.high))
|
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",
|
||||||
|
)
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
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")
|
||||||
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
INTEGER_OPERATIONS = Literal[
|
||||||
|
"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",
|
||||||
|
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", 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"] == "DIV" and v == 0:
|
||||||
|
raise ValueError("Cannot divide by zero")
|
||||||
|
elif values["operation"] == "MOD" and v == 0:
|
||||||
|
raise ValueError("Cannot divide by zero")
|
||||||
|
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":
|
||||||
|
return IntegerOutput(value=self.a + self.b)
|
||||||
|
elif self.operation == "SUB":
|
||||||
|
return IntegerOutput(value=self.a - self.b)
|
||||||
|
elif self.operation == "MUL":
|
||||||
|
return IntegerOutput(value=self.a * self.b)
|
||||||
|
elif self.operation == "DIV":
|
||||||
|
return IntegerOutput(value=int(self.a / self.b))
|
||||||
|
elif self.operation == "EXP":
|
||||||
|
return IntegerOutput(value=self.a**self.b)
|
||||||
|
elif self.operation == "MOD":
|
||||||
|
return IntegerOutput(value=self.a % self.b)
|
||||||
|
elif self.operation == "ABS":
|
||||||
|
return IntegerOutput(value=abs(self.a))
|
||||||
|
elif self.operation == "MIN":
|
||||||
|
return IntegerOutput(value=min(self.a, self.b))
|
||||||
|
else: # self.operation == "MAX":
|
||||||
|
return IntegerOutput(value=max(self.a, self.b))
|
||||||
|
|
||||||
|
|
||||||
|
FLOAT_OPERATIONS = Literal[
|
||||||
|
"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",
|
||||||
|
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", 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"] == "DIV" and v == 0:
|
||||||
|
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) is 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":
|
||||||
|
return FloatOutput(value=self.a + self.b)
|
||||||
|
elif self.operation == "SUB":
|
||||||
|
return FloatOutput(value=self.a - self.b)
|
||||||
|
elif self.operation == "MUL":
|
||||||
|
return FloatOutput(value=self.a * self.b)
|
||||||
|
elif self.operation == "DIV":
|
||||||
|
return FloatOutput(value=self.a / self.b)
|
||||||
|
elif self.operation == "EXP":
|
||||||
|
return FloatOutput(value=self.a**self.b)
|
||||||
|
elif self.operation == "SQRT":
|
||||||
|
return FloatOutput(value=np.sqrt(self.a))
|
||||||
|
elif self.operation == "ABS":
|
||||||
|
return FloatOutput(value=abs(self.a))
|
||||||
|
elif self.operation == "MIN":
|
||||||
|
return FloatOutput(value=min(self.a, self.b))
|
||||||
|
else: # self.operation == "MAX":
|
||||||
|
return FloatOutput(value=max(self.a, self.b))
|
||||||
|
139
invokeai/app/invocations/strings.py
Normal file
139
invokeai/app/invocations/strings.py
Normal file
@ -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)
|
@ -29,8 +29,12 @@ import torch
|
|||||||
|
|
||||||
import invokeai.backend.util.logging as logger
|
import invokeai.backend.util.logging as logger
|
||||||
|
|
||||||
|
from ..util.devices import choose_torch_device
|
||||||
from .models import BaseModelType, ModelBase, ModelType, SubModelType
|
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
|
# Maximum size of the cache, in gigs
|
||||||
# Default is roughly enough to hold three fp16 diffusers models in RAM simultaneously
|
# Default is roughly enough to hold three fp16 diffusers models in RAM simultaneously
|
||||||
DEFAULT_MAX_CACHE_SIZE = 6.0
|
DEFAULT_MAX_CACHE_SIZE = 6.0
|
||||||
@ -406,6 +410,8 @@ class ModelCache(object):
|
|||||||
|
|
||||||
gc.collect()
|
gc.collect()
|
||||||
torch.cuda.empty_cache()
|
torch.cuda.empty_cache()
|
||||||
|
if choose_torch_device() == torch.device("mps"):
|
||||||
|
mps.empty_cache()
|
||||||
|
|
||||||
self.logger.debug(f"After unloading: cached_models={len(self._cached_models)}")
|
self.logger.debug(f"After unloading: cached_models={len(self._cached_models)}")
|
||||||
|
|
||||||
@ -426,6 +432,8 @@ class ModelCache(object):
|
|||||||
|
|
||||||
gc.collect()
|
gc.collect()
|
||||||
torch.cuda.empty_cache()
|
torch.cuda.empty_cache()
|
||||||
|
if choose_torch_device() == torch.device("mps"):
|
||||||
|
mps.empty_cache()
|
||||||
|
|
||||||
def _local_model_hash(self, model_path: Union[str, Path]) -> str:
|
def _local_model_hash(self, model_path: Union[str, Path]) -> str:
|
||||||
sha = hashlib.sha256()
|
sha = hashlib.sha256()
|
||||||
|
@ -772,11 +772,13 @@ diffusers.models.controlnet.ControlNetModel = ControlNetModel
|
|||||||
# NOTE: with this patch, torch.compile crashes on 2.0 torch(already fixed in nightly)
|
# 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/pull/4315
|
||||||
# https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/lora.py#L96C18-L96C18
|
# 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:
|
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:
|
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
|
diffusers.models.lora.LoRACompatibleConv.forward = new_LoRACompatibleConv_forward
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -12,29 +12,26 @@ import { languageSelector } from 'features/system/store/systemSelectors';
|
|||||||
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
||||||
import i18n from 'i18n';
|
import i18n from 'i18n';
|
||||||
import { size } from 'lodash-es';
|
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 { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { usePreselectedImage } from '../../features/parameters/hooks/usePreselectedImage';
|
import { usePreselectedImage } from '../../features/parameters/hooks/usePreselectedImage';
|
||||||
import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
|
import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
|
||||||
import GlobalHotkeys from './GlobalHotkeys';
|
import GlobalHotkeys from './GlobalHotkeys';
|
||||||
import Toaster from './Toaster';
|
import Toaster from './Toaster';
|
||||||
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { $headerComponent } from 'app/store/nanostores/headerComponent';
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {};
|
const DEFAULT_CONFIG = {};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
config?: PartialAppConfig;
|
config?: PartialAppConfig;
|
||||||
headerComponent?: ReactNode;
|
|
||||||
selectedImage?: {
|
selectedImage?: {
|
||||||
imageName: string;
|
imageName: string;
|
||||||
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const App = ({
|
const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
||||||
config = DEFAULT_CONFIG,
|
|
||||||
headerComponent,
|
|
||||||
selectedImage,
|
|
||||||
}: Props) => {
|
|
||||||
const language = useAppSelector(languageSelector);
|
const language = useAppSelector(languageSelector);
|
||||||
|
|
||||||
const logger = useLogger('system');
|
const logger = useLogger('system');
|
||||||
@ -65,6 +62,8 @@ const App = ({
|
|||||||
handlePreselectedImage(selectedImage);
|
handlePreselectedImage(selectedImage);
|
||||||
}, [handlePreselectedImage, selectedImage]);
|
}, [handlePreselectedImage, selectedImage]);
|
||||||
|
|
||||||
|
const headerComponent = useStore($headerComponent);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
|
@ -15,6 +15,8 @@ import { socketMiddleware } from 'services/events/middleware';
|
|||||||
import Loading from '../../common/components/Loading/Loading';
|
import Loading from '../../common/components/Loading/Loading';
|
||||||
import '../../i18n';
|
import '../../i18n';
|
||||||
import AppDndContext from '../../features/dnd/components/AppDndContext';
|
import AppDndContext from '../../features/dnd/components/AppDndContext';
|
||||||
|
import { $customStarUI, CustomStarUi } from 'app/store/nanostores/customStarUI';
|
||||||
|
import { $headerComponent } from 'app/store/nanostores/headerComponent';
|
||||||
|
|
||||||
const App = lazy(() => import('./App'));
|
const App = lazy(() => import('./App'));
|
||||||
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
||||||
@ -30,6 +32,7 @@ interface Props extends PropsWithChildren {
|
|||||||
imageName: string;
|
imageName: string;
|
||||||
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
||||||
};
|
};
|
||||||
|
customStarUi?: CustomStarUi;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InvokeAIUI = ({
|
const InvokeAIUI = ({
|
||||||
@ -40,6 +43,7 @@ const InvokeAIUI = ({
|
|||||||
middleware,
|
middleware,
|
||||||
projectId,
|
projectId,
|
||||||
selectedImage,
|
selectedImage,
|
||||||
|
customStarUi,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// configure API client token
|
// configure API client token
|
||||||
@ -80,17 +84,33 @@ const InvokeAIUI = ({
|
|||||||
};
|
};
|
||||||
}, [apiUrl, token, middleware, projectId]);
|
}, [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 (
|
return (
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<React.Suspense fallback={<Loading />}>
|
<React.Suspense fallback={<Loading />}>
|
||||||
<ThemeLocaleProvider>
|
<ThemeLocaleProvider>
|
||||||
<AppDndContext>
|
<AppDndContext>
|
||||||
<App
|
<App config={config} selectedImage={selectedImage} />
|
||||||
config={config}
|
|
||||||
headerComponent={headerComponent}
|
|
||||||
selectedImage={selectedImage}
|
|
||||||
/>
|
|
||||||
</AppDndContext>
|
</AppDndContext>
|
||||||
</ThemeLocaleProvider>
|
</ThemeLocaleProvider>
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
|
@ -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<CustomStarUi | undefined>(undefined);
|
@ -0,0 +1,4 @@
|
|||||||
|
import { atom } from 'nanostores';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export const $headerComponent = atom<ReactNode | undefined>(undefined);
|
3
invokeai/frontend/web/src/app/store/nanostores/index.ts
Normal file
3
invokeai/frontend/web/src/app/store/nanostores/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/**
|
||||||
|
* For non-serializable data that needs to be available throughout the app, or when redux is not appropriate, use nanostores.
|
||||||
|
*/
|
@ -86,10 +86,7 @@ export const store = configureStore({
|
|||||||
.concat(autoBatchEnhancer());
|
.concat(autoBatchEnhancer());
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware({
|
getDefaultMiddleware({ immutableCheck: false })
|
||||||
immutableCheck: false,
|
|
||||||
serializableCheck: false,
|
|
||||||
})
|
|
||||||
.concat(api.middleware)
|
.concat(api.middleware)
|
||||||
.concat(dynamicMiddlewares)
|
.concat(dynamicMiddlewares)
|
||||||
.prepend(listenerMiddleware.middleware),
|
.prepend(listenerMiddleware.middleware),
|
||||||
|
@ -6,6 +6,7 @@ import { isInvocationNode } from 'features/nodes/types/types';
|
|||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
import { forEach, map } from 'lodash-es';
|
import { forEach, map } from 'lodash-es';
|
||||||
import { getConnectedEdges } from 'reactflow';
|
import { getConnectedEdges } from 'reactflow';
|
||||||
|
import i18n from 'i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector, activeTabNameSelector],
|
[stateSelector, activeTabNameSelector],
|
||||||
@ -19,22 +20,22 @@ const selector = createSelector(
|
|||||||
|
|
||||||
// Cannot generate if already processing an image
|
// Cannot generate if already processing an image
|
||||||
if (isProcessing) {
|
if (isProcessing) {
|
||||||
reasons.push('System busy');
|
reasons.push(i18n.t('parameters.invoke.systemBusy'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cannot generate if not connected
|
// Cannot generate if not connected
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
reasons.push('System disconnected');
|
reasons.push(i18n.t('parameters.invoke.systemDisconnected'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTabName === 'img2img' && !initialImage) {
|
if (activeTabName === 'img2img' && !initialImage) {
|
||||||
reasons.push('No initial image selected');
|
reasons.push(i18n.t('parameters.invoke.noInitialImageSelected'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTabName === 'nodes') {
|
if (activeTabName === 'nodes') {
|
||||||
if (nodes.shouldValidateGraph) {
|
if (nodes.shouldValidateGraph) {
|
||||||
if (!nodes.nodes.length) {
|
if (!nodes.nodes.length) {
|
||||||
reasons.push('No nodes in graph');
|
reasons.push(i18n.t('parameters.invoke.noNodesInGraph'));
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes.nodes.forEach((node) => {
|
nodes.nodes.forEach((node) => {
|
||||||
@ -46,7 +47,7 @@ const selector = createSelector(
|
|||||||
|
|
||||||
if (!nodeTemplate) {
|
if (!nodeTemplate) {
|
||||||
// Node type not found
|
// Node type not found
|
||||||
reasons.push('Missing node template');
|
reasons.push(i18n.t('parameters.invoke.missingNodeTemplate'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +61,7 @@ const selector = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!fieldTemplate) {
|
if (!fieldTemplate) {
|
||||||
reasons.push('Missing field template');
|
reasons.push(i18n.t('parameters.invoke.missingFieldTemplate'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,9 +71,10 @@ const selector = createSelector(
|
|||||||
!hasConnection
|
!hasConnection
|
||||||
) {
|
) {
|
||||||
reasons.push(
|
reasons.push(
|
||||||
`${node.data.label || nodeTemplate.title} -> ${
|
i18n.t('parameters.invoke.missingInputForField', {
|
||||||
field.label || fieldTemplate.title
|
nodeLabel: node.data.label || nodeTemplate.title,
|
||||||
} missing input`
|
fieldLabel: field.label || fieldTemplate.title,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -81,7 +83,7 @@ const selector = createSelector(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!model) {
|
if (!model) {
|
||||||
reasons.push('No model selected');
|
reasons.push(i18n.t('parameters.invoke.noModelSelected'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.controlNet.isEnabled) {
|
if (state.controlNet.isEnabled) {
|
||||||
@ -90,7 +92,9 @@ const selector = createSelector(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!controlNet.model) {
|
if (!controlNet.model) {
|
||||||
reasons.push(`ControlNet ${i + 1} has no model selected.`);
|
reasons.push(
|
||||||
|
i18n.t('parameters.invoke.noModelForControlNet', { index: i + 1 })
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -98,7 +102,11 @@ const selector = createSelector(
|
|||||||
(!controlNet.processedControlImage &&
|
(!controlNet.processedControlImage &&
|
||||||
controlNet.processorType !== 'none')
|
controlNet.processorType !== 'none')
|
||||||
) {
|
) {
|
||||||
reasons.push(`ControlNet ${i + 1} has no control image`);
|
reasons.push(
|
||||||
|
i18n.t('parameters.invoke.noControlImageForControlNet', {
|
||||||
|
index: i + 1,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -154,6 +154,8 @@ const IAICanvas = () => {
|
|||||||
|
|
||||||
resizeObserver.observe(containerRef.current);
|
resizeObserver.observe(containerRef.current);
|
||||||
|
|
||||||
|
dispatch(canvasResized(containerRef.current.getBoundingClientRect()));
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ const calculateScale = (
|
|||||||
const scaleX = (containerWidth * padding) / contentWidth;
|
const scaleX = (containerWidth * padding) / contentWidth;
|
||||||
const scaleY = (containerHeight * padding) / contentHeight;
|
const scaleY = (containerHeight * padding) / contentHeight;
|
||||||
const scaleFit = Math.min(1, Math.min(scaleX, scaleY));
|
const scaleFit = Math.min(1, Math.min(scaleX, scaleY));
|
||||||
return scaleFit;
|
return scaleFit ? scaleFit : 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default calculateScale;
|
export default calculateScale;
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
useRemoveImagesFromBoardMutation,
|
useRemoveImagesFromBoardMutation,
|
||||||
} from 'services/api/endpoints/images';
|
} from 'services/api/endpoints/images';
|
||||||
import { changeBoardReset, isModalOpenChanged } from '../store/slice';
|
import { changeBoardReset, isModalOpenChanged } from '../store/slice';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
@ -42,10 +43,11 @@ const ChangeBoardModal = () => {
|
|||||||
const { imagesToChange, isModalOpen } = useAppSelector(selector);
|
const { imagesToChange, isModalOpen } = useAppSelector(selector);
|
||||||
const [addImagesToBoard] = useAddImagesToBoardMutation();
|
const [addImagesToBoard] = useAddImagesToBoardMutation();
|
||||||
const [removeImagesFromBoard] = useRemoveImagesFromBoardMutation();
|
const [removeImagesFromBoard] = useRemoveImagesFromBoardMutation();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const data = useMemo(() => {
|
const data = useMemo(() => {
|
||||||
const data: { label: string; value: string }[] = [
|
const data: { label: string; value: string }[] = [
|
||||||
{ label: 'Uncategorized', value: 'none' },
|
{ label: t('boards.uncategorized'), value: 'none' },
|
||||||
];
|
];
|
||||||
(boards ?? []).forEach((board) =>
|
(boards ?? []).forEach((board) =>
|
||||||
data.push({
|
data.push({
|
||||||
@ -55,7 +57,7 @@ const ChangeBoardModal = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}, [boards]);
|
}, [boards, t]);
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
dispatch(changeBoardReset());
|
dispatch(changeBoardReset());
|
||||||
@ -97,7 +99,7 @@ const ChangeBoardModal = () => {
|
|||||||
<AlertDialogOverlay>
|
<AlertDialogOverlay>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||||
Change Board
|
{t('boards.changeBoard')}
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
|
|
||||||
<AlertDialogBody>
|
<AlertDialogBody>
|
||||||
@ -107,7 +109,9 @@ const ChangeBoardModal = () => {
|
|||||||
{`${imagesToChange.length > 1 ? 's' : ''}`} to board:
|
{`${imagesToChange.length > 1 ? 's' : ''}`} to board:
|
||||||
</Text>
|
</Text>
|
||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
placeholder={isFetching ? 'Loading...' : 'Select Board'}
|
placeholder={
|
||||||
|
isFetching ? t('boards.loading') : t('boards.selectBoard')
|
||||||
|
}
|
||||||
disabled={isFetching}
|
disabled={isFetching}
|
||||||
onChange={(v) => setSelectedBoard(v)}
|
onChange={(v) => setSelectedBoard(v)}
|
||||||
value={selectedBoard}
|
value={selectedBoard}
|
||||||
@ -117,10 +121,10 @@ const ChangeBoardModal = () => {
|
|||||||
</AlertDialogBody>
|
</AlertDialogBody>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<IAIButton ref={cancelRef} onClick={handleClose}>
|
<IAIButton ref={cancelRef} onClick={handleClose}>
|
||||||
Cancel
|
{t('boards.cancel')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
<IAIButton colorScheme="accent" onClick={handleChangeBoard} ml={3}>
|
<IAIButton colorScheme="accent" onClick={handleChangeBoard} ml={3}>
|
||||||
Move
|
{t('boards.move')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
|
@ -28,6 +28,7 @@ import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd';
|
|||||||
import ParamControlNetControlMode from './parameters/ParamControlNetControlMode';
|
import ParamControlNetControlMode from './parameters/ParamControlNetControlMode';
|
||||||
import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect';
|
import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect';
|
||||||
import ParamControlNetResizeMode from './parameters/ParamControlNetResizeMode';
|
import ParamControlNetResizeMode from './parameters/ParamControlNetResizeMode';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type ControlNetProps = {
|
type ControlNetProps = {
|
||||||
controlNet: ControlNetConfig;
|
controlNet: ControlNetConfig;
|
||||||
@ -37,6 +38,7 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
const { controlNet } = props;
|
const { controlNet } = props;
|
||||||
const { controlNetId } = controlNet;
|
const { controlNetId } = controlNet;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
|
|
||||||
@ -95,8 +97,8 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
>
|
>
|
||||||
<Flex sx={{ gap: 2, alignItems: 'center' }}>
|
<Flex sx={{ gap: 2, alignItems: 'center' }}>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
tooltip="Toggle this ControlNet"
|
tooltip={t('controlnet.toggleControlNet')}
|
||||||
aria-label="Toggle this ControlNet"
|
aria-label={t('controlnet.toggleControlNet')}
|
||||||
isChecked={isEnabled}
|
isChecked={isEnabled}
|
||||||
onChange={handleToggleIsEnabled}
|
onChange={handleToggleIsEnabled}
|
||||||
/>
|
/>
|
||||||
@ -117,23 +119,31 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
)}
|
)}
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
tooltip="Duplicate"
|
tooltip={t('controlnet.duplicate')}
|
||||||
aria-label="Duplicate"
|
aria-label={t('controlnet.duplicate')}
|
||||||
onClick={handleDuplicate}
|
onClick={handleDuplicate}
|
||||||
icon={<FaCopy />}
|
icon={<FaCopy />}
|
||||||
/>
|
/>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
tooltip="Delete"
|
tooltip={t('controlnet.delete')}
|
||||||
aria-label="Delete"
|
aria-label={t('controlnet.delete')}
|
||||||
colorScheme="error"
|
colorScheme="error"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
/>
|
/>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
tooltip={isExpanded ? 'Hide Advanced' : 'Show Advanced'}
|
tooltip={
|
||||||
aria-label={isExpanded ? 'Hide Advanced' : 'Show Advanced'}
|
isExpanded
|
||||||
|
? t('controlnet.hideAdvanced')
|
||||||
|
: t('controlnet.showAdvanced')
|
||||||
|
}
|
||||||
|
aria-label={
|
||||||
|
isExpanded
|
||||||
|
? t('controlnet.hideAdvanced')
|
||||||
|
: t('controlnet.showAdvanced')
|
||||||
|
}
|
||||||
onClick={toggleIsExpanded}
|
onClick={toggleIsExpanded}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
ControlNetConfig,
|
ControlNetConfig,
|
||||||
controlNetImageChanged,
|
controlNetImageChanged,
|
||||||
} from '../store/controlNetSlice';
|
} from '../store/controlNetSlice';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
controlNet: ControlNetConfig;
|
controlNet: ControlNetConfig;
|
||||||
@ -56,6 +57,7 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => {
|
|||||||
} = controlNet;
|
} = controlNet;
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { pendingControlImages, autoAddBoardId } = useAppSelector(selector);
|
const { pendingControlImages, autoAddBoardId } = useAppSelector(selector);
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
@ -208,18 +210,18 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => {
|
|||||||
<IAIDndImageIcon
|
<IAIDndImageIcon
|
||||||
onClick={handleResetControlImage}
|
onClick={handleResetControlImage}
|
||||||
icon={controlImage ? <FaUndo /> : undefined}
|
icon={controlImage ? <FaUndo /> : undefined}
|
||||||
tooltip="Reset Control Image"
|
tooltip={t('controlnet.resetControlImage')}
|
||||||
/>
|
/>
|
||||||
<IAIDndImageIcon
|
<IAIDndImageIcon
|
||||||
onClick={handleSaveControlImage}
|
onClick={handleSaveControlImage}
|
||||||
icon={controlImage ? <FaSave size={16} /> : undefined}
|
icon={controlImage ? <FaSave size={16} /> : undefined}
|
||||||
tooltip="Save Control Image"
|
tooltip={t('controlnet.saveControlImage')}
|
||||||
styleOverrides={{ marginTop: 6 }}
|
styleOverrides={{ marginTop: 6 }}
|
||||||
/>
|
/>
|
||||||
<IAIDndImageIcon
|
<IAIDndImageIcon
|
||||||
onClick={handleSetControlImageToDimensions}
|
onClick={handleSetControlImageToDimensions}
|
||||||
icon={controlImage ? <FaRulerVertical size={16} /> : undefined}
|
icon={controlImage ? <FaRulerVertical size={16} /> : undefined}
|
||||||
tooltip="Set Control Image Dimensions To W/H"
|
tooltip={t('controlnet.setControlImageDimensions')}
|
||||||
styleOverrides={{ marginTop: 12 }}
|
styleOverrides={{ marginTop: 12 }}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
} from 'features/controlNet/store/controlNetSlice';
|
} from 'features/controlNet/store/controlNetSlice';
|
||||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
controlNet: ControlNetConfig;
|
controlNet: ControlNetConfig;
|
||||||
@ -15,6 +16,7 @@ const ParamControlNetShouldAutoConfig = (props: Props) => {
|
|||||||
const { controlNetId, isEnabled, shouldAutoConfig } = props.controlNet;
|
const { controlNetId, isEnabled, shouldAutoConfig } = props.controlNet;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleShouldAutoConfigChanged = useCallback(() => {
|
const handleShouldAutoConfigChanged = useCallback(() => {
|
||||||
dispatch(controlNetAutoConfigToggled({ controlNetId }));
|
dispatch(controlNetAutoConfigToggled({ controlNetId }));
|
||||||
@ -22,8 +24,8 @@ const ParamControlNetShouldAutoConfig = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label="Auto configure processor"
|
label={t('controlnet.autoConfigure')}
|
||||||
aria-label="Auto configure processor"
|
aria-label={t('controlnet.autoConfigure')}
|
||||||
isChecked={shouldAutoConfig}
|
isChecked={shouldAutoConfig}
|
||||||
onChange={handleShouldAutoConfigChanged}
|
onChange={handleShouldAutoConfigChanged}
|
||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
import { ControlNetConfig } from 'features/controlNet/store/controlNetSlice';
|
import { ControlNetConfig } from 'features/controlNet/store/controlNetSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { FaImage, FaMask } from 'react-icons/fa';
|
import { FaImage, FaMask } from 'react-icons/fa';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type ControlNetCanvasImageImportsProps = {
|
type ControlNetCanvasImageImportsProps = {
|
||||||
controlNet: ControlNetConfig;
|
controlNet: ControlNetConfig;
|
||||||
@ -18,6 +19,7 @@ const ControlNetCanvasImageImports = (
|
|||||||
) => {
|
) => {
|
||||||
const { controlNet } = props;
|
const { controlNet } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleImportImageFromCanvas = useCallback(() => {
|
const handleImportImageFromCanvas = useCallback(() => {
|
||||||
dispatch(canvasImageToControlNet({ controlNet }));
|
dispatch(canvasImageToControlNet({ controlNet }));
|
||||||
@ -36,15 +38,15 @@ const ControlNetCanvasImageImports = (
|
|||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
icon={<FaImage />}
|
icon={<FaImage />}
|
||||||
tooltip="Import Image From Canvas"
|
tooltip={t('controlnet.importImageFromCanvas')}
|
||||||
aria-label="Import Image From Canvas"
|
aria-label={t('controlnet.importImageFromCanvas')}
|
||||||
onClick={handleImportImageFromCanvas}
|
onClick={handleImportImageFromCanvas}
|
||||||
/>
|
/>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
icon={<FaMask />}
|
icon={<FaMask />}
|
||||||
tooltip="Import Mask From Canvas"
|
tooltip={t('controlnet.importMaskFromCanvas')}
|
||||||
aria-label="Import Mask From Canvas"
|
aria-label={t('controlnet.importMaskFromCanvas')}
|
||||||
onClick={handleImportMaskFromCanvas}
|
onClick={handleImportMaskFromCanvas}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
} from 'features/controlNet/store/controlNetSlice';
|
} from 'features/controlNet/store/controlNetSlice';
|
||||||
import { ControlNetBeginEndPopover } from 'features/informationalPopovers/components/controlNetBeginEnd';
|
import { ControlNetBeginEndPopover } from 'features/informationalPopovers/components/controlNetBeginEnd';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
controlNet: ControlNetConfig;
|
controlNet: ControlNetConfig;
|
||||||
@ -28,6 +29,7 @@ const ParamControlNetBeginEnd = (props: Props) => {
|
|||||||
const { beginStepPct, endStepPct, isEnabled, controlNetId } =
|
const { beginStepPct, endStepPct, isEnabled, controlNetId } =
|
||||||
props.controlNet;
|
props.controlNet;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleStepPctChanged = useCallback(
|
const handleStepPctChanged = useCallback(
|
||||||
(v: number[]) => {
|
(v: number[]) => {
|
||||||
@ -50,10 +52,10 @@ const ParamControlNetBeginEnd = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ControlNetBeginEndPopover>
|
<ControlNetBeginEndPopover>
|
||||||
<FormControl isDisabled={!isEnabled}>
|
<FormControl isDisabled={!isEnabled}>
|
||||||
<FormLabel>Begin / End Step Percentage</FormLabel>
|
<FormLabel>{t('controlnet.beginEndStepPercent')}</FormLabel>
|
||||||
<HStack w="100%" gap={2} alignItems="center">
|
<HStack w="100%" gap={2} alignItems="center">
|
||||||
<RangeSlider
|
<RangeSlider
|
||||||
aria-label={['Begin Step %', 'End Step %']}
|
aria-label={['Begin Step %', 'End Step %!']}
|
||||||
value={[beginStepPct, endStepPct]}
|
value={[beginStepPct, endStepPct]}
|
||||||
onChange={handleStepPctChanged}
|
onChange={handleStepPctChanged}
|
||||||
min={0}
|
min={0}
|
||||||
@ -61,6 +63,22 @@ const ParamControlNetBeginEnd = (props: Props) => {
|
|||||||
step={0.01}
|
step={0.01}
|
||||||
minStepsBetweenThumbs={5}
|
minStepsBetweenThumbs={5}
|
||||||
isDisabled={!isEnabled}
|
isDisabled={!isEnabled}
|
||||||
|
>
|
||||||
|
<RangeSliderTrack>
|
||||||
|
<RangeSliderFilledTrack />
|
||||||
|
</RangeSliderTrack>
|
||||||
|
<Tooltip label={formatPct(beginStepPct)} placement="top" hasArrow>
|
||||||
|
<RangeSliderThumb index={0} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label={formatPct(endStepPct)} placement="top" hasArrow>
|
||||||
|
<RangeSliderThumb index={1} />
|
||||||
|
</Tooltip>
|
||||||
|
<RangeSliderMark
|
||||||
|
value={0}
|
||||||
|
sx={{
|
||||||
|
insetInlineStart: '0 !important',
|
||||||
|
insetInlineEnd: 'unset !important',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<RangeSliderTrack>
|
<RangeSliderTrack>
|
||||||
<RangeSliderFilledTrack />
|
<RangeSliderFilledTrack />
|
||||||
|
@ -7,23 +7,25 @@ import {
|
|||||||
} from 'features/controlNet/store/controlNetSlice';
|
} from 'features/controlNet/store/controlNetSlice';
|
||||||
import { ControlNetControlModePopover } from 'features/informationalPopovers/components/controlNetControlMode';
|
import { ControlNetControlModePopover } from 'features/informationalPopovers/components/controlNetControlMode';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type ParamControlNetControlModeProps = {
|
type ParamControlNetControlModeProps = {
|
||||||
controlNet: ControlNetConfig;
|
controlNet: ControlNetConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CONTROL_MODE_DATA = [
|
|
||||||
{ label: 'Balanced', value: 'balanced' },
|
|
||||||
{ label: 'Prompt', value: 'more_prompt' },
|
|
||||||
{ label: 'Control', value: 'more_control' },
|
|
||||||
{ label: 'Mega Control', value: 'unbalanced' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function ParamControlNetControlMode(
|
export default function ParamControlNetControlMode(
|
||||||
props: ParamControlNetControlModeProps
|
props: ParamControlNetControlModeProps
|
||||||
) {
|
) {
|
||||||
const { controlMode, isEnabled, controlNetId } = props.controlNet;
|
const { controlMode, isEnabled, controlNetId } = props.controlNet;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const CONTROL_MODE_DATA = [
|
||||||
|
{ label: t('controlnet.balanced'), value: 'balanced' },
|
||||||
|
{ label: t('controlnet.prompt'), value: 'more_prompt' },
|
||||||
|
{ label: t('controlnet.control'), value: 'more_control' },
|
||||||
|
{ label: t('controlnet.megaControl'), value: 'unbalanced' },
|
||||||
|
];
|
||||||
|
|
||||||
const handleControlModeChange = useCallback(
|
const handleControlModeChange = useCallback(
|
||||||
(controlMode: ControlModes) => {
|
(controlMode: ControlModes) => {
|
||||||
@ -36,7 +38,7 @@ export default function ParamControlNetControlMode(
|
|||||||
<ControlNetControlModePopover>
|
<ControlNetControlModePopover>
|
||||||
<IAIMantineSelect
|
<IAIMantineSelect
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
label="Control Mode"
|
label={t('controlnet.controlMode')}
|
||||||
data={CONTROL_MODE_DATA}
|
data={CONTROL_MODE_DATA}
|
||||||
value={String(controlMode)}
|
value={String(controlMode)}
|
||||||
onChange={handleControlModeChange}
|
onChange={handleControlModeChange}
|
||||||
|
@ -15,6 +15,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
|
|||||||
import { forEach } from 'lodash-es';
|
import { forEach } from 'lodash-es';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useGetControlNetModelsQuery } from 'services/api/endpoints/models';
|
import { useGetControlNetModelsQuery } from 'services/api/endpoints/models';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type ParamControlNetModelProps = {
|
type ParamControlNetModelProps = {
|
||||||
controlNet: ControlNetConfig;
|
controlNet: ControlNetConfig;
|
||||||
@ -35,6 +36,7 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => {
|
|||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
|
|
||||||
const { mainModel } = useAppSelector(selector);
|
const { mainModel } = useAppSelector(selector);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { data: controlNetModels } = useGetControlNetModelsQuery();
|
const { data: controlNetModels } = useGetControlNetModelsQuery();
|
||||||
|
|
||||||
@ -58,13 +60,13 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => {
|
|||||||
group: MODEL_TYPE_MAP[model.base_model],
|
group: MODEL_TYPE_MAP[model.base_model],
|
||||||
disabled,
|
disabled,
|
||||||
tooltip: disabled
|
tooltip: disabled
|
||||||
? `Incompatible base model: ${model.base_model}`
|
? `${t('controlnet.incompatibleBaseModel')} ${model.base_model}`
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}, [controlNetModels, mainModel?.base_model]);
|
}, [controlNetModels, mainModel?.base_model, t]);
|
||||||
|
|
||||||
// grab the full model entity from the RTK Query cache
|
// grab the full model entity from the RTK Query cache
|
||||||
const selectedModel = useMemo(
|
const selectedModel = useMemo(
|
||||||
@ -105,7 +107,7 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => {
|
|||||||
error={
|
error={
|
||||||
!selectedModel || mainModel?.base_model !== selectedModel.base_model
|
!selectedModel || mainModel?.base_model !== selectedModel.base_model
|
||||||
}
|
}
|
||||||
placeholder="Select a model"
|
placeholder={t('controlnet.selectModel')}
|
||||||
value={selectedModel?.id ?? null}
|
value={selectedModel?.id ?? null}
|
||||||
onChange={handleModelChanged}
|
onChange={handleModelChanged}
|
||||||
disabled={isBusy || !isEnabled}
|
disabled={isBusy || !isEnabled}
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
controlNetProcessorTypeChanged,
|
controlNetProcessorTypeChanged,
|
||||||
} from '../../store/controlNetSlice';
|
} from '../../store/controlNetSlice';
|
||||||
import { ControlNetProcessorType } from '../../store/types';
|
import { ControlNetProcessorType } from '../../store/types';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type ParamControlNetProcessorSelectProps = {
|
type ParamControlNetProcessorSelectProps = {
|
||||||
controlNet: ControlNetConfig;
|
controlNet: ControlNetConfig;
|
||||||
@ -57,6 +58,7 @@ const ParamControlNetProcessorSelect = (
|
|||||||
const { controlNetId, isEnabled, processorNode } = props.controlNet;
|
const { controlNetId, isEnabled, processorNode } = props.controlNet;
|
||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
const controlNetProcessors = useAppSelector(selector);
|
const controlNetProcessors = useAppSelector(selector);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleProcessorTypeChanged = useCallback(
|
const handleProcessorTypeChanged = useCallback(
|
||||||
(v: string | null) => {
|
(v: string | null) => {
|
||||||
@ -72,7 +74,7 @@ const ParamControlNetProcessorSelect = (
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
label="Processor"
|
label={t('controlnet.processor')}
|
||||||
value={processorNode.type ?? 'canny_image_processor'}
|
value={processorNode.type ?? 'canny_image_processor'}
|
||||||
data={controlNetProcessors}
|
data={controlNetProcessors}
|
||||||
onChange={handleProcessorTypeChanged}
|
onChange={handleProcessorTypeChanged}
|
||||||
|
@ -7,22 +7,24 @@ import {
|
|||||||
} from 'features/controlNet/store/controlNetSlice';
|
} from 'features/controlNet/store/controlNetSlice';
|
||||||
import { ControlNetResizeModePopover } from 'features/informationalPopovers/components/controlNetResizeMode';
|
import { ControlNetResizeModePopover } from 'features/informationalPopovers/components/controlNetResizeMode';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type ParamControlNetResizeModeProps = {
|
type ParamControlNetResizeModeProps = {
|
||||||
controlNet: ControlNetConfig;
|
controlNet: ControlNetConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RESIZE_MODE_DATA = [
|
|
||||||
{ label: 'Resize', value: 'just_resize' },
|
|
||||||
{ label: 'Crop', value: 'crop_resize' },
|
|
||||||
{ label: 'Fill', value: 'fill_resize' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function ParamControlNetResizeMode(
|
export default function ParamControlNetResizeMode(
|
||||||
props: ParamControlNetResizeModeProps
|
props: ParamControlNetResizeModeProps
|
||||||
) {
|
) {
|
||||||
const { resizeMode, isEnabled, controlNetId } = props.controlNet;
|
const { resizeMode, isEnabled, controlNetId } = props.controlNet;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const RESIZE_MODE_DATA = [
|
||||||
|
{ label: t('controlnet.resize'), value: 'just_resize' },
|
||||||
|
{ label: t('controlnet.crop'), value: 'crop_resize' },
|
||||||
|
{ label: t('controlnet.fill'), value: 'fill_resize' },
|
||||||
|
];
|
||||||
|
|
||||||
const handleResizeModeChange = useCallback(
|
const handleResizeModeChange = useCallback(
|
||||||
(resizeMode: ResizeModes) => {
|
(resizeMode: ResizeModes) => {
|
||||||
@ -35,7 +37,7 @@ export default function ParamControlNetResizeMode(
|
|||||||
<ControlNetResizeModePopover>
|
<ControlNetResizeModePopover>
|
||||||
<IAIMantineSelect
|
<IAIMantineSelect
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
label="Resize Mode"
|
label={t('controlnet.resizeMode')}
|
||||||
data={RESIZE_MODE_DATA}
|
data={RESIZE_MODE_DATA}
|
||||||
value={String(resizeMode)}
|
value={String(resizeMode)}
|
||||||
onChange={handleResizeModeChange}
|
onChange={handleResizeModeChange}
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
} from 'features/controlNet/store/controlNetSlice';
|
} from 'features/controlNet/store/controlNetSlice';
|
||||||
import { ControlNetWeightPopover } from 'features/informationalPopovers/components/controlNetWeight';
|
import { ControlNetWeightPopover } from 'features/informationalPopovers/components/controlNetWeight';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type ParamControlNetWeightProps = {
|
type ParamControlNetWeightProps = {
|
||||||
controlNet: ControlNetConfig;
|
controlNet: ControlNetConfig;
|
||||||
@ -14,6 +15,7 @@ type ParamControlNetWeightProps = {
|
|||||||
const ParamControlNetWeight = (props: ParamControlNetWeightProps) => {
|
const ParamControlNetWeight = (props: ParamControlNetWeightProps) => {
|
||||||
const { weight, isEnabled, controlNetId } = props.controlNet;
|
const { weight, isEnabled, controlNetId } = props.controlNet;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
const handleWeightChanged = useCallback(
|
const handleWeightChanged = useCallback(
|
||||||
(weight: number) => {
|
(weight: number) => {
|
||||||
dispatch(controlNetWeightChanged({ controlNetId, weight }));
|
dispatch(controlNetWeightChanged({ controlNetId, weight }));
|
||||||
@ -25,7 +27,7 @@ const ParamControlNetWeight = (props: ParamControlNetWeightProps) => {
|
|||||||
<ControlNetWeightPopover>
|
<ControlNetWeightPopover>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
isDisabled={!isEnabled}
|
isDisabled={!isEnabled}
|
||||||
label="Weight"
|
label={t('controlnet.weight')}
|
||||||
value={weight}
|
value={weight}
|
||||||
onChange={handleWeightChanged}
|
onChange={handleWeightChanged}
|
||||||
min={0}
|
min={0}
|
||||||
|
@ -6,6 +6,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS.canny_image_processor
|
const DEFAULTS = CONTROLNET_PROCESSORS.canny_image_processor
|
||||||
.default as RequiredCannyImageProcessorInvocation;
|
.default as RequiredCannyImageProcessorInvocation;
|
||||||
@ -21,6 +22,7 @@ const CannyProcessor = (props: CannyProcessorProps) => {
|
|||||||
const { low_threshold, high_threshold } = processorNode;
|
const { low_threshold, high_threshold } = processorNode;
|
||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
const processorChanged = useProcessorNodeChanged();
|
const processorChanged = useProcessorNodeChanged();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleLowThresholdChanged = useCallback(
|
const handleLowThresholdChanged = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -52,7 +54,7 @@ const CannyProcessor = (props: CannyProcessorProps) => {
|
|||||||
<ProcessorWrapper>
|
<ProcessorWrapper>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
label="Low Threshold"
|
label={t('controlnet.lowThreshold')}
|
||||||
value={low_threshold}
|
value={low_threshold}
|
||||||
onChange={handleLowThresholdChanged}
|
onChange={handleLowThresholdChanged}
|
||||||
handleReset={handleLowThresholdReset}
|
handleReset={handleLowThresholdReset}
|
||||||
@ -64,7 +66,7 @@ const CannyProcessor = (props: CannyProcessorProps) => {
|
|||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
label="High Threshold"
|
label={t('controlnet.highThreshold')}
|
||||||
value={high_threshold}
|
value={high_threshold}
|
||||||
onChange={handleHighThresholdChanged}
|
onChange={handleHighThresholdChanged}
|
||||||
handleReset={handleHighThresholdReset}
|
handleReset={handleHighThresholdReset}
|
||||||
|
@ -6,6 +6,7 @@ import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
|||||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS.content_shuffle_image_processor
|
const DEFAULTS = CONTROLNET_PROCESSORS.content_shuffle_image_processor
|
||||||
.default as RequiredContentShuffleImageProcessorInvocation;
|
.default as RequiredContentShuffleImageProcessorInvocation;
|
||||||
@ -21,6 +22,7 @@ const ContentShuffleProcessor = (props: Props) => {
|
|||||||
const { image_resolution, detect_resolution, w, h, f } = processorNode;
|
const { image_resolution, detect_resolution, w, h, f } = processorNode;
|
||||||
const processorChanged = useProcessorNodeChanged();
|
const processorChanged = useProcessorNodeChanged();
|
||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleDetectResolutionChanged = useCallback(
|
const handleDetectResolutionChanged = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -90,7 +92,7 @@ const ContentShuffleProcessor = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ProcessorWrapper>
|
<ProcessorWrapper>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Detect Resolution"
|
label={t('controlnet.detectResolution')}
|
||||||
value={detect_resolution}
|
value={detect_resolution}
|
||||||
onChange={handleDetectResolutionChanged}
|
onChange={handleDetectResolutionChanged}
|
||||||
handleReset={handleDetectResolutionReset}
|
handleReset={handleDetectResolutionReset}
|
||||||
@ -102,7 +104,7 @@ const ContentShuffleProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Image Resolution"
|
label={t('controlnet.imageResolution')}
|
||||||
value={image_resolution}
|
value={image_resolution}
|
||||||
onChange={handleImageResolutionChanged}
|
onChange={handleImageResolutionChanged}
|
||||||
handleReset={handleImageResolutionReset}
|
handleReset={handleImageResolutionReset}
|
||||||
@ -114,7 +116,7 @@ const ContentShuffleProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="W"
|
label={t('controlnet.w')}
|
||||||
value={w}
|
value={w}
|
||||||
onChange={handleWChanged}
|
onChange={handleWChanged}
|
||||||
handleReset={handleWReset}
|
handleReset={handleWReset}
|
||||||
@ -126,7 +128,7 @@ const ContentShuffleProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="H"
|
label={t('controlnet.h')}
|
||||||
value={h}
|
value={h}
|
||||||
onChange={handleHChanged}
|
onChange={handleHChanged}
|
||||||
handleReset={handleHReset}
|
handleReset={handleHReset}
|
||||||
@ -138,7 +140,7 @@ const ContentShuffleProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="F"
|
label={t('controlnet.f')}
|
||||||
value={f}
|
value={f}
|
||||||
onChange={handleFChanged}
|
onChange={handleFChanged}
|
||||||
handleReset={handleFReset}
|
handleReset={handleFReset}
|
||||||
|
@ -7,6 +7,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
|
|||||||
import { ChangeEvent, memo, useCallback } from 'react';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS.hed_image_processor
|
const DEFAULTS = CONTROLNET_PROCESSORS.hed_image_processor
|
||||||
.default as RequiredHedImageProcessorInvocation;
|
.default as RequiredHedImageProcessorInvocation;
|
||||||
@ -25,6 +26,7 @@ const HedPreprocessor = (props: HedProcessorProps) => {
|
|||||||
} = props;
|
} = props;
|
||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
const processorChanged = useProcessorNodeChanged();
|
const processorChanged = useProcessorNodeChanged();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleDetectResolutionChanged = useCallback(
|
const handleDetectResolutionChanged = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -62,7 +64,7 @@ const HedPreprocessor = (props: HedProcessorProps) => {
|
|||||||
return (
|
return (
|
||||||
<ProcessorWrapper>
|
<ProcessorWrapper>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Detect Resolution"
|
label={t('controlnet.detectResolution')}
|
||||||
value={detect_resolution}
|
value={detect_resolution}
|
||||||
onChange={handleDetectResolutionChanged}
|
onChange={handleDetectResolutionChanged}
|
||||||
handleReset={handleDetectResolutionReset}
|
handleReset={handleDetectResolutionReset}
|
||||||
@ -74,7 +76,7 @@ const HedPreprocessor = (props: HedProcessorProps) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Image Resolution"
|
label={t('controlnet.imageResolution')}
|
||||||
value={image_resolution}
|
value={image_resolution}
|
||||||
onChange={handleImageResolutionChanged}
|
onChange={handleImageResolutionChanged}
|
||||||
handleReset={handleImageResolutionReset}
|
handleReset={handleImageResolutionReset}
|
||||||
@ -86,7 +88,7 @@ const HedPreprocessor = (props: HedProcessorProps) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label="Scribble"
|
label={t('controlnet.scribble')}
|
||||||
isChecked={scribble}
|
isChecked={scribble}
|
||||||
onChange={handleScribbleChanged}
|
onChange={handleScribbleChanged}
|
||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
|
@ -6,6 +6,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS.lineart_anime_image_processor
|
const DEFAULTS = CONTROLNET_PROCESSORS.lineart_anime_image_processor
|
||||||
.default as RequiredLineartAnimeImageProcessorInvocation;
|
.default as RequiredLineartAnimeImageProcessorInvocation;
|
||||||
@ -21,6 +22,7 @@ const LineartAnimeProcessor = (props: Props) => {
|
|||||||
const { image_resolution, detect_resolution } = processorNode;
|
const { image_resolution, detect_resolution } = processorNode;
|
||||||
const processorChanged = useProcessorNodeChanged();
|
const processorChanged = useProcessorNodeChanged();
|
||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleDetectResolutionChanged = useCallback(
|
const handleDetectResolutionChanged = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -51,7 +53,7 @@ const LineartAnimeProcessor = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ProcessorWrapper>
|
<ProcessorWrapper>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Detect Resolution"
|
label={t('controlnet.detectResolution')}
|
||||||
value={detect_resolution}
|
value={detect_resolution}
|
||||||
onChange={handleDetectResolutionChanged}
|
onChange={handleDetectResolutionChanged}
|
||||||
handleReset={handleDetectResolutionReset}
|
handleReset={handleDetectResolutionReset}
|
||||||
@ -63,7 +65,7 @@ const LineartAnimeProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Image Resolution"
|
label={t('controlnet.imageResolution')}
|
||||||
value={image_resolution}
|
value={image_resolution}
|
||||||
onChange={handleImageResolutionChanged}
|
onChange={handleImageResolutionChanged}
|
||||||
handleReset={handleImageResolutionReset}
|
handleReset={handleImageResolutionReset}
|
||||||
|
@ -7,6 +7,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
|
|||||||
import { ChangeEvent, memo, useCallback } from 'react';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS.lineart_image_processor
|
const DEFAULTS = CONTROLNET_PROCESSORS.lineart_image_processor
|
||||||
.default as RequiredLineartImageProcessorInvocation;
|
.default as RequiredLineartImageProcessorInvocation;
|
||||||
@ -22,6 +23,7 @@ const LineartProcessor = (props: LineartProcessorProps) => {
|
|||||||
const { image_resolution, detect_resolution, coarse } = processorNode;
|
const { image_resolution, detect_resolution, coarse } = processorNode;
|
||||||
const processorChanged = useProcessorNodeChanged();
|
const processorChanged = useProcessorNodeChanged();
|
||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleDetectResolutionChanged = useCallback(
|
const handleDetectResolutionChanged = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -59,7 +61,7 @@ const LineartProcessor = (props: LineartProcessorProps) => {
|
|||||||
return (
|
return (
|
||||||
<ProcessorWrapper>
|
<ProcessorWrapper>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Detect Resolution"
|
label={t('controlnet.detectResolution')}
|
||||||
value={detect_resolution}
|
value={detect_resolution}
|
||||||
onChange={handleDetectResolutionChanged}
|
onChange={handleDetectResolutionChanged}
|
||||||
handleReset={handleDetectResolutionReset}
|
handleReset={handleDetectResolutionReset}
|
||||||
@ -71,7 +73,7 @@ const LineartProcessor = (props: LineartProcessorProps) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Image Resolution"
|
label={t('controlnet.imageResolution')}
|
||||||
value={image_resolution}
|
value={image_resolution}
|
||||||
onChange={handleImageResolutionChanged}
|
onChange={handleImageResolutionChanged}
|
||||||
handleReset={handleImageResolutionReset}
|
handleReset={handleImageResolutionReset}
|
||||||
@ -83,7 +85,7 @@ const LineartProcessor = (props: LineartProcessorProps) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label="Coarse"
|
label={t('controlnet.coarse')}
|
||||||
isChecked={coarse}
|
isChecked={coarse}
|
||||||
onChange={handleCoarseChanged}
|
onChange={handleCoarseChanged}
|
||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
|
@ -6,6 +6,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS.mediapipe_face_processor
|
const DEFAULTS = CONTROLNET_PROCESSORS.mediapipe_face_processor
|
||||||
.default as RequiredMediapipeFaceProcessorInvocation;
|
.default as RequiredMediapipeFaceProcessorInvocation;
|
||||||
@ -21,6 +22,7 @@ const MediapipeFaceProcessor = (props: Props) => {
|
|||||||
const { max_faces, min_confidence } = processorNode;
|
const { max_faces, min_confidence } = processorNode;
|
||||||
const processorChanged = useProcessorNodeChanged();
|
const processorChanged = useProcessorNodeChanged();
|
||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleMaxFacesChanged = useCallback(
|
const handleMaxFacesChanged = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -47,7 +49,7 @@ const MediapipeFaceProcessor = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ProcessorWrapper>
|
<ProcessorWrapper>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Max Faces"
|
label={t('controlnet.maxFaces')}
|
||||||
value={max_faces}
|
value={max_faces}
|
||||||
onChange={handleMaxFacesChanged}
|
onChange={handleMaxFacesChanged}
|
||||||
handleReset={handleMaxFacesReset}
|
handleReset={handleMaxFacesReset}
|
||||||
@ -59,7 +61,7 @@ const MediapipeFaceProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Min Confidence"
|
label={t('controlnet.minConfidence')}
|
||||||
value={min_confidence}
|
value={min_confidence}
|
||||||
onChange={handleMinConfidenceChanged}
|
onChange={handleMinConfidenceChanged}
|
||||||
handleReset={handleMinConfidenceReset}
|
handleReset={handleMinConfidenceReset}
|
||||||
|
@ -6,6 +6,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS.midas_depth_image_processor
|
const DEFAULTS = CONTROLNET_PROCESSORS.midas_depth_image_processor
|
||||||
.default as RequiredMidasDepthImageProcessorInvocation;
|
.default as RequiredMidasDepthImageProcessorInvocation;
|
||||||
@ -21,6 +22,7 @@ const MidasDepthProcessor = (props: Props) => {
|
|||||||
const { a_mult, bg_th } = processorNode;
|
const { a_mult, bg_th } = processorNode;
|
||||||
const processorChanged = useProcessorNodeChanged();
|
const processorChanged = useProcessorNodeChanged();
|
||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleAMultChanged = useCallback(
|
const handleAMultChanged = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -47,7 +49,7 @@ const MidasDepthProcessor = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ProcessorWrapper>
|
<ProcessorWrapper>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="a_mult"
|
label={t('controlnet.amult')}
|
||||||
value={a_mult}
|
value={a_mult}
|
||||||
onChange={handleAMultChanged}
|
onChange={handleAMultChanged}
|
||||||
handleReset={handleAMultReset}
|
handleReset={handleAMultReset}
|
||||||
@ -60,7 +62,7 @@ const MidasDepthProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="bg_th"
|
label={t('controlnet.bgth')}
|
||||||
value={bg_th}
|
value={bg_th}
|
||||||
onChange={handleBgThChanged}
|
onChange={handleBgThChanged}
|
||||||
handleReset={handleBgThReset}
|
handleReset={handleBgThReset}
|
||||||
|
@ -6,6 +6,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS.mlsd_image_processor
|
const DEFAULTS = CONTROLNET_PROCESSORS.mlsd_image_processor
|
||||||
.default as RequiredMlsdImageProcessorInvocation;
|
.default as RequiredMlsdImageProcessorInvocation;
|
||||||
@ -21,6 +22,7 @@ const MlsdImageProcessor = (props: Props) => {
|
|||||||
const { image_resolution, detect_resolution, thr_d, thr_v } = processorNode;
|
const { image_resolution, detect_resolution, thr_d, thr_v } = processorNode;
|
||||||
const processorChanged = useProcessorNodeChanged();
|
const processorChanged = useProcessorNodeChanged();
|
||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleDetectResolutionChanged = useCallback(
|
const handleDetectResolutionChanged = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -73,7 +75,7 @@ const MlsdImageProcessor = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ProcessorWrapper>
|
<ProcessorWrapper>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Detect Resolution"
|
label={t('controlnet.detectResolution')}
|
||||||
value={detect_resolution}
|
value={detect_resolution}
|
||||||
onChange={handleDetectResolutionChanged}
|
onChange={handleDetectResolutionChanged}
|
||||||
handleReset={handleDetectResolutionReset}
|
handleReset={handleDetectResolutionReset}
|
||||||
@ -85,7 +87,7 @@ const MlsdImageProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Image Resolution"
|
label={t('controlnet.imageResolution')}
|
||||||
value={image_resolution}
|
value={image_resolution}
|
||||||
onChange={handleImageResolutionChanged}
|
onChange={handleImageResolutionChanged}
|
||||||
handleReset={handleImageResolutionReset}
|
handleReset={handleImageResolutionReset}
|
||||||
@ -97,7 +99,7 @@ const MlsdImageProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="W"
|
label={t('controlnet.w')}
|
||||||
value={thr_d}
|
value={thr_d}
|
||||||
onChange={handleThrDChanged}
|
onChange={handleThrDChanged}
|
||||||
handleReset={handleThrDReset}
|
handleReset={handleThrDReset}
|
||||||
@ -110,7 +112,7 @@ const MlsdImageProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="H"
|
label={t('controlnet.h')}
|
||||||
value={thr_v}
|
value={thr_v}
|
||||||
onChange={handleThrVChanged}
|
onChange={handleThrVChanged}
|
||||||
handleReset={handleThrVReset}
|
handleReset={handleThrVReset}
|
||||||
|
@ -6,6 +6,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS.normalbae_image_processor
|
const DEFAULTS = CONTROLNET_PROCESSORS.normalbae_image_processor
|
||||||
.default as RequiredNormalbaeImageProcessorInvocation;
|
.default as RequiredNormalbaeImageProcessorInvocation;
|
||||||
@ -21,6 +22,7 @@ const NormalBaeProcessor = (props: Props) => {
|
|||||||
const { image_resolution, detect_resolution } = processorNode;
|
const { image_resolution, detect_resolution } = processorNode;
|
||||||
const processorChanged = useProcessorNodeChanged();
|
const processorChanged = useProcessorNodeChanged();
|
||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleDetectResolutionChanged = useCallback(
|
const handleDetectResolutionChanged = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -51,7 +53,7 @@ const NormalBaeProcessor = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ProcessorWrapper>
|
<ProcessorWrapper>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Detect Resolution"
|
label={t('controlnet.detectResolution')}
|
||||||
value={detect_resolution}
|
value={detect_resolution}
|
||||||
onChange={handleDetectResolutionChanged}
|
onChange={handleDetectResolutionChanged}
|
||||||
handleReset={handleDetectResolutionReset}
|
handleReset={handleDetectResolutionReset}
|
||||||
@ -63,7 +65,7 @@ const NormalBaeProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Image Resolution"
|
label={t('controlnet.imageResolution')}
|
||||||
value={image_resolution}
|
value={image_resolution}
|
||||||
onChange={handleImageResolutionChanged}
|
onChange={handleImageResolutionChanged}
|
||||||
handleReset={handleImageResolutionReset}
|
handleReset={handleImageResolutionReset}
|
||||||
|
@ -7,6 +7,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
|
|||||||
import { ChangeEvent, memo, useCallback } from 'react';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS.openpose_image_processor
|
const DEFAULTS = CONTROLNET_PROCESSORS.openpose_image_processor
|
||||||
.default as RequiredOpenposeImageProcessorInvocation;
|
.default as RequiredOpenposeImageProcessorInvocation;
|
||||||
@ -22,6 +23,7 @@ const OpenposeProcessor = (props: Props) => {
|
|||||||
const { image_resolution, detect_resolution, hand_and_face } = processorNode;
|
const { image_resolution, detect_resolution, hand_and_face } = processorNode;
|
||||||
const processorChanged = useProcessorNodeChanged();
|
const processorChanged = useProcessorNodeChanged();
|
||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleDetectResolutionChanged = useCallback(
|
const handleDetectResolutionChanged = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -59,7 +61,7 @@ const OpenposeProcessor = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ProcessorWrapper>
|
<ProcessorWrapper>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Detect Resolution"
|
label={t('controlnet.detectResolution')}
|
||||||
value={detect_resolution}
|
value={detect_resolution}
|
||||||
onChange={handleDetectResolutionChanged}
|
onChange={handleDetectResolutionChanged}
|
||||||
handleReset={handleDetectResolutionReset}
|
handleReset={handleDetectResolutionReset}
|
||||||
@ -71,7 +73,7 @@ const OpenposeProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Image Resolution"
|
label={t('controlnet.imageResolution')}
|
||||||
value={image_resolution}
|
value={image_resolution}
|
||||||
onChange={handleImageResolutionChanged}
|
onChange={handleImageResolutionChanged}
|
||||||
handleReset={handleImageResolutionReset}
|
handleReset={handleImageResolutionReset}
|
||||||
@ -83,7 +85,7 @@ const OpenposeProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label="Hand and Face"
|
label={t('controlnet.handAndFace')}
|
||||||
isChecked={hand_and_face}
|
isChecked={hand_and_face}
|
||||||
onChange={handleHandAndFaceChanged}
|
onChange={handleHandAndFaceChanged}
|
||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
|
@ -7,6 +7,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
|
|||||||
import { ChangeEvent, memo, useCallback } from 'react';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS.pidi_image_processor
|
const DEFAULTS = CONTROLNET_PROCESSORS.pidi_image_processor
|
||||||
.default as RequiredPidiImageProcessorInvocation;
|
.default as RequiredPidiImageProcessorInvocation;
|
||||||
@ -22,6 +23,7 @@ const PidiProcessor = (props: Props) => {
|
|||||||
const { image_resolution, detect_resolution, scribble, safe } = processorNode;
|
const { image_resolution, detect_resolution, scribble, safe } = processorNode;
|
||||||
const processorChanged = useProcessorNodeChanged();
|
const processorChanged = useProcessorNodeChanged();
|
||||||
const isBusy = useAppSelector(selectIsBusy);
|
const isBusy = useAppSelector(selectIsBusy);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleDetectResolutionChanged = useCallback(
|
const handleDetectResolutionChanged = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -66,7 +68,7 @@ const PidiProcessor = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ProcessorWrapper>
|
<ProcessorWrapper>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Detect Resolution"
|
label={t('controlnet.detectResolution')}
|
||||||
value={detect_resolution}
|
value={detect_resolution}
|
||||||
onChange={handleDetectResolutionChanged}
|
onChange={handleDetectResolutionChanged}
|
||||||
handleReset={handleDetectResolutionReset}
|
handleReset={handleDetectResolutionReset}
|
||||||
@ -78,7 +80,7 @@ const PidiProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Image Resolution"
|
label={t('controlnet.imageResolution')}
|
||||||
value={image_resolution}
|
value={image_resolution}
|
||||||
onChange={handleImageResolutionChanged}
|
onChange={handleImageResolutionChanged}
|
||||||
handleReset={handleImageResolutionReset}
|
handleReset={handleImageResolutionReset}
|
||||||
@ -90,12 +92,12 @@ const PidiProcessor = (props: Props) => {
|
|||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
/>
|
/>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label="Scribble"
|
label={t('controlnet.scribble')}
|
||||||
isChecked={scribble}
|
isChecked={scribble}
|
||||||
onChange={handleScribbleChanged}
|
onChange={handleScribbleChanged}
|
||||||
/>
|
/>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label="Safe"
|
label={t('controlnet.safe')}
|
||||||
isChecked={safe}
|
isChecked={safe}
|
||||||
onChange={handleSafeChanged}
|
onChange={handleSafeChanged}
|
||||||
isDisabled={isBusy || !isEnabled}
|
isDisabled={isBusy || !isEnabled}
|
||||||
|
@ -2,6 +2,7 @@ import {
|
|||||||
ControlNetProcessorType,
|
ControlNetProcessorType,
|
||||||
RequiredControlNetProcessorNode,
|
RequiredControlNetProcessorNode,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
import i18n from 'i18next';
|
||||||
|
|
||||||
type ControlNetProcessorsDict = Record<
|
type ControlNetProcessorsDict = Record<
|
||||||
ControlNetProcessorType,
|
ControlNetProcessorType,
|
||||||
@ -12,7 +13,6 @@ type ControlNetProcessorsDict = Record<
|
|||||||
default: RequiredControlNetProcessorNode | { type: 'none' };
|
default: RequiredControlNetProcessorNode | { type: 'none' };
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A dict of ControlNet processors, including:
|
* A dict of ControlNet processors, including:
|
||||||
* - type
|
* - type
|
||||||
@ -25,16 +25,24 @@ type ControlNetProcessorsDict = Record<
|
|||||||
export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
||||||
none: {
|
none: {
|
||||||
type: 'none',
|
type: 'none',
|
||||||
label: 'none',
|
get label() {
|
||||||
description: '',
|
return i18n.t('controlnet.none');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.noneDescription');
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
type: 'none',
|
type: 'none',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
canny_image_processor: {
|
canny_image_processor: {
|
||||||
type: 'canny_image_processor',
|
type: 'canny_image_processor',
|
||||||
label: 'Canny',
|
get label() {
|
||||||
description: '',
|
return i18n.t('controlnet.canny');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.cannyDescription');
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'canny_image_processor',
|
id: 'canny_image_processor',
|
||||||
type: 'canny_image_processor',
|
type: 'canny_image_processor',
|
||||||
@ -44,8 +52,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
|||||||
},
|
},
|
||||||
content_shuffle_image_processor: {
|
content_shuffle_image_processor: {
|
||||||
type: 'content_shuffle_image_processor',
|
type: 'content_shuffle_image_processor',
|
||||||
label: 'Content Shuffle',
|
get label() {
|
||||||
description: '',
|
return i18n.t('controlnet.contentShuffle');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.contentShuffleDescription');
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'content_shuffle_image_processor',
|
id: 'content_shuffle_image_processor',
|
||||||
type: 'content_shuffle_image_processor',
|
type: 'content_shuffle_image_processor',
|
||||||
@ -58,8 +70,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
|||||||
},
|
},
|
||||||
hed_image_processor: {
|
hed_image_processor: {
|
||||||
type: 'hed_image_processor',
|
type: 'hed_image_processor',
|
||||||
label: 'HED',
|
get label() {
|
||||||
description: '',
|
return i18n.t('controlnet.hed');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.hedDescription');
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'hed_image_processor',
|
id: 'hed_image_processor',
|
||||||
type: 'hed_image_processor',
|
type: 'hed_image_processor',
|
||||||
@ -70,8 +86,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
|||||||
},
|
},
|
||||||
lineart_anime_image_processor: {
|
lineart_anime_image_processor: {
|
||||||
type: 'lineart_anime_image_processor',
|
type: 'lineart_anime_image_processor',
|
||||||
label: 'Lineart Anime',
|
get label() {
|
||||||
description: '',
|
return i18n.t('controlnet.lineartAnime');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.lineartAnimeDescription');
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'lineart_anime_image_processor',
|
id: 'lineart_anime_image_processor',
|
||||||
type: 'lineart_anime_image_processor',
|
type: 'lineart_anime_image_processor',
|
||||||
@ -81,8 +101,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
|||||||
},
|
},
|
||||||
lineart_image_processor: {
|
lineart_image_processor: {
|
||||||
type: 'lineart_image_processor',
|
type: 'lineart_image_processor',
|
||||||
label: 'Lineart',
|
get label() {
|
||||||
description: '',
|
return i18n.t('controlnet.lineart');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.lineartDescription');
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'lineart_image_processor',
|
id: 'lineart_image_processor',
|
||||||
type: 'lineart_image_processor',
|
type: 'lineart_image_processor',
|
||||||
@ -93,8 +117,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
|||||||
},
|
},
|
||||||
mediapipe_face_processor: {
|
mediapipe_face_processor: {
|
||||||
type: 'mediapipe_face_processor',
|
type: 'mediapipe_face_processor',
|
||||||
label: 'Mediapipe Face',
|
get label() {
|
||||||
description: '',
|
return i18n.t('controlnet.mediapipeFace');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.mediapipeFaceDescription');
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'mediapipe_face_processor',
|
id: 'mediapipe_face_processor',
|
||||||
type: 'mediapipe_face_processor',
|
type: 'mediapipe_face_processor',
|
||||||
@ -104,8 +132,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
|||||||
},
|
},
|
||||||
midas_depth_image_processor: {
|
midas_depth_image_processor: {
|
||||||
type: 'midas_depth_image_processor',
|
type: 'midas_depth_image_processor',
|
||||||
label: 'Depth (Midas)',
|
get label() {
|
||||||
description: '',
|
return i18n.t('controlnet.depthMidas');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.depthMidasDescription');
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'midas_depth_image_processor',
|
id: 'midas_depth_image_processor',
|
||||||
type: 'midas_depth_image_processor',
|
type: 'midas_depth_image_processor',
|
||||||
@ -115,8 +147,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
|||||||
},
|
},
|
||||||
mlsd_image_processor: {
|
mlsd_image_processor: {
|
||||||
type: 'mlsd_image_processor',
|
type: 'mlsd_image_processor',
|
||||||
label: 'M-LSD',
|
get label() {
|
||||||
description: '',
|
return i18n.t('controlnet.mlsd');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.mlsdDescription');
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'mlsd_image_processor',
|
id: 'mlsd_image_processor',
|
||||||
type: 'mlsd_image_processor',
|
type: 'mlsd_image_processor',
|
||||||
@ -128,8 +164,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
|||||||
},
|
},
|
||||||
normalbae_image_processor: {
|
normalbae_image_processor: {
|
||||||
type: 'normalbae_image_processor',
|
type: 'normalbae_image_processor',
|
||||||
label: 'Normal BAE',
|
get label() {
|
||||||
description: '',
|
return i18n.t('controlnet.normalBae');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.normalBaeDescription');
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'normalbae_image_processor',
|
id: 'normalbae_image_processor',
|
||||||
type: 'normalbae_image_processor',
|
type: 'normalbae_image_processor',
|
||||||
@ -139,8 +179,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
|||||||
},
|
},
|
||||||
openpose_image_processor: {
|
openpose_image_processor: {
|
||||||
type: 'openpose_image_processor',
|
type: 'openpose_image_processor',
|
||||||
label: 'Openpose',
|
get label() {
|
||||||
description: '',
|
return i18n.t('controlnet.openPose');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.openPoseDescription');
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'openpose_image_processor',
|
id: 'openpose_image_processor',
|
||||||
type: 'openpose_image_processor',
|
type: 'openpose_image_processor',
|
||||||
@ -151,8 +195,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
|||||||
},
|
},
|
||||||
pidi_image_processor: {
|
pidi_image_processor: {
|
||||||
type: 'pidi_image_processor',
|
type: 'pidi_image_processor',
|
||||||
label: 'PIDI',
|
get label() {
|
||||||
description: '',
|
return i18n.t('controlnet.pidi');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.pidiDescription');
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'pidi_image_processor',
|
id: 'pidi_image_processor',
|
||||||
type: 'pidi_image_processor',
|
type: 'pidi_image_processor',
|
||||||
@ -164,8 +212,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
|||||||
},
|
},
|
||||||
zoe_depth_image_processor: {
|
zoe_depth_image_processor: {
|
||||||
type: 'zoe_depth_image_processor',
|
type: 'zoe_depth_image_processor',
|
||||||
label: 'Depth (Zoe)',
|
get label() {
|
||||||
description: '',
|
return i18n.t('controlnet.depthZoe');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.depthZoeDescription');
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'zoe_depth_image_processor',
|
id: 'zoe_depth_image_processor',
|
||||||
type: 'zoe_depth_image_processor',
|
type: 'zoe_depth_image_processor',
|
||||||
@ -186,4 +238,6 @@ export const CONTROLNET_MODEL_DEFAULT_PROCESSORS: {
|
|||||||
shuffle: 'content_shuffle_image_processor',
|
shuffle: 'content_shuffle_image_processor',
|
||||||
openpose: 'openpose_image_processor',
|
openpose: 'openpose_image_processor',
|
||||||
mediapipe: 'mediapipe_face_processor',
|
mediapipe: 'mediapipe_face_processor',
|
||||||
|
pidi: 'pidi_image_processor',
|
||||||
|
zoe: 'zoe_depth_image_processor',
|
||||||
};
|
};
|
||||||
|
@ -2,16 +2,19 @@ import { ListItem, Text, UnorderedList } from '@chakra-ui/react';
|
|||||||
import { some } from 'lodash-es';
|
import { some } from 'lodash-es';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { ImageUsage } from '../store/types';
|
import { ImageUsage } from '../store/types';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
imageUsage?: ImageUsage;
|
imageUsage?: ImageUsage;
|
||||||
topMessage?: string;
|
topMessage?: string;
|
||||||
bottomMessage?: string;
|
bottomMessage?: string;
|
||||||
};
|
};
|
||||||
const ImageUsageMessage = (props: Props) => {
|
const ImageUsageMessage = (props: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
imageUsage,
|
imageUsage,
|
||||||
topMessage = 'This image is currently in use in the following features:',
|
topMessage = t('gallery.currentlyInUse'),
|
||||||
bottomMessage = 'If you delete this image, those features will immediately be reset.',
|
bottomMessage = t('gallery.featuresWillReset'),
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
if (!imageUsage) {
|
if (!imageUsage) {
|
||||||
@ -26,10 +29,18 @@ const ImageUsageMessage = (props: Props) => {
|
|||||||
<>
|
<>
|
||||||
<Text>{topMessage}</Text>
|
<Text>{topMessage}</Text>
|
||||||
<UnorderedList sx={{ paddingInlineStart: 6 }}>
|
<UnorderedList sx={{ paddingInlineStart: 6 }}>
|
||||||
{imageUsage.isInitialImage && <ListItem>Image to Image</ListItem>}
|
{imageUsage.isInitialImage && (
|
||||||
{imageUsage.isCanvasImage && <ListItem>Unified Canvas</ListItem>}
|
<ListItem>{t('common.img2img')}</ListItem>
|
||||||
{imageUsage.isControlNetImage && <ListItem>ControlNet</ListItem>}
|
)}
|
||||||
{imageUsage.isNodesImage && <ListItem>Node Editor</ListItem>}
|
{imageUsage.isCanvasImage && (
|
||||||
|
<ListItem>{t('common.unifiedCanvas')}</ListItem>
|
||||||
|
)}
|
||||||
|
{imageUsage.isControlNetImage && (
|
||||||
|
<ListItem>{t('common.controlNet')}</ListItem>
|
||||||
|
)}
|
||||||
|
{imageUsage.isNodesImage && (
|
||||||
|
<ListItem>{t('common.nodeEditor')}</ListItem>
|
||||||
|
)}
|
||||||
</UnorderedList>
|
</UnorderedList>
|
||||||
<Text>{bottomMessage}</Text>
|
<Text>{bottomMessage}</Text>
|
||||||
</>
|
</>
|
||||||
|
@ -9,6 +9,7 @@ import { useFeatureStatus } from '../../system/hooks/useFeatureStatus';
|
|||||||
import ParamDynamicPromptsCombinatorial from './ParamDynamicPromptsCombinatorial';
|
import ParamDynamicPromptsCombinatorial from './ParamDynamicPromptsCombinatorial';
|
||||||
import ParamDynamicPromptsToggle from './ParamDynamicPromptsEnabled';
|
import ParamDynamicPromptsToggle from './ParamDynamicPromptsEnabled';
|
||||||
import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts';
|
import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -22,6 +23,7 @@ const selector = createSelector(
|
|||||||
|
|
||||||
const ParamDynamicPromptsCollapse = () => {
|
const ParamDynamicPromptsCollapse = () => {
|
||||||
const { activeLabel } = useAppSelector(selector);
|
const { activeLabel } = useAppSelector(selector);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const isDynamicPromptingEnabled =
|
const isDynamicPromptingEnabled =
|
||||||
useFeatureStatus('dynamicPrompting').isFeatureEnabled;
|
useFeatureStatus('dynamicPrompting').isFeatureEnabled;
|
||||||
@ -31,7 +33,7 @@ const ParamDynamicPromptsCollapse = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAICollapse label="Dynamic Prompts" activeLabel={activeLabel}>
|
<IAICollapse label={t('prompt.dynamicPrompts')} activeLabel={activeLabel}>
|
||||||
<Flex sx={{ gap: 2, flexDir: 'column' }}>
|
<Flex sx={{ gap: 2, flexDir: 'column' }}>
|
||||||
<ParamDynamicPromptsToggle />
|
<ParamDynamicPromptsToggle />
|
||||||
<ParamDynamicPromptsCombinatorial />
|
<ParamDynamicPromptsCombinatorial />
|
||||||
|
@ -6,6 +6,7 @@ import IAISwitch from 'common/components/IAISwitch';
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { combinatorialToggled } from '../store/dynamicPromptsSlice';
|
import { combinatorialToggled } from '../store/dynamicPromptsSlice';
|
||||||
import { DynamicPromptsCombinatorialPopover } from 'features/informationalPopovers/components/dynamicPromptsCombinatorial';
|
import { DynamicPromptsCombinatorialPopover } from 'features/informationalPopovers/components/dynamicPromptsCombinatorial';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -20,6 +21,7 @@ const selector = createSelector(
|
|||||||
const ParamDynamicPromptsCombinatorial = () => {
|
const ParamDynamicPromptsCombinatorial = () => {
|
||||||
const { combinatorial, isDisabled } = useAppSelector(selector);
|
const { combinatorial, isDisabled } = useAppSelector(selector);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChange = useCallback(() => {
|
const handleChange = useCallback(() => {
|
||||||
dispatch(combinatorialToggled());
|
dispatch(combinatorialToggled());
|
||||||
@ -29,7 +31,7 @@ const ParamDynamicPromptsCombinatorial = () => {
|
|||||||
<DynamicPromptsCombinatorialPopover>
|
<DynamicPromptsCombinatorialPopover>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
label="Combinatorial Generation"
|
label={t('prompt.combinatorial')}
|
||||||
isChecked={combinatorial}
|
isChecked={combinatorial}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
@ -6,6 +6,7 @@ import IAISwitch from 'common/components/IAISwitch';
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { isEnabledToggled } from '../store/dynamicPromptsSlice';
|
import { isEnabledToggled } from '../store/dynamicPromptsSlice';
|
||||||
import { DynamicPromptsTogglePopover } from 'features/informationalPopovers/components/dynamicPromptsToggle';
|
import { DynamicPromptsTogglePopover } from 'features/informationalPopovers/components/dynamicPromptsToggle';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -20,6 +21,7 @@ const selector = createSelector(
|
|||||||
const ParamDynamicPromptsToggle = () => {
|
const ParamDynamicPromptsToggle = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { isEnabled } = useAppSelector(selector);
|
const { isEnabled } = useAppSelector(selector);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleToggleIsEnabled = useCallback(() => {
|
const handleToggleIsEnabled = useCallback(() => {
|
||||||
dispatch(isEnabledToggled());
|
dispatch(isEnabledToggled());
|
||||||
@ -28,11 +30,12 @@ const ParamDynamicPromptsToggle = () => {
|
|||||||
return (
|
return (
|
||||||
<DynamicPromptsTogglePopover>
|
<DynamicPromptsTogglePopover>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label="Enable Dynamic Prompts"
|
label={t('prompt.enableDynamicPrompts')}
|
||||||
isChecked={isEnabled}
|
isChecked={isEnabled}
|
||||||
onChange={handleToggleIsEnabled}
|
onChange={handleToggleIsEnabled}
|
||||||
/>
|
/>
|
||||||
</DynamicPromptsTogglePopover>
|
</DynamicPromptsTogglePopover>
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
maxPromptsChanged,
|
maxPromptsChanged,
|
||||||
maxPromptsReset,
|
maxPromptsReset,
|
||||||
} from '../store/dynamicPromptsSlice';
|
} from '../store/dynamicPromptsSlice';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -31,6 +32,7 @@ const ParamDynamicPromptsMaxPrompts = () => {
|
|||||||
const { maxPrompts, min, sliderMax, inputMax, isDisabled } =
|
const { maxPrompts, min, sliderMax, inputMax, isDisabled } =
|
||||||
useAppSelector(selector);
|
useAppSelector(selector);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -45,7 +47,7 @@ const ParamDynamicPromptsMaxPrompts = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Max Prompts"
|
label={t('prompt.maxPrompts')}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
min={min}
|
min={min}
|
||||||
max={sliderMax}
|
max={sliderMax}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { FaCode } from 'react-icons/fa';
|
import { FaCode } from 'react-icons/fa';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
@ -8,11 +9,12 @@ type Props = {
|
|||||||
|
|
||||||
const AddEmbeddingButton = (props: Props) => {
|
const AddEmbeddingButton = (props: Props) => {
|
||||||
const { onClick } = props;
|
const { onClick } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
aria-label="Add Embedding"
|
aria-label={t('embedding.addEmbedding')}
|
||||||
tooltip="Add Embedding"
|
tooltip={t('embedding.addEmbedding')}
|
||||||
icon={<FaCode />}
|
icon={<FaCode />}
|
||||||
sx={{
|
sx={{
|
||||||
p: 2,
|
p: 2,
|
||||||
|
@ -16,6 +16,7 @@ import { forEach } from 'lodash-es';
|
|||||||
import { PropsWithChildren, memo, useCallback, useMemo, useRef } from 'react';
|
import { PropsWithChildren, memo, useCallback, useMemo, useRef } from 'react';
|
||||||
import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
|
import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
|
||||||
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
|
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = PropsWithChildren & {
|
type Props = PropsWithChildren & {
|
||||||
onSelect: (v: string) => void;
|
onSelect: (v: string) => void;
|
||||||
@ -27,6 +28,7 @@ const ParamEmbeddingPopover = (props: Props) => {
|
|||||||
const { onSelect, isOpen, onClose, children } = props;
|
const { onSelect, isOpen, onClose, children } = props;
|
||||||
const { data: embeddingQueryData } = useGetTextualInversionModelsQuery();
|
const { data: embeddingQueryData } = useGetTextualInversionModelsQuery();
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const currentMainModel = useAppSelector(
|
const currentMainModel = useAppSelector(
|
||||||
(state: RootState) => state.generation.model
|
(state: RootState) => state.generation.model
|
||||||
@ -52,7 +54,7 @@ const ParamEmbeddingPopover = (props: Props) => {
|
|||||||
group: MODEL_TYPE_MAP[embedding.base_model],
|
group: MODEL_TYPE_MAP[embedding.base_model],
|
||||||
disabled,
|
disabled,
|
||||||
tooltip: disabled
|
tooltip: disabled
|
||||||
? `Incompatible base model: ${embedding.base_model}`
|
? `${t('embedding.incompatibleModel')} ${embedding.base_model}`
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -63,7 +65,7 @@ const ParamEmbeddingPopover = (props: Props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return data.sort((a, b) => (a.disabled && !b.disabled ? 1 : -1));
|
return data.sort((a, b) => (a.disabled && !b.disabled ? 1 : -1));
|
||||||
}, [embeddingQueryData, currentMainModel?.base_model]);
|
}, [embeddingQueryData, currentMainModel?.base_model, t]);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(v: string | null) => {
|
(v: string | null) => {
|
||||||
@ -118,10 +120,10 @@ const ParamEmbeddingPopover = (props: Props) => {
|
|||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder="Add Embedding"
|
placeholder={t('embedding.addEmbedding')}
|
||||||
value={null}
|
value={null}
|
||||||
data={data}
|
data={data}
|
||||||
nothingFound="No matching Embeddings"
|
nothingFound={t('embedding.noMatchingEmbedding')}
|
||||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||||
disabled={data.length === 0}
|
disabled={data.length === 0}
|
||||||
onDropdownClose={onClose}
|
onDropdownClose={onClose}
|
||||||
|
@ -8,6 +8,7 @@ import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectI
|
|||||||
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import { memo, useCallback, useRef } from 'react';
|
import { memo, useCallback, useRef } from 'react';
|
||||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
@ -26,6 +27,7 @@ const selector = createSelector(
|
|||||||
|
|
||||||
const BoardAutoAddSelect = () => {
|
const BoardAutoAddSelect = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
const { autoAddBoardId, autoAssignBoardOnClick, isProcessing } =
|
const { autoAddBoardId, autoAssignBoardOnClick, isProcessing } =
|
||||||
useAppSelector(selector);
|
useAppSelector(selector);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
@ -63,13 +65,13 @@ const BoardAutoAddSelect = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
label="Auto-Add Board"
|
label={t('boards.autoAddBoard')}
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder="Select a Board"
|
placeholder={t('boards.selectBoard')}
|
||||||
value={autoAddBoardId}
|
value={autoAddBoardId}
|
||||||
data={boards}
|
data={boards}
|
||||||
nothingFound="No matching Boards"
|
nothingFound={t('boards.noMatching')}
|
||||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||||
disabled={!hasBoards || autoAssignBoardOnClick || isProcessing}
|
disabled={!hasBoards || autoAssignBoardOnClick || isProcessing}
|
||||||
filter={(value, item: SelectItem) =>
|
filter={(value, item: SelectItem) =>
|
||||||
|
@ -16,6 +16,7 @@ import { menuListMotionProps } from 'theme/components/menu';
|
|||||||
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
|
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
|
||||||
import NoBoardContextMenuItems from './NoBoardContextMenuItems';
|
import NoBoardContextMenuItems from './NoBoardContextMenuItems';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
board?: BoardDTO;
|
board?: BoardDTO;
|
||||||
@ -59,6 +60,8 @@ const BoardContextMenu = ({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIContextMenu<HTMLDivElement>
|
<IAIContextMenu<HTMLDivElement>
|
||||||
menuProps={{ size: 'sm', isLazy: true }}
|
menuProps={{ size: 'sm', isLazy: true }}
|
||||||
@ -78,7 +81,7 @@ const BoardContextMenu = ({
|
|||||||
isDisabled={isAutoAdd || isProcessing || autoAssignBoardOnClick}
|
isDisabled={isAutoAdd || isProcessing || autoAssignBoardOnClick}
|
||||||
onClick={handleSetAutoAdd}
|
onClick={handleSetAutoAdd}
|
||||||
>
|
>
|
||||||
Auto-add to this Board
|
{t('boards.menuItemAutoAdd')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{!board && <NoBoardContextMenuItems />}
|
{!board && <NoBoardContextMenuItems />}
|
||||||
{board && (
|
{board && (
|
||||||
|
@ -2,22 +2,22 @@ import IAIIconButton from 'common/components/IAIIconButton';
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { FaPlus } from 'react-icons/fa';
|
import { FaPlus } from 'react-icons/fa';
|
||||||
import { useCreateBoardMutation } from 'services/api/endpoints/boards';
|
import { useCreateBoardMutation } from 'services/api/endpoints/boards';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
const DEFAULT_BOARD_NAME = 'My Board';
|
|
||||||
|
|
||||||
const AddBoardButton = () => {
|
const AddBoardButton = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [createBoard, { isLoading }] = useCreateBoardMutation();
|
const [createBoard, { isLoading }] = useCreateBoardMutation();
|
||||||
|
const DEFAULT_BOARD_NAME = t('boards.myBoard');
|
||||||
const handleCreateBoard = useCallback(() => {
|
const handleCreateBoard = useCallback(() => {
|
||||||
createBoard(DEFAULT_BOARD_NAME);
|
createBoard(DEFAULT_BOARD_NAME);
|
||||||
}, [createBoard]);
|
}, [createBoard, DEFAULT_BOARD_NAME]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
icon={<FaPlus />}
|
icon={<FaPlus />}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
tooltip="Add Board"
|
tooltip={t('boards.addBoard')}
|
||||||
aria-label="Add Board"
|
aria-label={t('boards.addBoard')}
|
||||||
onClick={handleCreateBoard}
|
onClick={handleCreateBoard}
|
||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
@ -32,6 +33,7 @@ const BoardsSearch = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { boardSearchText } = useAppSelector(selector);
|
const { boardSearchText } = useAppSelector(selector);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleBoardSearch = useCallback(
|
const handleBoardSearch = useCallback(
|
||||||
(searchTerm: string) => {
|
(searchTerm: string) => {
|
||||||
@ -73,7 +75,7 @@ const BoardsSearch = () => {
|
|||||||
<InputGroup>
|
<InputGroup>
|
||||||
<Input
|
<Input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
placeholder="Search Boards..."
|
placeholder={t('boards.searchBoard')}
|
||||||
value={boardSearchText}
|
value={boardSearchText}
|
||||||
onKeyDown={handleKeydown}
|
onKeyDown={handleKeydown}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@ -84,7 +86,7 @@ const BoardsSearch = () => {
|
|||||||
onClick={clearBoardSearch}
|
onClick={clearBoardSearch}
|
||||||
size="xs"
|
size="xs"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
aria-label="Clear Search"
|
aria-label={t('boards.clearSearch')}
|
||||||
opacity={0.5}
|
opacity={0.5}
|
||||||
icon={<CloseIcon boxSize={2} />}
|
icon={<CloseIcon boxSize={2} />}
|
||||||
/>
|
/>
|
||||||
|
@ -132,8 +132,8 @@ const DeleteBoardModal = (props: Props) => {
|
|||||||
) : (
|
) : (
|
||||||
<ImageUsageMessage
|
<ImageUsageMessage
|
||||||
imageUsage={imageUsageSummary}
|
imageUsage={imageUsageSummary}
|
||||||
topMessage="This board contains images used in the following features:"
|
topMessage={t('boards.topMessage')}
|
||||||
bottomMessage="Deleting this board and its images will reset any features currently using them."
|
bottomMessage={t('boards.bottomMessage')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Text>Deleted boards cannot be restored.</Text>
|
<Text>Deleted boards cannot be restored.</Text>
|
||||||
|
@ -19,6 +19,7 @@ import { FaImage } from 'react-icons/fa';
|
|||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
import ImageMetadataViewer from '../ImageMetadataViewer/ImageMetadataViewer';
|
import ImageMetadataViewer from '../ImageMetadataViewer/ImageMetadataViewer';
|
||||||
import NextPrevImageButtons from '../NextPrevImageButtons';
|
import NextPrevImageButtons from '../NextPrevImageButtons';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const imagesSelector = createSelector(
|
export const imagesSelector = createSelector(
|
||||||
[stateSelector, selectLastSelectedImage],
|
[stateSelector, selectLastSelectedImage],
|
||||||
@ -117,6 +118,8 @@ const CurrentImagePreview = () => {
|
|||||||
|
|
||||||
const timeoutId = useRef(0);
|
const timeoutId = useRef(0);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleMouseOver = useCallback(() => {
|
const handleMouseOver = useCallback(() => {
|
||||||
setShouldShowNextPrevButtons(true);
|
setShouldShowNextPrevButtons(true);
|
||||||
window.clearTimeout(timeoutId.current);
|
window.clearTimeout(timeoutId.current);
|
||||||
@ -164,7 +167,7 @@ const CurrentImagePreview = () => {
|
|||||||
isUploadDisabled={true}
|
isUploadDisabled={true}
|
||||||
fitContainer
|
fitContainer
|
||||||
useThumbailFallback
|
useThumbailFallback
|
||||||
dropLabel="Set as Current Image"
|
dropLabel={t('gallery.setCurrentImage')}
|
||||||
noContentFallback={
|
noContentFallback={
|
||||||
<IAINoContentFallback icon={FaImage} label="No image selected" />
|
<IAINoContentFallback icon={FaImage} label="No image selected" />
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { MenuItem } from '@chakra-ui/react';
|
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 { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import {
|
||||||
imagesToChangeSelected,
|
imagesToChangeSelected,
|
||||||
@ -16,6 +18,7 @@ import {
|
|||||||
const MultipleSelectionMenuItems = () => {
|
const MultipleSelectionMenuItems = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const selection = useAppSelector((state) => state.gallery.selection);
|
const selection = useAppSelector((state) => state.gallery.selection);
|
||||||
|
const customStarUi = useStore($customStarUI);
|
||||||
|
|
||||||
const [starImages] = useStarImagesMutation();
|
const [starImages] = useStarImagesMutation();
|
||||||
const [unstarImages] = useUnstarImagesMutation();
|
const [unstarImages] = useUnstarImagesMutation();
|
||||||
@ -49,15 +52,18 @@ const MultipleSelectionMenuItems = () => {
|
|||||||
<>
|
<>
|
||||||
{areAllStarred && (
|
{areAllStarred && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<MdStarBorder />}
|
icon={customStarUi ? customStarUi.on.icon : <MdStarBorder />}
|
||||||
onClickCapture={handleUnstarSelection}
|
onClickCapture={handleUnstarSelection}
|
||||||
>
|
>
|
||||||
Unstar All
|
{customStarUi ? customStarUi.off.text : `Unstar All`}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{(areAllUnstarred || (!areAllStarred && !areAllUnstarred)) && (
|
{(areAllUnstarred || (!areAllStarred && !areAllUnstarred)) && (
|
||||||
<MenuItem icon={<MdStar />} onClickCapture={handleStarSelection}>
|
<MenuItem
|
||||||
Star All
|
icon={customStarUi ? customStarUi.on.icon : <MdStar />}
|
||||||
|
onClickCapture={handleStarSelection}
|
||||||
|
>
|
||||||
|
{customStarUi ? customStarUi.on.text : `Star All`}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
<MenuItem icon={<FaFolder />} onClickCapture={handleChangeBoard}>
|
<MenuItem icon={<FaFolder />} onClickCapture={handleChangeBoard}>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Flex, MenuItem, Spinner } from '@chakra-ui/react';
|
import { Flex, MenuItem, Spinner } from '@chakra-ui/react';
|
||||||
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppToaster } from 'app/components/Toaster';
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
|
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||||
import {
|
import {
|
||||||
@ -7,6 +9,7 @@ import {
|
|||||||
isModalOpenChanged,
|
isModalOpenChanged,
|
||||||
} from 'features/changeBoardModal/store/slice';
|
} from 'features/changeBoardModal/store/slice';
|
||||||
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
||||||
|
import { workflowLoadRequested } from 'features/nodes/store/actions';
|
||||||
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
||||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
@ -32,9 +35,9 @@ import {
|
|||||||
useUnstarImagesMutation,
|
useUnstarImagesMutation,
|
||||||
} from 'services/api/endpoints/images';
|
} from 'services/api/endpoints/images';
|
||||||
import { ImageDTO } from 'services/api/types';
|
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 { configSelector } from '../../../system/store/configSelectors';
|
||||||
|
import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions';
|
||||||
|
import { flushSync } from 'react-dom';
|
||||||
|
|
||||||
type SingleSelectionMenuItemsProps = {
|
type SingleSelectionMenuItemsProps = {
|
||||||
imageDTO: ImageDTO;
|
imageDTO: ImageDTO;
|
||||||
@ -50,6 +53,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
|
|
||||||
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
||||||
const { shouldFetchMetadataFromApi } = useAppSelector(configSelector);
|
const { shouldFetchMetadataFromApi } = useAppSelector(configSelector);
|
||||||
|
const customStarUi = useStore($customStarUI);
|
||||||
|
|
||||||
const { metadata, workflow, isLoading } = useGetImageMetadataFromFileQuery(
|
const { metadata, workflow, isLoading } = useGetImageMetadataFromFileQuery(
|
||||||
{ image: imageDTO, shouldFetchMetadataFromApi },
|
{ image: imageDTO, shouldFetchMetadataFromApi },
|
||||||
@ -112,8 +116,10 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
|
|
||||||
const handleSendToCanvas = useCallback(() => {
|
const handleSendToCanvas = useCallback(() => {
|
||||||
dispatch(sentImageToCanvas());
|
dispatch(sentImageToCanvas());
|
||||||
dispatch(setInitialCanvasImage(imageDTO));
|
flushSync(() => {
|
||||||
dispatch(setActiveTab('unifiedCanvas'));
|
dispatch(setActiveTab('unifiedCanvas'));
|
||||||
|
});
|
||||||
|
dispatch(setInitialCanvasImage(imageDTO));
|
||||||
|
|
||||||
toaster({
|
toaster({
|
||||||
title: t('toast.sentToUnifiedCanvas'),
|
title: t('toast.sentToUnifiedCanvas'),
|
||||||
@ -225,12 +231,18 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
Change Board
|
Change Board
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{imageDTO.starred ? (
|
{imageDTO.starred ? (
|
||||||
<MenuItem icon={<MdStar />} onClickCapture={handleUnstarImage}>
|
<MenuItem
|
||||||
Unstar Image
|
icon={customStarUi ? customStarUi.off.icon : <MdStar />}
|
||||||
|
onClickCapture={handleUnstarImage}
|
||||||
|
>
|
||||||
|
{customStarUi ? customStarUi.off.text : `Unstar Image`}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
) : (
|
) : (
|
||||||
<MenuItem icon={<MdStarBorder />} onClickCapture={handleStarImage}>
|
<MenuItem
|
||||||
Star Image
|
icon={customStarUi ? customStarUi.on.icon : <MdStarBorder />}
|
||||||
|
onClickCapture={handleStarImage}
|
||||||
|
>
|
||||||
|
{customStarUi ? customStarUi.on.text : `Star Image`}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Box, Flex } from '@chakra-ui/react';
|
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 { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import IAIFillSkeleton from 'common/components/IAIFillSkeleton';
|
import IAIFillSkeleton from 'common/components/IAIFillSkeleton';
|
||||||
@ -10,6 +12,7 @@ import {
|
|||||||
} from 'features/dnd/types';
|
} from 'features/dnd/types';
|
||||||
import { useMultiselect } from 'features/gallery/hooks/useMultiselect';
|
import { useMultiselect } from 'features/gallery/hooks/useMultiselect';
|
||||||
import { MouseEvent, memo, useCallback, useMemo, useState } from 'react';
|
import { MouseEvent, memo, useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaTrash } from 'react-icons/fa';
|
import { FaTrash } from 'react-icons/fa';
|
||||||
import { MdStar, MdStarBorder } from 'react-icons/md';
|
import { MdStar, MdStarBorder } from 'react-icons/md';
|
||||||
import {
|
import {
|
||||||
@ -28,10 +31,13 @@ const GalleryImage = (props: HoverableImageProps) => {
|
|||||||
const { imageName } = props;
|
const { imageName } = props;
|
||||||
const { currentData: imageDTO } = useGetImageDTOQuery(imageName);
|
const { currentData: imageDTO } = useGetImageDTOQuery(imageName);
|
||||||
const shift = useAppSelector((state) => state.hotkeys.shift);
|
const shift = useAppSelector((state) => state.hotkeys.shift);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { handleClick, isSelected, selection, selectionCount } =
|
const { handleClick, isSelected, selection, selectionCount } =
|
||||||
useMultiselect(imageDTO);
|
useMultiselect(imageDTO);
|
||||||
|
|
||||||
|
const customStarUi = useStore($customStarUI);
|
||||||
|
|
||||||
const handleDelete = useCallback(
|
const handleDelete = useCallback(
|
||||||
(e: MouseEvent<HTMLButtonElement>) => {
|
(e: MouseEvent<HTMLButtonElement>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -89,12 +95,22 @@ const GalleryImage = (props: HoverableImageProps) => {
|
|||||||
|
|
||||||
const starIcon = useMemo(() => {
|
const starIcon = useMemo(() => {
|
||||||
if (imageDTO?.starred) {
|
if (imageDTO?.starred) {
|
||||||
return <MdStar size="20" />;
|
return customStarUi ? customStarUi.on.icon : <MdStar size="20" />;
|
||||||
}
|
}
|
||||||
if (!imageDTO?.starred && isHovered) {
|
if (!imageDTO?.starred && isHovered) {
|
||||||
return <MdStarBorder size="20" />;
|
return customStarUi ? customStarUi.off.icon : <MdStarBorder size="20" />;
|
||||||
}
|
}
|
||||||
}, [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) {
|
if (!imageDTO) {
|
||||||
return <IAIFillSkeleton />;
|
return <IAIFillSkeleton />;
|
||||||
@ -129,14 +145,14 @@ const GalleryImage = (props: HoverableImageProps) => {
|
|||||||
<IAIDndImageIcon
|
<IAIDndImageIcon
|
||||||
onClick={toggleStarredState}
|
onClick={toggleStarredState}
|
||||||
icon={starIcon}
|
icon={starIcon}
|
||||||
tooltip={imageDTO.starred ? 'Unstar' : 'Star'}
|
tooltip={starTooltip}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isHovered && shift && (
|
{isHovered && shift && (
|
||||||
<IAIDndImageIcon
|
<IAIDndImageIcon
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
tooltip="Delete"
|
tooltip={t('gallery.deleteImage')}
|
||||||
styleOverrides={{
|
styleOverrides={{
|
||||||
bottom: 2,
|
bottom: 2,
|
||||||
top: 'auto',
|
top: 'auto',
|
||||||
|
@ -95,7 +95,7 @@ const GalleryImageGrid = () => {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IAINoContentFallback label="Loading..." icon={FaImage} />
|
<IAINoContentFallback label={t('gallery.loading')} icon={FaImage} />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -140,7 +140,7 @@ const GalleryImageGrid = () => {
|
|||||||
onClick={handleLoadMoreImages}
|
onClick={handleLoadMoreImages}
|
||||||
isDisabled={!areMoreAvailable}
|
isDisabled={!areMoreAvailable}
|
||||||
isLoading={isFetching}
|
isLoading={isFetching}
|
||||||
loadingText="Loading"
|
loadingText={t('gallery.loading')}
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
>
|
>
|
||||||
{`Load More (${currentData.ids.length} of ${currentViewTotal})`}
|
{`Load More (${currentData.ids.length} of ${currentViewTotal})`}
|
||||||
@ -153,7 +153,7 @@ const GalleryImageGrid = () => {
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ w: 'full', h: 'full' }}>
|
<Box sx={{ w: 'full', h: 'full' }}>
|
||||||
<IAINoContentFallback
|
<IAINoContentFallback
|
||||||
label="Unable to load Gallery"
|
label={t('gallery.unableToLoad')}
|
||||||
icon={FaExclamationCircle}
|
icon={FaExclamationCircle}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -3,6 +3,7 @@ import { isString } from 'lodash-es';
|
|||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { FaCopy, FaDownload } from 'react-icons/fa';
|
import { FaCopy, FaDownload } from 'react-icons/fa';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label: string;
|
label: string;
|
||||||
@ -33,6 +34,8 @@ const DataViewer = (props: Props) => {
|
|||||||
a.remove();
|
a.remove();
|
||||||
}, [dataString, label, fileName]);
|
}, [dataString, label, fileName]);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
layerStyle="second"
|
layerStyle="second"
|
||||||
@ -73,9 +76,9 @@ const DataViewer = (props: Props) => {
|
|||||||
</Box>
|
</Box>
|
||||||
<Flex sx={{ position: 'absolute', top: 0, insetInlineEnd: 0, p: 2 }}>
|
<Flex sx={{ position: 'absolute', top: 0, insetInlineEnd: 0, p: 2 }}>
|
||||||
{withDownload && (
|
{withDownload && (
|
||||||
<Tooltip label={`Download ${label} JSON`}>
|
<Tooltip label={`${t('gallery.download')} ${label} JSON`}>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={`Download ${label} JSON`}
|
aria-label={`${t('gallery.download')} ${label} JSON`}
|
||||||
icon={<FaDownload />}
|
icon={<FaDownload />}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
opacity={0.7}
|
opacity={0.7}
|
||||||
@ -84,9 +87,9 @@ const DataViewer = (props: Props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{withCopy && (
|
{withCopy && (
|
||||||
<Tooltip label={`Copy ${label} JSON`}>
|
<Tooltip label={`${t('gallery.copy')} ${label} JSON`}>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={`Copy ${label} JSON`}
|
aria-label={`${t('gallery.copy')} ${label} JSON`}
|
||||||
icon={<FaCopy />}
|
icon={<FaCopy />}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
opacity={0.7}
|
opacity={0.7}
|
||||||
|
@ -2,6 +2,7 @@ import { CoreMetadata } from 'features/nodes/types/types';
|
|||||||
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import ImageMetadataItem from './ImageMetadataItem';
|
import ImageMetadataItem from './ImageMetadataItem';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
metadata?: CoreMetadata;
|
metadata?: CoreMetadata;
|
||||||
@ -10,6 +11,8 @@ type Props = {
|
|||||||
const ImageMetadataActions = (props: Props) => {
|
const ImageMetadataActions = (props: Props) => {
|
||||||
const { metadata } = props;
|
const { metadata } = props;
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
recallPositivePrompt,
|
recallPositivePrompt,
|
||||||
recallNegativePrompt,
|
recallNegativePrompt,
|
||||||
@ -70,17 +73,20 @@ const ImageMetadataActions = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{metadata.created_by && (
|
{metadata.created_by && (
|
||||||
<ImageMetadataItem label="Created By" value={metadata.created_by} />
|
<ImageMetadataItem
|
||||||
|
label={t('metadata.createdBy')}
|
||||||
|
value={metadata.created_by}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{metadata.generation_mode && (
|
{metadata.generation_mode && (
|
||||||
<ImageMetadataItem
|
<ImageMetadataItem
|
||||||
label="Generation Mode"
|
label={t('metadata.generationMode')}
|
||||||
value={metadata.generation_mode}
|
value={metadata.generation_mode}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{metadata.positive_prompt && (
|
{metadata.positive_prompt && (
|
||||||
<ImageMetadataItem
|
<ImageMetadataItem
|
||||||
label="Positive Prompt"
|
label={t('metadata.positivePrompt')}
|
||||||
labelPosition="top"
|
labelPosition="top"
|
||||||
value={metadata.positive_prompt}
|
value={metadata.positive_prompt}
|
||||||
onClick={handleRecallPositivePrompt}
|
onClick={handleRecallPositivePrompt}
|
||||||
@ -88,7 +94,7 @@ const ImageMetadataActions = (props: Props) => {
|
|||||||
)}
|
)}
|
||||||
{metadata.negative_prompt && (
|
{metadata.negative_prompt && (
|
||||||
<ImageMetadataItem
|
<ImageMetadataItem
|
||||||
label="Negative Prompt"
|
label={t('metadata.NegativePrompt')}
|
||||||
labelPosition="top"
|
labelPosition="top"
|
||||||
value={metadata.negative_prompt}
|
value={metadata.negative_prompt}
|
||||||
onClick={handleRecallNegativePrompt}
|
onClick={handleRecallNegativePrompt}
|
||||||
@ -96,7 +102,7 @@ const ImageMetadataActions = (props: Props) => {
|
|||||||
)}
|
)}
|
||||||
{metadata.seed !== undefined && metadata.seed !== null && (
|
{metadata.seed !== undefined && metadata.seed !== null && (
|
||||||
<ImageMetadataItem
|
<ImageMetadataItem
|
||||||
label="Seed"
|
label={t('metadata.seed')}
|
||||||
value={metadata.seed}
|
value={metadata.seed}
|
||||||
onClick={handleRecallSeed}
|
onClick={handleRecallSeed}
|
||||||
/>
|
/>
|
||||||
@ -105,63 +111,63 @@ const ImageMetadataActions = (props: Props) => {
|
|||||||
metadata.model !== null &&
|
metadata.model !== null &&
|
||||||
metadata.model.model_name && (
|
metadata.model.model_name && (
|
||||||
<ImageMetadataItem
|
<ImageMetadataItem
|
||||||
label="Model"
|
label={t('metadata.model')}
|
||||||
value={metadata.model.model_name}
|
value={metadata.model.model_name}
|
||||||
onClick={handleRecallModel}
|
onClick={handleRecallModel}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{metadata.width && (
|
{metadata.width && (
|
||||||
<ImageMetadataItem
|
<ImageMetadataItem
|
||||||
label="Width"
|
label={t('metadata.width')}
|
||||||
value={metadata.width}
|
value={metadata.width}
|
||||||
onClick={handleRecallWidth}
|
onClick={handleRecallWidth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{metadata.height && (
|
{metadata.height && (
|
||||||
<ImageMetadataItem
|
<ImageMetadataItem
|
||||||
label="Height"
|
label={t('metadata.height')}
|
||||||
value={metadata.height}
|
value={metadata.height}
|
||||||
onClick={handleRecallHeight}
|
onClick={handleRecallHeight}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* {metadata.threshold !== undefined && (
|
{/* {metadata.threshold !== undefined && (
|
||||||
<MetadataItem
|
<MetadataItem
|
||||||
label="Noise Threshold"
|
label={t('metadata.threshold')}
|
||||||
value={metadata.threshold}
|
value={metadata.threshold}
|
||||||
onClick={() => dispatch(setThreshold(Number(metadata.threshold)))}
|
onClick={() => dispatch(setThreshold(Number(metadata.threshold)))}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{metadata.perlin !== undefined && (
|
{metadata.perlin !== undefined && (
|
||||||
<MetadataItem
|
<MetadataItem
|
||||||
label="Perlin Noise"
|
label={t('metadata.perlin')}
|
||||||
value={metadata.perlin}
|
value={metadata.perlin}
|
||||||
onClick={() => dispatch(setPerlin(Number(metadata.perlin)))}
|
onClick={() => dispatch(setPerlin(Number(metadata.perlin)))}
|
||||||
/>
|
/>
|
||||||
)} */}
|
)} */}
|
||||||
{metadata.scheduler && (
|
{metadata.scheduler && (
|
||||||
<ImageMetadataItem
|
<ImageMetadataItem
|
||||||
label="Scheduler"
|
label={t('metadata.scheduler')}
|
||||||
value={metadata.scheduler}
|
value={metadata.scheduler}
|
||||||
onClick={handleRecallScheduler}
|
onClick={handleRecallScheduler}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{metadata.steps && (
|
{metadata.steps && (
|
||||||
<ImageMetadataItem
|
<ImageMetadataItem
|
||||||
label="Steps"
|
label={t('metadata.steps')}
|
||||||
value={metadata.steps}
|
value={metadata.steps}
|
||||||
onClick={handleRecallSteps}
|
onClick={handleRecallSteps}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{metadata.cfg_scale !== undefined && metadata.cfg_scale !== null && (
|
{metadata.cfg_scale !== undefined && metadata.cfg_scale !== null && (
|
||||||
<ImageMetadataItem
|
<ImageMetadataItem
|
||||||
label="CFG scale"
|
label={t('metadata.cfgScale')}
|
||||||
value={metadata.cfg_scale}
|
value={metadata.cfg_scale}
|
||||||
onClick={handleRecallCfgScale}
|
onClick={handleRecallCfgScale}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* {metadata.variations && metadata.variations.length > 0 && (
|
{/* {metadata.variations && metadata.variations.length > 0 && (
|
||||||
<MetadataItem
|
<MetadataItem
|
||||||
label="Seed-weight pairs"
|
label="{t('metadata.variations')}
|
||||||
value={seedWeightsToString(metadata.variations)}
|
value={seedWeightsToString(metadata.variations)}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -172,14 +178,14 @@ const ImageMetadataActions = (props: Props) => {
|
|||||||
)}
|
)}
|
||||||
{metadata.seamless && (
|
{metadata.seamless && (
|
||||||
<MetadataItem
|
<MetadataItem
|
||||||
label="Seamless"
|
label={t('metadata.seamless')}
|
||||||
value={metadata.seamless}
|
value={metadata.seamless}
|
||||||
onClick={() => dispatch(setSeamless(metadata.seamless))}
|
onClick={() => dispatch(setSeamless(metadata.seamless))}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{metadata.hires_fix && (
|
{metadata.hires_fix && (
|
||||||
<MetadataItem
|
<MetadataItem
|
||||||
label="High Resolution Optimization"
|
label={t('metadata.hiresFix')}
|
||||||
value={metadata.hires_fix}
|
value={metadata.hires_fix}
|
||||||
onClick={() => dispatch(setHiresFix(metadata.hires_fix))}
|
onClick={() => dispatch(setHiresFix(metadata.hires_fix))}
|
||||||
/>
|
/>
|
||||||
@ -187,7 +193,7 @@ const ImageMetadataActions = (props: Props) => {
|
|||||||
|
|
||||||
{/* {init_image_path && (
|
{/* {init_image_path && (
|
||||||
<MetadataItem
|
<MetadataItem
|
||||||
label="Initial image"
|
label={t('metadata.initImage')}
|
||||||
value={init_image_path}
|
value={init_image_path}
|
||||||
isLink
|
isLink
|
||||||
onClick={() => dispatch(setInitialImage(init_image_path))}
|
onClick={() => dispatch(setInitialImage(init_image_path))}
|
||||||
@ -195,14 +201,14 @@ const ImageMetadataActions = (props: Props) => {
|
|||||||
)} */}
|
)} */}
|
||||||
{metadata.strength && (
|
{metadata.strength && (
|
||||||
<ImageMetadataItem
|
<ImageMetadataItem
|
||||||
label="Image to image strength"
|
label={t('metadata.strength')}
|
||||||
value={metadata.strength}
|
value={metadata.strength}
|
||||||
onClick={handleRecallStrength}
|
onClick={handleRecallStrength}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* {metadata.fit && (
|
{/* {metadata.fit && (
|
||||||
<MetadataItem
|
<MetadataItem
|
||||||
label="Image to image fit"
|
label={t('metadata.fit')}
|
||||||
value={metadata.fit}
|
value={metadata.fit}
|
||||||
onClick={() => dispatch(setShouldFitToWidthHeight(metadata.fit))}
|
onClick={() => dispatch(setShouldFitToWidthHeight(metadata.fit))}
|
||||||
/>
|
/>
|
||||||
|
@ -17,6 +17,7 @@ import DataViewer from './DataViewer';
|
|||||||
import ImageMetadataActions from './ImageMetadataActions';
|
import ImageMetadataActions from './ImageMetadataActions';
|
||||||
import { useAppSelector } from '../../../../app/store/storeHooks';
|
import { useAppSelector } from '../../../../app/store/storeHooks';
|
||||||
import { configSelector } from '../../../system/store/configSelectors';
|
import { configSelector } from '../../../system/store/configSelectors';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type ImageMetadataViewerProps = {
|
type ImageMetadataViewerProps = {
|
||||||
image: ImageDTO;
|
image: ImageDTO;
|
||||||
@ -28,6 +29,7 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
|
|||||||
// useHotkeys('esc', () => {
|
// useHotkeys('esc', () => {
|
||||||
// dispatch(setShouldShowImageDetails(false));
|
// dispatch(setShouldShowImageDetails(false));
|
||||||
// });
|
// });
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { shouldFetchMetadataFromApi } = useAppSelector(configSelector);
|
const { shouldFetchMetadataFromApi } = useAppSelector(configSelector);
|
||||||
|
|
||||||
@ -70,31 +72,31 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
|
|||||||
sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }}
|
sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }}
|
||||||
>
|
>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab>Metadata</Tab>
|
<Tab>{t('metadata.metadata')}</Tab>
|
||||||
<Tab>Image Details</Tab>
|
<Tab>{t('metadata.imageDetails')}</Tab>
|
||||||
<Tab>Workflow</Tab>
|
<Tab>{t('metadata.workflow')}</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
<TabPanels>
|
<TabPanels>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
{metadata ? (
|
{metadata ? (
|
||||||
<DataViewer data={metadata} label="Metadata" />
|
<DataViewer data={metadata} label={t('metadata.metadata')} />
|
||||||
) : (
|
) : (
|
||||||
<IAINoContentFallback label="No metadata found" />
|
<IAINoContentFallback label={t('metadata.noMetaData')} />
|
||||||
)}
|
)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
{image ? (
|
{image ? (
|
||||||
<DataViewer data={image} label="Image Details" />
|
<DataViewer data={image} label={t('metadata.imageDetails')} />
|
||||||
) : (
|
) : (
|
||||||
<IAINoContentFallback label="No image details found" />
|
<IAINoContentFallback label={t('metadata.noImageDetails')} />
|
||||||
)}
|
)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
{workflow ? (
|
{workflow ? (
|
||||||
<DataViewer data={workflow} label="Workflow" />
|
<DataViewer data={workflow} label={t('metadata.workflow')} />
|
||||||
) : (
|
) : (
|
||||||
<IAINoContentFallback label="No workflow found" />
|
<IAINoContentFallback label={t('metadata.noWorkFlow')} />
|
||||||
)}
|
)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
|
@ -12,9 +12,11 @@ import TopCenterPanel from './flow/panels/TopCenterPanel/TopCenterPanel';
|
|||||||
import TopRightPanel from './flow/panels/TopRightPanel/TopRightPanel';
|
import TopRightPanel from './flow/panels/TopRightPanel/TopRightPanel';
|
||||||
import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel';
|
import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel';
|
||||||
import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel';
|
import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const NodeEditor = () => {
|
const NodeEditor = () => {
|
||||||
const isReady = useAppSelector((state) => state.nodes.isReady);
|
const isReady = useAppSelector((state) => state.nodes.isReady);
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
layerStyle="first"
|
layerStyle="first"
|
||||||
@ -82,7 +84,7 @@ const NodeEditor = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IAINoContentFallback
|
<IAINoContentFallback
|
||||||
label="Loading Nodes..."
|
label={t('nodes.loadingNodes')}
|
||||||
icon={MdDeviceHub}
|
icon={MdDeviceHub}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -24,6 +24,7 @@ import { HotkeyCallback } from 'react-hotkeys-hook/dist/types';
|
|||||||
import 'reactflow/dist/style.css';
|
import 'reactflow/dist/style.css';
|
||||||
import { AnyInvocationType } from 'services/events/types';
|
import { AnyInvocationType } from 'services/events/types';
|
||||||
import { AddNodePopoverSelectItem } from './AddNodePopoverSelectItem';
|
import { AddNodePopoverSelectItem } from './AddNodePopoverSelectItem';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type NodeTemplate = {
|
type NodeTemplate = {
|
||||||
label: string;
|
label: string;
|
||||||
@ -48,6 +49,12 @@ const filter = (value: string, item: NodeTemplate) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AddNodePopover = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const buildInvocation = useBuildNodeData();
|
||||||
|
const toaster = useAppToaster();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
({ nodes }) => {
|
({ nodes }) => {
|
||||||
@ -61,30 +68,26 @@ const selector = createSelector(
|
|||||||
});
|
});
|
||||||
|
|
||||||
data.push({
|
data.push({
|
||||||
label: 'Progress Image',
|
label: t('nodes.currentImage'),
|
||||||
value: 'current_image',
|
value: 'current_image',
|
||||||
description: 'Displays the current image in the Node Editor',
|
description: t('nodes.currentImageDescription'),
|
||||||
tags: ['progress'],
|
tags: ['progress'],
|
||||||
});
|
});
|
||||||
|
|
||||||
data.push({
|
data.push({
|
||||||
label: 'Notes',
|
label: t('nodes.notes'),
|
||||||
value: 'notes',
|
value: 'notes',
|
||||||
description: 'Add notes about your workflow',
|
description: t('nodes.notesDescription'),
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
});
|
});
|
||||||
|
|
||||||
data.sort((a, b) => a.label.localeCompare(b.label));
|
data.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
|
|
||||||
return { data };
|
return { data, t };
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
const AddNodePopover = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const buildInvocation = useBuildNodeData();
|
|
||||||
const toaster = useAppToaster();
|
|
||||||
const { data } = useAppSelector(selector);
|
const { data } = useAppSelector(selector);
|
||||||
const isOpen = useAppSelector((state) => state.nodes.isAddNodePopoverOpen);
|
const isOpen = useAppSelector((state) => state.nodes.isAddNodePopoverOpen);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
@ -92,18 +95,20 @@ const AddNodePopover = () => {
|
|||||||
const addNode = useCallback(
|
const addNode = useCallback(
|
||||||
(nodeType: AnyInvocationType) => {
|
(nodeType: AnyInvocationType) => {
|
||||||
const invocation = buildInvocation(nodeType);
|
const invocation = buildInvocation(nodeType);
|
||||||
|
|
||||||
if (!invocation) {
|
if (!invocation) {
|
||||||
|
const errorMessage = t('nodes.unknownInvocation', {
|
||||||
|
nodeType: nodeType,
|
||||||
|
});
|
||||||
toaster({
|
toaster({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
title: `Unknown Invocation type ${nodeType}`,
|
title: errorMessage,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(nodeAdded(invocation));
|
dispatch(nodeAdded(invocation));
|
||||||
},
|
},
|
||||||
[dispatch, buildInvocation, toaster]
|
[dispatch, buildInvocation, toaster, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
@ -179,11 +184,11 @@ const AddNodePopover = () => {
|
|||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
selectOnBlur={false}
|
selectOnBlur={false}
|
||||||
placeholder="Search for nodes"
|
placeholder={t('nodes.nodeSearch')}
|
||||||
value={null}
|
value={null}
|
||||||
data={data}
|
data={data}
|
||||||
maxDropdownHeight={400}
|
maxDropdownHeight={400}
|
||||||
nothingFound="No matching nodes"
|
nothingFound={t('nodes.noMatchingNodes')}
|
||||||
itemComponent={AddNodePopoverSelectItem}
|
itemComponent={AddNodePopoverSelectItem}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
@ -22,6 +22,7 @@ import { memo, useMemo } from 'react';
|
|||||||
import { FaInfoCircle } from 'react-icons/fa';
|
import { FaInfoCircle } from 'react-icons/fa';
|
||||||
import NotesTextarea from './NotesTextarea';
|
import NotesTextarea from './NotesTextarea';
|
||||||
import { useDoNodeVersionsMatch } from 'features/nodes/hooks/useDoNodeVersionsMatch';
|
import { useDoNodeVersionsMatch } from 'features/nodes/hooks/useDoNodeVersionsMatch';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@ -32,6 +33,7 @@ const InvocationNodeNotes = ({ nodeId }: Props) => {
|
|||||||
const label = useNodeLabel(nodeId);
|
const label = useNodeLabel(nodeId);
|
||||||
const title = useNodeTemplateTitle(nodeId);
|
const title = useNodeTemplateTitle(nodeId);
|
||||||
const doVersionsMatch = useDoNodeVersionsMatch(nodeId);
|
const doVersionsMatch = useDoNodeVersionsMatch(nodeId);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -65,7 +67,7 @@ const InvocationNodeNotes = ({ nodeId }: Props) => {
|
|||||||
<Modal isOpen={isOpen} onClose={onClose} isCentered>
|
<Modal isOpen={isOpen} onClose={onClose} isCentered>
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
<ModalHeader>{label || title || 'Unknown Node'}</ModalHeader>
|
<ModalHeader>{label || title || t('nodes.unknownNode')}</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<NotesTextarea nodeId={nodeId} />
|
<NotesTextarea nodeId={nodeId} />
|
||||||
@ -82,6 +84,7 @@ export default memo(InvocationNodeNotes);
|
|||||||
const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
||||||
const data = useNodeData(nodeId);
|
const data = useNodeData(nodeId);
|
||||||
const nodeTemplate = useNodeTemplate(nodeId);
|
const nodeTemplate = useNodeTemplate(nodeId);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const title = useMemo(() => {
|
const title = useMemo(() => {
|
||||||
if (data?.label && nodeTemplate?.title) {
|
if (data?.label && nodeTemplate?.title) {
|
||||||
@ -96,8 +99,8 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
|||||||
return nodeTemplate.title;
|
return nodeTemplate.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Unknown Node';
|
return t('nodes.unknownNode');
|
||||||
}, [data, nodeTemplate]);
|
}, [data, nodeTemplate, t]);
|
||||||
|
|
||||||
const versionComponent = useMemo(() => {
|
const versionComponent = useMemo(() => {
|
||||||
if (!isInvocationNodeData(data) || !nodeTemplate) {
|
if (!isInvocationNodeData(data) || !nodeTemplate) {
|
||||||
@ -107,7 +110,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
|||||||
if (!data.version) {
|
if (!data.version) {
|
||||||
return (
|
return (
|
||||||
<Text as="span" sx={{ color: 'error.500' }}>
|
<Text as="span" sx={{ color: 'error.500' }}>
|
||||||
Version unknown
|
{t('nodes.versionUnknown')}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -115,7 +118,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
|||||||
if (!nodeTemplate.version) {
|
if (!nodeTemplate.version) {
|
||||||
return (
|
return (
|
||||||
<Text as="span" sx={{ color: 'error.500' }}>
|
<Text as="span" sx={{ color: 'error.500' }}>
|
||||||
Version {data.version} (unknown template)
|
{t('nodes.version')} {data.version} ({t('nodes.unknownTemplate')})
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -123,7 +126,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
|||||||
if (compare(data.version, nodeTemplate.version, '<')) {
|
if (compare(data.version, nodeTemplate.version, '<')) {
|
||||||
return (
|
return (
|
||||||
<Text as="span" sx={{ color: 'error.500' }}>
|
<Text as="span" sx={{ color: 'error.500' }}>
|
||||||
Version {data.version} (update node)
|
{t('nodes.version')} {data.version} ({t('nodes.updateNode')})
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -131,16 +134,20 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
|||||||
if (compare(data.version, nodeTemplate.version, '>')) {
|
if (compare(data.version, nodeTemplate.version, '>')) {
|
||||||
return (
|
return (
|
||||||
<Text as="span" sx={{ color: 'error.500' }}>
|
<Text as="span" sx={{ color: 'error.500' }}>
|
||||||
Version {data.version} (update app)
|
{t('nodes.version')} {data.version} ({t('nodes.updateApp')})
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Text as="span">Version {data.version}</Text>;
|
return (
|
||||||
}, [data, nodeTemplate]);
|
<Text as="span">
|
||||||
|
{t('nodes.version')} {data.version}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}, [data, nodeTemplate, t]);
|
||||||
|
|
||||||
if (!isInvocationNodeData(data)) {
|
if (!isInvocationNodeData(data)) {
|
||||||
return <Text sx={{ fontWeight: 600 }}>Unknown Node</Text>;
|
return <Text sx={{ fontWeight: 600 }}>{t('nodes.unknownNode')}</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -14,6 +14,7 @@ import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
|||||||
import { NodeExecutionState, NodeStatus } from 'features/nodes/types/types';
|
import { NodeExecutionState, NodeStatus } from 'features/nodes/types/types';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { FaCheck, FaEllipsisH, FaExclamation } from 'react-icons/fa';
|
import { FaCheck, FaEllipsisH, FaExclamation } from 'react-icons/fa';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@ -72,10 +73,10 @@ type TooltipLabelProps = {
|
|||||||
|
|
||||||
const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => {
|
const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => {
|
||||||
const { status, progress, progressImage } = nodeExecutionState;
|
const { status, progress, progressImage } = nodeExecutionState;
|
||||||
|
const { t } = useTranslation();
|
||||||
if (status === NodeStatus.PENDING) {
|
if (status === NodeStatus.PENDING) {
|
||||||
return <Text>Pending</Text>;
|
return <Text>Pending</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === NodeStatus.IN_PROGRESS) {
|
if (status === NodeStatus.IN_PROGRESS) {
|
||||||
if (progressImage) {
|
if (progressImage) {
|
||||||
return (
|
return (
|
||||||
@ -97,18 +98,22 @@ const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (progress !== null) {
|
if (progress !== null) {
|
||||||
return <Text>In Progress ({Math.round(progress * 100)}%)</Text>;
|
return (
|
||||||
|
<Text>
|
||||||
|
{t('nodes.executionStateInProgress')} ({Math.round(progress * 100)}%)
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Text>In Progress</Text>;
|
return <Text>{t('nodes.executionStateInProgress')}</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === NodeStatus.COMPLETED) {
|
if (status === NodeStatus.COMPLETED) {
|
||||||
return <Text>Completed</Text>;
|
return <Text>{t('nodes.executionStateCompleted')}</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === NodeStatus.FAILED) {
|
if (status === NodeStatus.FAILED) {
|
||||||
return <Text>nodeExecutionState.error</Text>;
|
return <Text>{t('nodes.executionStateError')}</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -5,10 +5,12 @@ import { useNodeData } from 'features/nodes/hooks/useNodeData';
|
|||||||
import { nodeNotesChanged } from 'features/nodes/store/nodesSlice';
|
import { nodeNotesChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { isInvocationNodeData } from 'features/nodes/types/types';
|
import { isInvocationNodeData } from 'features/nodes/types/types';
|
||||||
import { ChangeEvent, memo, useCallback } from 'react';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
|
const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const data = useNodeData(nodeId);
|
const data = useNodeData(nodeId);
|
||||||
|
const { t } = useTranslation();
|
||||||
const handleNotesChanged = useCallback(
|
const handleNotesChanged = useCallback(
|
||||||
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
dispatch(nodeNotesChanged({ nodeId, notes: e.target.value }));
|
dispatch(nodeNotesChanged({ nodeId, notes: e.target.value }));
|
||||||
@ -20,7 +22,7 @@ const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>Notes</FormLabel>
|
<FormLabel>{t('nodes.notes')}</FormLabel>
|
||||||
<IAITextarea
|
<IAITextarea
|
||||||
value={data?.notes}
|
value={data?.notes}
|
||||||
onChange={handleNotesChanged}
|
onChange={handleNotesChanged}
|
||||||
|
@ -14,6 +14,7 @@ import { fieldLabelChanged } from 'features/nodes/store/nodesSlice';
|
|||||||
import { MouseEvent, memo, useCallback, useEffect, useState } from 'react';
|
import { MouseEvent, memo, useCallback, useEffect, useState } from 'react';
|
||||||
import FieldTooltipContent from './FieldTooltipContent';
|
import FieldTooltipContent from './FieldTooltipContent';
|
||||||
import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
|
import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@ -33,10 +34,11 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
|
|||||||
} = props;
|
} = props;
|
||||||
const label = useFieldLabel(nodeId, fieldName);
|
const label = useFieldLabel(nodeId, fieldName);
|
||||||
const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind);
|
const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [localTitle, setLocalTitle] = useState(
|
const [localTitle, setLocalTitle] = useState(
|
||||||
label || fieldTemplateTitle || 'Unknown Field'
|
label || fieldTemplateTitle || t('nodes.unknownFeild')
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
@ -44,10 +46,10 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
|
|||||||
if (newTitle && (newTitle === label || newTitle === fieldTemplateTitle)) {
|
if (newTitle && (newTitle === label || newTitle === fieldTemplateTitle)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLocalTitle(newTitle || fieldTemplateTitle || 'Unknown Field');
|
setLocalTitle(newTitle || fieldTemplateTitle || t('nodes.unknownField'));
|
||||||
dispatch(fieldLabelChanged({ nodeId, fieldName, label: newTitle }));
|
dispatch(fieldLabelChanged({ nodeId, fieldName, label: newTitle }));
|
||||||
},
|
},
|
||||||
[label, fieldTemplateTitle, dispatch, nodeId, fieldName]
|
[label, fieldTemplateTitle, dispatch, nodeId, fieldName, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = useCallback((newTitle: string) => {
|
const handleChange = useCallback((newTitle: string) => {
|
||||||
@ -56,8 +58,8 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Another component may change the title; sync local title with global state
|
// Another component may change the title; sync local title with global state
|
||||||
setLocalTitle(label || fieldTemplateTitle || 'Unknown Field');
|
setLocalTitle(label || fieldTemplateTitle || t('nodes.unknownField'));
|
||||||
}, [label, fieldTemplateTitle]);
|
}, [label, fieldTemplateTitle, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
import { MouseEvent, ReactNode, memo, useCallback, useMemo } from 'react';
|
import { MouseEvent, ReactNode, memo, useCallback, useMemo } from 'react';
|
||||||
import { FaMinus, FaPlus } from 'react-icons/fa';
|
import { FaMinus, FaPlus } from 'react-icons/fa';
|
||||||
import { menuListMotionProps } from 'theme/components/menu';
|
import { menuListMotionProps } from 'theme/components/menu';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@ -30,6 +31,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
|||||||
const label = useFieldLabel(nodeId, fieldName);
|
const label = useFieldLabel(nodeId, fieldName);
|
||||||
const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind);
|
const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind);
|
||||||
const input = useFieldInputKind(nodeId, fieldName);
|
const input = useFieldInputKind(nodeId, fieldName);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const skipEvent = useCallback((e: MouseEvent<HTMLDivElement>) => {
|
const skipEvent = useCallback((e: MouseEvent<HTMLDivElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -119,7 +121,9 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
|||||||
motionProps={menuListMotionProps}
|
motionProps={menuListMotionProps}
|
||||||
onContextMenu={skipEvent}
|
onContextMenu={skipEvent}
|
||||||
>
|
>
|
||||||
<MenuGroup title={label || fieldTemplateTitle || 'Unknown Field'}>
|
<MenuGroup
|
||||||
|
title={label || fieldTemplateTitle || t('nodes.unknownField')}
|
||||||
|
>
|
||||||
{menuItems}
|
{menuItems}
|
||||||
</MenuGroup>
|
</MenuGroup>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
} from 'features/nodes/types/types';
|
} from 'features/nodes/types/types';
|
||||||
import { startCase } from 'lodash-es';
|
import { startCase } from 'lodash-es';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@ -19,6 +20,7 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => {
|
|||||||
const field = useFieldData(nodeId, fieldName);
|
const field = useFieldData(nodeId, fieldName);
|
||||||
const fieldTemplate = useFieldTemplate(nodeId, fieldName, kind);
|
const fieldTemplate = useFieldTemplate(nodeId, fieldName, kind);
|
||||||
const isInputTemplate = isInputFieldTemplate(fieldTemplate);
|
const isInputTemplate = isInputFieldTemplate(fieldTemplate);
|
||||||
|
const { t } = useTranslation();
|
||||||
const fieldTitle = useMemo(() => {
|
const fieldTitle = useMemo(() => {
|
||||||
if (isInputFieldValue(field)) {
|
if (isInputFieldValue(field)) {
|
||||||
if (field.label && fieldTemplate?.title) {
|
if (field.label && fieldTemplate?.title) {
|
||||||
@ -33,11 +35,11 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => {
|
|||||||
return fieldTemplate.title;
|
return fieldTemplate.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Unknown Field';
|
return t('nodes.unknownField');
|
||||||
} else {
|
} else {
|
||||||
return fieldTemplate?.title || 'Unknown Field';
|
return fieldTemplate?.title || t('nodes.unknownField');
|
||||||
}
|
}
|
||||||
}, [field, fieldTemplate]);
|
}, [field, fieldTemplate, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex sx={{ flexDir: 'column' }}>
|
<Flex sx={{ flexDir: 'column' }}>
|
||||||
|
@ -17,6 +17,7 @@ import { FaInfoCircle, FaTrash } from 'react-icons/fa';
|
|||||||
import EditableFieldTitle from './EditableFieldTitle';
|
import EditableFieldTitle from './EditableFieldTitle';
|
||||||
import FieldTooltipContent from './FieldTooltipContent';
|
import FieldTooltipContent from './FieldTooltipContent';
|
||||||
import InputFieldRenderer from './InputFieldRenderer';
|
import InputFieldRenderer from './InputFieldRenderer';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@ -27,7 +28,7 @@ const LinearViewField = ({ nodeId, fieldName }: Props) => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { isMouseOverNode, handleMouseOut, handleMouseOver } =
|
const { isMouseOverNode, handleMouseOut, handleMouseOver } =
|
||||||
useMouseOverNode(nodeId);
|
useMouseOverNode(nodeId);
|
||||||
|
const { t } = useTranslation();
|
||||||
const handleRemoveField = useCallback(() => {
|
const handleRemoveField = useCallback(() => {
|
||||||
dispatch(workflowExposedFieldRemoved({ nodeId, fieldName }));
|
dispatch(workflowExposedFieldRemoved({ nodeId, fieldName }));
|
||||||
}, [dispatch, fieldName, nodeId]);
|
}, [dispatch, fieldName, nodeId]);
|
||||||
@ -75,8 +76,8 @@ const LinearViewField = ({ nodeId, fieldName }: Props) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label="Remove from Linear View"
|
aria-label={t('nodes.removeLinearView')}
|
||||||
tooltip="Remove from Linear View"
|
tooltip={t('nodes.removeLinearView')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleRemoveField}
|
onClick={handleRemoveField}
|
||||||
|
@ -35,7 +35,11 @@ const EnumInputFieldComponent = (
|
|||||||
value={field.value}
|
value={field.value}
|
||||||
>
|
>
|
||||||
{fieldTemplate.options.map((option) => (
|
{fieldTemplate.options.map((option) => (
|
||||||
<option key={option}>{option}</option>
|
<option key={option} value={option}>
|
||||||
|
{fieldTemplate.ui_choice_labels
|
||||||
|
? fieldTemplate.ui_choice_labels[option]
|
||||||
|
: option}
|
||||||
|
</option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
|
@ -14,6 +14,7 @@ import { modelIdToLoRAModelParam } from 'features/parameters/util/modelIdToLoRAM
|
|||||||
import { forEach } from 'lodash-es';
|
import { forEach } from 'lodash-es';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useGetLoRAModelsQuery } from 'services/api/endpoints/models';
|
import { useGetLoRAModelsQuery } from 'services/api/endpoints/models';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const LoRAModelInputFieldComponent = (
|
const LoRAModelInputFieldComponent = (
|
||||||
props: FieldComponentProps<
|
props: FieldComponentProps<
|
||||||
@ -25,6 +26,7 @@ const LoRAModelInputFieldComponent = (
|
|||||||
const lora = field.value;
|
const lora = field.value;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { data: loraModels } = useGetLoRAModelsQuery();
|
const { data: loraModels } = useGetLoRAModelsQuery();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const data = useMemo(() => {
|
const data = useMemo(() => {
|
||||||
if (!loraModels) {
|
if (!loraModels) {
|
||||||
@ -92,9 +94,11 @@ const LoRAModelInputFieldComponent = (
|
|||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
className="nowheel nodrag"
|
className="nowheel nodrag"
|
||||||
value={selectedLoRAModel?.id ?? null}
|
value={selectedLoRAModel?.id ?? null}
|
||||||
placeholder={data.length > 0 ? 'Select a LoRA' : 'No LoRAs available'}
|
placeholder={
|
||||||
|
data.length > 0 ? t('models.selectLoRA') : t('models.noLoRAsAvailable')
|
||||||
|
}
|
||||||
data={data}
|
data={data}
|
||||||
nothingFound="No matching LoRAs"
|
nothingFound={t('models.noMatchingLoRAs')}
|
||||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||||
disabled={data.length === 0}
|
disabled={data.length === 0}
|
||||||
filter={(value, item: SelectItem) =>
|
filter={(value, item: SelectItem) =>
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
useGetMainModelsQuery,
|
useGetMainModelsQuery,
|
||||||
useGetOnnxModelsQuery,
|
useGetOnnxModelsQuery,
|
||||||
} from 'services/api/endpoints/models';
|
} from 'services/api/endpoints/models';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const MainModelInputFieldComponent = (
|
const MainModelInputFieldComponent = (
|
||||||
props: FieldComponentProps<
|
props: FieldComponentProps<
|
||||||
@ -29,7 +30,7 @@ const MainModelInputFieldComponent = (
|
|||||||
const { nodeId, field } = props;
|
const { nodeId, field } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled;
|
const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled;
|
||||||
|
const { t } = useTranslation();
|
||||||
const { data: onnxModels, isLoading: isLoadingOnnxModels } =
|
const { data: onnxModels, isLoading: isLoadingOnnxModels } =
|
||||||
useGetOnnxModelsQuery(NON_SDXL_MAIN_MODELS);
|
useGetOnnxModelsQuery(NON_SDXL_MAIN_MODELS);
|
||||||
const { data: mainModels, isLoading: isLoadingMainModels } =
|
const { data: mainModels, isLoading: isLoadingMainModels } =
|
||||||
@ -127,7 +128,9 @@ const MainModelInputFieldComponent = (
|
|||||||
tooltip={selectedModel?.description}
|
tooltip={selectedModel?.description}
|
||||||
value={selectedModel?.id}
|
value={selectedModel?.id}
|
||||||
placeholder={
|
placeholder={
|
||||||
data.length > 0 ? 'Select a model' : 'No models available'
|
data.length > 0
|
||||||
|
? t('models.selectModel')
|
||||||
|
: t('models.noModelsAvailable')
|
||||||
}
|
}
|
||||||
data={data}
|
data={data}
|
||||||
error={!selectedModel}
|
error={!selectedModel}
|
||||||
|
@ -89,7 +89,7 @@ const RefinerModelInputFieldComponent = (
|
|||||||
return isLoading ? (
|
return isLoading ? (
|
||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
label={t('modelManager.model')}
|
label={t('modelManager.model')}
|
||||||
placeholder="Loading..."
|
placeholder={t('models.loading')}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
data={[]}
|
data={[]}
|
||||||
/>
|
/>
|
||||||
@ -99,7 +99,11 @@ const RefinerModelInputFieldComponent = (
|
|||||||
className="nowheel nodrag"
|
className="nowheel nodrag"
|
||||||
tooltip={selectedModel?.description}
|
tooltip={selectedModel?.description}
|
||||||
value={selectedModel?.id}
|
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}
|
data={data}
|
||||||
error={!selectedModel}
|
error={!selectedModel}
|
||||||
disabled={data.length === 0}
|
disabled={data.length === 0}
|
||||||
|
@ -116,7 +116,7 @@ const ModelInputFieldComponent = (
|
|||||||
return isLoading ? (
|
return isLoading ? (
|
||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
label={t('modelManager.model')}
|
label={t('modelManager.model')}
|
||||||
placeholder="Loading..."
|
placeholder={t('models.loading')}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
data={[]}
|
data={[]}
|
||||||
/>
|
/>
|
||||||
@ -126,7 +126,11 @@ const ModelInputFieldComponent = (
|
|||||||
className="nowheel nodrag"
|
className="nowheel nodrag"
|
||||||
tooltip={selectedModel?.description}
|
tooltip={selectedModel?.description}
|
||||||
value={selectedModel?.id}
|
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}
|
data={data}
|
||||||
error={!selectedModel}
|
error={!selectedModel}
|
||||||
disabled={data.length === 0}
|
disabled={data.length === 0}
|
||||||
|
@ -12,6 +12,7 @@ import { useNodeTemplateTitle } from 'features/nodes/hooks/useNodeTemplateTitle'
|
|||||||
import { nodeLabelChanged } from 'features/nodes/store/nodesSlice';
|
import { nodeLabelChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||||
import { MouseEvent, memo, useCallback, useEffect, useState } from 'react';
|
import { MouseEvent, memo, useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@ -22,16 +23,17 @@ const NodeTitle = ({ nodeId, title }: Props) => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const label = useNodeLabel(nodeId);
|
const label = useNodeLabel(nodeId);
|
||||||
const templateTitle = useNodeTemplateTitle(nodeId);
|
const templateTitle = useNodeTemplateTitle(nodeId);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [localTitle, setLocalTitle] = useState('');
|
const [localTitle, setLocalTitle] = useState('');
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
async (newTitle: string) => {
|
async (newTitle: string) => {
|
||||||
dispatch(nodeLabelChanged({ nodeId, label: newTitle }));
|
dispatch(nodeLabelChanged({ nodeId, label: newTitle }));
|
||||||
setLocalTitle(
|
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) => {
|
const handleChange = useCallback((newTitle: string) => {
|
||||||
@ -40,8 +42,10 @@ const NodeTitle = ({ nodeId, title }: Props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Another component may change the title; sync local title with global state
|
// Another component may change the title; sync local title with global state
|
||||||
setLocalTitle(label || title || templateTitle || 'Problem Setting Title');
|
setLocalTitle(
|
||||||
}, [label, templateTitle, title]);
|
label || title || templateTitle || t('nodes.problemSettingTitle')
|
||||||
|
);
|
||||||
|
}, [label, templateTitle, title, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -8,10 +8,12 @@ import {
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { nodeOpacityChanged } from 'features/nodes/store/nodesSlice';
|
import { nodeOpacityChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export default function NodeOpacitySlider() {
|
export default function NodeOpacitySlider() {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const nodeOpacity = useAppSelector((state) => state.nodes.nodeOpacity);
|
const nodeOpacity = useAppSelector((state) => state.nodes.nodeOpacity);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -23,7 +25,7 @@ export default function NodeOpacitySlider() {
|
|||||||
return (
|
return (
|
||||||
<Flex alignItems="center">
|
<Flex alignItems="center">
|
||||||
<Slider
|
<Slider
|
||||||
aria-label="Node Opacity"
|
aria-label={t('nodes.nodeOpacity')}
|
||||||
value={nodeOpacity}
|
value={nodeOpacity}
|
||||||
min={0.5}
|
min={0.5}
|
||||||
max={1}
|
max={1}
|
||||||
|
@ -4,10 +4,11 @@ import IAIIconButton from 'common/components/IAIIconButton';
|
|||||||
import { addNodePopoverOpened } from 'features/nodes/store/nodesSlice';
|
import { addNodePopoverOpened } from 'features/nodes/store/nodesSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { FaPlus } from 'react-icons/fa';
|
import { FaPlus } from 'react-icons/fa';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const TopLeftPanel = () => {
|
const TopLeftPanel = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
const handleOpenAddNodePopover = useCallback(() => {
|
const handleOpenAddNodePopover = useCallback(() => {
|
||||||
dispatch(addNodePopoverOpened());
|
dispatch(addNodePopoverOpened());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
@ -15,8 +16,8 @@ const TopLeftPanel = () => {
|
|||||||
return (
|
return (
|
||||||
<Flex sx={{ gap: 2, position: 'absolute', top: 2, insetInlineStart: 2 }}>
|
<Flex sx={{ gap: 2, position: 'absolute', top: 2, insetInlineStart: 2 }}>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
tooltip="Add Node (Shift+A, Space)"
|
tooltip={t('nodes.addNodeToolTip')}
|
||||||
aria-label="Add Node"
|
aria-label={t('nodes.addNode')}
|
||||||
icon={<FaPlus />}
|
icon={<FaPlus />}
|
||||||
onClick={handleOpenAddNodePopover}
|
onClick={handleOpenAddNodePopover}
|
||||||
/>
|
/>
|
||||||
|
@ -29,6 +29,7 @@ import { ChangeEvent, memo, useCallback } from 'react';
|
|||||||
import { FaCog } from 'react-icons/fa';
|
import { FaCog } from 'react-icons/fa';
|
||||||
import { SelectionMode } from 'reactflow';
|
import { SelectionMode } from 'reactflow';
|
||||||
import ReloadNodeTemplatesButton from '../TopCenterPanel/ReloadSchemaButton';
|
import ReloadNodeTemplatesButton from '../TopCenterPanel/ReloadSchemaButton';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const formLabelProps: FormLabelProps = {
|
const formLabelProps: FormLabelProps = {
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@ -101,12 +102,14 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
ref={ref}
|
ref={ref}
|
||||||
aria-label="Workflow Editor Settings"
|
aria-label={t('nodes.workflowSettings')}
|
||||||
tooltip="Workflow Editor Settings"
|
tooltip={t('nodes.workflowSettings')}
|
||||||
icon={<FaCog />}
|
icon={<FaCog />}
|
||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
/>
|
/>
|
||||||
@ -114,7 +117,7 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
|
|||||||
<Modal isOpen={isOpen} onClose={onClose} size="2xl" isCentered>
|
<Modal isOpen={isOpen} onClose={onClose} size="2xl" isCentered>
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
<ModalHeader>Workflow Editor Settings</ModalHeader>
|
<ModalHeader>{t('nodes.workflowSettings')}</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<Flex
|
<Flex
|
||||||
@ -129,31 +132,31 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
|
|||||||
formLabelProps={formLabelProps}
|
formLabelProps={formLabelProps}
|
||||||
onChange={handleChangeShouldAnimate}
|
onChange={handleChangeShouldAnimate}
|
||||||
isChecked={shouldAnimateEdges}
|
isChecked={shouldAnimateEdges}
|
||||||
label="Animated Edges"
|
label={t('nodes.animatedEdges')}
|
||||||
helperText="Animate selected edges and edges connected to selected nodes"
|
helperText={t('nodes.animatedEdgesHelp')}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
formLabelProps={formLabelProps}
|
formLabelProps={formLabelProps}
|
||||||
isChecked={shouldSnapToGrid}
|
isChecked={shouldSnapToGrid}
|
||||||
onChange={handleChangeShouldSnap}
|
onChange={handleChangeShouldSnap}
|
||||||
label="Snap to Grid"
|
label={t('nodes.snapToGrid')}
|
||||||
helperText="Snap nodes to grid when moved"
|
helperText={t('nodes.snapToGridHelp')}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
formLabelProps={formLabelProps}
|
formLabelProps={formLabelProps}
|
||||||
isChecked={shouldColorEdges}
|
isChecked={shouldColorEdges}
|
||||||
onChange={handleChangeShouldColor}
|
onChange={handleChangeShouldColor}
|
||||||
label="Color-Code Edges"
|
label={t('nodes.colorCodeEdges')}
|
||||||
helperText="Color-code edges according to their connected fields"
|
helperText={t('nodes.colorCodeEdgesHelp')}
|
||||||
/>
|
/>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
formLabelProps={formLabelProps}
|
formLabelProps={formLabelProps}
|
||||||
isChecked={selectionModeIsChecked}
|
isChecked={selectionModeIsChecked}
|
||||||
onChange={handleChangeSelectionMode}
|
onChange={handleChangeSelectionMode}
|
||||||
label="Fully Contain Nodes to Select"
|
label={t('nodes.fullyContainNodes')}
|
||||||
helperText="Nodes must be fully inside the selection box to be selected"
|
helperText={t('nodes.fullyContainNodesHelp')}
|
||||||
/>
|
/>
|
||||||
<Heading size="sm" pt={4}>
|
<Heading size="sm" pt={4}>
|
||||||
Advanced
|
Advanced
|
||||||
@ -162,8 +165,8 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
|
|||||||
formLabelProps={formLabelProps}
|
formLabelProps={formLabelProps}
|
||||||
isChecked={shouldValidateGraph}
|
isChecked={shouldValidateGraph}
|
||||||
onChange={handleChangeShouldValidate}
|
onChange={handleChangeShouldValidate}
|
||||||
label="Validate Connections and Graph"
|
label={t('nodes.validateConnections')}
|
||||||
helperText="Prevent invalid connections from being made, and invalid graphs from being invoked"
|
helperText={t('nodes.validateConnectionsHelp')}
|
||||||
/>
|
/>
|
||||||
<ReloadNodeTemplatesButton />
|
<ReloadNodeTemplatesButton />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -9,6 +9,7 @@ import { memo } from 'react';
|
|||||||
import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea';
|
import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea';
|
||||||
import NodeTitle from '../../flow/nodes/common/NodeTitle';
|
import NodeTitle from '../../flow/nodes/common/NodeTitle';
|
||||||
import ScrollableContent from '../ScrollableContent';
|
import ScrollableContent from '../ScrollableContent';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -34,9 +35,12 @@ const selector = createSelector(
|
|||||||
|
|
||||||
const InspectorDetailsTab = () => {
|
const InspectorDetailsTab = () => {
|
||||||
const { data, template } = useAppSelector(selector);
|
const { data, template } = useAppSelector(selector);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (!template || !data) {
|
if (!template || !data) {
|
||||||
return <IAINoContentFallback label="No node selected" icon={null} />;
|
return (
|
||||||
|
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Content data={data} template={template} />;
|
return <Content data={data} template={template} />;
|
||||||
|
@ -11,6 +11,7 @@ import { ImageOutput } from 'services/api/types';
|
|||||||
import { AnyResult } from 'services/events/types';
|
import { AnyResult } from 'services/events/types';
|
||||||
import ScrollableContent from '../ScrollableContent';
|
import ScrollableContent from '../ScrollableContent';
|
||||||
import ImageOutputPreview from './outputs/ImageOutputPreview';
|
import ImageOutputPreview from './outputs/ImageOutputPreview';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -40,13 +41,18 @@ const selector = createSelector(
|
|||||||
|
|
||||||
const InspectorOutputsTab = () => {
|
const InspectorOutputsTab = () => {
|
||||||
const { node, template, nes } = useAppSelector(selector);
|
const { node, template, nes } = useAppSelector(selector);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (!node || !nes || !isInvocationNode(node)) {
|
if (!node || !nes || !isInvocationNode(node)) {
|
||||||
return <IAINoContentFallback label="No node selected" icon={null} />;
|
return (
|
||||||
|
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nes.outputs.length === 0) {
|
if (nes.outputs.length === 0) {
|
||||||
return <IAINoContentFallback label="No outputs recorded" icon={null} />;
|
return (
|
||||||
|
<IAINoContentFallback label={t('nodes.noOutputRecorded')} icon={null} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -77,7 +83,7 @@ const InspectorOutputsTab = () => {
|
|||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<DataViewer data={nes.outputs} label="Node Outputs" />
|
<DataViewer data={nes.outputs} label={t('nodes.nodesOutputs')} />
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</ScrollableContent>
|
</ScrollableContent>
|
||||||
|
@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
|
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -29,12 +30,15 @@ const selector = createSelector(
|
|||||||
|
|
||||||
const NodeTemplateInspector = () => {
|
const NodeTemplateInspector = () => {
|
||||||
const { template } = useAppSelector(selector);
|
const { template } = useAppSelector(selector);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (!template) {
|
if (!template) {
|
||||||
return <IAINoContentFallback label="No node selected" icon={null} />;
|
return (
|
||||||
|
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <DataViewer data={template} label="Node Template" />;
|
return <DataViewer data={template} label={t('nodes.NodeTemplate')} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(NodeTemplateInspector);
|
export default memo(NodeTemplateInspector);
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
} from 'features/nodes/store/nodesSlice';
|
} from 'features/nodes/store/nodesSlice';
|
||||||
import { ChangeEvent, memo, useCallback } from 'react';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
import ScrollableContent from '../ScrollableContent';
|
import ScrollableContent from '../ScrollableContent';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -85,6 +86,8 @@ const WorkflowGeneralTab = () => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollableContent>
|
<ScrollableContent>
|
||||||
<Flex
|
<Flex
|
||||||
@ -96,28 +99,36 @@ const WorkflowGeneralTab = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex sx={{ gap: 2, w: 'full' }}>
|
<Flex sx={{ gap: 2, w: 'full' }}>
|
||||||
<IAIInput label="Name" value={name} onChange={handleChangeName} />
|
|
||||||
<IAIInput
|
<IAIInput
|
||||||
label="Version"
|
label={t('nodes.workflowName')}
|
||||||
|
value={name}
|
||||||
|
onChange={handleChangeName}
|
||||||
|
/>
|
||||||
|
<IAIInput
|
||||||
|
label={t('nodes.workflowVersion')}
|
||||||
value={version}
|
value={version}
|
||||||
onChange={handleChangeVersion}
|
onChange={handleChangeVersion}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex sx={{ gap: 2, w: 'full' }}>
|
<Flex sx={{ gap: 2, w: 'full' }}>
|
||||||
<IAIInput
|
<IAIInput
|
||||||
label="Author"
|
label={t('nodes.workflowAuthor')}
|
||||||
value={author}
|
value={author}
|
||||||
onChange={handleChangeAuthor}
|
onChange={handleChangeAuthor}
|
||||||
/>
|
/>
|
||||||
<IAIInput
|
<IAIInput
|
||||||
label="Contact"
|
label={t('nodes.workflowContact')}
|
||||||
value={contact}
|
value={contact}
|
||||||
onChange={handleChangeContact}
|
onChange={handleChangeContact}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<IAIInput label="Tags" value={tags} onChange={handleChangeTags} />
|
<IAIInput
|
||||||
|
label={t('nodes.workflowTags')}
|
||||||
|
value={tags}
|
||||||
|
onChange={handleChangeTags}
|
||||||
|
/>
|
||||||
<FormControl as={Flex} sx={{ flexDir: 'column' }}>
|
<FormControl as={Flex} sx={{ flexDir: 'column' }}>
|
||||||
<FormLabel>Short Description</FormLabel>
|
<FormLabel>{t('nodes.workflowDescription')}</FormLabel>
|
||||||
<IAITextarea
|
<IAITextarea
|
||||||
onChange={handleChangeDescription}
|
onChange={handleChangeDescription}
|
||||||
value={description}
|
value={description}
|
||||||
@ -126,7 +137,7 @@ const WorkflowGeneralTab = () => {
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl as={Flex} sx={{ flexDir: 'column', h: 'full' }}>
|
<FormControl as={Flex} sx={{ flexDir: 'column', h: 'full' }}>
|
||||||
<FormLabel>Notes</FormLabel>
|
<FormLabel>{t('nodes.workflowNotes')}</FormLabel>
|
||||||
<IAITextarea
|
<IAITextarea
|
||||||
onChange={handleChangeNotes}
|
onChange={handleChangeNotes}
|
||||||
value={notes}
|
value={notes}
|
||||||
|
@ -2,9 +2,11 @@ import { Flex } from '@chakra-ui/react';
|
|||||||
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
|
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
|
||||||
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const WorkflowJSONTab = () => {
|
const WorkflowJSONTab = () => {
|
||||||
const workflow = useWorkflow();
|
const workflow = useWorkflow();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@ -15,7 +17,7 @@ const WorkflowJSONTab = () => {
|
|||||||
h: 'full',
|
h: 'full',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DataViewer data={workflow} label="Workflow" />
|
<DataViewer data={workflow} label={t('nodes.workflow')} />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,7 @@ import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import LinearViewField from '../../flow/nodes/Invocation/fields/LinearViewField';
|
import LinearViewField from '../../flow/nodes/Invocation/fields/LinearViewField';
|
||||||
import ScrollableContent from '../ScrollableContent';
|
import ScrollableContent from '../ScrollableContent';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -20,6 +21,7 @@ const selector = createSelector(
|
|||||||
|
|
||||||
const WorkflowLinearTab = () => {
|
const WorkflowLinearTab = () => {
|
||||||
const { fields } = useAppSelector(selector);
|
const { fields } = useAppSelector(selector);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -51,7 +53,7 @@ const WorkflowLinearTab = () => {
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<IAINoContentFallback
|
<IAINoContentFallback
|
||||||
label="No fields added to Linear View"
|
label={t('nodes.noFieldsLinearview')}
|
||||||
icon={null}
|
icon={null}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -9,10 +9,12 @@ import { memo, useCallback } from 'react';
|
|||||||
import { ZodError } from 'zod';
|
import { ZodError } from 'zod';
|
||||||
import { fromZodError, fromZodIssue } from 'zod-validation-error';
|
import { fromZodError, fromZodIssue } from 'zod-validation-error';
|
||||||
import { workflowLoadRequested } from '../store/actions';
|
import { workflowLoadRequested } from '../store/actions';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const useLoadWorkflowFromFile = () => {
|
export const useLoadWorkflowFromFile = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const logger = useLogger('nodes');
|
const logger = useLogger('nodes');
|
||||||
|
const { t } = useTranslation();
|
||||||
const loadWorkflowFromFile = useCallback(
|
const loadWorkflowFromFile = useCallback(
|
||||||
(file: File | null) => {
|
(file: File | null) => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
@ -28,7 +30,7 @@ export const useLoadWorkflowFromFile = () => {
|
|||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
const { message } = fromZodError(result.error, {
|
const { message } = fromZodError(result.error, {
|
||||||
prefix: 'Workflow Validation Error',
|
prefix: t('nodes.workflowValidation'),
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.error({ error: parseify(result.error) }, message);
|
logger.error({ error: parseify(result.error) }, message);
|
||||||
@ -36,7 +38,7 @@ export const useLoadWorkflowFromFile = () => {
|
|||||||
dispatch(
|
dispatch(
|
||||||
addToast(
|
addToast(
|
||||||
makeToast({
|
makeToast({
|
||||||
title: 'Unable to Validate Workflow',
|
title: t('nodes.unableToValidateWorkflow'),
|
||||||
status: 'error',
|
status: 'error',
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
})
|
})
|
||||||
@ -54,7 +56,7 @@ export const useLoadWorkflowFromFile = () => {
|
|||||||
dispatch(
|
dispatch(
|
||||||
addToast(
|
addToast(
|
||||||
makeToast({
|
makeToast({
|
||||||
title: 'Unable to Load Workflow',
|
title: t('nodes.unableToLoadWorkflow'),
|
||||||
status: 'error',
|
status: 'error',
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -64,7 +66,7 @@ export const useLoadWorkflowFromFile = () => {
|
|||||||
|
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
},
|
},
|
||||||
[dispatch, logger]
|
[dispatch, logger, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
return loadWorkflowFromFile;
|
return loadWorkflowFromFile;
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
} from 'features/nodes/types/constants';
|
} from 'features/nodes/types/constants';
|
||||||
import { FieldType } from 'features/nodes/types/types';
|
import { FieldType } from 'features/nodes/types/types';
|
||||||
import { HandleType } from 'reactflow';
|
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`
|
* 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,
|
fieldName: string,
|
||||||
handleType: HandleType,
|
handleType: HandleType,
|
||||||
fieldType?: FieldType
|
fieldType?: FieldType
|
||||||
) =>
|
) => {
|
||||||
createSelector(stateSelector, (state) => {
|
return createSelector(stateSelector, (state) => {
|
||||||
if (!fieldType) {
|
if (!fieldType) {
|
||||||
return 'No field type';
|
return i18n.t('nodes.noFieldType');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { currentConnectionFieldType, connectionStartParams, nodes, edges } =
|
const { currentConnectionFieldType, connectionStartParams, nodes, edges } =
|
||||||
state.nodes;
|
state.nodes;
|
||||||
|
|
||||||
if (!connectionStartParams || !currentConnectionFieldType) {
|
if (!connectionStartParams || !currentConnectionFieldType) {
|
||||||
return 'No connection in progress';
|
return i18n.t('nodes.noConnectionInProgress');
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -40,7 +41,7 @@ export const makeConnectionErrorSelector = (
|
|||||||
} = connectionStartParams;
|
} = connectionStartParams;
|
||||||
|
|
||||||
if (!connectionHandleType || !connectionNodeId || !connectionFieldName) {
|
if (!connectionHandleType || !connectionNodeId || !connectionFieldName) {
|
||||||
return 'No connection data';
|
return i18n.t('nodes.noConnectionData');
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetType =
|
const targetType =
|
||||||
@ -49,14 +50,14 @@ export const makeConnectionErrorSelector = (
|
|||||||
handleType === 'source' ? fieldType : currentConnectionFieldType;
|
handleType === 'source' ? fieldType : currentConnectionFieldType;
|
||||||
|
|
||||||
if (nodeId === connectionNodeId) {
|
if (nodeId === connectionNodeId) {
|
||||||
return 'Cannot connect to self';
|
return i18n.t('nodes.cannotConnectToSelf');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handleType === connectionHandleType) {
|
if (handleType === connectionHandleType) {
|
||||||
if (handleType === 'source') {
|
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 (
|
if (
|
||||||
@ -66,7 +67,7 @@ export const makeConnectionErrorSelector = (
|
|||||||
// except CollectionItem inputs can have multiples
|
// except CollectionItem inputs can have multiples
|
||||||
targetType !== 'CollectionItem'
|
targetType !== 'CollectionItem'
|
||||||
) {
|
) {
|
||||||
return 'Input may only have one connection';
|
return i18n.t('nodes.inputMayOnlyHaveOneConnection');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,7 +126,7 @@ export const makeConnectionErrorSelector = (
|
|||||||
isIntToFloat
|
isIntToFloat
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return 'Field types must match';
|
return i18n.t('nodes.fieldTypesMustMatch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,8 +138,9 @@ export const makeConnectionErrorSelector = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!isGraphAcyclic) {
|
if (!isGraphAcyclic) {
|
||||||
return 'Connection would create a cycle';
|
return i18n.t('nodes.connectionWouldCreateCycle');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
@ -286,7 +286,7 @@ export type BooleanPolymorphicInputFieldValue = z.infer<
|
|||||||
|
|
||||||
export const zEnumInputFieldValue = zInputFieldValueBase.extend({
|
export const zEnumInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: z.literal('enum'),
|
type: z.literal('enum'),
|
||||||
value: z.union([z.string(), z.number()]).optional(),
|
value: z.string().optional(),
|
||||||
});
|
});
|
||||||
export type EnumInputFieldValue = z.infer<typeof zEnumInputFieldValue>;
|
export type EnumInputFieldValue = z.infer<typeof zEnumInputFieldValue>;
|
||||||
|
|
||||||
@ -822,10 +822,10 @@ export type ControlPolymorphicInputFieldTemplate = Omit<
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type EnumInputFieldTemplate = InputFieldTemplateBase & {
|
export type EnumInputFieldTemplate = InputFieldTemplateBase & {
|
||||||
default: string | number;
|
default: string;
|
||||||
type: 'enum';
|
type: 'enum';
|
||||||
enumType: 'string' | 'number';
|
options: string[];
|
||||||
options: Array<string | number>;
|
labels?: { [key: string]: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MainModelInputFieldTemplate = InputFieldTemplateBase & {
|
export type MainModelInputFieldTemplate = InputFieldTemplateBase & {
|
||||||
|
@ -656,8 +656,8 @@ const buildEnumInputFieldTemplate = ({
|
|||||||
const template: EnumInputFieldTemplate = {
|
const template: EnumInputFieldTemplate = {
|
||||||
...baseField,
|
...baseField,
|
||||||
type: 'enum',
|
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],
|
default: schemaObject.default ?? options[0],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { InputFieldTemplate, InputFieldValue } from '../types/types';
|
import { InputFieldTemplate, InputFieldValue } from '../types/types';
|
||||||
|
|
||||||
const FIELD_VALUE_FALLBACK_MAP = {
|
const FIELD_VALUE_FALLBACK_MAP = {
|
||||||
'enum.number': 0,
|
enum: '',
|
||||||
'enum.string': '',
|
|
||||||
boolean: false,
|
boolean: false,
|
||||||
BooleanCollection: [],
|
BooleanCollection: [],
|
||||||
BooleanPolymorphic: false,
|
BooleanPolymorphic: false,
|
||||||
@ -62,19 +61,8 @@ export const buildInputFieldValue = (
|
|||||||
fieldKind: 'input',
|
fieldKind: 'input',
|
||||||
} as InputFieldValue;
|
} 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 =
|
fieldValue.value =
|
||||||
template.default ?? FIELD_VALUE_FALLBACK_MAP[template.type];
|
template.default ?? FIELD_VALUE_FALLBACK_MAP[template.type];
|
||||||
}
|
|
||||||
|
|
||||||
return fieldValue;
|
return fieldValue;
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAICollapse from 'common/components/IAICollapse';
|
import IAICollapse from 'common/components/IAICollapse';
|
||||||
import ParamClipSkip from './ParamClipSkip';
|
import ParamClipSkip from './ParamClipSkip';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -22,13 +23,13 @@ export default function ParamAdvancedCollapse() {
|
|||||||
const shouldShowAdvancedOptions = useAppSelector(
|
const shouldShowAdvancedOptions = useAppSelector(
|
||||||
(state: RootState) => state.generation.shouldShowAdvancedOptions
|
(state: RootState) => state.generation.shouldShowAdvancedOptions
|
||||||
);
|
);
|
||||||
|
const { t } = useTranslation();
|
||||||
if (!shouldShowAdvancedOptions) {
|
if (!shouldShowAdvancedOptions) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAICollapse label="Advanced" activeLabel={activeLabel}>
|
<IAICollapse label={t('common.advanced')} activeLabel={activeLabel}>
|
||||||
<Flex sx={{ flexDir: 'column', gap: 2 }}>
|
<Flex sx={{ flexDir: 'column', gap: 2 }}>
|
||||||
<ParamClipSkip />
|
<ParamClipSkip />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -6,6 +6,7 @@ import IAISwitch from 'common/components/IAISwitch';
|
|||||||
import { NoiseUseCPUPopover } from 'features/informationalPopovers/components/noiseUseCPU';
|
import { NoiseUseCPUPopover } from 'features/informationalPopovers/components/noiseUseCPU';
|
||||||
import { shouldUseCpuNoiseChanged } from 'features/parameters/store/generationSlice';
|
import { shouldUseCpuNoiseChanged } from 'features/parameters/store/generationSlice';
|
||||||
import { ChangeEvent } from 'react';
|
import { ChangeEvent } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -22,6 +23,7 @@ const selector = createSelector(
|
|||||||
export const ParamCpuNoiseToggle = () => {
|
export const ParamCpuNoiseToggle = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { isDisabled, shouldUseCpuNoise } = useAppSelector(selector);
|
const { isDisabled, shouldUseCpuNoise } = useAppSelector(selector);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) =>
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) =>
|
||||||
dispatch(shouldUseCpuNoiseChanged(e.target.checked));
|
dispatch(shouldUseCpuNoiseChanged(e.target.checked));
|
||||||
@ -30,7 +32,7 @@ export const ParamCpuNoiseToggle = () => {
|
|||||||
<NoiseUseCPUPopover>
|
<NoiseUseCPUPopover>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
label="Use CPU Noise"
|
label={t('parameters.useCpuNoise')}
|
||||||
isChecked={shouldUseCpuNoise}
|
isChecked={shouldUseCpuNoise}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
@ -4,9 +4,11 @@ import IAISwitch from 'common/components/IAISwitch';
|
|||||||
import { NoiseEnablePopover } from 'features/informationalPopovers/components/noiseEnable';
|
import { NoiseEnablePopover } from 'features/informationalPopovers/components/noiseEnable';
|
||||||
import { setShouldUseNoiseSettings } from 'features/parameters/store/generationSlice';
|
import { setShouldUseNoiseSettings } from 'features/parameters/store/generationSlice';
|
||||||
import { ChangeEvent } from 'react';
|
import { ChangeEvent } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const ParamNoiseToggle = () => {
|
export const ParamNoiseToggle = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const shouldUseNoiseSettings = useAppSelector(
|
const shouldUseNoiseSettings = useAppSelector(
|
||||||
(state: RootState) => state.generation.shouldUseNoiseSettings
|
(state: RootState) => state.generation.shouldUseNoiseSettings
|
||||||
@ -18,7 +20,7 @@ export const ParamNoiseToggle = () => {
|
|||||||
return (
|
return (
|
||||||
<NoiseEnablePopover>
|
<NoiseEnablePopover>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label="Enable Noise Settings"
|
label={t('parameters.enableNoiseSettings')}
|
||||||
isChecked={shouldUseNoiseSettings}
|
isChecked={shouldUseNoiseSettings}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
@ -146,7 +146,7 @@ const CancelButton = (props: Props) => {
|
|||||||
id="cancel-button"
|
id="cancel-button"
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
Cancel
|
{t('parameters.cancel.cancel')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
)}
|
)}
|
||||||
<Menu closeOnSelect={false}>
|
<Menu closeOnSelect={false}>
|
||||||
|
@ -76,7 +76,7 @@ export default function InvokeButton(props: InvokeButton) {
|
|||||||
)}
|
)}
|
||||||
{asIconButton ? (
|
{asIconButton ? (
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label={t('parameters.invoke')}
|
aria-label={t('parameters.invoke.invoke')}
|
||||||
type="submit"
|
type="submit"
|
||||||
icon={<FaPlay />}
|
icon={<FaPlay />}
|
||||||
isDisabled={!isReady}
|
isDisabled={!isReady}
|
||||||
@ -96,7 +96,7 @@ export default function InvokeButton(props: InvokeButton) {
|
|||||||
) : (
|
) : (
|
||||||
<IAIButton
|
<IAIButton
|
||||||
tooltip={<InvokeButtonTooltipContent />}
|
tooltip={<InvokeButtonTooltipContent />}
|
||||||
aria-label={t('parameters.invoke')}
|
aria-label={t('parameters.invoke.invoke')}
|
||||||
type="submit"
|
type="submit"
|
||||||
data-progress={isProcessing}
|
data-progress={isProcessing}
|
||||||
isDisabled={!isReady}
|
isDisabled={!isReady}
|
||||||
@ -105,7 +105,7 @@ export default function InvokeButton(props: InvokeButton) {
|
|||||||
id="invoke-button"
|
id="invoke-button"
|
||||||
leftIcon={isProcessing ? undefined : <FaPlay />}
|
leftIcon={isProcessing ? undefined : <FaPlay />}
|
||||||
isLoading={isProcessing}
|
isLoading={isProcessing}
|
||||||
loadingText={t('parameters.invoke')}
|
loadingText={t('parameters.invoke.invoke')}
|
||||||
sx={{
|
sx={{
|
||||||
w: 'full',
|
w: 'full',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
@ -138,11 +138,14 @@ export const InvokeButtonTooltipContent = memo(() => {
|
|||||||
const { isReady, reasons } = useIsReadyToInvoke();
|
const { isReady, reasons } = useIsReadyToInvoke();
|
||||||
const { autoAddBoardId } = useAppSelector(tooltipSelector);
|
const { autoAddBoardId } = useAppSelector(tooltipSelector);
|
||||||
const autoAddBoardName = useBoardName(autoAddBoardId);
|
const autoAddBoardName = useBoardName(autoAddBoardId);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" gap={1}>
|
<Flex flexDir="column" gap={1}>
|
||||||
<Text fontWeight={600}>
|
<Text fontWeight={600}>
|
||||||
{isReady ? 'Ready to Invoke' : 'Unable to Invoke'}
|
{isReady
|
||||||
|
? t('parameters.invoke.readyToInvoke')
|
||||||
|
: t('parameters.invoke.unableToInvoke')}
|
||||||
</Text>
|
</Text>
|
||||||
{reasons.length > 0 && (
|
{reasons.length > 0 && (
|
||||||
<UnorderedList>
|
<UnorderedList>
|
||||||
@ -159,7 +162,7 @@ export const InvokeButtonTooltipContent = memo(() => {
|
|||||||
_dark={{ borderColor: 'base.900' }}
|
_dark={{ borderColor: 'base.900' }}
|
||||||
/>
|
/>
|
||||||
<Text fontWeight={400} fontStyle="oblique 10deg">
|
<Text fontWeight={400} fontStyle="oblique 10deg">
|
||||||
Adding images to{' '}
|
{t('parameters.invoke.addingImagesTo')}{' '}
|
||||||
<Text as="span" fontWeight={600}>
|
<Text as="span" fontWeight={600}>
|
||||||
{autoAddBoardName || 'Uncategorized'}
|
{autoAddBoardName || 'Uncategorized'}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -3,6 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import { FaLink } from 'react-icons/fa';
|
import { FaLink } from 'react-icons/fa';
|
||||||
import { setShouldConcatSDXLStylePrompt } from '../store/sdxlSlice';
|
import { setShouldConcatSDXLStylePrompt } from '../store/sdxlSlice';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export default function ParamSDXLConcatButton() {
|
export default function ParamSDXLConcatButton() {
|
||||||
const shouldConcatSDXLStylePrompt = useAppSelector(
|
const shouldConcatSDXLStylePrompt = useAppSelector(
|
||||||
@ -10,6 +11,7 @@ export default function ParamSDXLConcatButton() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleShouldConcatPromptChange = () => {
|
const handleShouldConcatPromptChange = () => {
|
||||||
dispatch(setShouldConcatSDXLStylePrompt(!shouldConcatSDXLStylePrompt));
|
dispatch(setShouldConcatSDXLStylePrompt(!shouldConcatSDXLStylePrompt));
|
||||||
@ -17,8 +19,8 @@ export default function ParamSDXLConcatButton() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label="Concatenate Prompt & Style"
|
aria-label={t('sdxl.concatPromptStyle')}
|
||||||
tooltip="Concatenate Prompt & Style"
|
tooltip={t('sdxl.concatPromptStyle')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
isChecked={shouldConcatSDXLStylePrompt}
|
isChecked={shouldConcatSDXLStylePrompt}
|
||||||
onClick={handleShouldConcatPromptChange}
|
onClick={handleShouldConcatPromptChange}
|
||||||
|
@ -39,7 +39,7 @@ const ParamSDXLImg2ImgDenoisingStrength = () => {
|
|||||||
<SubParametersWrapper>
|
<SubParametersWrapper>
|
||||||
<ParamDenoisingStrengthPopover>
|
<ParamDenoisingStrengthPopover>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={`${t('parameters.denoisingStrength')}`}
|
label={t('sdxl.denoisingStrength')}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
min={0}
|
min={0}
|
||||||
max={1}
|
max={1}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user