mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Compare commits
23 Commits
bug-instal
...
feat/metad
Author | SHA1 | Date | |
---|---|---|---|
338081f855 | |||
57b0e175da | |||
38fbc7ef2b | |||
23771aadf5 | |||
aade61701c | |||
692e104c9c | |||
a33d3eaf8e | |||
809be4c584 | |||
1171706535 | |||
78658dcd91 | |||
33dc7ead71 | |||
e05179e3f8 | |||
bd8ea26f38 | |||
021f77e35e | |||
f718de98f8 | |||
5d274c25f5 | |||
e8c0f040ad | |||
3c38e735b6 | |||
f59c3f560a | |||
faabfe9520 | |||
ba5f001645 | |||
3d2a28b6e0 | |||
78dda533e2 |
@ -45,13 +45,17 @@ async def upload_image(
|
|||||||
if not file.content_type.startswith("image"):
|
if not file.content_type.startswith("image"):
|
||||||
raise HTTPException(status_code=415, detail="Not an image")
|
raise HTTPException(status_code=415, detail="Not an image")
|
||||||
|
|
||||||
contents = await file.read()
|
metadata: Optional[str] = None
|
||||||
|
workflow: Optional[str] = None
|
||||||
|
|
||||||
|
contents = await file.read()
|
||||||
try:
|
try:
|
||||||
pil_image = Image.open(io.BytesIO(contents))
|
pil_image = Image.open(io.BytesIO(contents))
|
||||||
if crop_visible:
|
if crop_visible:
|
||||||
bbox = pil_image.getbbox()
|
bbox = pil_image.getbbox()
|
||||||
pil_image = pil_image.crop(bbox)
|
pil_image = pil_image.crop(bbox)
|
||||||
|
metadata = pil_image.info.get("invokeai_metadata", None)
|
||||||
|
workflow = pil_image.info.get("invokeai_workflow", None)
|
||||||
except Exception:
|
except Exception:
|
||||||
# Error opening the image
|
# Error opening the image
|
||||||
raise HTTPException(status_code=415, detail="Failed to read image")
|
raise HTTPException(status_code=415, detail="Failed to read image")
|
||||||
@ -63,6 +67,8 @@ async def upload_image(
|
|||||||
image_category=image_category,
|
image_category=image_category,
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
board_id=board_id,
|
board_id=board_id,
|
||||||
|
metadata=metadata,
|
||||||
|
workflow=workflow,
|
||||||
is_intermediate=is_intermediate,
|
is_intermediate=is_intermediate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,7 +71,12 @@ class FieldDescriptions:
|
|||||||
denoised_latents = "Denoised latents tensor"
|
denoised_latents = "Denoised latents tensor"
|
||||||
latents = "Latents tensor"
|
latents = "Latents tensor"
|
||||||
strength = "Strength of denoising (proportional to steps)"
|
strength = "Strength of denoising (proportional to steps)"
|
||||||
core_metadata = "Optional core metadata to be written to image"
|
metadata = "Optional metadata to be saved with the image"
|
||||||
|
metadata_dict_collection = "Collection of MetadataDicts"
|
||||||
|
metadata_item_polymorphic = "A single metadata item or collection of metadata items"
|
||||||
|
metadata_item_label = "Label for this metadata item"
|
||||||
|
metadata_item_value = "The value for this metadata item (may be any type)"
|
||||||
|
workflow = "Optional workflow to be saved with the image"
|
||||||
interp_mode = "Interpolation mode"
|
interp_mode = "Interpolation mode"
|
||||||
torch_antialias = "Whether or not to apply antialiasing (bilinear or bicubic only)"
|
torch_antialias = "Whether or not to apply antialiasing (bilinear or bicubic only)"
|
||||||
fp32 = "Whether or not to use full float32 precision"
|
fp32 = "Whether or not to use full float32 precision"
|
||||||
@ -88,6 +93,9 @@ class FieldDescriptions:
|
|||||||
num_1 = "The first number"
|
num_1 = "The first number"
|
||||||
num_2 = "The second number"
|
num_2 = "The second number"
|
||||||
mask = "The mask to use for the operation"
|
mask = "The mask to use for the operation"
|
||||||
|
board = "The board to save the image to"
|
||||||
|
image = "The image to process"
|
||||||
|
tile_size = "Tile size"
|
||||||
|
|
||||||
|
|
||||||
class Input(str, Enum):
|
class Input(str, Enum):
|
||||||
@ -172,7 +180,12 @@ class UIType(str, Enum):
|
|||||||
Scheduler = "Scheduler"
|
Scheduler = "Scheduler"
|
||||||
WorkflowField = "WorkflowField"
|
WorkflowField = "WorkflowField"
|
||||||
IsIntermediate = "IsIntermediate"
|
IsIntermediate = "IsIntermediate"
|
||||||
MetadataField = "MetadataField"
|
BoardField = "BoardField"
|
||||||
|
Any = "Any"
|
||||||
|
MetadataItem = "MetadataItem"
|
||||||
|
MetadataItemCollection = "MetadataItemCollection"
|
||||||
|
MetadataItemPolymorphic = "MetadataItemPolymorphic"
|
||||||
|
MetadataDict = "MetadataDict"
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
|
|
||||||
@ -618,23 +631,8 @@ class BaseInvocation(ABC, BaseModel):
|
|||||||
is_intermediate: bool = InputField(
|
is_intermediate: bool = InputField(
|
||||||
default=False, description="Whether or not this is an intermediate invocation.", ui_type=UIType.IsIntermediate
|
default=False, description="Whether or not this is an intermediate invocation.", ui_type=UIType.IsIntermediate
|
||||||
)
|
)
|
||||||
workflow: Optional[str] = InputField(
|
|
||||||
default=None,
|
|
||||||
description="The workflow to save with the image",
|
|
||||||
ui_type=UIType.WorkflowField,
|
|
||||||
)
|
|
||||||
use_cache: bool = InputField(default=True, description="Whether or not to use the cache")
|
use_cache: bool = InputField(default=True, description="Whether or not to use the cache")
|
||||||
|
|
||||||
@validator("workflow", pre=True)
|
|
||||||
def validate_workflow_is_json(cls, v):
|
|
||||||
if v is None:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
json.loads(v)
|
|
||||||
except json.decoder.JSONDecodeError:
|
|
||||||
raise ValueError("Workflow must be valid JSON")
|
|
||||||
return v
|
|
||||||
|
|
||||||
UIConfig: ClassVar[Type[UIConfigBase]]
|
UIConfig: ClassVar[Type[UIConfigBase]]
|
||||||
|
|
||||||
|
|
||||||
@ -656,6 +654,8 @@ def invocation(
|
|||||||
:param Optional[str] title: Adds a title to the invocation. Use if the auto-generated title isn't quite right. Defaults to None.
|
:param Optional[str] title: Adds a title to the invocation. Use if the auto-generated title isn't quite right. Defaults to None.
|
||||||
:param Optional[list[str]] tags: Adds tags to the invocation. Invocations may be searched for by their tags. Defaults to None.
|
:param Optional[list[str]] tags: Adds tags to the invocation. Invocations may be searched for by their tags. Defaults to None.
|
||||||
:param Optional[str] category: Adds a category to the invocation. Used to group the invocations in the UI. Defaults to None.
|
:param Optional[str] category: Adds a category to the invocation. Used to group the invocations in the UI. Defaults to None.
|
||||||
|
:param Optional[str] version: Adds a version to the invocation. Must be a valid semver string. Defaults to None.
|
||||||
|
:param Optional[bool] use_cache: Whether or not to use the invocation cache. Defaults to True. The user may override this in the workflow editor.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def wrapper(cls: Type[GenericBaseInvocation]) -> Type[GenericBaseInvocation]:
|
def wrapper(cls: Type[GenericBaseInvocation]) -> Type[GenericBaseInvocation]:
|
||||||
@ -737,3 +737,19 @@ def invocation_output(
|
|||||||
return cls
|
return cls
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class WithWorkflow(BaseModel):
|
||||||
|
workflow: Optional[str] = InputField(
|
||||||
|
default=None, description=FieldDescriptions.workflow, ui_type=UIType.WorkflowField
|
||||||
|
)
|
||||||
|
|
||||||
|
@validator("workflow", pre=True)
|
||||||
|
def validate_workflow_is_json(cls, v):
|
||||||
|
if v is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
json.loads(v)
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
raise ValueError("Workflow must be valid JSON")
|
||||||
|
return v
|
||||||
|
@ -25,6 +25,7 @@ from controlnet_aux import (
|
|||||||
from controlnet_aux.util import HWC3, ade_palette
|
from controlnet_aux.util import HWC3, ade_palette
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from pydantic import BaseModel, Field, validator
|
from pydantic import BaseModel, Field, validator
|
||||||
|
from invokeai.app.invocations.metadata import WithMetadata
|
||||||
|
|
||||||
from invokeai.app.invocations.primitives import ImageField, ImageOutput
|
from invokeai.app.invocations.primitives import ImageField, ImageOutput
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ from .baseinvocation import (
|
|||||||
InputField,
|
InputField,
|
||||||
InvocationContext,
|
InvocationContext,
|
||||||
OutputField,
|
OutputField,
|
||||||
|
WithWorkflow,
|
||||||
invocation,
|
invocation,
|
||||||
invocation_output,
|
invocation_output,
|
||||||
)
|
)
|
||||||
@ -127,7 +129,7 @@ class ControlNetInvocation(BaseInvocation):
|
|||||||
@invocation(
|
@invocation(
|
||||||
"image_processor", title="Base Image Processor", tags=["controlnet"], category="controlnet", version="1.0.0"
|
"image_processor", title="Base Image Processor", tags=["controlnet"], category="controlnet", version="1.0.0"
|
||||||
)
|
)
|
||||||
class ImageProcessorInvocation(BaseInvocation):
|
class ImageProcessorInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
||||||
"""Base class for invocations that preprocess images for ControlNet"""
|
"""Base class for invocations that preprocess images for ControlNet"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to process")
|
image: ImageField = InputField(description="The image to process")
|
||||||
@ -150,6 +152,7 @@ class ImageProcessorInvocation(BaseInvocation):
|
|||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -559,3 +562,33 @@ class SamDetectorReproducibleColors(SamDetector):
|
|||||||
img[:, :] = ann_color
|
img[:, :] = ann_color
|
||||||
final_img.paste(Image.fromarray(img, mode="RGB"), (0, 0), Image.fromarray(np.uint8(m * 255)))
|
final_img.paste(Image.fromarray(img, mode="RGB"), (0, 0), Image.fromarray(np.uint8(m * 255)))
|
||||||
return np.array(final_img, dtype=np.uint8)
|
return np.array(final_img, dtype=np.uint8)
|
||||||
|
|
||||||
|
|
||||||
|
@invocation(
|
||||||
|
"color_map_image_processor",
|
||||||
|
title="Color Map Processor",
|
||||||
|
tags=["controlnet"],
|
||||||
|
category="controlnet",
|
||||||
|
version="1.0.0",
|
||||||
|
)
|
||||||
|
class ColorMapImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
|
"""Generates a color map from the provided image"""
|
||||||
|
|
||||||
|
color_map_tile_size: int = InputField(default=64, ge=0, description=FieldDescriptions.tile_size)
|
||||||
|
|
||||||
|
def run_processor(self, image: Image.Image):
|
||||||
|
image = image.convert("RGB")
|
||||||
|
image = np.array(image, dtype=np.uint8)
|
||||||
|
height, width = image.shape[:2]
|
||||||
|
|
||||||
|
width_tile_size = min(self.color_map_tile_size, width)
|
||||||
|
height_tile_size = min(self.color_map_tile_size, height)
|
||||||
|
|
||||||
|
color_map = cv2.resize(
|
||||||
|
image,
|
||||||
|
(width // width_tile_size, height // height_tile_size),
|
||||||
|
interpolation=cv2.INTER_CUBIC,
|
||||||
|
)
|
||||||
|
color_map = cv2.resize(color_map, (width, height), interpolation=cv2.INTER_NEAREST)
|
||||||
|
color_map = Image.fromarray(color_map)
|
||||||
|
return color_map
|
||||||
|
@ -7,13 +7,21 @@ import cv2
|
|||||||
import numpy
|
import numpy
|
||||||
from PIL import Image, ImageChops, ImageFilter, ImageOps
|
from PIL import Image, ImageChops, ImageFilter, ImageOps
|
||||||
|
|
||||||
from invokeai.app.invocations.metadata import CoreMetadata
|
from invokeai.app.invocations.metadata import WithMetadata
|
||||||
from invokeai.app.invocations.primitives import ColorField, ImageField, ImageOutput
|
from invokeai.app.invocations.primitives import BoardField, ColorField, ImageField, ImageOutput
|
||||||
from invokeai.backend.image_util.invisible_watermark import InvisibleWatermark
|
from invokeai.backend.image_util.invisible_watermark import InvisibleWatermark
|
||||||
from invokeai.backend.image_util.safety_checker import SafetyChecker
|
from invokeai.backend.image_util.safety_checker import SafetyChecker
|
||||||
|
|
||||||
from ..models.image import ImageCategory, ResourceOrigin
|
from ..models.image import ImageCategory, ResourceOrigin
|
||||||
from .baseinvocation import BaseInvocation, FieldDescriptions, InputField, InvocationContext, invocation
|
from .baseinvocation import (
|
||||||
|
BaseInvocation,
|
||||||
|
FieldDescriptions,
|
||||||
|
Input,
|
||||||
|
InputField,
|
||||||
|
InvocationContext,
|
||||||
|
WithWorkflow,
|
||||||
|
invocation,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@invocation("show_image", title="Show Image", tags=["image"], category="image", version="1.0.0")
|
@invocation("show_image", title="Show Image", tags=["image"], category="image", version="1.0.0")
|
||||||
@ -37,7 +45,7 @@ class ShowImageInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("blank_image", title="Blank Image", tags=["image"], category="image", version="1.0.0")
|
@invocation("blank_image", title="Blank Image", tags=["image"], category="image", version="1.0.0")
|
||||||
class BlankImageInvocation(BaseInvocation):
|
class BlankImageInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
||||||
"""Creates a blank image and forwards it to the pipeline"""
|
"""Creates a blank image and forwards it to the pipeline"""
|
||||||
|
|
||||||
width: int = InputField(default=512, description="The width of the image")
|
width: int = InputField(default=512, description="The width of the image")
|
||||||
@ -55,6 +63,7 @@ class BlankImageInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,7 +75,7 @@ class BlankImageInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("img_crop", title="Crop Image", tags=["image", "crop"], category="image", version="1.0.0")
|
@invocation("img_crop", title="Crop Image", tags=["image", "crop"], category="image", version="1.0.0")
|
||||||
class ImageCropInvocation(BaseInvocation):
|
class ImageCropInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Crops an image to a specified box. The box can be outside of the image."""
|
"""Crops an image to a specified box. The box can be outside of the image."""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to crop")
|
image: ImageField = InputField(description="The image to crop")
|
||||||
@ -88,6 +97,7 @@ class ImageCropInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -99,7 +109,7 @@ class ImageCropInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("img_paste", title="Paste Image", tags=["image", "paste"], category="image", version="1.0.1")
|
@invocation("img_paste", title="Paste Image", tags=["image", "paste"], category="image", version="1.0.1")
|
||||||
class ImagePasteInvocation(BaseInvocation):
|
class ImagePasteInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Pastes an image into another image."""
|
"""Pastes an image into another image."""
|
||||||
|
|
||||||
base_image: ImageField = InputField(description="The base image")
|
base_image: ImageField = InputField(description="The base image")
|
||||||
@ -141,6 +151,7 @@ class ImagePasteInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -152,7 +163,7 @@ class ImagePasteInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("tomask", title="Mask from Alpha", tags=["image", "mask"], category="image", version="1.0.0")
|
@invocation("tomask", title="Mask from Alpha", tags=["image", "mask"], category="image", version="1.0.0")
|
||||||
class MaskFromAlphaInvocation(BaseInvocation):
|
class MaskFromAlphaInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Extracts the alpha channel of an image as a mask."""
|
"""Extracts the alpha channel of an image as a mask."""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to create the mask from")
|
image: ImageField = InputField(description="The image to create the mask from")
|
||||||
@ -172,6 +183,7 @@ class MaskFromAlphaInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -183,7 +195,7 @@ class MaskFromAlphaInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("img_mul", title="Multiply Images", tags=["image", "multiply"], category="image", version="1.0.0")
|
@invocation("img_mul", title="Multiply Images", tags=["image", "multiply"], category="image", version="1.0.0")
|
||||||
class ImageMultiplyInvocation(BaseInvocation):
|
class ImageMultiplyInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Multiplies two images together using `PIL.ImageChops.multiply()`."""
|
"""Multiplies two images together using `PIL.ImageChops.multiply()`."""
|
||||||
|
|
||||||
image1: ImageField = InputField(description="The first image to multiply")
|
image1: ImageField = InputField(description="The first image to multiply")
|
||||||
@ -202,6 +214,7 @@ class ImageMultiplyInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -216,7 +229,7 @@ IMAGE_CHANNELS = Literal["A", "R", "G", "B"]
|
|||||||
|
|
||||||
|
|
||||||
@invocation("img_chan", title="Extract Image Channel", tags=["image", "channel"], category="image", version="1.0.0")
|
@invocation("img_chan", title="Extract Image Channel", tags=["image", "channel"], category="image", version="1.0.0")
|
||||||
class ImageChannelInvocation(BaseInvocation):
|
class ImageChannelInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Gets a channel from an image."""
|
"""Gets a channel from an image."""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to get the channel from")
|
image: ImageField = InputField(description="The image to get the channel from")
|
||||||
@ -234,6 +247,7 @@ class ImageChannelInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -248,7 +262,7 @@ IMAGE_MODES = Literal["L", "RGB", "RGBA", "CMYK", "YCbCr", "LAB", "HSV", "I", "F
|
|||||||
|
|
||||||
|
|
||||||
@invocation("img_conv", title="Convert Image Mode", tags=["image", "convert"], category="image", version="1.0.0")
|
@invocation("img_conv", title="Convert Image Mode", tags=["image", "convert"], category="image", version="1.0.0")
|
||||||
class ImageConvertInvocation(BaseInvocation):
|
class ImageConvertInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Converts an image to a different mode."""
|
"""Converts an image to a different mode."""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to convert")
|
image: ImageField = InputField(description="The image to convert")
|
||||||
@ -266,6 +280,7 @@ class ImageConvertInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -277,7 +292,7 @@ class ImageConvertInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("img_blur", title="Blur Image", tags=["image", "blur"], category="image", version="1.0.0")
|
@invocation("img_blur", title="Blur Image", tags=["image", "blur"], category="image", version="1.0.0")
|
||||||
class ImageBlurInvocation(BaseInvocation):
|
class ImageBlurInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Blurs an image"""
|
"""Blurs an image"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to blur")
|
image: ImageField = InputField(description="The image to blur")
|
||||||
@ -300,6 +315,7 @@ class ImageBlurInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -331,16 +347,13 @@ PIL_RESAMPLING_MAP = {
|
|||||||
|
|
||||||
|
|
||||||
@invocation("img_resize", title="Resize Image", tags=["image", "resize"], category="image", version="1.0.0")
|
@invocation("img_resize", title="Resize Image", tags=["image", "resize"], category="image", version="1.0.0")
|
||||||
class ImageResizeInvocation(BaseInvocation):
|
class ImageResizeInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
||||||
"""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, gt=0, 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, gt=0, 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(
|
|
||||||
default=None, description=FieldDescriptions.core_metadata, ui_hidden=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
image = context.services.images.get_pil_image(self.image.image_name)
|
image = context.services.images.get_pil_image(self.image.image_name)
|
||||||
@ -359,7 +372,7 @@ class ImageResizeInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
metadata=self.metadata.dict() if self.metadata else None,
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -371,7 +384,7 @@ class ImageResizeInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("img_scale", title="Scale Image", tags=["image", "scale"], category="image", version="1.0.0")
|
@invocation("img_scale", title="Scale Image", tags=["image", "scale"], category="image", version="1.0.0")
|
||||||
class ImageScaleInvocation(BaseInvocation):
|
class ImageScaleInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
||||||
"""Scales an image by a factor"""
|
"""Scales an image by a factor"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to scale")
|
image: ImageField = InputField(description="The image to scale")
|
||||||
@ -401,6 +414,7 @@ class ImageScaleInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -412,7 +426,7 @@ class ImageScaleInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("img_lerp", title="Lerp Image", tags=["image", "lerp"], category="image", version="1.0.0")
|
@invocation("img_lerp", title="Lerp Image", tags=["image", "lerp"], category="image", version="1.0.0")
|
||||||
class ImageLerpInvocation(BaseInvocation):
|
class ImageLerpInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Linear interpolation of all pixels of an image"""
|
"""Linear interpolation of all pixels of an image"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to lerp")
|
image: ImageField = InputField(description="The image to lerp")
|
||||||
@ -434,6 +448,7 @@ class ImageLerpInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -445,7 +460,7 @@ class ImageLerpInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("img_ilerp", title="Inverse Lerp Image", tags=["image", "ilerp"], category="image", version="1.0.0")
|
@invocation("img_ilerp", title="Inverse Lerp Image", tags=["image", "ilerp"], category="image", version="1.0.0")
|
||||||
class ImageInverseLerpInvocation(BaseInvocation):
|
class ImageInverseLerpInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Inverse linear interpolation of all pixels of an image"""
|
"""Inverse linear interpolation of all pixels of an image"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to lerp")
|
image: ImageField = InputField(description="The image to lerp")
|
||||||
@ -467,6 +482,7 @@ class ImageInverseLerpInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -478,13 +494,10 @@ class ImageInverseLerpInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("img_nsfw", title="Blur NSFW Image", tags=["image", "nsfw"], category="image", version="1.0.0")
|
@invocation("img_nsfw", title="Blur NSFW Image", tags=["image", "nsfw"], category="image", version="1.0.0")
|
||||||
class ImageNSFWBlurInvocation(BaseInvocation):
|
class ImageNSFWBlurInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
||||||
"""Add blur to NSFW-flagged images"""
|
"""Add blur to NSFW-flagged images"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to check")
|
image: ImageField = InputField(description="The image to check")
|
||||||
metadata: Optional[CoreMetadata] = InputField(
|
|
||||||
default=None, description=FieldDescriptions.core_metadata, ui_hidden=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
image = context.services.images.get_pil_image(self.image.image_name)
|
image = context.services.images.get_pil_image(self.image.image_name)
|
||||||
@ -505,7 +518,7 @@ class ImageNSFWBlurInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
metadata=self.metadata.dict() if self.metadata else None,
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -525,14 +538,11 @@ class ImageNSFWBlurInvocation(BaseInvocation):
|
|||||||
@invocation(
|
@invocation(
|
||||||
"img_watermark", title="Add Invisible Watermark", tags=["image", "watermark"], category="image", version="1.0.0"
|
"img_watermark", title="Add Invisible Watermark", tags=["image", "watermark"], category="image", version="1.0.0"
|
||||||
)
|
)
|
||||||
class ImageWatermarkInvocation(BaseInvocation):
|
class ImageWatermarkInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
||||||
"""Add an invisible watermark to an image"""
|
"""Add an invisible watermark to an image"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to check")
|
image: ImageField = InputField(description="The image to check")
|
||||||
text: str = InputField(default="InvokeAI", description="Watermark text")
|
text: str = InputField(default="InvokeAI", description="Watermark text")
|
||||||
metadata: Optional[CoreMetadata] = InputField(
|
|
||||||
default=None, description=FieldDescriptions.core_metadata, ui_hidden=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
image = context.services.images.get_pil_image(self.image.image_name)
|
image = context.services.images.get_pil_image(self.image.image_name)
|
||||||
@ -544,7 +554,7 @@ class ImageWatermarkInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
metadata=self.metadata.dict() if self.metadata else None,
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -556,7 +566,7 @@ class ImageWatermarkInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("mask_edge", title="Mask Edge", tags=["image", "mask", "inpaint"], category="image", version="1.0.0")
|
@invocation("mask_edge", title="Mask Edge", tags=["image", "mask", "inpaint"], category="image", version="1.0.0")
|
||||||
class MaskEdgeInvocation(BaseInvocation):
|
class MaskEdgeInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Applies an edge mask to an image"""
|
"""Applies an edge mask to an image"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to apply the mask to")
|
image: ImageField = InputField(description="The image to apply the mask to")
|
||||||
@ -590,6 +600,7 @@ class MaskEdgeInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -603,7 +614,7 @@ class MaskEdgeInvocation(BaseInvocation):
|
|||||||
@invocation(
|
@invocation(
|
||||||
"mask_combine", title="Combine Masks", tags=["image", "mask", "multiply"], category="image", version="1.0.0"
|
"mask_combine", title="Combine Masks", tags=["image", "mask", "multiply"], category="image", version="1.0.0"
|
||||||
)
|
)
|
||||||
class MaskCombineInvocation(BaseInvocation):
|
class MaskCombineInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Combine two masks together by multiplying them using `PIL.ImageChops.multiply()`."""
|
"""Combine two masks together by multiplying them using `PIL.ImageChops.multiply()`."""
|
||||||
|
|
||||||
mask1: ImageField = InputField(description="The first mask to combine")
|
mask1: ImageField = InputField(description="The first mask to combine")
|
||||||
@ -622,6 +633,7 @@ class MaskCombineInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -633,7 +645,7 @@ class MaskCombineInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("color_correct", title="Color Correct", tags=["image", "color"], category="image", version="1.0.0")
|
@invocation("color_correct", title="Color Correct", tags=["image", "color"], category="image", version="1.0.0")
|
||||||
class ColorCorrectInvocation(BaseInvocation):
|
class ColorCorrectInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""
|
"""
|
||||||
Shifts the colors of a target image to match the reference image, optionally
|
Shifts the colors of a target image to match the reference image, optionally
|
||||||
using a mask to only color-correct certain regions of the target image.
|
using a mask to only color-correct certain regions of the target image.
|
||||||
@ -732,6 +744,7 @@ class ColorCorrectInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -743,7 +756,7 @@ class ColorCorrectInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("img_hue_adjust", title="Adjust Image Hue", tags=["image", "hue"], category="image", version="1.0.0")
|
@invocation("img_hue_adjust", title="Adjust Image Hue", tags=["image", "hue"], category="image", version="1.0.0")
|
||||||
class ImageHueAdjustmentInvocation(BaseInvocation):
|
class ImageHueAdjustmentInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Adjusts the Hue of an image."""
|
"""Adjusts the Hue of an image."""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to adjust")
|
image: ImageField = InputField(description="The image to adjust")
|
||||||
@ -771,6 +784,7 @@ class ImageHueAdjustmentInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -846,7 +860,7 @@ CHANNEL_FORMATS = {
|
|||||||
category="image",
|
category="image",
|
||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
)
|
)
|
||||||
class ImageChannelOffsetInvocation(BaseInvocation):
|
class ImageChannelOffsetInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Add or subtract a value from a specific color channel of an image."""
|
"""Add or subtract a value from a specific color channel of an image."""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to adjust")
|
image: ImageField = InputField(description="The image to adjust")
|
||||||
@ -880,6 +894,7 @@ class ImageChannelOffsetInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -916,7 +931,7 @@ class ImageChannelOffsetInvocation(BaseInvocation):
|
|||||||
category="image",
|
category="image",
|
||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
)
|
)
|
||||||
class ImageChannelMultiplyInvocation(BaseInvocation):
|
class ImageChannelMultiplyInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Scale a specific color channel of an image."""
|
"""Scale a specific color channel of an image."""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to adjust")
|
image: ImageField = InputField(description="The image to adjust")
|
||||||
@ -956,6 +971,7 @@ class ImageChannelMultiplyInvocation(BaseInvocation):
|
|||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
return ImageOutput(
|
return ImageOutput(
|
||||||
@ -972,18 +988,14 @@ class ImageChannelMultiplyInvocation(BaseInvocation):
|
|||||||
title="Save Image",
|
title="Save Image",
|
||||||
tags=["primitives", "image"],
|
tags=["primitives", "image"],
|
||||||
category="primitives",
|
category="primitives",
|
||||||
version="1.0.0",
|
version="1.0.1",
|
||||||
use_cache=False,
|
use_cache=False,
|
||||||
)
|
)
|
||||||
class SaveImageInvocation(BaseInvocation):
|
class SaveImageInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Saves an image. Unlike an image primitive, this invocation stores a copy of the image."""
|
"""Saves an image. Unlike an image primitive, this invocation stores a copy of the image."""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to load")
|
image: ImageField = InputField(description=FieldDescriptions.image)
|
||||||
metadata: CoreMetadata = InputField(
|
board: Optional[BoardField] = InputField(default=None, description=FieldDescriptions.board, input=Input.Direct)
|
||||||
default=None,
|
|
||||||
description=FieldDescriptions.core_metadata,
|
|
||||||
ui_hidden=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
image = context.services.images.get_pil_image(self.image.image_name)
|
image = context.services.images.get_pil_image(self.image.image_name)
|
||||||
@ -992,10 +1004,11 @@ class SaveImageInvocation(BaseInvocation):
|
|||||||
image=image,
|
image=image,
|
||||||
image_origin=ResourceOrigin.INTERNAL,
|
image_origin=ResourceOrigin.INTERNAL,
|
||||||
image_category=ImageCategory.GENERAL,
|
image_category=ImageCategory.GENERAL,
|
||||||
|
board_id=self.board.board_id if self.board else None,
|
||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
metadata=self.metadata.dict() if self.metadata else None,
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ from typing import Literal, Optional, get_args
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
|
from invokeai.app.invocations.metadata import WithMetadata
|
||||||
|
|
||||||
from invokeai.app.invocations.primitives import ColorField, ImageField, ImageOutput
|
from invokeai.app.invocations.primitives import ColorField, ImageField, ImageOutput
|
||||||
from invokeai.app.util.misc import SEED_MAX, get_random_seed
|
from invokeai.app.util.misc import SEED_MAX, get_random_seed
|
||||||
@ -13,7 +14,7 @@ from invokeai.backend.image_util.lama import LaMA
|
|||||||
from invokeai.backend.image_util.patchmatch import PatchMatch
|
from invokeai.backend.image_util.patchmatch import PatchMatch
|
||||||
|
|
||||||
from ..models.image import ImageCategory, ResourceOrigin
|
from ..models.image import ImageCategory, ResourceOrigin
|
||||||
from .baseinvocation import BaseInvocation, InputField, InvocationContext, invocation
|
from .baseinvocation import BaseInvocation, InputField, InvocationContext, WithWorkflow, invocation
|
||||||
from .image import PIL_RESAMPLING_MAP, PIL_RESAMPLING_MODES
|
from .image import PIL_RESAMPLING_MAP, PIL_RESAMPLING_MODES
|
||||||
|
|
||||||
|
|
||||||
@ -119,7 +120,7 @@ def tile_fill_missing(im: Image.Image, tile_size: int = 16, seed: Optional[int]
|
|||||||
|
|
||||||
|
|
||||||
@invocation("infill_rgba", title="Solid Color Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0")
|
@invocation("infill_rgba", title="Solid Color Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0")
|
||||||
class InfillColorInvocation(BaseInvocation):
|
class InfillColorInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Infills transparent areas of an image with a solid color"""
|
"""Infills transparent areas of an image with a solid color"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to infill")
|
image: ImageField = InputField(description="The image to infill")
|
||||||
@ -143,6 +144,7 @@ class InfillColorInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -154,7 +156,7 @@ class InfillColorInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("infill_tile", title="Tile Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0")
|
@invocation("infill_tile", title="Tile Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0")
|
||||||
class InfillTileInvocation(BaseInvocation):
|
class InfillTileInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Infills transparent areas of an image with tiles of the image"""
|
"""Infills transparent areas of an image with tiles of the image"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to infill")
|
image: ImageField = InputField(description="The image to infill")
|
||||||
@ -179,6 +181,7 @@ class InfillTileInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -192,7 +195,7 @@ class InfillTileInvocation(BaseInvocation):
|
|||||||
@invocation(
|
@invocation(
|
||||||
"infill_patchmatch", title="PatchMatch Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0"
|
"infill_patchmatch", title="PatchMatch Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0"
|
||||||
)
|
)
|
||||||
class InfillPatchMatchInvocation(BaseInvocation):
|
class InfillPatchMatchInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Infills transparent areas of an image using the PatchMatch algorithm"""
|
"""Infills transparent areas of an image using the PatchMatch algorithm"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to infill")
|
image: ImageField = InputField(description="The image to infill")
|
||||||
@ -232,6 +235,7 @@ class InfillPatchMatchInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -243,7 +247,7 @@ class InfillPatchMatchInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("infill_lama", title="LaMa Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0")
|
@invocation("infill_lama", title="LaMa Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0")
|
||||||
class LaMaInfillInvocation(BaseInvocation):
|
class LaMaInfillInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Infills transparent areas of an image using the LaMa model"""
|
"""Infills transparent areas of an image using the LaMa model"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to infill")
|
image: ImageField = InputField(description="The image to infill")
|
||||||
@ -260,6 +264,8 @@ class LaMaInfillInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
return ImageOutput(
|
return ImageOutput(
|
||||||
@ -270,7 +276,7 @@ class LaMaInfillInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("infill_cv2", title="CV2 Infill", tags=["image", "inpaint"], category="inpaint")
|
@invocation("infill_cv2", title="CV2 Infill", tags=["image", "inpaint"], category="inpaint")
|
||||||
class CV2InfillInvocation(BaseInvocation):
|
class CV2InfillInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Infills transparent areas of an image using OpenCV Inpainting"""
|
"""Infills transparent areas of an image using OpenCV Inpainting"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to infill")
|
image: ImageField = InputField(description="The image to infill")
|
||||||
@ -287,6 +293,8 @@ class CV2InfillInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
return ImageOutput(
|
return ImageOutput(
|
||||||
|
@ -23,7 +23,7 @@ from pydantic import validator
|
|||||||
from torchvision.transforms.functional import resize as tv_resize
|
from torchvision.transforms.functional import resize as tv_resize
|
||||||
|
|
||||||
from invokeai.app.invocations.ip_adapter import IPAdapterField
|
from invokeai.app.invocations.ip_adapter import IPAdapterField
|
||||||
from invokeai.app.invocations.metadata import CoreMetadata
|
from invokeai.app.invocations.metadata import WithMetadata
|
||||||
from invokeai.app.invocations.primitives import (
|
from invokeai.app.invocations.primitives import (
|
||||||
DenoiseMaskField,
|
DenoiseMaskField,
|
||||||
DenoiseMaskOutput,
|
DenoiseMaskOutput,
|
||||||
@ -62,6 +62,7 @@ from .baseinvocation import (
|
|||||||
InvocationContext,
|
InvocationContext,
|
||||||
OutputField,
|
OutputField,
|
||||||
UIType,
|
UIType,
|
||||||
|
WithWorkflow,
|
||||||
invocation,
|
invocation,
|
||||||
invocation_output,
|
invocation_output,
|
||||||
)
|
)
|
||||||
@ -621,7 +622,7 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
|||||||
@invocation(
|
@invocation(
|
||||||
"l2i", title="Latents to Image", tags=["latents", "image", "vae", "l2i"], category="latents", version="1.0.0"
|
"l2i", title="Latents to Image", tags=["latents", "image", "vae", "l2i"], category="latents", version="1.0.0"
|
||||||
)
|
)
|
||||||
class LatentsToImageInvocation(BaseInvocation):
|
class LatentsToImageInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
||||||
"""Generates an image from latents."""
|
"""Generates an image from latents."""
|
||||||
|
|
||||||
latents: LatentsField = InputField(
|
latents: LatentsField = InputField(
|
||||||
@ -634,11 +635,6 @@ class LatentsToImageInvocation(BaseInvocation):
|
|||||||
)
|
)
|
||||||
tiled: bool = InputField(default=False, description=FieldDescriptions.tiled)
|
tiled: bool = InputField(default=False, description=FieldDescriptions.tiled)
|
||||||
fp32: bool = InputField(default=DEFAULT_PRECISION == "float32", description=FieldDescriptions.fp32)
|
fp32: bool = InputField(default=DEFAULT_PRECISION == "float32", description=FieldDescriptions.fp32)
|
||||||
metadata: CoreMetadata = InputField(
|
|
||||||
default=None,
|
|
||||||
description=FieldDescriptions.core_metadata,
|
|
||||||
ui_hidden=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
@ -707,7 +703,7 @@ class LatentsToImageInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
metadata=self.metadata.dict() if self.metadata else None,
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
from typing import Optional
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from invokeai.app.invocations.baseinvocation import (
|
from invokeai.app.invocations.baseinvocation import (
|
||||||
BaseInvocation,
|
BaseInvocation,
|
||||||
BaseInvocationOutput,
|
BaseInvocationOutput,
|
||||||
|
FieldDescriptions,
|
||||||
InputField,
|
InputField,
|
||||||
InvocationContext,
|
InvocationContext,
|
||||||
OutputField,
|
OutputField,
|
||||||
|
UIType,
|
||||||
invocation,
|
invocation,
|
||||||
invocation_output,
|
invocation_output,
|
||||||
)
|
)
|
||||||
from invokeai.app.invocations.controlnet_image_processors import ControlField
|
from invokeai.app.invocations.model import LoRAModelField
|
||||||
from invokeai.app.invocations.model import LoRAModelField, MainModelField, VAEModelField
|
|
||||||
from invokeai.app.util.model_exclude_null import BaseModelExcludeNull
|
from invokeai.app.util.model_exclude_null import BaseModelExcludeNull
|
||||||
|
|
||||||
from ...version import __version__
|
from ...version import __version__
|
||||||
@ -25,159 +26,78 @@ class LoRAMetadataField(BaseModelExcludeNull):
|
|||||||
weight: float = Field(description="The weight of the LoRA model")
|
weight: float = Field(description="The weight of the LoRA model")
|
||||||
|
|
||||||
|
|
||||||
class CoreMetadata(BaseModelExcludeNull):
|
|
||||||
"""Core generation metadata for an image generated in InvokeAI."""
|
|
||||||
|
|
||||||
app_version: str = Field(default=__version__, description="The version of InvokeAI used to generate this image")
|
|
||||||
generation_mode: str = Field(
|
|
||||||
description="The generation mode that output this image",
|
|
||||||
)
|
|
||||||
created_by: Optional[str] = Field(description="The name of the creator of the image")
|
|
||||||
positive_prompt: str = Field(description="The positive prompt parameter")
|
|
||||||
negative_prompt: str = Field(description="The negative prompt parameter")
|
|
||||||
width: int = Field(description="The width parameter")
|
|
||||||
height: int = Field(description="The height parameter")
|
|
||||||
seed: int = Field(description="The seed used for noise generation")
|
|
||||||
rand_device: str = Field(description="The device used for random number generation")
|
|
||||||
cfg_scale: float = Field(description="The classifier-free guidance scale parameter")
|
|
||||||
steps: int = Field(description="The number of steps used for inference")
|
|
||||||
scheduler: str = Field(description="The scheduler used for inference")
|
|
||||||
clip_skip: Optional[int] = Field(
|
|
||||||
default=None,
|
|
||||||
description="The number of skipped CLIP layers",
|
|
||||||
)
|
|
||||||
model: MainModelField = Field(description="The main model used for inference")
|
|
||||||
controlnets: list[ControlField] = Field(description="The ControlNets used for inference")
|
|
||||||
loras: list[LoRAMetadataField] = Field(description="The LoRAs used for inference")
|
|
||||||
vae: Optional[VAEModelField] = Field(
|
|
||||||
default=None,
|
|
||||||
description="The VAE used for decoding, if the main model's default was not used",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Latents-to-Latents
|
|
||||||
strength: Optional[float] = Field(
|
|
||||||
default=None,
|
|
||||||
description="The strength used for latents-to-latents",
|
|
||||||
)
|
|
||||||
init_image: Optional[str] = Field(default=None, description="The name of the initial image")
|
|
||||||
|
|
||||||
# SDXL
|
|
||||||
positive_style_prompt: Optional[str] = Field(default=None, description="The positive style prompt parameter")
|
|
||||||
negative_style_prompt: Optional[str] = Field(default=None, description="The negative style prompt parameter")
|
|
||||||
|
|
||||||
# SDXL Refiner
|
|
||||||
refiner_model: Optional[MainModelField] = Field(default=None, description="The SDXL Refiner model used")
|
|
||||||
refiner_cfg_scale: Optional[float] = Field(
|
|
||||||
default=None,
|
|
||||||
description="The classifier-free guidance scale parameter used for the refiner",
|
|
||||||
)
|
|
||||||
refiner_steps: Optional[int] = Field(default=None, description="The number of steps used for the refiner")
|
|
||||||
refiner_scheduler: Optional[str] = Field(default=None, description="The scheduler used for the refiner")
|
|
||||||
refiner_positive_aesthetic_score: Optional[float] = Field(
|
|
||||||
default=None, description="The aesthetic score used for the refiner"
|
|
||||||
)
|
|
||||||
refiner_negative_aesthetic_score: Optional[float] = Field(
|
|
||||||
default=None, description="The aesthetic score used for the refiner"
|
|
||||||
)
|
|
||||||
refiner_start: Optional[float] = Field(default=None, description="The start value used for refiner denoising")
|
|
||||||
|
|
||||||
|
|
||||||
class ImageMetadata(BaseModelExcludeNull):
|
class ImageMetadata(BaseModelExcludeNull):
|
||||||
"""An image's generation metadata"""
|
"""An image's generation metadata"""
|
||||||
|
|
||||||
metadata: Optional[dict] = Field(
|
metadata: Optional[dict] = Field(default=None, description="The metadata associated with the image")
|
||||||
default=None,
|
workflow: Optional[dict] = Field(default=None, description="The workflow associated with the image")
|
||||||
description="The image's core metadata, if it was created in the Linear or Canvas UI",
|
|
||||||
)
|
|
||||||
graph: Optional[dict] = Field(default=None, description="The graph that created the image")
|
|
||||||
|
|
||||||
|
|
||||||
@invocation_output("metadata_accumulator_output")
|
class MetadataItem(BaseModel):
|
||||||
class MetadataAccumulatorOutput(BaseInvocationOutput):
|
label: str = Field(description=FieldDescriptions.metadata_item_label)
|
||||||
"""The output of the MetadataAccumulator node"""
|
value: Any = Field(description=FieldDescriptions.metadata_item_value)
|
||||||
|
|
||||||
metadata: CoreMetadata = OutputField(description="The core metadata for the image")
|
|
||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation_output("metadata_item_output")
|
||||||
"metadata_accumulator", title="Metadata Accumulator", tags=["metadata"], category="metadata", version="1.0.0"
|
class MetadataItemOutput(BaseInvocationOutput):
|
||||||
)
|
"""Metadata Item Output"""
|
||||||
class MetadataAccumulatorInvocation(BaseInvocation):
|
|
||||||
"""Outputs a Core Metadata Object"""
|
|
||||||
|
|
||||||
generation_mode: str = InputField(
|
item: MetadataItem = OutputField(description="Metadata Item")
|
||||||
description="The generation mode that output this image",
|
|
||||||
)
|
|
||||||
positive_prompt: str = InputField(description="The positive prompt parameter")
|
|
||||||
negative_prompt: str = InputField(description="The negative prompt parameter")
|
|
||||||
width: int = InputField(description="The width parameter")
|
|
||||||
height: int = InputField(description="The height parameter")
|
|
||||||
seed: int = InputField(description="The seed used for noise generation")
|
|
||||||
rand_device: str = InputField(description="The device used for random number generation")
|
|
||||||
cfg_scale: float = InputField(description="The classifier-free guidance scale parameter")
|
|
||||||
steps: int = InputField(description="The number of steps used for inference")
|
|
||||||
scheduler: str = InputField(description="The scheduler used for inference")
|
|
||||||
clip_skip: Optional[int] = Field(
|
|
||||||
default=None,
|
|
||||||
description="The number of skipped CLIP layers",
|
|
||||||
)
|
|
||||||
model: MainModelField = InputField(description="The main model used for inference")
|
|
||||||
controlnets: list[ControlField] = InputField(description="The ControlNets used for inference")
|
|
||||||
loras: list[LoRAMetadataField] = InputField(description="The LoRAs used for inference")
|
|
||||||
strength: Optional[float] = InputField(
|
|
||||||
default=None,
|
|
||||||
description="The strength used for latents-to-latents",
|
|
||||||
)
|
|
||||||
init_image: Optional[str] = InputField(
|
|
||||||
default=None,
|
|
||||||
description="The name of the initial image",
|
|
||||||
)
|
|
||||||
vae: Optional[VAEModelField] = InputField(
|
|
||||||
default=None,
|
|
||||||
description="The VAE used for decoding, if the main model's default was not used",
|
|
||||||
)
|
|
||||||
|
|
||||||
# SDXL
|
|
||||||
positive_style_prompt: Optional[str] = InputField(
|
|
||||||
default=None,
|
|
||||||
description="The positive style prompt parameter",
|
|
||||||
)
|
|
||||||
negative_style_prompt: Optional[str] = InputField(
|
|
||||||
default=None,
|
|
||||||
description="The negative style prompt parameter",
|
|
||||||
)
|
|
||||||
|
|
||||||
# SDXL Refiner
|
@invocation("metadata_item", title="Metadata Item", tags=["metadata"], category="metadata", version="1.0.0")
|
||||||
refiner_model: Optional[MainModelField] = InputField(
|
class MetadataItemInvocation(BaseInvocation):
|
||||||
default=None,
|
"""Used to create an arbitrary metadata item. Provide "label" and make a connection to "value" to store that data as the value."""
|
||||||
description="The SDXL Refiner model used",
|
|
||||||
)
|
|
||||||
refiner_cfg_scale: Optional[float] = InputField(
|
|
||||||
default=None,
|
|
||||||
description="The classifier-free guidance scale parameter used for the refiner",
|
|
||||||
)
|
|
||||||
refiner_steps: Optional[int] = InputField(
|
|
||||||
default=None,
|
|
||||||
description="The number of steps used for the refiner",
|
|
||||||
)
|
|
||||||
refiner_scheduler: Optional[str] = InputField(
|
|
||||||
default=None,
|
|
||||||
description="The scheduler used for the refiner",
|
|
||||||
)
|
|
||||||
refiner_positive_aesthetic_score: Optional[float] = InputField(
|
|
||||||
default=None,
|
|
||||||
description="The aesthetic score used for the refiner",
|
|
||||||
)
|
|
||||||
refiner_negative_aesthetic_score: Optional[float] = InputField(
|
|
||||||
default=None,
|
|
||||||
description="The aesthetic score used for the refiner",
|
|
||||||
)
|
|
||||||
refiner_start: Optional[float] = InputField(
|
|
||||||
default=None,
|
|
||||||
description="The start value used for refiner denoising",
|
|
||||||
)
|
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> MetadataAccumulatorOutput:
|
label: str = InputField(description=FieldDescriptions.metadata_item_label)
|
||||||
"""Collects and outputs a CoreMetadata object"""
|
value: Any = InputField(description=FieldDescriptions.metadata_item_value, ui_type=UIType.Any)
|
||||||
|
|
||||||
return MetadataAccumulatorOutput(metadata=CoreMetadata(**self.dict()))
|
def invoke(self, context: InvocationContext) -> MetadataItemOutput:
|
||||||
|
return MetadataItemOutput(item=MetadataItem(label=self.label, value=self.value))
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataDict(BaseModel):
|
||||||
|
"""Accepts a single MetadataItem or collection of MetadataItems (use a Collect node)."""
|
||||||
|
|
||||||
|
data: dict[str, Any] = Field(description="Metadata dict")
|
||||||
|
|
||||||
|
|
||||||
|
@invocation_output("metadata_dict")
|
||||||
|
class MetadataDictOutput(BaseInvocationOutput):
|
||||||
|
metadata_dict: MetadataDict = OutputField(description="Metadata Dict")
|
||||||
|
|
||||||
|
|
||||||
|
@invocation("metadata", title="Metadata", tags=["metadata"], category="metadata", version="1.0.0")
|
||||||
|
class MetadataInvocation(BaseInvocation):
|
||||||
|
"""Takes a MetadataItem or collection of MetadataItems and outputs a MetadataDict."""
|
||||||
|
|
||||||
|
items: Union[list[MetadataItem], MetadataItem] = InputField(description=FieldDescriptions.metadata_item_polymorphic)
|
||||||
|
|
||||||
|
def invoke(self, context: InvocationContext) -> MetadataDictOutput:
|
||||||
|
if isinstance(self.items, MetadataItem):
|
||||||
|
# single metadata item
|
||||||
|
data = {self.items.label: self.items.value}
|
||||||
|
else:
|
||||||
|
# collection of metadata items
|
||||||
|
data = {item.label: item.value for item in self.items}
|
||||||
|
|
||||||
|
data.update({"app_version": __version__})
|
||||||
|
return MetadataDictOutput(metadata_dict=(MetadataDict(data=data)))
|
||||||
|
|
||||||
|
|
||||||
|
@invocation("merge_metadata_dict", title="Metadata Merge", tags=["metadata"], category="metadata", version="1.0.0")
|
||||||
|
class MergeMetadataDictInvocation(BaseInvocation):
|
||||||
|
"""Merged a collection of MetadataDict into a single MetadataDict."""
|
||||||
|
|
||||||
|
collection: list[MetadataDict] = InputField(description=FieldDescriptions.metadata_dict_collection)
|
||||||
|
|
||||||
|
def invoke(self, context: InvocationContext) -> MetadataDictOutput:
|
||||||
|
data = {}
|
||||||
|
for item in self.collection:
|
||||||
|
data.update(item.data)
|
||||||
|
|
||||||
|
return MetadataDictOutput(metadata_dict=MetadataDict(data=data))
|
||||||
|
|
||||||
|
|
||||||
|
class WithMetadata(BaseModel):
|
||||||
|
metadata: Optional[MetadataDict] = InputField(default=None, description=FieldDescriptions.metadata)
|
||||||
|
@ -12,7 +12,7 @@ from diffusers.image_processor import VaeImageProcessor
|
|||||||
from pydantic import BaseModel, Field, validator
|
from pydantic import BaseModel, Field, validator
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
from invokeai.app.invocations.metadata import CoreMetadata
|
from invokeai.app.invocations.metadata import WithMetadata
|
||||||
from invokeai.app.invocations.primitives import ConditioningField, ConditioningOutput, ImageField, ImageOutput
|
from invokeai.app.invocations.primitives import ConditioningField, ConditioningOutput, ImageField, ImageOutput
|
||||||
from invokeai.app.util.step_callback import stable_diffusion_step_callback
|
from invokeai.app.util.step_callback import stable_diffusion_step_callback
|
||||||
from invokeai.backend import BaseModelType, ModelType, SubModelType
|
from invokeai.backend import BaseModelType, ModelType, SubModelType
|
||||||
@ -28,6 +28,7 @@ from .baseinvocation import (
|
|||||||
Input,
|
Input,
|
||||||
InputField,
|
InputField,
|
||||||
InvocationContext,
|
InvocationContext,
|
||||||
|
WithWorkflow,
|
||||||
OutputField,
|
OutputField,
|
||||||
UIComponent,
|
UIComponent,
|
||||||
UIType,
|
UIType,
|
||||||
@ -321,7 +322,7 @@ class ONNXTextToLatentsInvocation(BaseInvocation):
|
|||||||
category="image",
|
category="image",
|
||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
)
|
)
|
||||||
class ONNXLatentsToImageInvocation(BaseInvocation):
|
class ONNXLatentsToImageInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
||||||
"""Generates an image from latents."""
|
"""Generates an image from latents."""
|
||||||
|
|
||||||
latents: LatentsField = InputField(
|
latents: LatentsField = InputField(
|
||||||
@ -332,11 +333,6 @@ class ONNXLatentsToImageInvocation(BaseInvocation):
|
|||||||
description=FieldDescriptions.vae,
|
description=FieldDescriptions.vae,
|
||||||
input=Input.Connection,
|
input=Input.Connection,
|
||||||
)
|
)
|
||||||
metadata: Optional[CoreMetadata] = InputField(
|
|
||||||
default=None,
|
|
||||||
description=FieldDescriptions.core_metadata,
|
|
||||||
ui_hidden=True,
|
|
||||||
)
|
|
||||||
# tiled: bool = InputField(default=False, description="Decode latents by overlaping tiles(less memory consumption)")
|
# tiled: bool = InputField(default=False, description="Decode latents by overlaping tiles(less memory consumption)")
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
@ -375,7 +371,7 @@ class ONNXLatentsToImageInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
metadata=self.metadata.dict() if self.metadata else None,
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -226,6 +226,12 @@ class ImageField(BaseModel):
|
|||||||
image_name: str = Field(description="The name of the image")
|
image_name: str = Field(description="The name of the image")
|
||||||
|
|
||||||
|
|
||||||
|
class BoardField(BaseModel):
|
||||||
|
"""A board primitive field"""
|
||||||
|
|
||||||
|
board_id: str = Field(description="The id of the board")
|
||||||
|
|
||||||
|
|
||||||
@invocation_output("image_output")
|
@invocation_output("image_output")
|
||||||
class ImageOutput(BaseInvocationOutput):
|
class ImageOutput(BaseInvocationOutput):
|
||||||
"""Base class for nodes that output a single image"""
|
"""Base class for nodes that output a single image"""
|
||||||
@ -245,7 +251,9 @@ class ImageCollectionOutput(BaseInvocationOutput):
|
|||||||
|
|
||||||
|
|
||||||
@invocation("image", title="Image Primitive", tags=["primitives", "image"], category="primitives", version="1.0.0")
|
@invocation("image", title="Image Primitive", tags=["primitives", "image"], category="primitives", version="1.0.0")
|
||||||
class ImageInvocation(BaseInvocation):
|
class ImageInvocation(
|
||||||
|
BaseInvocation,
|
||||||
|
):
|
||||||
"""An image primitive value"""
|
"""An image primitive value"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to load")
|
image: ImageField = InputField(description="The image to load")
|
||||||
|
@ -7,11 +7,12 @@ import numpy as np
|
|||||||
from basicsr.archs.rrdbnet_arch import RRDBNet
|
from basicsr.archs.rrdbnet_arch import RRDBNet
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from realesrgan import RealESRGANer
|
from realesrgan import RealESRGANer
|
||||||
|
from invokeai.app.invocations.metadata import WithMetadata
|
||||||
|
|
||||||
from invokeai.app.invocations.primitives import ImageField, ImageOutput
|
from invokeai.app.invocations.primitives import ImageField, ImageOutput
|
||||||
from invokeai.app.models.image import ImageCategory, ResourceOrigin
|
from invokeai.app.models.image import ImageCategory, ResourceOrigin
|
||||||
|
|
||||||
from .baseinvocation import BaseInvocation, InputField, InvocationContext, invocation
|
from .baseinvocation import BaseInvocation, InputField, InvocationContext, WithWorkflow, invocation
|
||||||
|
|
||||||
# TODO: Populate this from disk?
|
# TODO: Populate this from disk?
|
||||||
# TODO: Use model manager to load?
|
# TODO: Use model manager to load?
|
||||||
@ -24,7 +25,7 @@ ESRGAN_MODELS = Literal[
|
|||||||
|
|
||||||
|
|
||||||
@invocation("esrgan", title="Upscale (RealESRGAN)", tags=["esrgan", "upscale"], category="esrgan", version="1.0.0")
|
@invocation("esrgan", title="Upscale (RealESRGAN)", tags=["esrgan", "upscale"], category="esrgan", version="1.0.0")
|
||||||
class ESRGANInvocation(BaseInvocation):
|
class ESRGANInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
||||||
"""Upscales an image using RealESRGAN."""
|
"""Upscales an image using RealESRGAN."""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The input image")
|
image: ImageField = InputField(description="The input image")
|
||||||
@ -106,6 +107,7 @@ class ESRGANInvocation(BaseInvocation):
|
|||||||
node_id=self.id,
|
node_id=self.id,
|
||||||
session_id=context.graph_execution_state_id,
|
session_id=context.graph_execution_state_id,
|
||||||
is_intermediate=self.is_intermediate,
|
is_intermediate=self.is_intermediate,
|
||||||
|
metadata=self.metadata.data if self.metadata else None,
|
||||||
workflow=self.workflow,
|
workflow=self.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -421,6 +421,14 @@ class Graph(BaseModel):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _is_destination_field_Any(self, edge: Edge) -> bool:
|
||||||
|
"""Checks if the destination field for an edge is of type typing.Any"""
|
||||||
|
return get_input_field(self.get_node(edge.destination.node_id), edge.destination.field) == Any
|
||||||
|
|
||||||
|
def _is_destination_field_list_of_Any(self, edge: Edge) -> bool:
|
||||||
|
"""Checks if the destination field for an edge is of type typing.Any"""
|
||||||
|
return get_input_field(self.get_node(edge.destination.node_id), edge.destination.field) == list[Any]
|
||||||
|
|
||||||
def _validate_edge(self, edge: Edge):
|
def _validate_edge(self, edge: Edge):
|
||||||
"""Validates that a new edge doesn't create a cycle in the graph"""
|
"""Validates that a new edge doesn't create a cycle in the graph"""
|
||||||
|
|
||||||
@ -473,8 +481,19 @@ class Graph(BaseModel):
|
|||||||
f"Collector output type does not match collector input type: {edge.source.node_id}.{edge.source.field} to {edge.destination.node_id}.{edge.destination.field}"
|
f"Collector output type does not match collector input type: {edge.source.node_id}.{edge.source.field} to {edge.destination.node_id}.{edge.destination.field}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate if collector output type matches input type (if this edge results in both being set)
|
# Validate that we are not connecting collector to iterator (currently unsupported)
|
||||||
if isinstance(from_node, CollectInvocation) and edge.source.field == "collection":
|
if isinstance(from_node, CollectInvocation) and isinstance(to_node, IterateInvocation):
|
||||||
|
raise InvalidEdgeError(
|
||||||
|
f"Cannot connect collector to iterator: {edge.source.node_id}.{edge.source.field} to {edge.destination.node_id}.{edge.destination.field}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate if collector output type matches input type (if this edge results in both being set) - skip if the destination field is not Any or list[Any]
|
||||||
|
if (
|
||||||
|
isinstance(from_node, CollectInvocation)
|
||||||
|
and edge.source.field == "collection"
|
||||||
|
and not self._is_destination_field_list_of_Any(edge)
|
||||||
|
and not self._is_destination_field_Any(edge)
|
||||||
|
):
|
||||||
if not self._is_collector_connection_valid(edge.source.node_id, new_output=edge.destination):
|
if not self._is_collector_connection_valid(edge.source.node_id, new_output=edge.destination):
|
||||||
raise InvalidEdgeError(
|
raise InvalidEdgeError(
|
||||||
f"Collector input type does not match collector output type: {edge.source.node_id}.{edge.source.field} to {edge.destination.node_id}.{edge.destination.field}"
|
f"Collector input type does not match collector output type: {edge.source.node_id}.{edge.source.field} to {edge.destination.node_id}.{edge.destination.field}"
|
||||||
@ -707,16 +726,15 @@ class Graph(BaseModel):
|
|||||||
# Get the input root type
|
# Get the input root type
|
||||||
input_root_type = next(t[0] for t in type_degrees if t[1] == 0) # type: ignore
|
input_root_type = next(t[0] for t in type_degrees if t[1] == 0) # type: ignore
|
||||||
|
|
||||||
# Verify that all outputs are lists
|
|
||||||
# if not all((get_origin(f) == list for f in output_fields)):
|
|
||||||
# return False
|
|
||||||
|
|
||||||
# Verify that all outputs are lists
|
# Verify that all outputs are lists
|
||||||
if not all(is_list_or_contains_list(f) for f in output_fields):
|
if not all(is_list_or_contains_list(f) for f in output_fields):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Verify that all outputs match the input type (are a base class or the same class)
|
# Verify that all outputs match the input type (are a base class or the same class)
|
||||||
if not all((issubclass(input_root_type, get_args(f)[0]) for f in output_fields)):
|
if not all(
|
||||||
|
is_union_subtype(input_root_type, get_args(f)[0]) or issubclass(input_root_type, get_args(f)[0])
|
||||||
|
for f in output_fields
|
||||||
|
):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -59,7 +59,7 @@ class ImageFileStorageBase(ABC):
|
|||||||
self,
|
self,
|
||||||
image: PILImageType,
|
image: PILImageType,
|
||||||
image_name: str,
|
image_name: str,
|
||||||
metadata: Optional[dict] = None,
|
metadata: Optional[Union[str, dict]] = None,
|
||||||
workflow: Optional[str] = None,
|
workflow: Optional[str] = None,
|
||||||
thumbnail_size: int = 256,
|
thumbnail_size: int = 256,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -109,7 +109,7 @@ class DiskImageFileStorage(ImageFileStorageBase):
|
|||||||
self,
|
self,
|
||||||
image: PILImageType,
|
image: PILImageType,
|
||||||
image_name: str,
|
image_name: str,
|
||||||
metadata: Optional[dict] = None,
|
metadata: Optional[Union[str, dict]] = None,
|
||||||
workflow: Optional[str] = None,
|
workflow: Optional[str] = None,
|
||||||
thumbnail_size: int = 256,
|
thumbnail_size: int = 256,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -119,20 +119,10 @@ class DiskImageFileStorage(ImageFileStorageBase):
|
|||||||
|
|
||||||
pnginfo = PngImagePlugin.PngInfo()
|
pnginfo = PngImagePlugin.PngInfo()
|
||||||
|
|
||||||
if metadata is not None or workflow is not None:
|
if metadata is not None:
|
||||||
if metadata is not None:
|
pnginfo.add_text("invokeai_metadata", json.dumps(metadata) if type(metadata) is dict else metadata)
|
||||||
pnginfo.add_text("invokeai_metadata", json.dumps(metadata))
|
if workflow is not None:
|
||||||
if workflow is not None:
|
pnginfo.add_text("invokeai_workflow", workflow)
|
||||||
pnginfo.add_text("invokeai_workflow", workflow)
|
|
||||||
else:
|
|
||||||
# For uploaded images, we want to retain metadata. PIL strips it on save; manually add it back
|
|
||||||
# TODO: retain non-invokeai metadata on save...
|
|
||||||
original_metadata = image.info.get("invokeai_metadata", None)
|
|
||||||
if original_metadata is not None:
|
|
||||||
pnginfo.add_text("invokeai_metadata", original_metadata)
|
|
||||||
original_workflow = image.info.get("invokeai_workflow", None)
|
|
||||||
if original_workflow is not None:
|
|
||||||
pnginfo.add_text("invokeai_workflow", original_workflow)
|
|
||||||
|
|
||||||
image.save(image_path, "PNG", pnginfo=pnginfo)
|
image.save(image_path, "PNG", pnginfo=pnginfo)
|
||||||
|
|
||||||
|
@ -3,11 +3,12 @@ import sqlite3
|
|||||||
import threading
|
import threading
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Generic, Optional, TypeVar, cast
|
from typing import Generic, Optional, TypeVar, Union, cast
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from pydantic.generics import GenericModel
|
from pydantic.generics import GenericModel
|
||||||
|
|
||||||
|
from invokeai.app.invocations.metadata import ImageMetadata
|
||||||
from invokeai.app.models.image import ImageCategory, ResourceOrigin
|
from invokeai.app.models.image import ImageCategory, ResourceOrigin
|
||||||
from invokeai.app.services.models.image_record import ImageRecord, ImageRecordChanges, deserialize_image_record
|
from invokeai.app.services.models.image_record import ImageRecord, ImageRecordChanges, deserialize_image_record
|
||||||
|
|
||||||
@ -81,7 +82,7 @@ class ImageRecordStorageBase(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_metadata(self, image_name: str) -> Optional[dict]:
|
def get_metadata(self, image_name: str) -> ImageMetadata:
|
||||||
"""Gets an image's metadata'."""
|
"""Gets an image's metadata'."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -134,7 +135,8 @@ class ImageRecordStorageBase(ABC):
|
|||||||
height: int,
|
height: int,
|
||||||
session_id: Optional[str],
|
session_id: Optional[str],
|
||||||
node_id: Optional[str],
|
node_id: Optional[str],
|
||||||
metadata: Optional[dict],
|
metadata: Optional[Union[str, dict]],
|
||||||
|
workflow: Optional[str],
|
||||||
is_intermediate: bool = False,
|
is_intermediate: bool = False,
|
||||||
starred: bool = False,
|
starred: bool = False,
|
||||||
) -> datetime:
|
) -> datetime:
|
||||||
@ -204,6 +206,13 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if "workflow" not in columns:
|
||||||
|
self._cursor.execute(
|
||||||
|
"""--sql
|
||||||
|
ALTER TABLE images ADD COLUMN workflow TEXT;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
# Create the `images` table indices.
|
# Create the `images` table indices.
|
||||||
self._cursor.execute(
|
self._cursor.execute(
|
||||||
"""--sql
|
"""--sql
|
||||||
@ -269,22 +278,31 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
|||||||
|
|
||||||
return deserialize_image_record(dict(result))
|
return deserialize_image_record(dict(result))
|
||||||
|
|
||||||
def get_metadata(self, image_name: str) -> Optional[dict]:
|
def get_metadata(self, image_name: str) -> ImageMetadata:
|
||||||
try:
|
try:
|
||||||
self._lock.acquire()
|
self._lock.acquire()
|
||||||
|
|
||||||
self._cursor.execute(
|
self._cursor.execute(
|
||||||
"""--sql
|
"""--sql
|
||||||
SELECT images.metadata FROM images
|
SELECT metadata, workflow FROM images
|
||||||
WHERE image_name = ?;
|
WHERE image_name = ?;
|
||||||
""",
|
""",
|
||||||
(image_name,),
|
(image_name,),
|
||||||
)
|
)
|
||||||
|
|
||||||
result = cast(Optional[sqlite3.Row], self._cursor.fetchone())
|
result = cast(Optional[sqlite3.Row], self._cursor.fetchone())
|
||||||
if not result or not result[0]:
|
|
||||||
return None
|
if not result:
|
||||||
return json.loads(result[0])
|
return ImageMetadata()
|
||||||
|
|
||||||
|
as_dict = dict(result)
|
||||||
|
metadata_raw = cast(Optional[str], as_dict.get("metadata", None))
|
||||||
|
workflow_raw = cast(Optional[str], as_dict.get("workflow", None))
|
||||||
|
|
||||||
|
return ImageMetadata(
|
||||||
|
metadata=json.loads(metadata_raw) if metadata_raw is not None else None,
|
||||||
|
workflow=json.loads(workflow_raw) if workflow_raw is not None else None,
|
||||||
|
)
|
||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
self._conn.rollback()
|
self._conn.rollback()
|
||||||
raise ImageRecordNotFoundException from e
|
raise ImageRecordNotFoundException from e
|
||||||
@ -519,12 +537,15 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
|||||||
width: int,
|
width: int,
|
||||||
height: int,
|
height: int,
|
||||||
node_id: Optional[str],
|
node_id: Optional[str],
|
||||||
metadata: Optional[dict],
|
metadata: Optional[Union[str, dict]],
|
||||||
|
workflow: Optional[str],
|
||||||
is_intermediate: bool = False,
|
is_intermediate: bool = False,
|
||||||
starred: bool = False,
|
starred: bool = False,
|
||||||
) -> datetime:
|
) -> datetime:
|
||||||
try:
|
try:
|
||||||
metadata_json = None if metadata is None else json.dumps(metadata)
|
metadata_json: Optional[str] = None
|
||||||
|
if metadata is not None:
|
||||||
|
metadata_json = metadata if type(metadata) is str else json.dumps(metadata)
|
||||||
self._lock.acquire()
|
self._lock.acquire()
|
||||||
self._cursor.execute(
|
self._cursor.execute(
|
||||||
"""--sql
|
"""--sql
|
||||||
@ -537,10 +558,11 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
|||||||
node_id,
|
node_id,
|
||||||
session_id,
|
session_id,
|
||||||
metadata,
|
metadata,
|
||||||
|
workflow,
|
||||||
is_intermediate,
|
is_intermediate,
|
||||||
starred
|
starred
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
image_name,
|
image_name,
|
||||||
@ -551,6 +573,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
|||||||
node_id,
|
node_id,
|
||||||
session_id,
|
session_id,
|
||||||
metadata_json,
|
metadata_json,
|
||||||
|
workflow,
|
||||||
is_intermediate,
|
is_intermediate,
|
||||||
starred,
|
starred,
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
from typing import TYPE_CHECKING, Callable, Optional
|
from typing import TYPE_CHECKING, Callable, Optional, Union
|
||||||
|
|
||||||
from PIL.Image import Image as PILImageType
|
from PIL.Image import Image as PILImageType
|
||||||
|
|
||||||
@ -29,7 +29,6 @@ from invokeai.app.services.item_storage import ItemStorageABC
|
|||||||
from invokeai.app.services.models.image_record import ImageDTO, ImageRecord, ImageRecordChanges, image_record_to_dto
|
from invokeai.app.services.models.image_record import ImageDTO, ImageRecord, ImageRecordChanges, image_record_to_dto
|
||||||
from invokeai.app.services.resource_name import NameServiceBase
|
from invokeai.app.services.resource_name import NameServiceBase
|
||||||
from invokeai.app.services.urls import UrlServiceBase
|
from invokeai.app.services.urls import UrlServiceBase
|
||||||
from invokeai.app.util.metadata import get_metadata_graph_from_raw_session
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from invokeai.app.services.graph import GraphExecutionState
|
from invokeai.app.services.graph import GraphExecutionState
|
||||||
@ -71,7 +70,7 @@ class ImageServiceABC(ABC):
|
|||||||
session_id: Optional[str] = None,
|
session_id: Optional[str] = None,
|
||||||
board_id: Optional[str] = None,
|
board_id: Optional[str] = None,
|
||||||
is_intermediate: bool = False,
|
is_intermediate: bool = False,
|
||||||
metadata: Optional[dict] = None,
|
metadata: Optional[Union[str, dict]] = None,
|
||||||
workflow: Optional[str] = None,
|
workflow: Optional[str] = None,
|
||||||
) -> ImageDTO:
|
) -> ImageDTO:
|
||||||
"""Creates an image, storing the file and its metadata."""
|
"""Creates an image, storing the file and its metadata."""
|
||||||
@ -196,7 +195,7 @@ class ImageService(ImageServiceABC):
|
|||||||
session_id: Optional[str] = None,
|
session_id: Optional[str] = None,
|
||||||
board_id: Optional[str] = None,
|
board_id: Optional[str] = None,
|
||||||
is_intermediate: bool = False,
|
is_intermediate: bool = False,
|
||||||
metadata: Optional[dict] = None,
|
metadata: Optional[Union[str, dict]] = None,
|
||||||
workflow: Optional[str] = None,
|
workflow: Optional[str] = None,
|
||||||
) -> ImageDTO:
|
) -> ImageDTO:
|
||||||
if image_origin not in ResourceOrigin:
|
if image_origin not in ResourceOrigin:
|
||||||
@ -234,6 +233,7 @@ class ImageService(ImageServiceABC):
|
|||||||
# Nullable fields
|
# Nullable fields
|
||||||
node_id=node_id,
|
node_id=node_id,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
|
workflow=workflow,
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
)
|
)
|
||||||
if board_id is not None:
|
if board_id is not None:
|
||||||
@ -311,23 +311,7 @@ class ImageService(ImageServiceABC):
|
|||||||
|
|
||||||
def get_metadata(self, image_name: str) -> Optional[ImageMetadata]:
|
def get_metadata(self, image_name: str) -> Optional[ImageMetadata]:
|
||||||
try:
|
try:
|
||||||
image_record = self._services.image_records.get(image_name)
|
return self._services.image_records.get_metadata(image_name)
|
||||||
metadata = self._services.image_records.get_metadata(image_name)
|
|
||||||
|
|
||||||
if not image_record.session_id:
|
|
||||||
return ImageMetadata(metadata=metadata)
|
|
||||||
|
|
||||||
session_raw = self._services.graph_execution_manager.get_raw(image_record.session_id)
|
|
||||||
graph = None
|
|
||||||
|
|
||||||
if session_raw:
|
|
||||||
try:
|
|
||||||
graph = get_metadata_graph_from_raw_session(session_raw)
|
|
||||||
except Exception as e:
|
|
||||||
self._services.logger.warn(f"Failed to parse session graph: {e}")
|
|
||||||
graph = None
|
|
||||||
|
|
||||||
return ImageMetadata(graph=graph, metadata=metadata)
|
|
||||||
except ImageRecordNotFoundException:
|
except ImageRecordNotFoundException:
|
||||||
self._services.logger.error("Image record not found")
|
self._services.logger.error("Image record not found")
|
||||||
raise
|
raise
|
||||||
|
@ -8,97 +8,97 @@ from invokeai.app.services.invoker import Invoker
|
|||||||
|
|
||||||
|
|
||||||
class MemoryInvocationCache(InvocationCacheBase):
|
class MemoryInvocationCache(InvocationCacheBase):
|
||||||
__cache: dict[Union[int, str], tuple[BaseInvocationOutput, str]]
|
_cache: dict[Union[int, str], tuple[BaseInvocationOutput, str]]
|
||||||
__max_cache_size: int
|
_max_cache_size: int
|
||||||
__disabled: bool
|
_disabled: bool
|
||||||
__hits: int
|
_hits: int
|
||||||
__misses: int
|
_misses: int
|
||||||
__cache_ids: Queue
|
_cache_ids: Queue
|
||||||
__invoker: Invoker
|
_invoker: Invoker
|
||||||
|
|
||||||
def __init__(self, max_cache_size: int = 0) -> None:
|
def __init__(self, max_cache_size: int = 0) -> None:
|
||||||
self.__cache = dict()
|
self._cache = dict()
|
||||||
self.__max_cache_size = max_cache_size
|
self._max_cache_size = max_cache_size
|
||||||
self.__disabled = False
|
self._disabled = False
|
||||||
self.__hits = 0
|
self._hits = 0
|
||||||
self.__misses = 0
|
self._misses = 0
|
||||||
self.__cache_ids = Queue()
|
self._cache_ids = Queue()
|
||||||
|
|
||||||
def start(self, invoker: Invoker) -> None:
|
def start(self, invoker: Invoker) -> None:
|
||||||
self.__invoker = invoker
|
self._invoker = invoker
|
||||||
if self.__max_cache_size == 0:
|
if self._max_cache_size == 0:
|
||||||
return
|
return
|
||||||
self.__invoker.services.images.on_deleted(self._delete_by_match)
|
self._invoker.services.images.on_deleted(self._delete_by_match)
|
||||||
self.__invoker.services.latents.on_deleted(self._delete_by_match)
|
self._invoker.services.latents.on_deleted(self._delete_by_match)
|
||||||
|
|
||||||
def get(self, key: Union[int, str]) -> Optional[BaseInvocationOutput]:
|
def get(self, key: Union[int, str]) -> Optional[BaseInvocationOutput]:
|
||||||
if self.__max_cache_size == 0 or self.__disabled:
|
if self._max_cache_size == 0 or self._disabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
item = self.__cache.get(key, None)
|
item = self._cache.get(key, None)
|
||||||
if item is not None:
|
if item is not None:
|
||||||
self.__hits += 1
|
self._hits += 1
|
||||||
return item[0]
|
return item[0]
|
||||||
self.__misses += 1
|
self._misses += 1
|
||||||
|
|
||||||
def save(self, key: Union[int, str], invocation_output: BaseInvocationOutput) -> None:
|
def save(self, key: Union[int, str], invocation_output: BaseInvocationOutput) -> None:
|
||||||
if self.__max_cache_size == 0 or self.__disabled:
|
if self._max_cache_size == 0 or self._disabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
if key not in self.__cache:
|
if key not in self._cache:
|
||||||
self.__cache[key] = (invocation_output, invocation_output.json())
|
self._cache[key] = (invocation_output, invocation_output.json())
|
||||||
self.__cache_ids.put(key)
|
self._cache_ids.put(key)
|
||||||
if self.__cache_ids.qsize() > self.__max_cache_size:
|
if self._cache_ids.qsize() > self._max_cache_size:
|
||||||
try:
|
try:
|
||||||
self.__cache.pop(self.__cache_ids.get())
|
self._cache.pop(self._cache_ids.get())
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# this means the cache_ids are somehow out of sync w/ the cache
|
# this means the cache_ids are somehow out of sync w/ the cache
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def delete(self, key: Union[int, str]) -> None:
|
def delete(self, key: Union[int, str]) -> None:
|
||||||
if self.__max_cache_size == 0 or self.__disabled:
|
if self._max_cache_size == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
if key in self.__cache:
|
if key in self._cache:
|
||||||
del self.__cache[key]
|
del self._cache[key]
|
||||||
|
|
||||||
def clear(self, *args, **kwargs) -> None:
|
def clear(self, *args, **kwargs) -> None:
|
||||||
if self.__max_cache_size == 0 or self.__disabled:
|
if self._max_cache_size == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.__cache.clear()
|
self._cache.clear()
|
||||||
self.__cache_ids = Queue()
|
self._cache_ids = Queue()
|
||||||
self.__misses = 0
|
self._misses = 0
|
||||||
self.__hits = 0
|
self._hits = 0
|
||||||
|
|
||||||
def create_key(self, invocation: BaseInvocation) -> int:
|
def create_key(self, invocation: BaseInvocation) -> int:
|
||||||
return hash(invocation.json(exclude={"id"}))
|
return hash(invocation.json(exclude={"id"}))
|
||||||
|
|
||||||
def disable(self) -> None:
|
def disable(self) -> None:
|
||||||
if self.__max_cache_size == 0:
|
if self._max_cache_size == 0:
|
||||||
return
|
return
|
||||||
self.__disabled = True
|
self._disabled = True
|
||||||
|
|
||||||
def enable(self) -> None:
|
def enable(self) -> None:
|
||||||
if self.__max_cache_size == 0:
|
if self._max_cache_size == 0:
|
||||||
return
|
return
|
||||||
self.__disabled = False
|
self._disabled = False
|
||||||
|
|
||||||
def get_status(self) -> InvocationCacheStatus:
|
def get_status(self) -> InvocationCacheStatus:
|
||||||
return InvocationCacheStatus(
|
return InvocationCacheStatus(
|
||||||
hits=self.__hits,
|
hits=self._hits,
|
||||||
misses=self.__misses,
|
misses=self._misses,
|
||||||
enabled=not self.__disabled and self.__max_cache_size > 0,
|
enabled=not self._disabled and self._max_cache_size > 0,
|
||||||
size=len(self.__cache),
|
size=len(self._cache),
|
||||||
max_size=self.__max_cache_size,
|
max_size=self._max_cache_size,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _delete_by_match(self, to_match: str) -> None:
|
def _delete_by_match(self, to_match: str) -> None:
|
||||||
if self.__max_cache_size == 0 or self.__disabled:
|
if self._max_cache_size == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
keys_to_delete = set()
|
keys_to_delete = set()
|
||||||
for key, value_tuple in self.__cache.items():
|
for key, value_tuple in self._cache.items():
|
||||||
if to_match in value_tuple[1]:
|
if to_match in value_tuple[1]:
|
||||||
keys_to_delete.add(key)
|
keys_to_delete.add(key)
|
||||||
|
|
||||||
@ -108,4 +108,4 @@ class MemoryInvocationCache(InvocationCacheBase):
|
|||||||
for key in keys_to_delete:
|
for key in keys_to_delete:
|
||||||
self.delete(key)
|
self.delete(key)
|
||||||
|
|
||||||
self.__invoker.services.logger.debug(f"Deleted {len(keys_to_delete)} cached invocation outputs for {to_match}")
|
self._invoker.services.logger.debug(f"Deleted {len(keys_to_delete)} cached invocation outputs for {to_match}")
|
||||||
|
@ -81,6 +81,7 @@
|
|||||||
"load": "Load",
|
"load": "Load",
|
||||||
"loading": "Loading",
|
"loading": "Loading",
|
||||||
"loadingInvokeAI": "Loading Invoke AI",
|
"loadingInvokeAI": "Loading Invoke AI",
|
||||||
|
"learnMore": "Learn More",
|
||||||
"modelManager": "Model Manager",
|
"modelManager": "Model Manager",
|
||||||
"nodeEditor": "Node Editor",
|
"nodeEditor": "Node Editor",
|
||||||
"nodes": "Workflow Editor",
|
"nodes": "Workflow Editor",
|
||||||
@ -135,6 +136,8 @@
|
|||||||
"bgth": "bg_th",
|
"bgth": "bg_th",
|
||||||
"canny": "Canny",
|
"canny": "Canny",
|
||||||
"cannyDescription": "Canny edge detection",
|
"cannyDescription": "Canny edge detection",
|
||||||
|
"colorMap": "Color",
|
||||||
|
"colorMapDescription": "Generates a color map from the image",
|
||||||
"coarse": "Coarse",
|
"coarse": "Coarse",
|
||||||
"contentShuffle": "Content Shuffle",
|
"contentShuffle": "Content Shuffle",
|
||||||
"contentShuffleDescription": "Shuffles the content in an image",
|
"contentShuffleDescription": "Shuffles the content in an image",
|
||||||
@ -158,6 +161,7 @@
|
|||||||
"hideAdvanced": "Hide Advanced",
|
"hideAdvanced": "Hide Advanced",
|
||||||
"highThreshold": "High Threshold",
|
"highThreshold": "High Threshold",
|
||||||
"imageResolution": "Image Resolution",
|
"imageResolution": "Image Resolution",
|
||||||
|
"colorMapTileSize": "Tile Size",
|
||||||
"importImageFromCanvas": "Import Image From Canvas",
|
"importImageFromCanvas": "Import Image From Canvas",
|
||||||
"importMaskFromCanvas": "Import Mask From Canvas",
|
"importMaskFromCanvas": "Import Mask From Canvas",
|
||||||
"incompatibleBaseModel": "Incompatible base model:",
|
"incompatibleBaseModel": "Incompatible base model:",
|
||||||
@ -701,6 +705,8 @@
|
|||||||
"addNodeToolTip": "Add Node (Shift+A, Space)",
|
"addNodeToolTip": "Add Node (Shift+A, Space)",
|
||||||
"animatedEdges": "Animated Edges",
|
"animatedEdges": "Animated Edges",
|
||||||
"animatedEdgesHelp": "Animate selected edges and edges connected to selected nodes",
|
"animatedEdgesHelp": "Animate selected edges and edges connected to selected nodes",
|
||||||
|
"boardField": "Board",
|
||||||
|
"boardFieldDescription": "A gallery board",
|
||||||
"boolean": "Booleans",
|
"boolean": "Booleans",
|
||||||
"booleanCollection": "Boolean Collection",
|
"booleanCollection": "Boolean Collection",
|
||||||
"booleanCollectionDescription": "A collection of booleans.",
|
"booleanCollectionDescription": "A collection of booleans.",
|
||||||
@ -888,7 +894,7 @@
|
|||||||
"zoomOutNodes": "Zoom Out"
|
"zoomOutNodes": "Zoom Out"
|
||||||
},
|
},
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"aspectRatio": "Ratio",
|
"aspectRatio": "Aspect Ratio",
|
||||||
"boundingBoxHeader": "Bounding Box",
|
"boundingBoxHeader": "Bounding Box",
|
||||||
"boundingBoxHeight": "Bounding Box Height",
|
"boundingBoxHeight": "Bounding Box Height",
|
||||||
"boundingBoxWidth": "Bounding Box Width",
|
"boundingBoxWidth": "Bounding Box Width",
|
||||||
@ -1020,8 +1026,8 @@
|
|||||||
"label": "Seed Behaviour",
|
"label": "Seed Behaviour",
|
||||||
"perIterationLabel": "Seed per Iteration",
|
"perIterationLabel": "Seed per Iteration",
|
||||||
"perIterationDesc": "Use a different seed for each iteration",
|
"perIterationDesc": "Use a different seed for each iteration",
|
||||||
"perPromptLabel": "Seed per Prompt",
|
"perPromptLabel": "Seed per Image",
|
||||||
"perPromptDesc": "Use a different seed for each prompt"
|
"perPromptDesc": "Use a different seed for each image"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sdxl": {
|
"sdxl": {
|
||||||
@ -1173,131 +1179,205 @@
|
|||||||
"popovers": {
|
"popovers": {
|
||||||
"clipSkip": {
|
"clipSkip": {
|
||||||
"heading": "CLIP Skip",
|
"heading": "CLIP Skip",
|
||||||
"paragraph": "Choose how many layers of the CLIP model to skip. Certain models are better suited to be used with CLIP Skip."
|
"paragraphs": [
|
||||||
},
|
"Choose how many layers of the CLIP model to skip.",
|
||||||
"compositingBlur": {
|
"Some models work better with certain CLIP Skip settings.",
|
||||||
"heading": "Blur",
|
"A higher value typically results in a less detailed image."
|
||||||
"paragraph": "The blur radius of the mask."
|
]
|
||||||
},
|
|
||||||
"compositingBlurMethod": {
|
|
||||||
"heading": "Blur Method",
|
|
||||||
"paragraph": "The method of blur applied to the masked area."
|
|
||||||
},
|
|
||||||
"compositingCoherencePass": {
|
|
||||||
"heading": "Coherence Pass",
|
|
||||||
"paragraph": "Composite the Inpainted/Outpainted images."
|
|
||||||
},
|
|
||||||
"compositingCoherenceMode": {
|
|
||||||
"heading": "Mode",
|
|
||||||
"paragraph": "The mode of the Coherence Pass."
|
|
||||||
},
|
|
||||||
"compositingCoherenceSteps": {
|
|
||||||
"heading": "Steps",
|
|
||||||
"paragraph": "Number of steps in the Coherence Pass. Similar to Denoising Steps."
|
|
||||||
},
|
|
||||||
"compositingStrength": {
|
|
||||||
"heading": "Strength",
|
|
||||||
"paragraph": "Amount of noise added for the Coherence Pass. Similar to Denoising Strength."
|
|
||||||
},
|
|
||||||
"compositingMaskAdjustments": {
|
|
||||||
"heading": "Mask Adjustments",
|
|
||||||
"paragraph": "Adjust the mask."
|
|
||||||
},
|
|
||||||
"controlNetBeginEnd": {
|
|
||||||
"heading": "Begin / End Step Percentage",
|
|
||||||
"paragraph": "Which parts of the denoising process will have the ControlNet applied. ControlNets applied at the start of the process guide composition, and ControlNets applied at the end guide details."
|
|
||||||
},
|
|
||||||
"controlNetControlMode": {
|
|
||||||
"heading": "Control Mode",
|
|
||||||
"paragraph": "Lends more weight to either the prompt or ControlNet."
|
|
||||||
},
|
|
||||||
"controlNetResizeMode": {
|
|
||||||
"heading": "Resize Mode",
|
|
||||||
"paragraph": "How the ControlNet image will be fit to the image generation Ratio"
|
|
||||||
},
|
|
||||||
"controlNetToggle": {
|
|
||||||
"heading": "Enable ControlNet",
|
|
||||||
"paragraph": "ControlNets provide guidance to the generation process, helping create images with controlled composition, structure, or style, depending on the model selected."
|
|
||||||
},
|
|
||||||
"controlNetWeight": {
|
|
||||||
"heading": "Weight",
|
|
||||||
"paragraph": "How strongly the ControlNet will impact the generated image."
|
|
||||||
},
|
|
||||||
"dynamicPromptsToggle": {
|
|
||||||
"heading": "Enable Dynamic Prompts",
|
|
||||||
"paragraph": "Dynamic prompts allow multiple options within a prompt. Dynamic prompts can be used by: {option1|option2|option3}. Combinations of prompts will be randomly generated until the “Images” number has been reached."
|
|
||||||
},
|
|
||||||
"dynamicPromptsCombinatorial": {
|
|
||||||
"heading": "Combinatorial Generation",
|
|
||||||
"paragraph": "Generate an image for every possible combination of Dynamic Prompts until the Max Prompts is reached."
|
|
||||||
},
|
|
||||||
"infillMethod": {
|
|
||||||
"heading": "Infill Method",
|
|
||||||
"paragraph": "Method to infill the selected area."
|
|
||||||
},
|
|
||||||
"lora": {
|
|
||||||
"heading": "LoRA Weight",
|
|
||||||
"paragraph": "Weight of the LoRA. Higher weight will lead to larger impacts on the final image."
|
|
||||||
},
|
|
||||||
"noiseEnable": {
|
|
||||||
"heading": "Enable Noise Settings",
|
|
||||||
"paragraph": "Advanced control over noise generation."
|
|
||||||
},
|
|
||||||
"noiseUseCPU": {
|
|
||||||
"heading": "Use CPU Noise",
|
|
||||||
"paragraph": "Uses the CPU to generate random noise."
|
|
||||||
},
|
|
||||||
"paramCFGScale": {
|
|
||||||
"heading": "CFG Scale",
|
|
||||||
"paragraph": "Controls how much your prompt influences the generation process."
|
|
||||||
},
|
|
||||||
"paramDenoisingStrength": {
|
|
||||||
"heading": "Denoising Strength",
|
|
||||||
"paragraph": "How much noise is added to the input image. 0 will result in an identical image, while 1 will result in a completely new image."
|
|
||||||
},
|
|
||||||
"paramIterations": {
|
|
||||||
"heading": "Iterations",
|
|
||||||
"paragraph": "The number of images to generate. If Dynamic Prompts is enabled, each of the prompts will be generated this many times."
|
|
||||||
},
|
|
||||||
"paramModel": {
|
|
||||||
"heading": "Model",
|
|
||||||
"paragraph": "Model used for the denoising steps. Different models are trained to specialize in producing different aesthetic results and content."
|
|
||||||
},
|
},
|
||||||
"paramNegativeConditioning": {
|
"paramNegativeConditioning": {
|
||||||
"heading": "Negative Prompt",
|
"heading": "Negative Prompt",
|
||||||
"paragraph": "The generation process avoids the concepts in the negative prompt. Use this to exclude qualities or objects from the output. Supports Compel syntax and embeddings."
|
"paragraphs": [
|
||||||
|
"The generation process avoids the concepts in the negative prompt. Use this to exclude qualities or objects from the output.",
|
||||||
|
"Supports Compel syntax and embeddings."
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"paramPositiveConditioning": {
|
"paramPositiveConditioning": {
|
||||||
"heading": "Positive Prompt",
|
"heading": "Positive Prompt",
|
||||||
"paragraph": "Guides the generation process. You may use any words or phrases. Supports Compel and Dynamic Prompts syntaxes and embeddings."
|
"paragraphs": [
|
||||||
},
|
"Guides the generation process. You may use any words or phrases.",
|
||||||
"paramRatio": {
|
"Compel and Dynamic Prompts syntaxes and embeddings."
|
||||||
"heading": "Ratio",
|
]
|
||||||
"paragraph": "The ratio of the dimensions of the image generated. An image size (in number of pixels) equivalent to 512x512 is recommended for SD1.5 models and a size equivalent to 1024x1024 is recommended for SDXL models."
|
|
||||||
},
|
},
|
||||||
"paramScheduler": {
|
"paramScheduler": {
|
||||||
"heading": "Scheduler",
|
"heading": "Scheduler",
|
||||||
"paragraph": "Scheduler defines how to iteratively add noise to an image or how to update a sample based on a model's output."
|
"paragraphs": [
|
||||||
|
"Scheduler defines how to iteratively add noise to an image or how to update a sample based on a model's output."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"compositingBlur": {
|
||||||
|
"heading": "Blur",
|
||||||
|
"paragraphs": ["The blur radius of the mask."]
|
||||||
|
},
|
||||||
|
"compositingBlurMethod": {
|
||||||
|
"heading": "Blur Method",
|
||||||
|
"paragraphs": ["The method of blur applied to the masked area."]
|
||||||
|
},
|
||||||
|
"compositingCoherencePass": {
|
||||||
|
"heading": "Coherence Pass",
|
||||||
|
"paragraphs": [
|
||||||
|
"A second round of denoising helps to composite the Inpainted/Outpainted image."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"compositingCoherenceMode": {
|
||||||
|
"heading": "Mode",
|
||||||
|
"paragraphs": ["The mode of the Coherence Pass."]
|
||||||
|
},
|
||||||
|
"compositingCoherenceSteps": {
|
||||||
|
"heading": "Steps",
|
||||||
|
"paragraphs": [
|
||||||
|
"Number of denoising steps used in the Coherence Pass.",
|
||||||
|
"Same as the main Steps parameter."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"compositingStrength": {
|
||||||
|
"heading": "Strength",
|
||||||
|
"paragraphs": [
|
||||||
|
"Denoising strength for the Coherence Pass.",
|
||||||
|
"Same as the Image to Image Denoising Strength parameter."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"compositingMaskAdjustments": {
|
||||||
|
"heading": "Mask Adjustments",
|
||||||
|
"paragraphs": ["Adjust the mask."]
|
||||||
|
},
|
||||||
|
"controlNetBeginEnd": {
|
||||||
|
"heading": "Begin / End Step Percentage",
|
||||||
|
"paragraphs": [
|
||||||
|
"Which steps of the denoising process will have the ControlNet applied.",
|
||||||
|
"ControlNets applied at the beginning of the process guide composition, and ControlNets applied at the end guide details."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"controlNetControlMode": {
|
||||||
|
"heading": "Control Mode",
|
||||||
|
"paragraphs": [
|
||||||
|
"Lends more weight to either the prompt or ControlNet."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"controlNetResizeMode": {
|
||||||
|
"heading": "Resize Mode",
|
||||||
|
"paragraphs": [
|
||||||
|
"How the ControlNet image will be fit to the image output size."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"controlNet": {
|
||||||
|
"heading": "ControlNet",
|
||||||
|
"paragraphs": [
|
||||||
|
"ControlNets provide guidance to the generation process, helping create images with controlled composition, structure, or style, depending on the model selected."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"controlNetWeight": {
|
||||||
|
"heading": "Weight",
|
||||||
|
"paragraphs": [
|
||||||
|
"How strongly the ControlNet will impact the generated image."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dynamicPrompts": {
|
||||||
|
"heading": "Dynamic Prompts",
|
||||||
|
"paragraphs": [
|
||||||
|
"Dynamic Prompts parses a single prompt into many.",
|
||||||
|
"The basic syntax is \"a {red|green|blue} ball\". This will produce three prompts: \"a red ball\", \"a green ball\" and \"a blue ball\".",
|
||||||
|
"You can use the syntax as many times as you like in a single prompt, but be sure to keep the number of prompts generated in check with the Max Prompts setting."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dynamicPromptsMaxPrompts": {
|
||||||
|
"heading": "Max Prompts",
|
||||||
|
"paragraphs": [
|
||||||
|
"Limits the number of prompts that can be generated by Dynamic Prompts."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dynamicPromptsSeedBehaviour": {
|
||||||
|
"heading": "Seed Behaviour",
|
||||||
|
"paragraphs": [
|
||||||
|
"Controls how the seed is used when generating prompts.",
|
||||||
|
"Per Iteration will use a unique seed for each iteration. Use this to explore prompt variations on a single seed.",
|
||||||
|
"For example, if you have 5 prompts, each image will use the same seed.",
|
||||||
|
"Per Image will use a unique seed for each image. This provides more variation."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"infillMethod": {
|
||||||
|
"heading": "Infill Method",
|
||||||
|
"paragraphs": ["Method to infill the selected area."]
|
||||||
|
},
|
||||||
|
"lora": {
|
||||||
|
"heading": "LoRA Weight",
|
||||||
|
"paragraphs": [
|
||||||
|
"Higher LoRA weight will lead to larger impacts on the final image."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"noiseUseCPU": {
|
||||||
|
"heading": "Use CPU Noise",
|
||||||
|
"paragraphs": [
|
||||||
|
"Controls whether noise is generated on the CPU or GPU.",
|
||||||
|
"With CPU Noise enabled, a particular seed will produce the same image on any machine.",
|
||||||
|
"There is no performance impact to enabling CPU Noise."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"paramCFGScale": {
|
||||||
|
"heading": "CFG Scale",
|
||||||
|
"paragraphs": [
|
||||||
|
"Controls how much your prompt influences the generation process."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"paramDenoisingStrength": {
|
||||||
|
"heading": "Denoising Strength",
|
||||||
|
"paragraphs": [
|
||||||
|
"How much noise is added to the input image.",
|
||||||
|
"0 will result in an identical image, while 1 will result in a completely new image."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"paramIterations": {
|
||||||
|
"heading": "Iterations",
|
||||||
|
"paragraphs": [
|
||||||
|
"The number of images to generate.",
|
||||||
|
"If Dynamic Prompts is enabled, each of the prompts will be generated this many times."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"paramModel": {
|
||||||
|
"heading": "Model",
|
||||||
|
"paragraphs": [
|
||||||
|
"Model used for the denoising steps.",
|
||||||
|
"Different models are typically trained to specialize in producing particular aesthetic results and content."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"paramRatio": {
|
||||||
|
"heading": "Aspect Ratio",
|
||||||
|
"paragraphs": [
|
||||||
|
"The aspect ratio of the dimensions of the image generated.",
|
||||||
|
"An image size (in number of pixels) equivalent to 512x512 is recommended for SD1.5 models and a size equivalent to 1024x1024 is recommended for SDXL models."
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"paramSeed": {
|
"paramSeed": {
|
||||||
"heading": "Seed",
|
"heading": "Seed",
|
||||||
"paragraph": "Controls the starting noise used for generation. Disable “Random Seed” to produce identical results with the same generation settings."
|
"paragraphs": [
|
||||||
|
"Controls the starting noise used for generation.",
|
||||||
|
"Disable “Random Seed” to produce identical results with the same generation settings."
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"paramSteps": {
|
"paramSteps": {
|
||||||
"heading": "Steps",
|
"heading": "Steps",
|
||||||
"paragraph": "Number of steps that will be performed in each generation. Higher step counts will typically create better images but will require more generation time."
|
"paragraphs": [
|
||||||
|
"Number of steps that will be performed in each generation.",
|
||||||
|
"Higher step counts will typically create better images but will require more generation time."
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"paramVAE": {
|
"paramVAE": {
|
||||||
"heading": "VAE",
|
"heading": "VAE",
|
||||||
"paragraph": "Model used for translating AI output into the final image."
|
"paragraphs": [
|
||||||
|
"Model used for translating AI output into the final image."
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"paramVAEPrecision": {
|
"paramVAEPrecision": {
|
||||||
"heading": "VAE Precision",
|
"heading": "VAE Precision",
|
||||||
"paragraph": "The precision used during VAE encoding and decoding. Fp16/Half precision is more efficient, at the expense of minor image variations."
|
"paragraphs": [
|
||||||
|
"The precision used during VAE encoding and decoding. FP16/half precision is more efficient, at the expense of minor image variations."
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"scaleBeforeProcessing": {
|
"scaleBeforeProcessing": {
|
||||||
"heading": "Scale Before Processing",
|
"heading": "Scale Before Processing",
|
||||||
"paragraph": "Scales the selected area to the size best suited for the model before the image generation process."
|
"paragraphs": [
|
||||||
|
"Scales the selected area to the size best suited for the model before the image generation process."
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ui": {
|
"ui": {
|
||||||
|
@ -17,7 +17,8 @@ import {
|
|||||||
} from 'services/events/actions';
|
} from 'services/events/actions';
|
||||||
import { startAppListening } from '../..';
|
import { startAppListening } from '../..';
|
||||||
|
|
||||||
const nodeDenylist = ['load_image'];
|
// These nodes output an image, but do not actually *save* an image, so we don't want to handle the gallery logic on them
|
||||||
|
const nodeDenylist = ['load_image', 'image'];
|
||||||
|
|
||||||
export const addInvocationCompleteEventListener = () => {
|
export const addInvocationCompleteEventListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
@ -37,6 +38,7 @@ export const addInvocationCompleteEventListener = () => {
|
|||||||
const { image_name } = result.image;
|
const { image_name } = result.image;
|
||||||
const { canvas, gallery } = getState();
|
const { canvas, gallery } = getState();
|
||||||
|
|
||||||
|
// This populates the `getImageDTO` cache
|
||||||
const imageDTO = await dispatch(
|
const imageDTO = await dispatch(
|
||||||
imagesApi.endpoints.getImageDTO.initiate(image_name)
|
imagesApi.endpoints.getImageDTO.initiate(image_name)
|
||||||
).unwrap();
|
).unwrap();
|
||||||
@ -52,54 +54,36 @@ export const addInvocationCompleteEventListener = () => {
|
|||||||
if (!imageDTO.is_intermediate) {
|
if (!imageDTO.is_intermediate) {
|
||||||
/**
|
/**
|
||||||
* Cache updates for when an image result is received
|
* Cache updates for when an image result is received
|
||||||
* - *add* to getImageDTO
|
* - add it to the no_board/images
|
||||||
* - IF `autoAddBoardId` is set:
|
|
||||||
* - THEN add it to the board_id/images
|
|
||||||
* - ELSE (`autoAddBoardId` is not set):
|
|
||||||
* - THEN add it to the no_board/images
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { autoAddBoardId } = gallery;
|
dispatch(
|
||||||
if (autoAddBoardId && autoAddBoardId !== 'none') {
|
imagesApi.util.updateQueryData(
|
||||||
dispatch(
|
'listImages',
|
||||||
imagesApi.endpoints.addImageToBoard.initiate({
|
{
|
||||||
board_id: autoAddBoardId,
|
board_id: imageDTO.board_id ?? 'none',
|
||||||
imageDTO,
|
categories: IMAGE_CATEGORIES,
|
||||||
})
|
},
|
||||||
);
|
(draft) => {
|
||||||
} else {
|
imagesAdapter.addOne(draft, imageDTO);
|
||||||
dispatch(
|
}
|
||||||
imagesApi.util.updateQueryData(
|
)
|
||||||
'listImages',
|
);
|
||||||
{
|
|
||||||
board_id: 'none',
|
|
||||||
categories: IMAGE_CATEGORIES,
|
|
||||||
},
|
|
||||||
(draft) => {
|
|
||||||
imagesAdapter.addOne(draft, imageDTO);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
imagesApi.util.invalidateTags([
|
imagesApi.util.invalidateTags([
|
||||||
{ type: 'BoardImagesTotal', id: autoAddBoardId },
|
{ type: 'BoardImagesTotal', id: imageDTO.board_id },
|
||||||
{ type: 'BoardAssetsTotal', id: autoAddBoardId },
|
{ type: 'BoardAssetsTotal', id: imageDTO.board_id },
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
const { selectedBoardId, shouldAutoSwitch } = gallery;
|
const { shouldAutoSwitch } = gallery;
|
||||||
|
|
||||||
// If auto-switch is enabled, select the new image
|
// If auto-switch is enabled, select the new image
|
||||||
if (shouldAutoSwitch) {
|
if (shouldAutoSwitch) {
|
||||||
// if auto-add is enabled, switch the board as the image comes in
|
// if auto-add is enabled, switch the board as the image comes in
|
||||||
if (autoAddBoardId && autoAddBoardId !== selectedBoardId) {
|
dispatch(galleryViewChanged('images'));
|
||||||
dispatch(boardIdSelected(autoAddBoardId));
|
dispatch(boardIdSelected(imageDTO.board_id ?? 'none'));
|
||||||
dispatch(galleryViewChanged('images'));
|
|
||||||
} else if (!autoAddBoardId) {
|
|
||||||
dispatch(galleryViewChanged('images'));
|
|
||||||
}
|
|
||||||
dispatch(imageSelected(imageDTO));
|
dispatch(imageSelected(imageDTO));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,14 @@ export const addUpscaleRequestedListener = () => {
|
|||||||
const log = logger('session');
|
const log = logger('session');
|
||||||
|
|
||||||
const { image_name } = action.payload;
|
const { image_name } = action.payload;
|
||||||
const { esrganModelName } = getState().postprocessing;
|
const state = getState();
|
||||||
|
const { esrganModelName } = state.postprocessing;
|
||||||
|
const { autoAddBoardId } = state.gallery;
|
||||||
|
|
||||||
const graph = buildAdHocUpscaleGraph({
|
const graph = buildAdHocUpscaleGraph({
|
||||||
image_name,
|
image_name,
|
||||||
esrganModelName,
|
esrganModelName,
|
||||||
|
autoAddBoardId,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1,124 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Divider,
|
|
||||||
Flex,
|
|
||||||
Heading,
|
|
||||||
Image,
|
|
||||||
Popover,
|
|
||||||
PopoverArrow,
|
|
||||||
PopoverBody,
|
|
||||||
PopoverCloseButton,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverProps,
|
|
||||||
PopoverTrigger,
|
|
||||||
Portal,
|
|
||||||
Text,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { ReactNode, memo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useAppSelector } from '../../app/store/storeHooks';
|
|
||||||
|
|
||||||
const OPEN_DELAY = 1500;
|
|
||||||
|
|
||||||
type Props = Omit<PopoverProps, 'children'> & {
|
|
||||||
details: string;
|
|
||||||
children: ReactNode;
|
|
||||||
image?: string;
|
|
||||||
buttonLabel?: string;
|
|
||||||
buttonHref?: string;
|
|
||||||
placement?: PopoverProps['placement'];
|
|
||||||
};
|
|
||||||
|
|
||||||
const IAIInformationalPopover = ({
|
|
||||||
details,
|
|
||||||
image,
|
|
||||||
buttonLabel,
|
|
||||||
buttonHref,
|
|
||||||
children,
|
|
||||||
placement,
|
|
||||||
}: Props) => {
|
|
||||||
const shouldEnableInformationalPopovers = useAppSelector(
|
|
||||||
(state) => state.system.shouldEnableInformationalPopovers
|
|
||||||
);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const heading = t(`popovers.${details}.heading`);
|
|
||||||
const paragraph = t(`popovers.${details}.paragraph`);
|
|
||||||
|
|
||||||
if (!shouldEnableInformationalPopovers) {
|
|
||||||
return <>{children}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover
|
|
||||||
placement={placement || 'top'}
|
|
||||||
closeOnBlur={false}
|
|
||||||
trigger="hover"
|
|
||||||
variant="informational"
|
|
||||||
openDelay={OPEN_DELAY}
|
|
||||||
>
|
|
||||||
<PopoverTrigger>
|
|
||||||
<Box w="full">{children}</Box>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<Portal>
|
|
||||||
<PopoverContent>
|
|
||||||
<PopoverArrow />
|
|
||||||
<PopoverCloseButton />
|
|
||||||
|
|
||||||
<PopoverBody>
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
gap: 3,
|
|
||||||
flexDirection: 'column',
|
|
||||||
width: '100%',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{image && (
|
|
||||||
<Image
|
|
||||||
sx={{
|
|
||||||
objectFit: 'contain',
|
|
||||||
maxW: '60%',
|
|
||||||
maxH: '60%',
|
|
||||||
backgroundColor: 'white',
|
|
||||||
}}
|
|
||||||
src={image}
|
|
||||||
alt="Optional Image"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
gap: 3,
|
|
||||||
flexDirection: 'column',
|
|
||||||
width: '100%',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{heading && (
|
|
||||||
<>
|
|
||||||
<Heading size="sm">{heading}</Heading>
|
|
||||||
<Divider />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Text>{paragraph}</Text>
|
|
||||||
{buttonLabel && (
|
|
||||||
<Flex justifyContent="flex-end">
|
|
||||||
<Button
|
|
||||||
onClick={() => window.open(buttonHref)}
|
|
||||||
size="sm"
|
|
||||||
variant="invokeAIOutline"
|
|
||||||
>
|
|
||||||
{buttonLabel}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</PopoverBody>
|
|
||||||
</PopoverContent>
|
|
||||||
</Portal>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(IAIInformationalPopover);
|
|
@ -0,0 +1,155 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
BoxProps,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Flex,
|
||||||
|
Heading,
|
||||||
|
Image,
|
||||||
|
Popover,
|
||||||
|
PopoverBody,
|
||||||
|
PopoverCloseButton,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverProps,
|
||||||
|
PopoverTrigger,
|
||||||
|
Portal,
|
||||||
|
Text,
|
||||||
|
forwardRef,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { merge, omit } from 'lodash-es';
|
||||||
|
import { PropsWithChildren, memo, useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaExternalLinkAlt } from 'react-icons/fa';
|
||||||
|
import { useAppSelector } from '../../../app/store/storeHooks';
|
||||||
|
import {
|
||||||
|
Feature,
|
||||||
|
OPEN_DELAY,
|
||||||
|
POPOVER_DATA,
|
||||||
|
POPPER_MODIFIERS,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
type Props = PropsWithChildren & {
|
||||||
|
feature: Feature;
|
||||||
|
wrapperProps?: BoxProps;
|
||||||
|
popoverProps?: PopoverProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
const IAIInformationalPopover = forwardRef(
|
||||||
|
({ feature, children, wrapperProps, ...rest }: Props, ref) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const shouldEnableInformationalPopovers = useAppSelector(
|
||||||
|
(state) => state.system.shouldEnableInformationalPopovers
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = useMemo(() => POPOVER_DATA[feature], [feature]);
|
||||||
|
|
||||||
|
const popoverProps = useMemo(
|
||||||
|
() => merge(omit(data, ['image', 'href', 'buttonLabel']), rest),
|
||||||
|
[data, rest]
|
||||||
|
);
|
||||||
|
|
||||||
|
const heading = useMemo<string | undefined>(
|
||||||
|
() => t(`popovers.${feature}.heading`),
|
||||||
|
[feature, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const paragraphs = useMemo<string[]>(
|
||||||
|
() =>
|
||||||
|
t(`popovers.${feature}.paragraphs`, {
|
||||||
|
returnObjects: true,
|
||||||
|
}) ?? [],
|
||||||
|
[feature, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
if (!data?.href) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.open(data.href);
|
||||||
|
}, [data?.href]);
|
||||||
|
|
||||||
|
if (!shouldEnableInformationalPopovers) {
|
||||||
|
return (
|
||||||
|
<Box ref={ref} w="full" {...wrapperProps}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
isLazy
|
||||||
|
closeOnBlur={false}
|
||||||
|
trigger="hover"
|
||||||
|
variant="informational"
|
||||||
|
openDelay={OPEN_DELAY}
|
||||||
|
modifiers={POPPER_MODIFIERS}
|
||||||
|
placement="top"
|
||||||
|
{...popoverProps}
|
||||||
|
>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<Box ref={ref} w="full" {...wrapperProps}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<Portal>
|
||||||
|
<PopoverContent w={96}>
|
||||||
|
<PopoverCloseButton />
|
||||||
|
<PopoverBody>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
gap: 2,
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{heading && (
|
||||||
|
<>
|
||||||
|
<Heading size="sm">{heading}</Heading>
|
||||||
|
<Divider />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{data?.image && (
|
||||||
|
<>
|
||||||
|
<Image
|
||||||
|
sx={{
|
||||||
|
objectFit: 'contain',
|
||||||
|
maxW: '60%',
|
||||||
|
maxH: '60%',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
}}
|
||||||
|
src={data.image}
|
||||||
|
alt="Optional Image"
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{paragraphs.map((p) => (
|
||||||
|
<Text key={p}>{p}</Text>
|
||||||
|
))}
|
||||||
|
{data?.href && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<Button
|
||||||
|
pt={1}
|
||||||
|
onClick={handleClick}
|
||||||
|
leftIcon={<FaExternalLinkAlt />}
|
||||||
|
alignSelf="flex-end"
|
||||||
|
variant="link"
|
||||||
|
>
|
||||||
|
{t('common.learnMore') ?? heading}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</PopoverBody>
|
||||||
|
</PopoverContent>
|
||||||
|
</Portal>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
IAIInformationalPopover.displayName = 'IAIInformationalPopover';
|
||||||
|
|
||||||
|
export default memo(IAIInformationalPopover);
|
@ -0,0 +1,98 @@
|
|||||||
|
import { PopoverProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export type Feature =
|
||||||
|
| 'clipSkip'
|
||||||
|
| 'paramNegativeConditioning'
|
||||||
|
| 'paramPositiveConditioning'
|
||||||
|
| 'paramScheduler'
|
||||||
|
| 'compositingBlur'
|
||||||
|
| 'compositingBlurMethod'
|
||||||
|
| 'compositingCoherencePass'
|
||||||
|
| 'compositingCoherenceMode'
|
||||||
|
| 'compositingCoherenceSteps'
|
||||||
|
| 'compositingStrength'
|
||||||
|
| 'compositingMaskAdjustments'
|
||||||
|
| 'controlNetBeginEnd'
|
||||||
|
| 'controlNetControlMode'
|
||||||
|
| 'controlNetResizeMode'
|
||||||
|
| 'controlNet'
|
||||||
|
| 'controlNetWeight'
|
||||||
|
| 'dynamicPrompts'
|
||||||
|
| 'dynamicPromptsMaxPrompts'
|
||||||
|
| 'dynamicPromptsSeedBehaviour'
|
||||||
|
| 'infillMethod'
|
||||||
|
| 'lora'
|
||||||
|
| 'noiseUseCPU'
|
||||||
|
| 'paramCFGScale'
|
||||||
|
| 'paramDenoisingStrength'
|
||||||
|
| 'paramIterations'
|
||||||
|
| 'paramModel'
|
||||||
|
| 'paramRatio'
|
||||||
|
| 'paramSeed'
|
||||||
|
| 'paramSteps'
|
||||||
|
| 'paramVAE'
|
||||||
|
| 'paramVAEPrecision'
|
||||||
|
| 'scaleBeforeProcessing';
|
||||||
|
|
||||||
|
export type PopoverData = PopoverProps & {
|
||||||
|
image?: string;
|
||||||
|
href?: string;
|
||||||
|
buttonLabel?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const POPOVER_DATA: { [key in Feature]?: PopoverData } = {
|
||||||
|
paramNegativeConditioning: {
|
||||||
|
placement: 'right',
|
||||||
|
},
|
||||||
|
controlNet: {
|
||||||
|
href: 'https://support.invoke.ai/support/solutions/articles/151000105880',
|
||||||
|
},
|
||||||
|
lora: {
|
||||||
|
href: 'https://support.invoke.ai/support/solutions/articles/151000159072',
|
||||||
|
},
|
||||||
|
compositingCoherenceMode: {
|
||||||
|
href: 'https://support.invoke.ai/support/solutions/articles/151000158838',
|
||||||
|
},
|
||||||
|
infillMethod: {
|
||||||
|
href: 'https://support.invoke.ai/support/solutions/articles/151000158841',
|
||||||
|
},
|
||||||
|
scaleBeforeProcessing: {
|
||||||
|
href: 'https://support.invoke.ai/support/solutions/articles/151000158841',
|
||||||
|
},
|
||||||
|
paramIterations: {
|
||||||
|
href: 'https://support.invoke.ai/support/solutions/articles/151000159073',
|
||||||
|
},
|
||||||
|
paramPositiveConditioning: {
|
||||||
|
href: 'https://support.invoke.ai/support/solutions/articles/151000096606-tips-on-crafting-prompts',
|
||||||
|
placement: 'right',
|
||||||
|
},
|
||||||
|
paramScheduler: {
|
||||||
|
placement: 'right',
|
||||||
|
href: 'https://support.invoke.ai/support/solutions/articles/151000159073',
|
||||||
|
},
|
||||||
|
paramModel: {
|
||||||
|
placement: 'right',
|
||||||
|
href: 'https://support.invoke.ai/support/solutions/articles/151000096601-what-is-a-model-which-should-i-use-',
|
||||||
|
},
|
||||||
|
paramRatio: {
|
||||||
|
gutter: 16,
|
||||||
|
},
|
||||||
|
controlNetControlMode: {
|
||||||
|
placement: 'right',
|
||||||
|
},
|
||||||
|
controlNetResizeMode: {
|
||||||
|
placement: 'right',
|
||||||
|
},
|
||||||
|
paramVAE: {
|
||||||
|
placement: 'right',
|
||||||
|
},
|
||||||
|
paramVAEPrecision: {
|
||||||
|
placement: 'right',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const OPEN_DELAY = 1000; // in milliseconds
|
||||||
|
|
||||||
|
export const POPPER_MODIFIERS: PopoverProps['modifiers'] = [
|
||||||
|
{ name: 'preventOverflow', options: { padding: 10 } },
|
||||||
|
];
|
@ -44,23 +44,19 @@ const IAIMantineMultiSelect = forwardRef((props: IAIMultiSelectProps, ref) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip label={tooltip} placement="top" hasArrow isOpen={true}>
|
<Tooltip label={tooltip} placement="top" hasArrow isOpen={true}>
|
||||||
<MultiSelect
|
<FormControl ref={ref} isDisabled={disabled}>
|
||||||
label={
|
{label && <FormLabel>{label}</FormLabel>}
|
||||||
label ? (
|
<MultiSelect
|
||||||
<FormControl ref={ref} isDisabled={disabled}>
|
ref={inputRef}
|
||||||
<FormLabel>{label}</FormLabel>
|
disabled={disabled}
|
||||||
</FormControl>
|
onKeyDown={handleKeyDown}
|
||||||
) : undefined
|
onKeyUp={handleKeyUp}
|
||||||
}
|
searchable={searchable}
|
||||||
ref={inputRef}
|
maxDropdownHeight={300}
|
||||||
disabled={disabled}
|
styles={styles}
|
||||||
onKeyDown={handleKeyDown}
|
{...rest}
|
||||||
onKeyUp={handleKeyUp}
|
/>
|
||||||
searchable={searchable}
|
</FormControl>
|
||||||
maxDropdownHeight={300}
|
|
||||||
styles={styles}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -70,26 +70,23 @@ const IAIMantineSearchableSelect = forwardRef((props: IAISelectProps, ref) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip label={tooltip} placement="top" hasArrow>
|
<Tooltip label={tooltip} placement="top" hasArrow>
|
||||||
<Select
|
<FormControl ref={ref} isDisabled={disabled}>
|
||||||
ref={inputRef}
|
{label && <FormLabel>{label}</FormLabel>}
|
||||||
label={
|
<Select
|
||||||
label ? (
|
ref={inputRef}
|
||||||
<FormControl ref={ref} isDisabled={disabled}>
|
withinPortal
|
||||||
<FormLabel>{label}</FormLabel>
|
disabled={disabled}
|
||||||
</FormControl>
|
searchValue={searchValue}
|
||||||
) : undefined
|
onSearchChange={setSearchValue}
|
||||||
}
|
onChange={handleChange}
|
||||||
disabled={disabled}
|
onKeyDown={handleKeyDown}
|
||||||
searchValue={searchValue}
|
onKeyUp={handleKeyUp}
|
||||||
onSearchChange={setSearchValue}
|
searchable={searchable}
|
||||||
onChange={handleChange}
|
maxDropdownHeight={300}
|
||||||
onKeyDown={handleKeyDown}
|
styles={styles}
|
||||||
onKeyUp={handleKeyUp}
|
{...rest}
|
||||||
searchable={searchable}
|
/>
|
||||||
maxDropdownHeight={300}
|
</FormControl>
|
||||||
styles={styles}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -22,19 +22,10 @@ const IAIMantineSelect = forwardRef((props: IAISelectProps, ref) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip label={tooltip} placement="top" hasArrow>
|
<Tooltip label={tooltip} placement="top" hasArrow>
|
||||||
<Select
|
<FormControl ref={ref} isRequired={required} isDisabled={disabled}>
|
||||||
label={
|
<FormLabel>{label}</FormLabel>
|
||||||
label ? (
|
<Select disabled={disabled} ref={inputRef} styles={styles} {...rest} />
|
||||||
<FormControl ref={ref} isRequired={required} isDisabled={disabled}>
|
</FormControl>
|
||||||
<FormLabel>{label}</FormLabel>
|
|
||||||
</FormControl>
|
|
||||||
) : undefined
|
|
||||||
}
|
|
||||||
disabled={disabled}
|
|
||||||
ref={inputRef}
|
|
||||||
styles={styles}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { ControlNetConfig } from '../store/controlNetSlice';
|
import { ControlNetConfig } from '../store/controlNetSlice';
|
||||||
import CannyProcessor from './processors/CannyProcessor';
|
import CannyProcessor from './processors/CannyProcessor';
|
||||||
|
import ColorMapProcessor from './processors/ColorMapProcessor';
|
||||||
import ContentShuffleProcessor from './processors/ContentShuffleProcessor';
|
import ContentShuffleProcessor from './processors/ContentShuffleProcessor';
|
||||||
import HedProcessor from './processors/HedProcessor';
|
import HedProcessor from './processors/HedProcessor';
|
||||||
import LineartAnimeProcessor from './processors/LineartAnimeProcessor';
|
import LineartAnimeProcessor from './processors/LineartAnimeProcessor';
|
||||||
@ -30,6 +31,16 @@ const ControlNetProcessorComponent = (props: ControlNetProcessorProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (processorNode.type === 'color_map_image_processor') {
|
||||||
|
return (
|
||||||
|
<ColorMapProcessor
|
||||||
|
controlNetId={controlNetId}
|
||||||
|
processorNode={processorNode}
|
||||||
|
isEnabled={isEnabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (processorNode.type === 'hed_image_processor') {
|
if (processorNode.type === 'hed_image_processor') {
|
||||||
return (
|
return (
|
||||||
<HedProcessor
|
<HedProcessor
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import {
|
import {
|
||||||
ControlNetConfig,
|
ControlNetConfig,
|
||||||
controlNetBeginStepPctChanged,
|
controlNetBeginStepPctChanged,
|
||||||
@ -50,7 +50,7 @@ const ParamControlNetBeginEnd = (props: Props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="controlNetBeginEnd">
|
<IAIInformationalPopover feature="controlNetBeginEnd">
|
||||||
<FormControl isDisabled={!isEnabled}>
|
<FormControl isDisabled={!isEnabled}>
|
||||||
<FormLabel>{t('controlnet.beginEndStepPercent')}</FormLabel>
|
<FormLabel>{t('controlnet.beginEndStepPercent')}</FormLabel>
|
||||||
<HStack w="100%" gap={2} alignItems="center">
|
<HStack w="100%" gap={2} alignItems="center">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||||
import {
|
import {
|
||||||
ControlModes,
|
ControlModes,
|
||||||
@ -35,7 +35,7 @@ export default function ParamControlNetControlMode(
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="controlNetControlMode">
|
<IAIInformationalPopover feature="controlNetControlMode">
|
||||||
<IAIMantineSelect
|
<IAIMantineSelect
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
label={t('controlnet.controlMode')}
|
label={t('controlnet.controlMode')}
|
||||||
|
@ -3,7 +3,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAISwitch from 'common/components/IAISwitch';
|
import IAISwitch from 'common/components/IAISwitch';
|
||||||
import { isControlNetEnabledToggled } from 'features/controlNet/store/controlNetSlice';
|
import { isControlNetEnabledToggled } from 'features/controlNet/store/controlNetSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
@ -28,7 +28,7 @@ const ParamControlNetFeatureToggle = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box width="100%">
|
<Box width="100%">
|
||||||
<IAIInformationalPopover details="controlNetToggle">
|
<IAIInformationalPopover feature="controlNet">
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label="Enable ControlNet"
|
label="Enable ControlNet"
|
||||||
isChecked={isEnabled}
|
isChecked={isEnabled}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||||
import {
|
import {
|
||||||
ControlNetConfig,
|
ControlNetConfig,
|
||||||
@ -34,7 +34,7 @@ export default function ParamControlNetResizeMode(
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="controlNetResizeMode">
|
<IAIInformationalPopover feature="controlNetResizeMode">
|
||||||
<IAIMantineSelect
|
<IAIMantineSelect
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
label={t('controlnet.resizeMode')}
|
label={t('controlnet.resizeMode')}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
import {
|
import {
|
||||||
ControlNetConfig,
|
ControlNetConfig,
|
||||||
@ -24,7 +24,7 @@ const ParamControlNetWeight = (props: ParamControlNetWeightProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="controlNetWeight">
|
<IAIInformationalPopover feature="controlNetWeight">
|
||||||
<IAISlider
|
<IAISlider
|
||||||
isDisabled={!isEnabled}
|
isDisabled={!isEnabled}
|
||||||
label={t('controlnet.weight')}
|
label={t('controlnet.weight')}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
import IAISlider from 'common/components/IAISlider';
|
||||||
|
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||||
|
import { RequiredColorMapImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||||
|
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||||
|
|
||||||
|
const DEFAULTS = CONTROLNET_PROCESSORS.color_map_image_processor
|
||||||
|
.default as RequiredColorMapImageProcessorInvocation;
|
||||||
|
|
||||||
|
type ColorMapProcessorProps = {
|
||||||
|
controlNetId: string;
|
||||||
|
processorNode: RequiredColorMapImageProcessorInvocation;
|
||||||
|
isEnabled: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ColorMapProcessor = (props: ColorMapProcessorProps) => {
|
||||||
|
const { controlNetId, processorNode, isEnabled } = props;
|
||||||
|
const { color_map_tile_size } = processorNode;
|
||||||
|
const processorChanged = useProcessorNodeChanged();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleColorMapTileSizeChanged = useCallback(
|
||||||
|
(v: number) => {
|
||||||
|
processorChanged(controlNetId, { color_map_tile_size: v });
|
||||||
|
},
|
||||||
|
[controlNetId, processorChanged]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleColorMapTileSizeReset = useCallback(() => {
|
||||||
|
processorChanged(controlNetId, {
|
||||||
|
color_map_tile_size: DEFAULTS.color_map_tile_size,
|
||||||
|
});
|
||||||
|
}, [controlNetId, processorChanged]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProcessorWrapper>
|
||||||
|
<IAISlider
|
||||||
|
isDisabled={!isEnabled}
|
||||||
|
label={t('controlnet.colorMapTileSize')}
|
||||||
|
value={color_map_tile_size}
|
||||||
|
onChange={handleColorMapTileSizeChanged}
|
||||||
|
handleReset={handleColorMapTileSizeReset}
|
||||||
|
withReset
|
||||||
|
min={1}
|
||||||
|
max={256}
|
||||||
|
step={1}
|
||||||
|
withInput
|
||||||
|
withSliderMarks
|
||||||
|
sliderNumberInputProps={{
|
||||||
|
max: 4096,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ProcessorWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ColorMapProcessor);
|
@ -4,5 +4,9 @@ import { PropsWithChildren } from 'react';
|
|||||||
type Props = PropsWithChildren;
|
type Props = PropsWithChildren;
|
||||||
|
|
||||||
export default function ProcessorWrapper(props: Props) {
|
export default function ProcessorWrapper(props: Props) {
|
||||||
return <Flex sx={{ flexDirection: 'column', gap: 2 }}>{props.children}</Flex>;
|
return (
|
||||||
|
<Flex sx={{ flexDirection: 'column', gap: 2, pb: 2 }}>
|
||||||
|
{props.children}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
import i18n from 'i18next';
|
||||||
import {
|
import {
|
||||||
ControlNetProcessorType,
|
ControlNetProcessorType,
|
||||||
RequiredControlNetProcessorNode,
|
RequiredControlNetProcessorNode,
|
||||||
} from './types';
|
} from './types';
|
||||||
import i18n from 'i18next';
|
|
||||||
|
|
||||||
type ControlNetProcessorsDict = Record<
|
type ControlNetProcessorsDict = Record<
|
||||||
ControlNetProcessorType,
|
ControlNetProcessorType,
|
||||||
@ -50,6 +50,20 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
|||||||
high_threshold: 200,
|
high_threshold: 200,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
color_map_image_processor: {
|
||||||
|
type: 'color_map_image_processor',
|
||||||
|
get label() {
|
||||||
|
return i18n.t('controlnet.colorMap');
|
||||||
|
},
|
||||||
|
get description() {
|
||||||
|
return i18n.t('controlnet.colorMapDescription');
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
id: 'color_map_image_processor',
|
||||||
|
type: 'color_map_image_processor',
|
||||||
|
color_map_tile_size: 64,
|
||||||
|
},
|
||||||
|
},
|
||||||
content_shuffle_image_processor: {
|
content_shuffle_image_processor: {
|
||||||
type: 'content_shuffle_image_processor',
|
type: 'content_shuffle_image_processor',
|
||||||
get label() {
|
get label() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { isObject } from 'lodash-es';
|
import { isObject } from 'lodash-es';
|
||||||
import {
|
import {
|
||||||
CannyImageProcessorInvocation,
|
CannyImageProcessorInvocation,
|
||||||
|
ColorMapImageProcessorInvocation,
|
||||||
ContentShuffleImageProcessorInvocation,
|
ContentShuffleImageProcessorInvocation,
|
||||||
HedImageProcessorInvocation,
|
HedImageProcessorInvocation,
|
||||||
LineartAnimeImageProcessorInvocation,
|
LineartAnimeImageProcessorInvocation,
|
||||||
@ -20,6 +21,7 @@ import { O } from 'ts-toolbelt';
|
|||||||
*/
|
*/
|
||||||
export type ControlNetProcessorNode =
|
export type ControlNetProcessorNode =
|
||||||
| CannyImageProcessorInvocation
|
| CannyImageProcessorInvocation
|
||||||
|
| ColorMapImageProcessorInvocation
|
||||||
| ContentShuffleImageProcessorInvocation
|
| ContentShuffleImageProcessorInvocation
|
||||||
| HedImageProcessorInvocation
|
| HedImageProcessorInvocation
|
||||||
| LineartAnimeImageProcessorInvocation
|
| LineartAnimeImageProcessorInvocation
|
||||||
@ -47,6 +49,14 @@ export type RequiredCannyImageProcessorInvocation = O.Required<
|
|||||||
'type' | 'low_threshold' | 'high_threshold'
|
'type' | 'low_threshold' | 'high_threshold'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Color Map processor node, with parameters flagged as required
|
||||||
|
*/
|
||||||
|
export type RequiredColorMapImageProcessorInvocation = O.Required<
|
||||||
|
ColorMapImageProcessorInvocation,
|
||||||
|
'type' | 'color_map_tile_size'
|
||||||
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ContentShuffle processor node, with parameters flagged as required
|
* The ContentShuffle processor node, with parameters flagged as required
|
||||||
*/
|
*/
|
||||||
@ -140,6 +150,7 @@ export type RequiredZoeDepthImageProcessorInvocation = O.Required<
|
|||||||
*/
|
*/
|
||||||
export type RequiredControlNetProcessorNode = O.Required<
|
export type RequiredControlNetProcessorNode = O.Required<
|
||||||
| RequiredCannyImageProcessorInvocation
|
| RequiredCannyImageProcessorInvocation
|
||||||
|
| RequiredColorMapImageProcessorInvocation
|
||||||
| RequiredContentShuffleImageProcessorInvocation
|
| RequiredContentShuffleImageProcessorInvocation
|
||||||
| RequiredHedImageProcessorInvocation
|
| RequiredHedImageProcessorInvocation
|
||||||
| RequiredLineartAnimeImageProcessorInvocation
|
| RequiredLineartAnimeImageProcessorInvocation
|
||||||
@ -166,6 +177,22 @@ export const isCannyImageProcessorInvocation = (
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard for ColorMapImageProcessorInvocation
|
||||||
|
*/
|
||||||
|
export const isColorMapImageProcessorInvocation = (
|
||||||
|
obj: unknown
|
||||||
|
): obj is ColorMapImageProcessorInvocation => {
|
||||||
|
if (
|
||||||
|
isObject(obj) &&
|
||||||
|
'type' in obj &&
|
||||||
|
obj.type === 'color_map_image_processor'
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type guard for ContentShuffleImageProcessorInvocation
|
* Type guard for ContentShuffleImageProcessorInvocation
|
||||||
*/
|
*/
|
||||||
|
@ -43,8 +43,8 @@ const ParamDynamicPromptsCollapse = () => {
|
|||||||
activeLabel={activeLabel}
|
activeLabel={activeLabel}
|
||||||
>
|
>
|
||||||
<Flex sx={{ gap: 2, flexDir: 'column' }}>
|
<Flex sx={{ gap: 2, flexDir: 'column' }}>
|
||||||
<ParamDynamicPromptsSeedBehaviour />
|
|
||||||
<ParamDynamicPromptsPreview />
|
<ParamDynamicPromptsPreview />
|
||||||
|
<ParamDynamicPromptsSeedBehaviour />
|
||||||
<ParamDynamicPromptsMaxPrompts />
|
<ParamDynamicPromptsMaxPrompts />
|
||||||
</Flex>
|
</Flex>
|
||||||
</IAICollapse>
|
</IAICollapse>
|
||||||
|
@ -4,9 +4,8 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAISwitch from 'common/components/IAISwitch';
|
import IAISwitch from 'common/components/IAISwitch';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { combinatorialToggled } from '../store/dynamicPromptsSlice';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import { combinatorialToggled } from '../store/dynamicPromptsSlice';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -28,13 +27,11 @@ const ParamDynamicPromptsCombinatorial = () => {
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="dynamicPromptsCombinatorial">
|
<IAISwitch
|
||||||
<IAISwitch
|
label={t('dynamicPrompts.combinatorial')}
|
||||||
label={t('dynamicPrompts.combinatorial')}
|
isChecked={combinatorial}
|
||||||
isChecked={combinatorial}
|
onChange={handleChange}
|
||||||
onChange={handleChange}
|
/>
|
||||||
/>
|
|
||||||
</IAIInformationalPopover>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
maxPromptsReset,
|
maxPromptsReset,
|
||||||
} from '../store/dynamicPromptsSlice';
|
} from '../store/dynamicPromptsSlice';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -46,19 +47,21 @@ const ParamDynamicPromptsMaxPrompts = () => {
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAISlider
|
<IAIInformationalPopover feature="dynamicPromptsMaxPrompts">
|
||||||
label={t('dynamicPrompts.maxPrompts')}
|
<IAISlider
|
||||||
isDisabled={isDisabled}
|
label={t('dynamicPrompts.maxPrompts')}
|
||||||
min={min}
|
isDisabled={isDisabled}
|
||||||
max={sliderMax}
|
min={min}
|
||||||
value={maxPrompts}
|
max={sliderMax}
|
||||||
onChange={handleChange}
|
value={maxPrompts}
|
||||||
sliderNumberInputProps={{ max: inputMax }}
|
onChange={handleChange}
|
||||||
withSliderMarks
|
sliderNumberInputProps={{ max: inputMax }}
|
||||||
withInput
|
withSliderMarks
|
||||||
withReset
|
withInput
|
||||||
handleReset={handleReset}
|
withReset
|
||||||
/>
|
handleReset={handleReset}
|
||||||
|
/>
|
||||||
|
</IAIInformationalPopover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import { stateSelector } from 'app/store/store';
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
|
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { FaCircleExclamation } from 'react-icons/fa6';
|
import { FaCircleExclamation } from 'react-icons/fa6';
|
||||||
@ -42,58 +43,73 @@ const ParamDynamicPromptsPreview = () => {
|
|||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<IAIInformationalPopover feature="dynamicPrompts">
|
||||||
w="full"
|
<Flex
|
||||||
h="full"
|
w="full"
|
||||||
layerStyle="second"
|
h="full"
|
||||||
alignItems="center"
|
layerStyle="second"
|
||||||
justifyContent="center"
|
alignItems="center"
|
||||||
p={8}
|
justifyContent="center"
|
||||||
>
|
p={8}
|
||||||
<IAINoContentFallback
|
>
|
||||||
icon={FaCircleExclamation}
|
<IAINoContentFallback
|
||||||
label="Problem generating prompts"
|
icon={FaCircleExclamation}
|
||||||
/>
|
label="Problem generating prompts"
|
||||||
</Flex>
|
/>
|
||||||
|
</Flex>
|
||||||
|
</IAIInformationalPopover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl isInvalid={Boolean(parsingError)}>
|
<IAIInformationalPopover feature="dynamicPrompts">
|
||||||
<FormLabel whiteSpace="nowrap" overflow="hidden" textOverflow="ellipsis">
|
<FormControl isInvalid={Boolean(parsingError)}>
|
||||||
Prompts Preview ({prompts.length}){parsingError && ` - ${parsingError}`}
|
<FormLabel
|
||||||
</FormLabel>
|
whiteSpace="nowrap"
|
||||||
<Flex h={64} pos="relative" layerStyle="third" borderRadius="base" p={2}>
|
overflow="hidden"
|
||||||
<ScrollableContent>
|
textOverflow="ellipsis"
|
||||||
<OrderedList stylePosition="inside" ms={0}>
|
>
|
||||||
{prompts.map((prompt, i) => (
|
Prompts Preview ({prompts.length})
|
||||||
<ListItem
|
{parsingError && ` - ${parsingError}`}
|
||||||
fontSize="sm"
|
</FormLabel>
|
||||||
key={`${prompt}.${i}`}
|
<Flex
|
||||||
sx={listItemStyles}
|
h={64}
|
||||||
>
|
pos="relative"
|
||||||
<Text as="span">{prompt}</Text>
|
layerStyle="third"
|
||||||
</ListItem>
|
borderRadius="base"
|
||||||
))}
|
p={2}
|
||||||
</OrderedList>
|
>
|
||||||
</ScrollableContent>
|
<ScrollableContent>
|
||||||
{isLoading && (
|
<OrderedList stylePosition="inside" ms={0}>
|
||||||
<Flex
|
{prompts.map((prompt, i) => (
|
||||||
pos="absolute"
|
<ListItem
|
||||||
w="full"
|
fontSize="sm"
|
||||||
h="full"
|
key={`${prompt}.${i}`}
|
||||||
top={0}
|
sx={listItemStyles}
|
||||||
insetInlineStart={0}
|
>
|
||||||
layerStyle="second"
|
<Text as="span">{prompt}</Text>
|
||||||
opacity={0.7}
|
</ListItem>
|
||||||
alignItems="center"
|
))}
|
||||||
justifyContent="center"
|
</OrderedList>
|
||||||
>
|
</ScrollableContent>
|
||||||
<Spinner />
|
{isLoading && (
|
||||||
</Flex>
|
<Flex
|
||||||
)}
|
pos="absolute"
|
||||||
</Flex>
|
w="full"
|
||||||
</FormControl>
|
h="full"
|
||||||
|
top={0}
|
||||||
|
insetInlineStart={0}
|
||||||
|
layerStyle="second"
|
||||||
|
opacity={0.7}
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
<Spinner />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</FormControl>
|
||||||
|
</IAIInformationalPopover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
seedBehaviourChanged,
|
seedBehaviourChanged,
|
||||||
} from '../store/dynamicPromptsSlice';
|
} from '../store/dynamicPromptsSlice';
|
||||||
import IAIMantineSelectItemWithDescription from 'common/components/IAIMantineSelectItemWithDescription';
|
import IAIMantineSelectItemWithDescription from 'common/components/IAIMantineSelectItemWithDescription';
|
||||||
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
|
|
||||||
type Item = {
|
type Item = {
|
||||||
label: string;
|
label: string;
|
||||||
@ -47,13 +48,15 @@ const ParamDynamicPromptsSeedBehaviour = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIMantineSelect
|
<IAIInformationalPopover feature="dynamicPromptsSeedBehaviour">
|
||||||
label={t('dynamicPrompts.seedBehaviour.label')}
|
<IAIMantineSelect
|
||||||
value={seedBehaviour}
|
label={t('dynamicPrompts.seedBehaviour.label')}
|
||||||
data={data}
|
value={seedBehaviour}
|
||||||
itemComponent={IAIMantineSelectItemWithDescription}
|
data={data}
|
||||||
onChange={handleChange}
|
itemComponent={IAIMantineSelectItemWithDescription}
|
||||||
/>
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</IAIInformationalPopover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ import {
|
|||||||
setShouldShowImageDetails,
|
setShouldShowImageDetails,
|
||||||
setShouldShowProgressInViewer,
|
setShouldShowProgressInViewer,
|
||||||
} from 'features/ui/store/uiSlice';
|
} from 'features/ui/store/uiSlice';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -41,9 +41,10 @@ import {
|
|||||||
import { FaCircleNodes, FaEllipsis } from 'react-icons/fa6';
|
import { FaCircleNodes, FaEllipsis } from 'react-icons/fa6';
|
||||||
import {
|
import {
|
||||||
useGetImageDTOQuery,
|
useGetImageDTOQuery,
|
||||||
useGetImageMetadataFromFileQuery,
|
useGetImageMetadataQuery,
|
||||||
} from 'services/api/endpoints/images';
|
} from 'services/api/endpoints/images';
|
||||||
import { menuListMotionProps } from 'theme/components/menu';
|
import { menuListMotionProps } from 'theme/components/menu';
|
||||||
|
import { useDebounce } from 'use-debounce';
|
||||||
import { sentImageToImg2Img } from '../../store/actions';
|
import { sentImageToImg2Img } from '../../store/actions';
|
||||||
import SingleSelectionMenuItems from '../ImageContextMenu/SingleSelectionMenuItems';
|
import SingleSelectionMenuItems from '../ImageContextMenu/SingleSelectionMenuItems';
|
||||||
|
|
||||||
@ -92,7 +93,6 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
shouldShowImageDetails,
|
shouldShowImageDetails,
|
||||||
lastSelectedImage,
|
lastSelectedImage,
|
||||||
shouldShowProgressInViewer,
|
shouldShowProgressInViewer,
|
||||||
shouldFetchMetadataFromApi,
|
|
||||||
} = useAppSelector(currentImageButtonsSelector);
|
} = useAppSelector(currentImageButtonsSelector);
|
||||||
|
|
||||||
const isUpscalingEnabled = useFeatureStatus('upscaling').isFeatureEnabled;
|
const isUpscalingEnabled = useFeatureStatus('upscaling').isFeatureEnabled;
|
||||||
@ -107,16 +107,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
lastSelectedImage?.image_name ?? skipToken
|
lastSelectedImage?.image_name ?? skipToken
|
||||||
);
|
);
|
||||||
|
|
||||||
const getMetadataArg = useMemo(() => {
|
const [debouncedImageName] = useDebounce(lastSelectedImage?.image_name, 300);
|
||||||
if (lastSelectedImage) {
|
|
||||||
return { image: lastSelectedImage, shouldFetchMetadataFromApi };
|
|
||||||
} else {
|
|
||||||
return skipToken;
|
|
||||||
}
|
|
||||||
}, [lastSelectedImage, shouldFetchMetadataFromApi]);
|
|
||||||
|
|
||||||
const { metadata, workflow, isLoading } = useGetImageMetadataFromFileQuery(
|
const { metadata, workflow, isLoading } = useGetImageMetadataQuery(
|
||||||
getMetadataArg,
|
debouncedImageName ?? skipToken,
|
||||||
{
|
{
|
||||||
selectFromResult: (res) => ({
|
selectFromResult: (res) => ({
|
||||||
isLoading: res.isFetching,
|
isLoading: res.isFetching,
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { Flex, MenuItem, Spinner } from '@chakra-ui/react';
|
import { Flex, MenuItem, Spinner } from '@chakra-ui/react';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
||||||
import { useAppToaster } from 'app/components/Toaster';
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||||
import {
|
import {
|
||||||
imagesToChangeSelected,
|
imagesToChangeSelected,
|
||||||
@ -32,12 +33,12 @@ import {
|
|||||||
import { FaCircleNodes } from 'react-icons/fa6';
|
import { FaCircleNodes } from 'react-icons/fa6';
|
||||||
import { MdStar, MdStarBorder } from 'react-icons/md';
|
import { MdStar, MdStarBorder } from 'react-icons/md';
|
||||||
import {
|
import {
|
||||||
useGetImageMetadataFromFileQuery,
|
useGetImageMetadataQuery,
|
||||||
useStarImagesMutation,
|
useStarImagesMutation,
|
||||||
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 { configSelector } from '../../../system/store/configSelectors';
|
import { useDebounce } from 'use-debounce';
|
||||||
import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions';
|
import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions';
|
||||||
|
|
||||||
type SingleSelectionMenuItemsProps = {
|
type SingleSelectionMenuItemsProps = {
|
||||||
@ -53,11 +54,12 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
const toaster = useAppToaster();
|
const toaster = useAppToaster();
|
||||||
|
|
||||||
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
||||||
const { shouldFetchMetadataFromApi } = useAppSelector(configSelector);
|
|
||||||
const customStarUi = useStore($customStarUI);
|
const customStarUi = useStore($customStarUI);
|
||||||
|
|
||||||
const { metadata, workflow, isLoading } = useGetImageMetadataFromFileQuery(
|
const [debouncedImageName] = useDebounce(imageDTO.image_name, 300);
|
||||||
{ image: imageDTO, shouldFetchMetadataFromApi },
|
|
||||||
|
const { metadata, workflow, isLoading } = useGetImageMetadataQuery(
|
||||||
|
debouncedImageName ?? skipToken,
|
||||||
{
|
{
|
||||||
selectFromResult: (res) => ({
|
selectFromResult: (res) => ({
|
||||||
isLoading: res.isFetching,
|
isLoading: res.isFetching,
|
||||||
|
@ -9,15 +9,15 @@ import {
|
|||||||
Tabs,
|
Tabs,
|
||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useGetImageMetadataFromFileQuery } from 'services/api/endpoints/images';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useGetImageMetadataQuery } from 'services/api/endpoints/images';
|
||||||
import { ImageDTO } from 'services/api/types';
|
import { ImageDTO } from 'services/api/types';
|
||||||
|
import { useDebounce } from 'use-debounce';
|
||||||
import DataViewer from './DataViewer';
|
import DataViewer from './DataViewer';
|
||||||
import ImageMetadataActions from './ImageMetadataActions';
|
import ImageMetadataActions from './ImageMetadataActions';
|
||||||
import { useAppSelector } from '../../../../app/store/storeHooks';
|
|
||||||
import { configSelector } from '../../../system/store/configSelectors';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type ImageMetadataViewerProps = {
|
type ImageMetadataViewerProps = {
|
||||||
image: ImageDTO;
|
image: ImageDTO;
|
||||||
@ -31,10 +31,10 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
|
|||||||
// });
|
// });
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { shouldFetchMetadataFromApi } = useAppSelector(configSelector);
|
const [debouncedImageName] = useDebounce(image.image_name, 300);
|
||||||
|
|
||||||
const { metadata, workflow } = useGetImageMetadataFromFileQuery(
|
const { metadata, workflow } = useGetImageMetadataQuery(
|
||||||
{ image, shouldFetchMetadataFromApi },
|
debouncedImageName ?? skipToken,
|
||||||
{
|
{
|
||||||
selectFromResult: (res) => ({
|
selectFromResult: (res) => ({
|
||||||
metadata: res?.currentData?.metadata,
|
metadata: res?.currentData?.metadata,
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
loraWeightChanged,
|
loraWeightChanged,
|
||||||
loraWeightReset,
|
loraWeightReset,
|
||||||
} from '../store/loraSlice';
|
} from '../store/loraSlice';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
lora: LoRA;
|
lora: LoRA;
|
||||||
@ -36,7 +36,7 @@ const ParamLora = (props: Props) => {
|
|||||||
}, [dispatch, lora.id]);
|
}, [dispatch, lora.id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="lora">
|
<IAIInformationalPopover feature="lora">
|
||||||
<Flex sx={{ gap: 2.5, alignItems: 'flex-end' }}>
|
<Flex sx={{ gap: 2.5, alignItems: 'flex-end' }}>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={lora.model_name}
|
label={lora.model_name}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { Checkbox, Flex, FormControl, FormLabel } from '@chakra-ui/react';
|
import { Checkbox, Flex, FormControl, FormLabel } from '@chakra-ui/react';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { useEmbedWorkflow } from 'features/nodes/hooks/useEmbedWorkflow';
|
import { useEmbedWorkflow } from 'features/nodes/hooks/useEmbedWorkflow';
|
||||||
import { useHasImageOutput } from 'features/nodes/hooks/useHasImageOutput';
|
import { useWithWorkflow } from 'features/nodes/hooks/useWithWorkflow';
|
||||||
import { nodeEmbedWorkflowChanged } from 'features/nodes/store/nodesSlice';
|
import { nodeEmbedWorkflowChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { ChangeEvent, memo, useCallback } from 'react';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
|
|
||||||
const EmbedWorkflowCheckbox = ({ nodeId }: { nodeId: string }) => {
|
const EmbedWorkflowCheckbox = ({ nodeId }: { nodeId: string }) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const hasImageOutput = useHasImageOutput(nodeId);
|
const withWorkflow = useWithWorkflow(nodeId);
|
||||||
const embedWorkflow = useEmbedWorkflow(nodeId);
|
const embedWorkflow = useEmbedWorkflow(nodeId);
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
@ -21,7 +21,7 @@ const EmbedWorkflowCheckbox = ({ nodeId }: { nodeId: string }) => {
|
|||||||
[dispatch, nodeId]
|
[dispatch, nodeId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasImageOutput) {
|
if (!withWorkflow) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Flex } from '@chakra-ui/react';
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
import { useHasImageOutput } from 'features/nodes/hooks/useHasImageOutput';
|
||||||
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import EmbedWorkflowCheckbox from './EmbedWorkflowCheckbox';
|
import EmbedWorkflowCheckbox from './EmbedWorkflowCheckbox';
|
||||||
import SaveToGalleryCheckbox from './SaveToGalleryCheckbox';
|
import SaveToGalleryCheckbox from './SaveToGalleryCheckbox';
|
||||||
import UseCacheCheckbox from './UseCacheCheckbox';
|
import UseCacheCheckbox from './UseCacheCheckbox';
|
||||||
import { useHasImageOutput } from 'features/nodes/hooks/useHasImageOutput';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@ -25,8 +25,8 @@ const InvocationNodeFooter = ({ nodeId }: Props) => {
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{hasImageOutput && <EmbedWorkflowCheckbox nodeId={nodeId} />}
|
|
||||||
<UseCacheCheckbox nodeId={nodeId} />
|
<UseCacheCheckbox nodeId={nodeId} />
|
||||||
|
{hasImageOutput && <EmbedWorkflowCheckbox nodeId={nodeId} />}
|
||||||
{hasImageOutput && <SaveToGalleryCheckbox nodeId={nodeId} />}
|
{hasImageOutput && <SaveToGalleryCheckbox nodeId={nodeId} />}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -16,6 +16,7 @@ import SchedulerInputField from './inputs/SchedulerInputField';
|
|||||||
import StringInputField from './inputs/StringInputField';
|
import StringInputField from './inputs/StringInputField';
|
||||||
import VaeModelInputField from './inputs/VaeModelInputField';
|
import VaeModelInputField from './inputs/VaeModelInputField';
|
||||||
import IPAdapterModelInputField from './inputs/IPAdapterModelInputField';
|
import IPAdapterModelInputField from './inputs/IPAdapterModelInputField';
|
||||||
|
import BoardInputField from './inputs/BoardInputField';
|
||||||
|
|
||||||
type InputFieldProps = {
|
type InputFieldProps = {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@ -99,6 +100,16 @@ const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (field?.type === 'BoardField' && fieldTemplate?.type === 'BoardField') {
|
||||||
|
return (
|
||||||
|
<BoardInputField
|
||||||
|
nodeId={nodeId}
|
||||||
|
field={field}
|
||||||
|
fieldTemplate={fieldTemplate}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
field?.type === 'MainModelField' &&
|
field?.type === 'MainModelField' &&
|
||||||
fieldTemplate?.type === 'MainModelField'
|
fieldTemplate?.type === 'MainModelField'
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
import { SelectItem } from '@mantine/core';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
||||||
|
import { fieldBoardValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
|
import {
|
||||||
|
BoardInputFieldTemplate,
|
||||||
|
BoardInputFieldValue,
|
||||||
|
FieldComponentProps,
|
||||||
|
} from 'features/nodes/types/types';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||||
|
|
||||||
|
const BoardInputFieldComponent = (
|
||||||
|
props: FieldComponentProps<BoardInputFieldValue, BoardInputFieldTemplate>
|
||||||
|
) => {
|
||||||
|
const { nodeId, field } = props;
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const { data, hasBoards } = useListAllBoardsQuery(undefined, {
|
||||||
|
selectFromResult: ({ data }) => {
|
||||||
|
const boards: SelectItem[] = [
|
||||||
|
{
|
||||||
|
label: 'None',
|
||||||
|
value: 'none',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
data?.forEach(({ board_id, board_name }) => {
|
||||||
|
boards.push({
|
||||||
|
label: board_name,
|
||||||
|
value: board_id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
data: boards,
|
||||||
|
hasBoards: boards.length > 1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(v: string | null) => {
|
||||||
|
dispatch(
|
||||||
|
fieldBoardValueChanged({
|
||||||
|
nodeId,
|
||||||
|
fieldName: field.name,
|
||||||
|
value: v && v !== 'none' ? { board_id: v } : undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatch, field.name, nodeId]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIMantineSearchableSelect
|
||||||
|
className="nowheel nodrag"
|
||||||
|
value={field.value?.board_id ?? 'none'}
|
||||||
|
data={data}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={!hasBoards}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(BoardInputFieldComponent);
|
@ -65,11 +65,6 @@ const SchedulerInputField = (
|
|||||||
return (
|
return (
|
||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
className="nowheel nodrag"
|
className="nowheel nodrag"
|
||||||
sx={{
|
|
||||||
'.mantine-Select-dropdown': {
|
|
||||||
width: '14rem !important',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
value={field.value}
|
value={field.value}
|
||||||
data={data}
|
data={data}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
@ -143,7 +143,7 @@ export const useBuildNodeData = () => {
|
|||||||
notes: '',
|
notes: '',
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
embedWorkflow: false,
|
embedWorkflow: false,
|
||||||
isIntermediate: true,
|
isIntermediate: type === 'save_image' ? false : true,
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
useCache: template.useCache,
|
useCache: template.useCache,
|
||||||
|
@ -17,8 +17,12 @@ export const useHasImageOutput = (nodeId: string) => {
|
|||||||
if (!isInvocationNode(node)) {
|
if (!isInvocationNode(node)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return some(node.data.outputs, (output) =>
|
return some(
|
||||||
IMAGE_FIELDS.includes(output.type)
|
node.data.outputs,
|
||||||
|
(output) =>
|
||||||
|
IMAGE_FIELDS.includes(output.type) &&
|
||||||
|
// the image primitive node does not actually save the image, do not show the image-saving checkboxes
|
||||||
|
node.data.type !== 'image'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
|
@ -122,13 +122,16 @@ export const useIsValidConnection = () => {
|
|||||||
|
|
||||||
const isIntToFloat = sourceType === 'integer' && targetType === 'float';
|
const isIntToFloat = sourceType === 'integer' && targetType === 'float';
|
||||||
|
|
||||||
|
const isEitherAnyType = sourceType === 'Any' || targetType === 'Any';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isCollectionItemToNonCollection ||
|
isCollectionItemToNonCollection ||
|
||||||
isNonCollectionToCollectionItem ||
|
isNonCollectionToCollectionItem ||
|
||||||
isAnythingToPolymorphicOfSameBaseType ||
|
isAnythingToPolymorphicOfSameBaseType ||
|
||||||
isGenericCollectionToAnyCollectionOrPolymorphic ||
|
isGenericCollectionToAnyCollectionOrPolymorphic ||
|
||||||
isCollectionToGenericCollection ||
|
isCollectionToGenericCollection ||
|
||||||
isIntToFloat
|
isIntToFloat ||
|
||||||
|
isEitherAnyType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { stateSelector } from 'app/store/store';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { isInvocationNode } from '../types/types';
|
||||||
|
|
||||||
|
export const useWithWorkflow = (nodeId: string) => {
|
||||||
|
const selector = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(
|
||||||
|
stateSelector,
|
||||||
|
({ nodes }) => {
|
||||||
|
const node = nodes.nodes.find((node) => node.id === nodeId);
|
||||||
|
if (!isInvocationNode(node)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
|
||||||
|
if (!nodeTemplate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return nodeTemplate.withWorkflow;
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
),
|
||||||
|
[nodeId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const withWorkflow = useAppSelector(selector);
|
||||||
|
return withWorkflow;
|
||||||
|
};
|
@ -30,6 +30,7 @@ import {
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { DRAG_HANDLE_CLASSNAME } from '../types/constants';
|
import { DRAG_HANDLE_CLASSNAME } from '../types/constants';
|
||||||
import {
|
import {
|
||||||
|
BoardInputFieldValue,
|
||||||
BooleanInputFieldValue,
|
BooleanInputFieldValue,
|
||||||
ColorInputFieldValue,
|
ColorInputFieldValue,
|
||||||
ControlNetModelInputFieldValue,
|
ControlNetModelInputFieldValue,
|
||||||
@ -494,6 +495,12 @@ const nodesSlice = createSlice({
|
|||||||
) => {
|
) => {
|
||||||
fieldValueReducer(state, action);
|
fieldValueReducer(state, action);
|
||||||
},
|
},
|
||||||
|
fieldBoardValueChanged: (
|
||||||
|
state,
|
||||||
|
action: FieldValueAction<BoardInputFieldValue>
|
||||||
|
) => {
|
||||||
|
fieldValueReducer(state, action);
|
||||||
|
},
|
||||||
fieldImageValueChanged: (
|
fieldImageValueChanged: (
|
||||||
state,
|
state,
|
||||||
action: FieldValueAction<ImageInputFieldValue>
|
action: FieldValueAction<ImageInputFieldValue>
|
||||||
@ -871,7 +878,7 @@ const nodesSlice = createSlice({
|
|||||||
builder.addCase(appSocketQueueItemStatusChanged, (state, action) => {
|
builder.addCase(appSocketQueueItemStatusChanged, (state, action) => {
|
||||||
if (['in_progress'].includes(action.payload.data.status)) {
|
if (['in_progress'].includes(action.payload.data.status)) {
|
||||||
forEach(state.nodeExecutionStates, (nes) => {
|
forEach(state.nodeExecutionStates, (nes) => {
|
||||||
nes.status = NodeStatus.IN_PROGRESS;
|
nes.status = NodeStatus.PENDING;
|
||||||
nes.error = null;
|
nes.error = null;
|
||||||
nes.progress = null;
|
nes.progress = null;
|
||||||
nes.progressImage = null;
|
nes.progressImage = null;
|
||||||
@ -897,6 +904,7 @@ export const {
|
|||||||
imageCollectionFieldValueChanged,
|
imageCollectionFieldValueChanged,
|
||||||
fieldStringValueChanged,
|
fieldStringValueChanged,
|
||||||
fieldNumberValueChanged,
|
fieldNumberValueChanged,
|
||||||
|
fieldBoardValueChanged,
|
||||||
fieldBooleanValueChanged,
|
fieldBooleanValueChanged,
|
||||||
fieldImageValueChanged,
|
fieldImageValueChanged,
|
||||||
fieldColorValueChanged,
|
fieldColorValueChanged,
|
||||||
|
@ -116,6 +116,8 @@ export const makeConnectionErrorSelector = (
|
|||||||
|
|
||||||
const isIntToFloat = sourceType === 'integer' && targetType === 'float';
|
const isIntToFloat = sourceType === 'integer' && targetType === 'float';
|
||||||
|
|
||||||
|
const isEitherAnyType = sourceType === 'Any' || targetType === 'Any';
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
isCollectionItemToNonCollection ||
|
isCollectionItemToNonCollection ||
|
||||||
@ -123,7 +125,8 @@ export const makeConnectionErrorSelector = (
|
|||||||
isAnythingToPolymorphicOfSameBaseType ||
|
isAnythingToPolymorphicOfSameBaseType ||
|
||||||
isGenericCollectionToAnyCollectionOrPolymorphic ||
|
isGenericCollectionToAnyCollectionOrPolymorphic ||
|
||||||
isCollectionToGenericCollection ||
|
isCollectionToGenericCollection ||
|
||||||
isIntToFloat
|
isIntToFloat ||
|
||||||
|
isEitherAnyType
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return i18n.t('nodes.fieldTypesMustMatch');
|
return i18n.t('nodes.fieldTypesMustMatch');
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
import { FieldType, FieldUIConfig } from './types';
|
import {
|
||||||
|
FieldType,
|
||||||
|
FieldTypeMap,
|
||||||
|
FieldTypeMapWithNumber,
|
||||||
|
FieldUIConfig,
|
||||||
|
} from './types';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
|
||||||
export const HANDLE_TOOLTIP_OPEN_DELAY = 500;
|
export const HANDLE_TOOLTIP_OPEN_DELAY = 500;
|
||||||
@ -26,9 +31,11 @@ export const COLLECTION_TYPES: FieldType[] = [
|
|||||||
'ConditioningCollection',
|
'ConditioningCollection',
|
||||||
'ControlCollection',
|
'ControlCollection',
|
||||||
'ColorCollection',
|
'ColorCollection',
|
||||||
|
'MetadataItemCollection',
|
||||||
|
'MetadataDictCollection',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const POLYMORPHIC_TYPES = [
|
export const POLYMORPHIC_TYPES: FieldType[] = [
|
||||||
'IntegerPolymorphic',
|
'IntegerPolymorphic',
|
||||||
'BooleanPolymorphic',
|
'BooleanPolymorphic',
|
||||||
'FloatPolymorphic',
|
'FloatPolymorphic',
|
||||||
@ -38,9 +45,10 @@ export const POLYMORPHIC_TYPES = [
|
|||||||
'ConditioningPolymorphic',
|
'ConditioningPolymorphic',
|
||||||
'ControlPolymorphic',
|
'ControlPolymorphic',
|
||||||
'ColorPolymorphic',
|
'ColorPolymorphic',
|
||||||
|
'MetadataItemPolymorphic',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const MODEL_TYPES = [
|
export const MODEL_TYPES: FieldType[] = [
|
||||||
'IPAdapterModelField',
|
'IPAdapterModelField',
|
||||||
'ControlNetModelField',
|
'ControlNetModelField',
|
||||||
'LoRAModelField',
|
'LoRAModelField',
|
||||||
@ -54,7 +62,7 @@ export const MODEL_TYPES = [
|
|||||||
'ClipField',
|
'ClipField',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const COLLECTION_MAP = {
|
export const COLLECTION_MAP: FieldTypeMapWithNumber = {
|
||||||
integer: 'IntegerCollection',
|
integer: 'IntegerCollection',
|
||||||
boolean: 'BooleanCollection',
|
boolean: 'BooleanCollection',
|
||||||
number: 'FloatCollection',
|
number: 'FloatCollection',
|
||||||
@ -65,13 +73,15 @@ export const COLLECTION_MAP = {
|
|||||||
ConditioningField: 'ConditioningCollection',
|
ConditioningField: 'ConditioningCollection',
|
||||||
ControlField: 'ControlCollection',
|
ControlField: 'ControlCollection',
|
||||||
ColorField: 'ColorCollection',
|
ColorField: 'ColorCollection',
|
||||||
|
MetadataItem: 'MetadataItemCollection',
|
||||||
|
MetadataDict: 'MetadataDictCollection',
|
||||||
};
|
};
|
||||||
export const isCollectionItemType = (
|
export const isCollectionItemType = (
|
||||||
itemType: string | undefined
|
itemType: string | undefined
|
||||||
): itemType is keyof typeof COLLECTION_MAP =>
|
): itemType is keyof typeof COLLECTION_MAP =>
|
||||||
Boolean(itemType && itemType in COLLECTION_MAP);
|
Boolean(itemType && itemType in COLLECTION_MAP);
|
||||||
|
|
||||||
export const SINGLE_TO_POLYMORPHIC_MAP = {
|
export const SINGLE_TO_POLYMORPHIC_MAP: FieldTypeMapWithNumber = {
|
||||||
integer: 'IntegerPolymorphic',
|
integer: 'IntegerPolymorphic',
|
||||||
boolean: 'BooleanPolymorphic',
|
boolean: 'BooleanPolymorphic',
|
||||||
number: 'FloatPolymorphic',
|
number: 'FloatPolymorphic',
|
||||||
@ -82,9 +92,10 @@ export const SINGLE_TO_POLYMORPHIC_MAP = {
|
|||||||
ConditioningField: 'ConditioningPolymorphic',
|
ConditioningField: 'ConditioningPolymorphic',
|
||||||
ControlField: 'ControlPolymorphic',
|
ControlField: 'ControlPolymorphic',
|
||||||
ColorField: 'ColorPolymorphic',
|
ColorField: 'ColorPolymorphic',
|
||||||
|
MetadataItem: 'MetadataItemPolymorphic',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const POLYMORPHIC_TO_SINGLE_MAP = {
|
export const POLYMORPHIC_TO_SINGLE_MAP: FieldTypeMap = {
|
||||||
IntegerPolymorphic: 'integer',
|
IntegerPolymorphic: 'integer',
|
||||||
BooleanPolymorphic: 'boolean',
|
BooleanPolymorphic: 'boolean',
|
||||||
FloatPolymorphic: 'float',
|
FloatPolymorphic: 'float',
|
||||||
@ -94,9 +105,10 @@ export const POLYMORPHIC_TO_SINGLE_MAP = {
|
|||||||
ConditioningPolymorphic: 'ConditioningField',
|
ConditioningPolymorphic: 'ConditioningField',
|
||||||
ControlPolymorphic: 'ControlField',
|
ControlPolymorphic: 'ControlField',
|
||||||
ColorPolymorphic: 'ColorField',
|
ColorPolymorphic: 'ColorField',
|
||||||
|
MetadataItemPolymorphic: 'MetadataItem',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TYPES_WITH_INPUT_COMPONENTS = [
|
export const TYPES_WITH_INPUT_COMPONENTS: FieldType[] = [
|
||||||
'string',
|
'string',
|
||||||
'StringPolymorphic',
|
'StringPolymorphic',
|
||||||
'boolean',
|
'boolean',
|
||||||
@ -117,6 +129,7 @@ export const TYPES_WITH_INPUT_COMPONENTS = [
|
|||||||
'SDXLMainModelField',
|
'SDXLMainModelField',
|
||||||
'Scheduler',
|
'Scheduler',
|
||||||
'IPAdapterModelField',
|
'IPAdapterModelField',
|
||||||
|
'BoardField',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const isPolymorphicItemType = (
|
export const isPolymorphicItemType = (
|
||||||
@ -125,6 +138,37 @@ export const isPolymorphicItemType = (
|
|||||||
Boolean(itemType && itemType in SINGLE_TO_POLYMORPHIC_MAP);
|
Boolean(itemType && itemType in SINGLE_TO_POLYMORPHIC_MAP);
|
||||||
|
|
||||||
export const FIELDS: Record<FieldType, FieldUIConfig> = {
|
export const FIELDS: Record<FieldType, FieldUIConfig> = {
|
||||||
|
Any: {
|
||||||
|
color: 'gray.500',
|
||||||
|
description: 'Any field type is accepted.',
|
||||||
|
title: 'Any',
|
||||||
|
},
|
||||||
|
MetadataDict: {
|
||||||
|
color: 'gray.500',
|
||||||
|
description: 'A metadata dict.',
|
||||||
|
title: 'Metadata Dict',
|
||||||
|
},
|
||||||
|
MetadataDictCollection: {
|
||||||
|
color: 'gray.500',
|
||||||
|
description: 'A collection of metadata dicts.',
|
||||||
|
title: 'Metadata Dict Collection',
|
||||||
|
},
|
||||||
|
MetadataItem: {
|
||||||
|
color: 'gray.500',
|
||||||
|
description: 'A metadata item.',
|
||||||
|
title: 'Metadata Item',
|
||||||
|
},
|
||||||
|
MetadataItemCollection: {
|
||||||
|
color: 'gray.500',
|
||||||
|
description: 'Any field type is accepted.',
|
||||||
|
title: 'Metadata Item Collection',
|
||||||
|
},
|
||||||
|
MetadataItemPolymorphic: {
|
||||||
|
color: 'gray.500',
|
||||||
|
description:
|
||||||
|
'MetadataItem or MetadataItemCollection field types are accepted.',
|
||||||
|
title: 'Metadata Item Polymorphic',
|
||||||
|
},
|
||||||
boolean: {
|
boolean: {
|
||||||
color: 'green.500',
|
color: 'green.500',
|
||||||
description: t('nodes.booleanDescription'),
|
description: t('nodes.booleanDescription'),
|
||||||
@ -240,6 +284,11 @@ export const FIELDS: Record<FieldType, FieldUIConfig> = {
|
|||||||
description: t('nodes.imageFieldDescription'),
|
description: t('nodes.imageFieldDescription'),
|
||||||
title: t('nodes.imageField'),
|
title: t('nodes.imageField'),
|
||||||
},
|
},
|
||||||
|
BoardField: {
|
||||||
|
color: 'purple.500',
|
||||||
|
description: t('nodes.imageFieldDescription'),
|
||||||
|
title: t('nodes.imageField'),
|
||||||
|
},
|
||||||
ImagePolymorphic: {
|
ImagePolymorphic: {
|
||||||
color: 'purple.500',
|
color: 'purple.500',
|
||||||
description: t('nodes.imagePolymorphicDescription'),
|
description: t('nodes.imagePolymorphicDescription'),
|
||||||
|
@ -54,6 +54,10 @@ export type InvocationTemplate = {
|
|||||||
* The type of this node's output
|
* The type of this node's output
|
||||||
*/
|
*/
|
||||||
outputType: string; // TODO: generate a union of output types
|
outputType: string; // TODO: generate a union of output types
|
||||||
|
/**
|
||||||
|
* Whether or not this invocation supports workflows
|
||||||
|
*/
|
||||||
|
withWorkflow: boolean;
|
||||||
/**
|
/**
|
||||||
* The invocation's version.
|
* The invocation's version.
|
||||||
*/
|
*/
|
||||||
@ -72,6 +76,8 @@ export type FieldUIConfig = {
|
|||||||
|
|
||||||
// TODO: Get this from the OpenAPI schema? may be tricky...
|
// TODO: Get this from the OpenAPI schema? may be tricky...
|
||||||
export const zFieldType = z.enum([
|
export const zFieldType = z.enum([
|
||||||
|
'Any',
|
||||||
|
'BoardField',
|
||||||
'boolean',
|
'boolean',
|
||||||
'BooleanCollection',
|
'BooleanCollection',
|
||||||
'BooleanPolymorphic',
|
'BooleanPolymorphic',
|
||||||
@ -106,6 +112,11 @@ export const zFieldType = z.enum([
|
|||||||
'LatentsPolymorphic',
|
'LatentsPolymorphic',
|
||||||
'LoRAModelField',
|
'LoRAModelField',
|
||||||
'MainModelField',
|
'MainModelField',
|
||||||
|
'MetadataDict',
|
||||||
|
'MetadataDictCollection',
|
||||||
|
'MetadataItem',
|
||||||
|
'MetadataItemCollection',
|
||||||
|
'MetadataItemPolymorphic',
|
||||||
'ONNXModelField',
|
'ONNXModelField',
|
||||||
'Scheduler',
|
'Scheduler',
|
||||||
'SDXLMainModelField',
|
'SDXLMainModelField',
|
||||||
@ -119,6 +130,10 @@ export const zFieldType = z.enum([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
export type FieldType = z.infer<typeof zFieldType>;
|
export type FieldType = z.infer<typeof zFieldType>;
|
||||||
|
export type FieldTypeMap = { [key in FieldType]?: FieldType };
|
||||||
|
export type FieldTypeMapWithNumber = {
|
||||||
|
[key in FieldType | 'number']?: FieldType;
|
||||||
|
};
|
||||||
|
|
||||||
export const zReservedFieldType = z.enum([
|
export const zReservedFieldType = z.enum([
|
||||||
'WorkflowField',
|
'WorkflowField',
|
||||||
@ -187,6 +202,11 @@ export const zImageField = z.object({
|
|||||||
});
|
});
|
||||||
export type ImageField = z.infer<typeof zImageField>;
|
export type ImageField = z.infer<typeof zImageField>;
|
||||||
|
|
||||||
|
export const zBoardField = z.object({
|
||||||
|
board_id: z.string().trim().min(1),
|
||||||
|
});
|
||||||
|
export type BoardField = z.infer<typeof zBoardField>;
|
||||||
|
|
||||||
export const zLatentsField = z.object({
|
export const zLatentsField = z.object({
|
||||||
latents_name: z.string().trim().min(1),
|
latents_name: z.string().trim().min(1),
|
||||||
seed: z.number().int().optional(),
|
seed: z.number().int().optional(),
|
||||||
@ -494,6 +514,12 @@ export const zImageInputFieldValue = zInputFieldValueBase.extend({
|
|||||||
});
|
});
|
||||||
export type ImageInputFieldValue = z.infer<typeof zImageInputFieldValue>;
|
export type ImageInputFieldValue = z.infer<typeof zImageInputFieldValue>;
|
||||||
|
|
||||||
|
export const zBoardInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('BoardField'),
|
||||||
|
value: zBoardField.optional(),
|
||||||
|
});
|
||||||
|
export type BoardInputFieldValue = z.infer<typeof zBoardInputFieldValue>;
|
||||||
|
|
||||||
export const zImagePolymorphicInputFieldValue = zInputFieldValueBase.extend({
|
export const zImagePolymorphicInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: z.literal('ImagePolymorphic'),
|
type: z.literal('ImagePolymorphic'),
|
||||||
value: zImageField.optional(),
|
value: zImageField.optional(),
|
||||||
@ -591,6 +617,58 @@ export type CollectionItemInputFieldValue = z.infer<
|
|||||||
typeof zCollectionItemInputFieldValue
|
typeof zCollectionItemInputFieldValue
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export const zMetadataItem = z.object({
|
||||||
|
label: z.string(),
|
||||||
|
value: z.any(),
|
||||||
|
});
|
||||||
|
export type MetadataItem = z.infer<typeof zMetadataItem>;
|
||||||
|
|
||||||
|
export const zMetadataItemInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('MetadataItem'),
|
||||||
|
value: zMetadataItem.optional(),
|
||||||
|
});
|
||||||
|
export type MetadataItemInputFieldValue = z.infer<
|
||||||
|
typeof zMetadataItemInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const zMetadataItemCollectionInputFieldValue =
|
||||||
|
zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('MetadataItemCollection'),
|
||||||
|
value: z.array(zMetadataItem).optional(),
|
||||||
|
});
|
||||||
|
export type MetadataItemCollectionInputFieldValue = z.infer<
|
||||||
|
typeof zMetadataItemCollectionInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const zMetadataItemPolymorphicInputFieldValue =
|
||||||
|
zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('MetadataItemPolymorphic'),
|
||||||
|
value: z.union([zMetadataItem, z.array(zMetadataItem)]).optional(),
|
||||||
|
});
|
||||||
|
export type MetadataItemPolymorphicInputFieldValue = z.infer<
|
||||||
|
typeof zMetadataItemPolymorphicInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const zMetadataDict = z.record(z.any());
|
||||||
|
export type MetadataDict = z.infer<typeof zMetadataDict>;
|
||||||
|
|
||||||
|
export const zMetadataDictInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('MetadataDict'),
|
||||||
|
value: zMetadataDict.optional(),
|
||||||
|
});
|
||||||
|
export type MetadataDictInputFieldValue = z.infer<
|
||||||
|
typeof zMetadataDictInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const zMetadataDictCollectionInputFieldValue =
|
||||||
|
zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('MetadataDictCollection'),
|
||||||
|
value: z.array(zMetadataDict).optional(),
|
||||||
|
});
|
||||||
|
export type MetadataDictCollectionInputFieldValue = z.infer<
|
||||||
|
typeof zMetadataDictCollectionInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
export const zColorField = z.object({
|
export const zColorField = z.object({
|
||||||
r: z.number().int().min(0).max(255),
|
r: z.number().int().min(0).max(255),
|
||||||
g: z.number().int().min(0).max(255),
|
g: z.number().int().min(0).max(255),
|
||||||
@ -629,7 +707,14 @@ export type SchedulerInputFieldValue = z.infer<
|
|||||||
typeof zSchedulerInputFieldValue
|
typeof zSchedulerInputFieldValue
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export const zAnyInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('Any'),
|
||||||
|
value: z.any().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
export const zInputFieldValue = z.discriminatedUnion('type', [
|
export const zInputFieldValue = z.discriminatedUnion('type', [
|
||||||
|
zAnyInputFieldValue,
|
||||||
|
zBoardInputFieldValue,
|
||||||
zBooleanCollectionInputFieldValue,
|
zBooleanCollectionInputFieldValue,
|
||||||
zBooleanInputFieldValue,
|
zBooleanInputFieldValue,
|
||||||
zBooleanPolymorphicInputFieldValue,
|
zBooleanPolymorphicInputFieldValue,
|
||||||
@ -673,6 +758,11 @@ export const zInputFieldValue = z.discriminatedUnion('type', [
|
|||||||
zUNetInputFieldValue,
|
zUNetInputFieldValue,
|
||||||
zVaeInputFieldValue,
|
zVaeInputFieldValue,
|
||||||
zVaeModelInputFieldValue,
|
zVaeModelInputFieldValue,
|
||||||
|
zMetadataItemInputFieldValue,
|
||||||
|
zMetadataItemCollectionInputFieldValue,
|
||||||
|
zMetadataItemPolymorphicInputFieldValue,
|
||||||
|
zMetadataDictInputFieldValue,
|
||||||
|
zMetadataDictCollectionInputFieldValue,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type InputFieldValue = z.infer<typeof zInputFieldValue>;
|
export type InputFieldValue = z.infer<typeof zInputFieldValue>;
|
||||||
@ -685,6 +775,11 @@ export type InputFieldTemplateBase = {
|
|||||||
fieldKind: 'input';
|
fieldKind: 'input';
|
||||||
} & _InputField;
|
} & _InputField;
|
||||||
|
|
||||||
|
export type AnyInputFieldTemplate = InputFieldTemplateBase & {
|
||||||
|
type: 'Any';
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
export type IntegerInputFieldTemplate = InputFieldTemplateBase & {
|
export type IntegerInputFieldTemplate = InputFieldTemplateBase & {
|
||||||
type: 'integer';
|
type: 'integer';
|
||||||
default: number;
|
default: number;
|
||||||
@ -770,6 +865,11 @@ export type BooleanPolymorphicInputFieldTemplate = Omit<
|
|||||||
type: 'BooleanPolymorphic';
|
type: 'BooleanPolymorphic';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BoardInputFieldTemplate = InputFieldTemplateBase & {
|
||||||
|
default: BoardField;
|
||||||
|
type: 'BoardField';
|
||||||
|
};
|
||||||
|
|
||||||
export type ImageInputFieldTemplate = InputFieldTemplateBase & {
|
export type ImageInputFieldTemplate = InputFieldTemplateBase & {
|
||||||
default: ImageField;
|
default: ImageField;
|
||||||
type: 'ImageField';
|
type: 'ImageField';
|
||||||
@ -833,6 +933,11 @@ export type UNetInputFieldTemplate = InputFieldTemplateBase & {
|
|||||||
type: 'UNetField';
|
type: 'UNetField';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type MetadataItemFieldTemplate = InputFieldTemplateBase & {
|
||||||
|
default: undefined;
|
||||||
|
type: 'UNetField';
|
||||||
|
};
|
||||||
|
|
||||||
export type ClipInputFieldTemplate = InputFieldTemplateBase & {
|
export type ClipInputFieldTemplate = InputFieldTemplateBase & {
|
||||||
default: undefined;
|
default: undefined;
|
||||||
type: 'ClipField';
|
type: 'ClipField';
|
||||||
@ -945,6 +1050,35 @@ export type WorkflowInputFieldTemplate = InputFieldTemplateBase & {
|
|||||||
type: 'WorkflowField';
|
type: 'WorkflowField';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type MetadataItemInputFieldTemplate = InputFieldTemplateBase & {
|
||||||
|
default: undefined;
|
||||||
|
type: 'MetadataItem';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MetadataItemCollectionInputFieldTemplate =
|
||||||
|
InputFieldTemplateBase & {
|
||||||
|
default: undefined;
|
||||||
|
type: 'MetadataItemCollection';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MetadataItemPolymorphicInputFieldTemplate = Omit<
|
||||||
|
MetadataItemInputFieldTemplate,
|
||||||
|
'type'
|
||||||
|
> & {
|
||||||
|
type: 'MetadataItemPolymorphic';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MetadataDictInputFieldTemplate = InputFieldTemplateBase & {
|
||||||
|
default: undefined;
|
||||||
|
type: 'MetadataDict';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MetadataDictCollectionInputFieldTemplate =
|
||||||
|
InputFieldTemplateBase & {
|
||||||
|
default: undefined;
|
||||||
|
type: 'MetadataDictCollection';
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An input field template is generated on each page load from the OpenAPI schema.
|
* An input field template is generated on each page load from the OpenAPI schema.
|
||||||
*
|
*
|
||||||
@ -952,6 +1086,8 @@ export type WorkflowInputFieldTemplate = InputFieldTemplateBase & {
|
|||||||
* maximum length, pattern to match, etc).
|
* maximum length, pattern to match, etc).
|
||||||
*/
|
*/
|
||||||
export type InputFieldTemplate =
|
export type InputFieldTemplate =
|
||||||
|
| AnyInputFieldTemplate
|
||||||
|
| BoardInputFieldTemplate
|
||||||
| BooleanCollectionInputFieldTemplate
|
| BooleanCollectionInputFieldTemplate
|
||||||
| BooleanPolymorphicInputFieldTemplate
|
| BooleanPolymorphicInputFieldTemplate
|
||||||
| BooleanInputFieldTemplate
|
| BooleanInputFieldTemplate
|
||||||
@ -994,7 +1130,12 @@ export type InputFieldTemplate =
|
|||||||
| StringInputFieldTemplate
|
| StringInputFieldTemplate
|
||||||
| UNetInputFieldTemplate
|
| UNetInputFieldTemplate
|
||||||
| VaeInputFieldTemplate
|
| VaeInputFieldTemplate
|
||||||
| VaeModelInputFieldTemplate;
|
| VaeModelInputFieldTemplate
|
||||||
|
| MetadataItemInputFieldTemplate
|
||||||
|
| MetadataItemCollectionInputFieldTemplate
|
||||||
|
| MetadataDictInputFieldTemplate
|
||||||
|
| MetadataItemPolymorphicInputFieldTemplate
|
||||||
|
| MetadataDictCollectionInputFieldTemplate;
|
||||||
|
|
||||||
export const isInputFieldValue = (
|
export const isInputFieldValue = (
|
||||||
field?: InputFieldValue | OutputFieldValue
|
field?: InputFieldValue | OutputFieldValue
|
||||||
@ -1111,7 +1252,7 @@ export const isInvocationFieldSchema = (
|
|||||||
|
|
||||||
export type InvocationEdgeExtra = { type: 'default' | 'collapsed' };
|
export type InvocationEdgeExtra = { type: 'default' | 'collapsed' };
|
||||||
|
|
||||||
const zLoRAMetadataItem = z.object({
|
export const zLoRAMetadataItem = z.object({
|
||||||
lora: zLoRAModelField.deepPartial(),
|
lora: zLoRAModelField.deepPartial(),
|
||||||
weight: z.number(),
|
weight: z.number(),
|
||||||
});
|
});
|
||||||
@ -1138,15 +1279,7 @@ export const zCoreMetadata = z
|
|||||||
.nullish()
|
.nullish()
|
||||||
.catch(null),
|
.catch(null),
|
||||||
controlnets: z.array(zControlField.deepPartial()).nullish().catch(null),
|
controlnets: z.array(zControlField.deepPartial()).nullish().catch(null),
|
||||||
loras: z
|
loras: z.array(zLoRAMetadataItem).nullish().catch(null),
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
lora: zLoRAModelField.deepPartial(),
|
|
||||||
weight: z.number(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.nullish()
|
|
||||||
.catch(null),
|
|
||||||
vae: zVaeModelField.nullish().catch(null),
|
vae: zVaeModelField.nullish().catch(null),
|
||||||
strength: z.number().nullish().catch(null),
|
strength: z.number().nullish().catch(null),
|
||||||
init_image: z.string().nullish().catch(null),
|
init_image: z.string().nullish().catch(null),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { isBoolean, isInteger, isNumber, isString } from 'lodash-es';
|
import { isBoolean, isInteger, isNumber, isString } from 'lodash-es';
|
||||||
import { OpenAPIV3 } from 'openapi-types';
|
import { OpenAPIV3 } from 'openapi-types';
|
||||||
|
import { ControlField } from 'services/api/types';
|
||||||
import {
|
import {
|
||||||
COLLECTION_MAP,
|
COLLECTION_MAP,
|
||||||
POLYMORPHIC_TYPES,
|
POLYMORPHIC_TYPES,
|
||||||
@ -8,36 +9,61 @@ import {
|
|||||||
isPolymorphicItemType,
|
isPolymorphicItemType,
|
||||||
} from '../types/constants';
|
} from '../types/constants';
|
||||||
import {
|
import {
|
||||||
|
AnyInputFieldTemplate,
|
||||||
|
BoardInputFieldTemplate,
|
||||||
BooleanCollectionInputFieldTemplate,
|
BooleanCollectionInputFieldTemplate,
|
||||||
BooleanInputFieldTemplate,
|
BooleanInputFieldTemplate,
|
||||||
|
BooleanPolymorphicInputFieldTemplate,
|
||||||
ClipInputFieldTemplate,
|
ClipInputFieldTemplate,
|
||||||
CollectionInputFieldTemplate,
|
CollectionInputFieldTemplate,
|
||||||
CollectionItemInputFieldTemplate,
|
CollectionItemInputFieldTemplate,
|
||||||
|
ColorCollectionInputFieldTemplate,
|
||||||
ColorInputFieldTemplate,
|
ColorInputFieldTemplate,
|
||||||
|
ColorPolymorphicInputFieldTemplate,
|
||||||
|
ConditioningCollectionInputFieldTemplate,
|
||||||
|
ConditioningField,
|
||||||
ConditioningInputFieldTemplate,
|
ConditioningInputFieldTemplate,
|
||||||
|
ConditioningPolymorphicInputFieldTemplate,
|
||||||
|
ControlCollectionInputFieldTemplate,
|
||||||
ControlInputFieldTemplate,
|
ControlInputFieldTemplate,
|
||||||
ControlNetModelInputFieldTemplate,
|
ControlNetModelInputFieldTemplate,
|
||||||
|
ControlPolymorphicInputFieldTemplate,
|
||||||
DenoiseMaskInputFieldTemplate,
|
DenoiseMaskInputFieldTemplate,
|
||||||
EnumInputFieldTemplate,
|
EnumInputFieldTemplate,
|
||||||
FieldType,
|
FieldType,
|
||||||
FloatCollectionInputFieldTemplate,
|
FloatCollectionInputFieldTemplate,
|
||||||
FloatPolymorphicInputFieldTemplate,
|
|
||||||
FloatInputFieldTemplate,
|
FloatInputFieldTemplate,
|
||||||
|
FloatPolymorphicInputFieldTemplate,
|
||||||
|
IPAdapterInputFieldTemplate,
|
||||||
|
IPAdapterModelInputFieldTemplate,
|
||||||
ImageCollectionInputFieldTemplate,
|
ImageCollectionInputFieldTemplate,
|
||||||
|
ImageField,
|
||||||
ImageInputFieldTemplate,
|
ImageInputFieldTemplate,
|
||||||
|
ImagePolymorphicInputFieldTemplate,
|
||||||
|
InputFieldTemplate,
|
||||||
InputFieldTemplateBase,
|
InputFieldTemplateBase,
|
||||||
IntegerCollectionInputFieldTemplate,
|
IntegerCollectionInputFieldTemplate,
|
||||||
IntegerInputFieldTemplate,
|
IntegerInputFieldTemplate,
|
||||||
|
IntegerPolymorphicInputFieldTemplate,
|
||||||
InvocationFieldSchema,
|
InvocationFieldSchema,
|
||||||
InvocationSchemaObject,
|
InvocationSchemaObject,
|
||||||
|
LatentsCollectionInputFieldTemplate,
|
||||||
|
LatentsField,
|
||||||
LatentsInputFieldTemplate,
|
LatentsInputFieldTemplate,
|
||||||
|
LatentsPolymorphicInputFieldTemplate,
|
||||||
LoRAModelInputFieldTemplate,
|
LoRAModelInputFieldTemplate,
|
||||||
MainModelInputFieldTemplate,
|
MainModelInputFieldTemplate,
|
||||||
|
MetadataDictCollectionInputFieldTemplate,
|
||||||
|
MetadataDictInputFieldTemplate,
|
||||||
|
MetadataItemCollectionInputFieldTemplate,
|
||||||
|
MetadataItemInputFieldTemplate,
|
||||||
|
MetadataItemPolymorphicInputFieldTemplate,
|
||||||
SDXLMainModelInputFieldTemplate,
|
SDXLMainModelInputFieldTemplate,
|
||||||
SDXLRefinerModelInputFieldTemplate,
|
SDXLRefinerModelInputFieldTemplate,
|
||||||
SchedulerInputFieldTemplate,
|
SchedulerInputFieldTemplate,
|
||||||
StringCollectionInputFieldTemplate,
|
StringCollectionInputFieldTemplate,
|
||||||
StringInputFieldTemplate,
|
StringInputFieldTemplate,
|
||||||
|
StringPolymorphicInputFieldTemplate,
|
||||||
UNetInputFieldTemplate,
|
UNetInputFieldTemplate,
|
||||||
VaeInputFieldTemplate,
|
VaeInputFieldTemplate,
|
||||||
VaeModelInputFieldTemplate,
|
VaeModelInputFieldTemplate,
|
||||||
@ -45,25 +71,7 @@ import {
|
|||||||
isNonArraySchemaObject,
|
isNonArraySchemaObject,
|
||||||
isRefObject,
|
isRefObject,
|
||||||
isSchemaObject,
|
isSchemaObject,
|
||||||
ControlPolymorphicInputFieldTemplate,
|
|
||||||
ColorPolymorphicInputFieldTemplate,
|
|
||||||
ColorCollectionInputFieldTemplate,
|
|
||||||
IntegerPolymorphicInputFieldTemplate,
|
|
||||||
StringPolymorphicInputFieldTemplate,
|
|
||||||
BooleanPolymorphicInputFieldTemplate,
|
|
||||||
ImagePolymorphicInputFieldTemplate,
|
|
||||||
LatentsPolymorphicInputFieldTemplate,
|
|
||||||
LatentsCollectionInputFieldTemplate,
|
|
||||||
ConditioningPolymorphicInputFieldTemplate,
|
|
||||||
ConditioningCollectionInputFieldTemplate,
|
|
||||||
ControlCollectionInputFieldTemplate,
|
|
||||||
ImageField,
|
|
||||||
LatentsField,
|
|
||||||
ConditioningField,
|
|
||||||
IPAdapterInputFieldTemplate,
|
|
||||||
IPAdapterModelInputFieldTemplate,
|
|
||||||
} from '../types/types';
|
} from '../types/types';
|
||||||
import { ControlField } from 'services/api/types';
|
|
||||||
|
|
||||||
export type BaseFieldProperties = 'name' | 'title' | 'description';
|
export type BaseFieldProperties = 'name' | 'title' | 'description';
|
||||||
|
|
||||||
@ -450,6 +458,19 @@ const buildIPAdapterModelInputFieldTemplate = ({
|
|||||||
return template;
|
return template;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildBoardInputFieldTemplate = ({
|
||||||
|
schemaObject,
|
||||||
|
baseField,
|
||||||
|
}: BuildInputFieldArg): BoardInputFieldTemplate => {
|
||||||
|
const template: BoardInputFieldTemplate = {
|
||||||
|
...baseField,
|
||||||
|
type: 'BoardField',
|
||||||
|
default: schemaObject.default ?? undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return template;
|
||||||
|
};
|
||||||
|
|
||||||
const buildImageInputFieldTemplate = ({
|
const buildImageInputFieldTemplate = ({
|
||||||
schemaObject,
|
schemaObject,
|
||||||
baseField,
|
baseField,
|
||||||
@ -716,6 +737,78 @@ const buildCollectionItemInputFieldTemplate = ({
|
|||||||
return template;
|
return template;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildAnyInputFieldTemplate = ({
|
||||||
|
baseField,
|
||||||
|
}: BuildInputFieldArg): AnyInputFieldTemplate => {
|
||||||
|
const template: AnyInputFieldTemplate = {
|
||||||
|
...baseField,
|
||||||
|
type: 'Any',
|
||||||
|
default: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return template;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildMetadataItemInputFieldTemplate = ({
|
||||||
|
baseField,
|
||||||
|
}: BuildInputFieldArg): MetadataItemInputFieldTemplate => {
|
||||||
|
const template: MetadataItemInputFieldTemplate = {
|
||||||
|
...baseField,
|
||||||
|
type: 'MetadataItem',
|
||||||
|
default: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return template;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildMetadataItemCollectionInputFieldTemplate = ({
|
||||||
|
baseField,
|
||||||
|
}: BuildInputFieldArg): MetadataItemCollectionInputFieldTemplate => {
|
||||||
|
const template: MetadataItemCollectionInputFieldTemplate = {
|
||||||
|
...baseField,
|
||||||
|
type: 'MetadataItemCollection',
|
||||||
|
default: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return template;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildMetadataItemPolymorphicInputFieldTemplate = ({
|
||||||
|
baseField,
|
||||||
|
}: BuildInputFieldArg): MetadataItemPolymorphicInputFieldTemplate => {
|
||||||
|
const template: MetadataItemPolymorphicInputFieldTemplate = {
|
||||||
|
...baseField,
|
||||||
|
type: 'MetadataItemPolymorphic',
|
||||||
|
default: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return template;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildMetadataDictInputFieldTemplate = ({
|
||||||
|
baseField,
|
||||||
|
}: BuildInputFieldArg): MetadataDictInputFieldTemplate => {
|
||||||
|
const template: MetadataDictInputFieldTemplate = {
|
||||||
|
...baseField,
|
||||||
|
type: 'MetadataDict',
|
||||||
|
default: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return template;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildMetadataDictCollectionInputFieldTemplate = ({
|
||||||
|
baseField,
|
||||||
|
}: BuildInputFieldArg): MetadataDictCollectionInputFieldTemplate => {
|
||||||
|
const template: MetadataDictCollectionInputFieldTemplate = {
|
||||||
|
...baseField,
|
||||||
|
type: 'MetadataDictCollection',
|
||||||
|
default: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return template;
|
||||||
|
};
|
||||||
|
|
||||||
const buildColorInputFieldTemplate = ({
|
const buildColorInputFieldTemplate = ({
|
||||||
schemaObject,
|
schemaObject,
|
||||||
baseField,
|
baseField,
|
||||||
@ -851,7 +944,11 @@ export const getFieldType = (
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TEMPLATE_BUILDER_MAP = {
|
const TEMPLATE_BUILDER_MAP: {
|
||||||
|
[key in FieldType]?: (arg: BuildInputFieldArg) => InputFieldTemplate;
|
||||||
|
} = {
|
||||||
|
BoardField: buildBoardInputFieldTemplate,
|
||||||
|
Any: buildAnyInputFieldTemplate,
|
||||||
boolean: buildBooleanInputFieldTemplate,
|
boolean: buildBooleanInputFieldTemplate,
|
||||||
BooleanCollection: buildBooleanCollectionInputFieldTemplate,
|
BooleanCollection: buildBooleanCollectionInputFieldTemplate,
|
||||||
BooleanPolymorphic: buildBooleanPolymorphicInputFieldTemplate,
|
BooleanPolymorphic: buildBooleanPolymorphicInputFieldTemplate,
|
||||||
@ -885,6 +982,11 @@ const TEMPLATE_BUILDER_MAP = {
|
|||||||
LatentsField: buildLatentsInputFieldTemplate,
|
LatentsField: buildLatentsInputFieldTemplate,
|
||||||
LatentsPolymorphic: buildLatentsPolymorphicInputFieldTemplate,
|
LatentsPolymorphic: buildLatentsPolymorphicInputFieldTemplate,
|
||||||
LoRAModelField: buildLoRAModelInputFieldTemplate,
|
LoRAModelField: buildLoRAModelInputFieldTemplate,
|
||||||
|
MetadataItem: buildMetadataItemInputFieldTemplate,
|
||||||
|
MetadataItemCollection: buildMetadataItemCollectionInputFieldTemplate,
|
||||||
|
MetadataItemPolymorphic: buildMetadataItemPolymorphicInputFieldTemplate,
|
||||||
|
MetadataDict: buildMetadataDictInputFieldTemplate,
|
||||||
|
MetadataDictCollection: buildMetadataDictCollectionInputFieldTemplate,
|
||||||
MainModelField: buildMainModelInputFieldTemplate,
|
MainModelField: buildMainModelInputFieldTemplate,
|
||||||
Scheduler: buildSchedulerInputFieldTemplate,
|
Scheduler: buildSchedulerInputFieldTemplate,
|
||||||
SDXLMainModelField: buildSDXLMainModelInputFieldTemplate,
|
SDXLMainModelField: buildSDXLMainModelInputFieldTemplate,
|
||||||
@ -937,7 +1039,13 @@ export const buildInputFieldTemplate = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return TEMPLATE_BUILDER_MAP[fieldType]({
|
const builder = TEMPLATE_BUILDER_MAP[fieldType];
|
||||||
|
|
||||||
|
if (!builder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder({
|
||||||
schemaObject: fieldSchema,
|
schemaObject: fieldSchema,
|
||||||
baseField,
|
baseField,
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { InputFieldTemplate, InputFieldValue } from '../types/types';
|
import { FieldType, InputFieldTemplate, InputFieldValue } from '../types/types';
|
||||||
|
|
||||||
const FIELD_VALUE_FALLBACK_MAP = {
|
const FIELD_VALUE_FALLBACK_MAP: {
|
||||||
|
[key in FieldType]: InputFieldValue['value'];
|
||||||
|
} = {
|
||||||
|
Any: undefined,
|
||||||
enum: '',
|
enum: '',
|
||||||
|
BoardField: undefined,
|
||||||
boolean: false,
|
boolean: false,
|
||||||
BooleanCollection: [],
|
BooleanCollection: [],
|
||||||
BooleanPolymorphic: false,
|
BooleanPolymorphic: false,
|
||||||
@ -33,6 +37,11 @@ const FIELD_VALUE_FALLBACK_MAP = {
|
|||||||
LatentsCollection: [],
|
LatentsCollection: [],
|
||||||
LatentsField: undefined,
|
LatentsField: undefined,
|
||||||
LatentsPolymorphic: undefined,
|
LatentsPolymorphic: undefined,
|
||||||
|
MetadataItem: undefined,
|
||||||
|
MetadataItemCollection: [],
|
||||||
|
MetadataItemPolymorphic: undefined,
|
||||||
|
MetadataDict: undefined,
|
||||||
|
MetadataDictCollection: [],
|
||||||
LoRAModelField: undefined,
|
LoRAModelField: undefined,
|
||||||
MainModelField: undefined,
|
MainModelField: undefined,
|
||||||
ONNXModelField: undefined,
|
ONNXModelField: undefined,
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { getValidControlNets } from 'features/controlNet/util/getValidControlNets';
|
import { getValidControlNets } from 'features/controlNet/util/getValidControlNets';
|
||||||
import { omit } from 'lodash-es';
|
|
||||||
import {
|
import {
|
||||||
CollectInvocation,
|
CollectInvocation,
|
||||||
ControlField,
|
ControlField,
|
||||||
ControlNetInvocation,
|
ControlNetInvocation,
|
||||||
MetadataAccumulatorInvocation,
|
|
||||||
} from 'services/api/types';
|
} from 'services/api/types';
|
||||||
import { NonNullableGraph } from '../../types/types';
|
import { NonNullableGraph, zControlField } from '../../types/types';
|
||||||
import {
|
import {
|
||||||
CANVAS_COHERENCE_DENOISE_LATENTS,
|
CANVAS_COHERENCE_DENOISE_LATENTS,
|
||||||
CONTROL_NET_COLLECT,
|
CONTROL_NET_COLLECT,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import { addMainMetadata } from './metadata';
|
||||||
|
|
||||||
export const addControlNetToLinearGraph = (
|
export const addControlNetToLinearGraph = (
|
||||||
state: RootState,
|
state: RootState,
|
||||||
@ -23,12 +21,9 @@ export const addControlNetToLinearGraph = (
|
|||||||
|
|
||||||
const validControlNets = getValidControlNets(controlNets);
|
const validControlNets = getValidControlNets(controlNets);
|
||||||
|
|
||||||
const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
|
|
||||||
| MetadataAccumulatorInvocation
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
if (isControlNetEnabled && Boolean(validControlNets.length)) {
|
if (isControlNetEnabled && Boolean(validControlNets.length)) {
|
||||||
if (validControlNets.length) {
|
if (validControlNets.length) {
|
||||||
|
const controlnets: ControlField[] = [];
|
||||||
// We have multiple controlnets, add ControlNet collector
|
// We have multiple controlnets, add ControlNet collector
|
||||||
const controlNetIterateNode: CollectInvocation = {
|
const controlNetIterateNode: CollectInvocation = {
|
||||||
id: CONTROL_NET_COLLECT,
|
id: CONTROL_NET_COLLECT,
|
||||||
@ -87,15 +82,7 @@ export const addControlNetToLinearGraph = (
|
|||||||
|
|
||||||
graph.nodes[controlNetNode.id] = controlNetNode as ControlNetInvocation;
|
graph.nodes[controlNetNode.id] = controlNetNode as ControlNetInvocation;
|
||||||
|
|
||||||
if (metadataAccumulator?.controlnets) {
|
controlnets.push(zControlField.parse(controlNetNode));
|
||||||
// metadata accumulator only needs a control field - not the whole node
|
|
||||||
// extract what we need and add to the accumulator
|
|
||||||
const controlField = omit(controlNetNode, [
|
|
||||||
'id',
|
|
||||||
'type',
|
|
||||||
]) as ControlField;
|
|
||||||
metadataAccumulator.controlnets.push(controlField);
|
|
||||||
}
|
|
||||||
|
|
||||||
graph.edges.push({
|
graph.edges.push({
|
||||||
source: { node_id: controlNetNode.id, field: 'control' },
|
source: { node_id: controlNetNode.id, field: 'control' },
|
||||||
@ -115,6 +102,8 @@ export const addControlNetToLinearGraph = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addMainMetadata(graph, { controlnets });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -38,15 +38,7 @@ export const addIPAdapterToLinearGraph = (
|
|||||||
|
|
||||||
graph.nodes[ipAdapterNode.id] = ipAdapterNode as IPAdapterInvocation;
|
graph.nodes[ipAdapterNode.id] = ipAdapterNode as IPAdapterInvocation;
|
||||||
|
|
||||||
// if (metadataAccumulator?.ip_adapters) {
|
// TODO: add metadata
|
||||||
// // metadata accumulator only needs the ip_adapter field - not the whole node
|
|
||||||
// // extract what we need and add to the accumulator
|
|
||||||
// const ipAdapterField = omit(ipAdapterNode, [
|
|
||||||
// 'id',
|
|
||||||
// 'type',
|
|
||||||
// ]) as IPAdapterField;
|
|
||||||
// metadataAccumulator.ip_adapters.push(ipAdapterField);
|
|
||||||
// }
|
|
||||||
|
|
||||||
graph.edges.push({
|
graph.edges.push({
|
||||||
source: { node_id: ipAdapterNode.id, field: 'ip_adapter' },
|
source: { node_id: ipAdapterNode.id, field: 'ip_adapter' },
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
import {
|
||||||
|
LoRAMetadataItem,
|
||||||
|
NonNullableGraph,
|
||||||
|
zLoRAMetadataItem,
|
||||||
|
} from 'features/nodes/types/types';
|
||||||
import { forEach, size } from 'lodash-es';
|
import { forEach, size } from 'lodash-es';
|
||||||
|
import { LoraLoaderInvocation } from 'services/api/types';
|
||||||
import {
|
import {
|
||||||
LoraLoaderInvocation,
|
CANVAS_COHERENCE_DENOISE_LATENTS,
|
||||||
MetadataAccumulatorInvocation,
|
|
||||||
} from 'services/api/types';
|
|
||||||
import {
|
|
||||||
CANVAS_INPAINT_GRAPH,
|
CANVAS_INPAINT_GRAPH,
|
||||||
CANVAS_OUTPAINT_GRAPH,
|
CANVAS_OUTPAINT_GRAPH,
|
||||||
CANVAS_COHERENCE_DENOISE_LATENTS,
|
|
||||||
CLIP_SKIP,
|
CLIP_SKIP,
|
||||||
LORA_LOADER,
|
LORA_LOADER,
|
||||||
MAIN_MODEL_LOADER,
|
MAIN_MODEL_LOADER,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
NEGATIVE_CONDITIONING,
|
NEGATIVE_CONDITIONING,
|
||||||
POSITIVE_CONDITIONING,
|
POSITIVE_CONDITIONING,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import { addMainMetadata } from './metadata';
|
||||||
|
|
||||||
export const addLoRAsToGraph = (
|
export const addLoRAsToGraph = (
|
||||||
state: RootState,
|
state: RootState,
|
||||||
@ -33,29 +34,29 @@ export const addLoRAsToGraph = (
|
|||||||
|
|
||||||
const { loras } = state.lora;
|
const { loras } = state.lora;
|
||||||
const loraCount = size(loras);
|
const loraCount = size(loras);
|
||||||
const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
|
|
||||||
| MetadataAccumulatorInvocation
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
if (loraCount > 0) {
|
if (loraCount === 0) {
|
||||||
// Remove modelLoaderNodeId unet connection to feed it to LoRAs
|
return;
|
||||||
graph.edges = graph.edges.filter(
|
|
||||||
(e) =>
|
|
||||||
!(
|
|
||||||
e.source.node_id === modelLoaderNodeId &&
|
|
||||||
['unet'].includes(e.source.field)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
// Remove CLIP_SKIP connections to conditionings to feed it through LoRAs
|
|
||||||
graph.edges = graph.edges.filter(
|
|
||||||
(e) =>
|
|
||||||
!(e.source.node_id === CLIP_SKIP && ['clip'].includes(e.source.field))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove modelLoaderNodeId unet connection to feed it to LoRAs
|
||||||
|
graph.edges = graph.edges.filter(
|
||||||
|
(e) =>
|
||||||
|
!(
|
||||||
|
e.source.node_id === modelLoaderNodeId &&
|
||||||
|
['unet'].includes(e.source.field)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Remove CLIP_SKIP connections to conditionings to feed it through LoRAs
|
||||||
|
graph.edges = graph.edges.filter(
|
||||||
|
(e) =>
|
||||||
|
!(e.source.node_id === CLIP_SKIP && ['clip'].includes(e.source.field))
|
||||||
|
);
|
||||||
|
|
||||||
// we need to remember the last lora so we can chain from it
|
// we need to remember the last lora so we can chain from it
|
||||||
let lastLoraNodeId = '';
|
let lastLoraNodeId = '';
|
||||||
let currentLoraIndex = 0;
|
let currentLoraIndex = 0;
|
||||||
|
const loraMetadata: LoRAMetadataItem[] = [];
|
||||||
|
|
||||||
forEach(loras, (lora) => {
|
forEach(loras, (lora) => {
|
||||||
const { model_name, base_model, weight } = lora;
|
const { model_name, base_model, weight } = lora;
|
||||||
@ -69,13 +70,12 @@ export const addLoRAsToGraph = (
|
|||||||
weight,
|
weight,
|
||||||
};
|
};
|
||||||
|
|
||||||
// add the lora to the metadata accumulator
|
loraMetadata.push(
|
||||||
if (metadataAccumulator?.loras) {
|
zLoRAMetadataItem.parse({
|
||||||
metadataAccumulator.loras.push({
|
|
||||||
lora: { model_name, base_model },
|
lora: { model_name, base_model },
|
||||||
weight,
|
weight,
|
||||||
});
|
})
|
||||||
}
|
);
|
||||||
|
|
||||||
// add to graph
|
// add to graph
|
||||||
graph.nodes[currentLoraNodeId] = loraLoaderNode;
|
graph.nodes[currentLoraNodeId] = loraLoaderNode;
|
||||||
@ -182,4 +182,6 @@ export const addLoRAsToGraph = (
|
|||||||
lastLoraNodeId = currentLoraNodeId;
|
lastLoraNodeId = currentLoraNodeId;
|
||||||
currentLoraIndex += 1;
|
currentLoraIndex += 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addMainMetadata(graph, { loras: loraMetadata });
|
||||||
};
|
};
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
|
||||||
import { forEach, size } from 'lodash-es';
|
|
||||||
import {
|
import {
|
||||||
MetadataAccumulatorInvocation,
|
LoRAMetadataItem,
|
||||||
SDXLLoraLoaderInvocation,
|
NonNullableGraph,
|
||||||
} from 'services/api/types';
|
zLoRAMetadataItem,
|
||||||
|
} from 'features/nodes/types/types';
|
||||||
|
import { forEach, size } from 'lodash-es';
|
||||||
|
import { SDXLLoraLoaderInvocation } from 'services/api/types';
|
||||||
import {
|
import {
|
||||||
CANVAS_COHERENCE_DENOISE_LATENTS,
|
CANVAS_COHERENCE_DENOISE_LATENTS,
|
||||||
LORA_LOADER,
|
LORA_LOADER,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
NEGATIVE_CONDITIONING,
|
NEGATIVE_CONDITIONING,
|
||||||
POSITIVE_CONDITIONING,
|
POSITIVE_CONDITIONING,
|
||||||
SDXL_CANVAS_INPAINT_GRAPH,
|
SDXL_CANVAS_INPAINT_GRAPH,
|
||||||
@ -17,6 +17,7 @@ import {
|
|||||||
SDXL_REFINER_INPAINT_CREATE_MASK,
|
SDXL_REFINER_INPAINT_CREATE_MASK,
|
||||||
SEAMLESS,
|
SEAMLESS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import { addMainMetadata } from './metadata';
|
||||||
|
|
||||||
export const addSDXLLoRAsToGraph = (
|
export const addSDXLLoRAsToGraph = (
|
||||||
state: RootState,
|
state: RootState,
|
||||||
@ -34,9 +35,12 @@ export const addSDXLLoRAsToGraph = (
|
|||||||
|
|
||||||
const { loras } = state.lora;
|
const { loras } = state.lora;
|
||||||
const loraCount = size(loras);
|
const loraCount = size(loras);
|
||||||
const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
|
|
||||||
| MetadataAccumulatorInvocation
|
if (loraCount === 0) {
|
||||||
| undefined;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loraMetadata: LoRAMetadataItem[] = [];
|
||||||
|
|
||||||
// Handle Seamless Plugs
|
// Handle Seamless Plugs
|
||||||
const unetLoaderId = modelLoaderNodeId;
|
const unetLoaderId = modelLoaderNodeId;
|
||||||
@ -47,22 +51,17 @@ export const addSDXLLoRAsToGraph = (
|
|||||||
clipLoaderId = SDXL_MODEL_LOADER;
|
clipLoaderId = SDXL_MODEL_LOADER;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loraCount > 0) {
|
// Remove modelLoaderNodeId unet/clip/clip2 connections to feed it to LoRAs
|
||||||
// Remove modelLoaderNodeId unet/clip/clip2 connections to feed it to LoRAs
|
graph.edges = graph.edges.filter(
|
||||||
graph.edges = graph.edges.filter(
|
(e) =>
|
||||||
(e) =>
|
!(
|
||||||
!(
|
e.source.node_id === unetLoaderId && ['unet'].includes(e.source.field)
|
||||||
e.source.node_id === unetLoaderId && ['unet'].includes(e.source.field)
|
) &&
|
||||||
) &&
|
!(
|
||||||
!(
|
e.source.node_id === clipLoaderId && ['clip'].includes(e.source.field)
|
||||||
e.source.node_id === clipLoaderId && ['clip'].includes(e.source.field)
|
) &&
|
||||||
) &&
|
!(e.source.node_id === clipLoaderId && ['clip2'].includes(e.source.field))
|
||||||
!(
|
);
|
||||||
e.source.node_id === clipLoaderId &&
|
|
||||||
['clip2'].includes(e.source.field)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need to remember the last lora so we can chain from it
|
// we need to remember the last lora so we can chain from it
|
||||||
let lastLoraNodeId = '';
|
let lastLoraNodeId = '';
|
||||||
@ -80,16 +79,12 @@ export const addSDXLLoRAsToGraph = (
|
|||||||
weight,
|
weight,
|
||||||
};
|
};
|
||||||
|
|
||||||
// add the lora to the metadata accumulator
|
loraMetadata.push(
|
||||||
if (metadataAccumulator) {
|
zLoRAMetadataItem.parse({
|
||||||
if (!metadataAccumulator.loras) {
|
|
||||||
metadataAccumulator.loras = [];
|
|
||||||
}
|
|
||||||
metadataAccumulator.loras.push({
|
|
||||||
lora: { model_name, base_model },
|
lora: { model_name, base_model },
|
||||||
weight,
|
weight,
|
||||||
});
|
})
|
||||||
}
|
);
|
||||||
|
|
||||||
// add to graph
|
// add to graph
|
||||||
graph.nodes[currentLoraNodeId] = loraLoaderNode;
|
graph.nodes[currentLoraNodeId] = loraLoaderNode;
|
||||||
@ -242,4 +237,6 @@ export const addSDXLLoRAsToGraph = (
|
|||||||
lastLoraNodeId = currentLoraNodeId;
|
lastLoraNodeId = currentLoraNodeId;
|
||||||
currentLoraIndex += 1;
|
currentLoraIndex += 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addMainMetadata(graph, { loras: loraMetadata });
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,6 @@ import { RootState } from 'app/store/store';
|
|||||||
import {
|
import {
|
||||||
CreateDenoiseMaskInvocation,
|
CreateDenoiseMaskInvocation,
|
||||||
ImageDTO,
|
ImageDTO,
|
||||||
MetadataAccumulatorInvocation,
|
|
||||||
SeamlessModeInvocation,
|
SeamlessModeInvocation,
|
||||||
} from 'services/api/types';
|
} from 'services/api/types';
|
||||||
import { NonNullableGraph } from '../../types/types';
|
import { NonNullableGraph } from '../../types/types';
|
||||||
@ -12,7 +11,6 @@ import {
|
|||||||
LATENTS_TO_IMAGE,
|
LATENTS_TO_IMAGE,
|
||||||
MASK_COMBINE,
|
MASK_COMBINE,
|
||||||
MASK_RESIZE_UP,
|
MASK_RESIZE_UP,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
SDXL_CANVAS_IMAGE_TO_IMAGE_GRAPH,
|
SDXL_CANVAS_IMAGE_TO_IMAGE_GRAPH,
|
||||||
SDXL_CANVAS_INPAINT_GRAPH,
|
SDXL_CANVAS_INPAINT_GRAPH,
|
||||||
SDXL_CANVAS_OUTPAINT_GRAPH,
|
SDXL_CANVAS_OUTPAINT_GRAPH,
|
||||||
@ -26,6 +24,7 @@ import {
|
|||||||
SDXL_REFINER_SEAMLESS,
|
SDXL_REFINER_SEAMLESS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
||||||
|
import { addMainMetadata } from './metadata';
|
||||||
|
|
||||||
export const addSDXLRefinerToGraph = (
|
export const addSDXLRefinerToGraph = (
|
||||||
state: RootState,
|
state: RootState,
|
||||||
@ -57,21 +56,15 @@ export const addSDXLRefinerToGraph = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
|
addMainMetadata(graph, {
|
||||||
| MetadataAccumulatorInvocation
|
refiner_model: refinerModel,
|
||||||
| undefined;
|
refiner_positive_aesthetic_score: refinerPositiveAestheticScore,
|
||||||
|
refiner_negative_aesthetic_score: refinerNegativeAestheticScore,
|
||||||
if (metadataAccumulator) {
|
refiner_cfg_scale: refinerCFGScale,
|
||||||
metadataAccumulator.refiner_model = refinerModel;
|
refiner_scheduler: refinerScheduler,
|
||||||
metadataAccumulator.refiner_positive_aesthetic_score =
|
refiner_start: refinerStart,
|
||||||
refinerPositiveAestheticScore;
|
refiner_steps: refinerSteps,
|
||||||
metadataAccumulator.refiner_negative_aesthetic_score =
|
});
|
||||||
refinerNegativeAestheticScore;
|
|
||||||
metadataAccumulator.refiner_cfg_scale = refinerCFGScale;
|
|
||||||
metadataAccumulator.refiner_scheduler = refinerScheduler;
|
|
||||||
metadataAccumulator.refiner_start = refinerStart;
|
|
||||||
metadataAccumulator.refiner_steps = refinerSteps;
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelLoaderId = modelLoaderNodeId
|
const modelLoaderId = modelLoaderNodeId
|
||||||
? modelLoaderNodeId
|
? modelLoaderNodeId
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
|
import { RootState } from 'app/store/store';
|
||||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||||
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
|
import { SaveImageInvocation } from 'services/api/types';
|
||||||
import {
|
import {
|
||||||
CANVAS_OUTPUT,
|
CANVAS_OUTPUT,
|
||||||
LATENTS_TO_IMAGE,
|
LATENTS_TO_IMAGE,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
NSFW_CHECKER,
|
NSFW_CHECKER,
|
||||||
SAVE_IMAGE,
|
SAVE_IMAGE,
|
||||||
WATERMARKER,
|
WATERMARKER,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import {
|
|
||||||
MetadataAccumulatorInvocation,
|
|
||||||
SaveImageInvocation,
|
|
||||||
} from 'services/api/types';
|
|
||||||
import { RootState } from 'app/store/store';
|
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the `use_cache` field on the linear/canvas graph's final image output node to False.
|
* Set the `use_cache` field on the linear/canvas graph's final image output node to False.
|
||||||
@ -24,33 +20,18 @@ export const addSaveImageNode = (
|
|||||||
const activeTabName = activeTabNameSelector(state);
|
const activeTabName = activeTabNameSelector(state);
|
||||||
const is_intermediate =
|
const is_intermediate =
|
||||||
activeTabName === 'unifiedCanvas' ? !state.canvas.shouldAutoSave : false;
|
activeTabName === 'unifiedCanvas' ? !state.canvas.shouldAutoSave : false;
|
||||||
|
const { autoAddBoardId } = state.gallery;
|
||||||
|
|
||||||
const saveImageNode: SaveImageInvocation = {
|
const saveImageNode: SaveImageInvocation = {
|
||||||
id: SAVE_IMAGE,
|
id: SAVE_IMAGE,
|
||||||
type: 'save_image',
|
type: 'save_image',
|
||||||
is_intermediate,
|
is_intermediate,
|
||||||
use_cache: false,
|
use_cache: false,
|
||||||
|
board: autoAddBoardId === 'none' ? undefined : { board_id: autoAddBoardId },
|
||||||
};
|
};
|
||||||
|
|
||||||
graph.nodes[SAVE_IMAGE] = saveImageNode;
|
graph.nodes[SAVE_IMAGE] = saveImageNode;
|
||||||
|
|
||||||
const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
|
|
||||||
| MetadataAccumulatorInvocation
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
if (metadataAccumulator) {
|
|
||||||
graph.edges.push({
|
|
||||||
source: {
|
|
||||||
node_id: METADATA_ACCUMULATOR,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
destination: {
|
|
||||||
node_id: SAVE_IMAGE,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const destination = {
|
const destination = {
|
||||||
node_id: SAVE_IMAGE,
|
node_id: SAVE_IMAGE,
|
||||||
field: 'image',
|
field: 'image',
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { SeamlessModeInvocation } from 'services/api/types';
|
import { SeamlessModeInvocation } from 'services/api/types';
|
||||||
import { NonNullableGraph } from '../../types/types';
|
import { NonNullableGraph } from '../../types/types';
|
||||||
|
import { addMainMetadata } from './metadata';
|
||||||
import {
|
import {
|
||||||
CANVAS_COHERENCE_DENOISE_LATENTS,
|
CANVAS_COHERENCE_DENOISE_LATENTS,
|
||||||
CANVAS_INPAINT_GRAPH,
|
CANVAS_INPAINT_GRAPH,
|
||||||
@ -31,6 +32,11 @@ export const addSeamlessToLinearGraph = (
|
|||||||
seamless_y: seamlessYAxis,
|
seamless_y: seamlessYAxis,
|
||||||
} as SeamlessModeInvocation;
|
} as SeamlessModeInvocation;
|
||||||
|
|
||||||
|
addMainMetadata(graph, {
|
||||||
|
seamless_x: seamlessXAxis,
|
||||||
|
seamless_y: seamlessYAxis,
|
||||||
|
});
|
||||||
|
|
||||||
let denoisingNodeId = DENOISE_LATENTS;
|
let denoisingNodeId = DENOISE_LATENTS;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||||
import { MetadataAccumulatorInvocation } from 'services/api/types';
|
|
||||||
import {
|
import {
|
||||||
CANVAS_COHERENCE_INPAINT_CREATE_MASK,
|
CANVAS_COHERENCE_INPAINT_CREATE_MASK,
|
||||||
CANVAS_IMAGE_TO_IMAGE_GRAPH,
|
CANVAS_IMAGE_TO_IMAGE_GRAPH,
|
||||||
@ -14,7 +13,6 @@ import {
|
|||||||
INPAINT_IMAGE,
|
INPAINT_IMAGE,
|
||||||
LATENTS_TO_IMAGE,
|
LATENTS_TO_IMAGE,
|
||||||
MAIN_MODEL_LOADER,
|
MAIN_MODEL_LOADER,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
ONNX_MODEL_LOADER,
|
ONNX_MODEL_LOADER,
|
||||||
SDXL_CANVAS_IMAGE_TO_IMAGE_GRAPH,
|
SDXL_CANVAS_IMAGE_TO_IMAGE_GRAPH,
|
||||||
SDXL_CANVAS_INPAINT_GRAPH,
|
SDXL_CANVAS_INPAINT_GRAPH,
|
||||||
@ -26,6 +24,7 @@ import {
|
|||||||
TEXT_TO_IMAGE_GRAPH,
|
TEXT_TO_IMAGE_GRAPH,
|
||||||
VAE_LOADER,
|
VAE_LOADER,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import { addMainMetadata } from './metadata';
|
||||||
|
|
||||||
export const addVAEToGraph = (
|
export const addVAEToGraph = (
|
||||||
state: RootState,
|
state: RootState,
|
||||||
@ -41,9 +40,6 @@ export const addVAEToGraph = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isAutoVae = !vae;
|
const isAutoVae = !vae;
|
||||||
const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
|
|
||||||
| MetadataAccumulatorInvocation
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
if (!isAutoVae) {
|
if (!isAutoVae) {
|
||||||
graph.nodes[VAE_LOADER] = {
|
graph.nodes[VAE_LOADER] = {
|
||||||
@ -181,7 +177,7 @@ export const addVAEToGraph = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vae && metadataAccumulator) {
|
if (vae) {
|
||||||
metadataAccumulator.vae = vae;
|
addMainMetadata(graph, { vae });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -5,14 +5,8 @@ import {
|
|||||||
ImageNSFWBlurInvocation,
|
ImageNSFWBlurInvocation,
|
||||||
ImageWatermarkInvocation,
|
ImageWatermarkInvocation,
|
||||||
LatentsToImageInvocation,
|
LatentsToImageInvocation,
|
||||||
MetadataAccumulatorInvocation,
|
|
||||||
} from 'services/api/types';
|
} from 'services/api/types';
|
||||||
import {
|
import { LATENTS_TO_IMAGE, NSFW_CHECKER, WATERMARKER } from './constants';
|
||||||
LATENTS_TO_IMAGE,
|
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
NSFW_CHECKER,
|
|
||||||
WATERMARKER,
|
|
||||||
} from './constants';
|
|
||||||
|
|
||||||
export const addWatermarkerToGraph = (
|
export const addWatermarkerToGraph = (
|
||||||
state: RootState,
|
state: RootState,
|
||||||
@ -32,10 +26,6 @@ export const addWatermarkerToGraph = (
|
|||||||
| ImageNSFWBlurInvocation
|
| ImageNSFWBlurInvocation
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
|
|
||||||
| MetadataAccumulatorInvocation
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
if (!nodeToAddTo) {
|
if (!nodeToAddTo) {
|
||||||
// something has gone terribly awry
|
// something has gone terribly awry
|
||||||
return;
|
return;
|
||||||
@ -80,17 +70,4 @@ export const addWatermarkerToGraph = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadataAccumulator) {
|
|
||||||
graph.edges.push({
|
|
||||||
source: {
|
|
||||||
node_id: METADATA_ACCUMULATOR,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
destination: {
|
|
||||||
node_id: WATERMARKER,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
|
import { BoardId } from 'features/gallery/store/types';
|
||||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||||
import { ESRGANModelName } from 'features/parameters/store/postprocessingSlice';
|
import { ESRGANModelName } from 'features/parameters/store/postprocessingSlice';
|
||||||
import {
|
import {
|
||||||
Graph,
|
|
||||||
ESRGANInvocation,
|
ESRGANInvocation,
|
||||||
|
Graph,
|
||||||
SaveImageInvocation,
|
SaveImageInvocation,
|
||||||
} from 'services/api/types';
|
} from 'services/api/types';
|
||||||
import { REALESRGAN as ESRGAN, SAVE_IMAGE } from './constants';
|
import { REALESRGAN as ESRGAN, SAVE_IMAGE } from './constants';
|
||||||
|
import { addMainMetadataNodeToGraph } from './metadata';
|
||||||
|
|
||||||
type Arg = {
|
type Arg = {
|
||||||
image_name: string;
|
image_name: string;
|
||||||
esrganModelName: ESRGANModelName;
|
esrganModelName: ESRGANModelName;
|
||||||
|
autoAddBoardId: BoardId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildAdHocUpscaleGraph = ({
|
export const buildAdHocUpscaleGraph = ({
|
||||||
image_name,
|
image_name,
|
||||||
esrganModelName,
|
esrganModelName,
|
||||||
|
autoAddBoardId,
|
||||||
}: Arg): Graph => {
|
}: Arg): Graph => {
|
||||||
const realesrganNode: ESRGANInvocation = {
|
const realesrganNode: ESRGANInvocation = {
|
||||||
id: ESRGAN,
|
id: ESRGAN,
|
||||||
@ -28,6 +32,8 @@ export const buildAdHocUpscaleGraph = ({
|
|||||||
id: SAVE_IMAGE,
|
id: SAVE_IMAGE,
|
||||||
type: 'save_image',
|
type: 'save_image',
|
||||||
use_cache: false,
|
use_cache: false,
|
||||||
|
is_intermediate: false,
|
||||||
|
board: autoAddBoardId === 'none' ? undefined : { board_id: autoAddBoardId },
|
||||||
};
|
};
|
||||||
|
|
||||||
const graph: NonNullableGraph = {
|
const graph: NonNullableGraph = {
|
||||||
@ -50,5 +56,9 @@ export const buildAdHocUpscaleGraph = ({
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
addMainMetadataNodeToGraph(graph, {
|
||||||
|
model: esrganModelName,
|
||||||
|
});
|
||||||
|
|
||||||
return graph;
|
return graph;
|
||||||
};
|
};
|
||||||
|
@ -19,12 +19,12 @@ import {
|
|||||||
IMG2IMG_RESIZE,
|
IMG2IMG_RESIZE,
|
||||||
LATENTS_TO_IMAGE,
|
LATENTS_TO_IMAGE,
|
||||||
MAIN_MODEL_LOADER,
|
MAIN_MODEL_LOADER,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
NEGATIVE_CONDITIONING,
|
NEGATIVE_CONDITIONING,
|
||||||
NOISE,
|
NOISE,
|
||||||
POSITIVE_CONDITIONING,
|
POSITIVE_CONDITIONING,
|
||||||
SEAMLESS,
|
SEAMLESS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import { addMainMetadataNodeToGraph } from './metadata';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the Canvas tab's Image to Image graph.
|
* Builds the Canvas tab's Image to Image graph.
|
||||||
@ -307,10 +307,7 @@ export const buildCanvasImageToImageGraph = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// add metadata accumulator, which is only mostly populated - some fields are added later
|
addMainMetadataNodeToGraph(graph, {
|
||||||
graph.nodes[METADATA_ACCUMULATOR] = {
|
|
||||||
id: METADATA_ACCUMULATOR,
|
|
||||||
type: 'metadata_accumulator',
|
|
||||||
generation_mode: 'img2img',
|
generation_mode: 'img2img',
|
||||||
cfg_scale,
|
cfg_scale,
|
||||||
width: !isUsingScaledDimensions ? width : scaledBoundingBoxDimensions.width,
|
width: !isUsingScaledDimensions ? width : scaledBoundingBoxDimensions.width,
|
||||||
@ -324,13 +321,10 @@ export const buildCanvasImageToImageGraph = (
|
|||||||
steps,
|
steps,
|
||||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||||
scheduler,
|
scheduler,
|
||||||
vae: undefined, // option; set in addVAEToGraph
|
|
||||||
controlnets: [], // populated in addControlNetToLinearGraph
|
|
||||||
loras: [], // populated in addLoRAsToGraph
|
|
||||||
clip_skip: clipSkip,
|
clip_skip: clipSkip,
|
||||||
strength,
|
strength,
|
||||||
init_image: initialImage.image_name,
|
init_image: initialImage.image_name,
|
||||||
};
|
});
|
||||||
|
|
||||||
// Add Seamless To Graph
|
// Add Seamless To Graph
|
||||||
if (seamlessXAxis || seamlessYAxis) {
|
if (seamlessXAxis || seamlessYAxis) {
|
||||||
|
@ -16,7 +16,6 @@ import {
|
|||||||
IMAGE_TO_LATENTS,
|
IMAGE_TO_LATENTS,
|
||||||
IMG2IMG_RESIZE,
|
IMG2IMG_RESIZE,
|
||||||
LATENTS_TO_IMAGE,
|
LATENTS_TO_IMAGE,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
NEGATIVE_CONDITIONING,
|
NEGATIVE_CONDITIONING,
|
||||||
NOISE,
|
NOISE,
|
||||||
POSITIVE_CONDITIONING,
|
POSITIVE_CONDITIONING,
|
||||||
@ -27,6 +26,7 @@ import {
|
|||||||
SEAMLESS,
|
SEAMLESS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
||||||
|
import { addMainMetadataNodeToGraph } from './metadata';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the Canvas tab's Image to Image graph.
|
* Builds the Canvas tab's Image to Image graph.
|
||||||
@ -318,10 +318,7 @@ export const buildCanvasSDXLImageToImageGraph = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// add metadata accumulator, which is only mostly populated - some fields are added later
|
addMainMetadataNodeToGraph(graph, {
|
||||||
graph.nodes[METADATA_ACCUMULATOR] = {
|
|
||||||
id: METADATA_ACCUMULATOR,
|
|
||||||
type: 'metadata_accumulator',
|
|
||||||
generation_mode: 'img2img',
|
generation_mode: 'img2img',
|
||||||
cfg_scale,
|
cfg_scale,
|
||||||
width: !isUsingScaledDimensions ? width : scaledBoundingBoxDimensions.width,
|
width: !isUsingScaledDimensions ? width : scaledBoundingBoxDimensions.width,
|
||||||
@ -335,22 +332,8 @@ export const buildCanvasSDXLImageToImageGraph = (
|
|||||||
steps,
|
steps,
|
||||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||||
scheduler,
|
scheduler,
|
||||||
vae: undefined, // option; set in addVAEToGraph
|
|
||||||
controlnets: [], // populated in addControlNetToLinearGraph
|
|
||||||
loras: [], // populated in addLoRAsToGraph
|
|
||||||
strength,
|
strength,
|
||||||
init_image: initialImage.image_name,
|
init_image: initialImage.image_name,
|
||||||
};
|
|
||||||
|
|
||||||
graph.edges.push({
|
|
||||||
source: {
|
|
||||||
node_id: METADATA_ACCUMULATOR,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
destination: {
|
|
||||||
node_id: CANVAS_OUTPUT,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add Seamless To Graph
|
// Add Seamless To Graph
|
||||||
|
@ -17,7 +17,6 @@ import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
|||||||
import {
|
import {
|
||||||
CANVAS_OUTPUT,
|
CANVAS_OUTPUT,
|
||||||
LATENTS_TO_IMAGE,
|
LATENTS_TO_IMAGE,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
NEGATIVE_CONDITIONING,
|
NEGATIVE_CONDITIONING,
|
||||||
NOISE,
|
NOISE,
|
||||||
ONNX_MODEL_LOADER,
|
ONNX_MODEL_LOADER,
|
||||||
@ -29,6 +28,7 @@ import {
|
|||||||
SEAMLESS,
|
SEAMLESS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
||||||
|
import { addMainMetadataNodeToGraph } from './metadata';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the Canvas tab's Text to Image graph.
|
* Builds the Canvas tab's Text to Image graph.
|
||||||
@ -300,10 +300,7 @@ export const buildCanvasSDXLTextToImageGraph = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// add metadata accumulator, which is only mostly populated - some fields are added later
|
addMainMetadataNodeToGraph(graph, {
|
||||||
graph.nodes[METADATA_ACCUMULATOR] = {
|
|
||||||
id: METADATA_ACCUMULATOR,
|
|
||||||
type: 'metadata_accumulator',
|
|
||||||
generation_mode: 'txt2img',
|
generation_mode: 'txt2img',
|
||||||
cfg_scale,
|
cfg_scale,
|
||||||
width: !isUsingScaledDimensions ? width : scaledBoundingBoxDimensions.width,
|
width: !isUsingScaledDimensions ? width : scaledBoundingBoxDimensions.width,
|
||||||
@ -317,20 +314,6 @@ export const buildCanvasSDXLTextToImageGraph = (
|
|||||||
steps,
|
steps,
|
||||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||||
scheduler,
|
scheduler,
|
||||||
vae: undefined, // option; set in addVAEToGraph
|
|
||||||
controlnets: [], // populated in addControlNetToLinearGraph
|
|
||||||
loras: [], // populated in addLoRAsToGraph
|
|
||||||
};
|
|
||||||
|
|
||||||
graph.edges.push({
|
|
||||||
source: {
|
|
||||||
node_id: METADATA_ACCUMULATOR,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
destination: {
|
|
||||||
node_id: CANVAS_OUTPUT,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add Seamless To Graph
|
// Add Seamless To Graph
|
||||||
|
@ -20,13 +20,13 @@ import {
|
|||||||
DENOISE_LATENTS,
|
DENOISE_LATENTS,
|
||||||
LATENTS_TO_IMAGE,
|
LATENTS_TO_IMAGE,
|
||||||
MAIN_MODEL_LOADER,
|
MAIN_MODEL_LOADER,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
NEGATIVE_CONDITIONING,
|
NEGATIVE_CONDITIONING,
|
||||||
NOISE,
|
NOISE,
|
||||||
ONNX_MODEL_LOADER,
|
ONNX_MODEL_LOADER,
|
||||||
POSITIVE_CONDITIONING,
|
POSITIVE_CONDITIONING,
|
||||||
SEAMLESS,
|
SEAMLESS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import { addMainMetadataNodeToGraph } from './metadata';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the Canvas tab's Text to Image graph.
|
* Builds the Canvas tab's Text to Image graph.
|
||||||
@ -288,10 +288,7 @@ export const buildCanvasTextToImageGraph = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// add metadata accumulator, which is only mostly populated - some fields are added later
|
addMainMetadataNodeToGraph(graph, {
|
||||||
graph.nodes[METADATA_ACCUMULATOR] = {
|
|
||||||
id: METADATA_ACCUMULATOR,
|
|
||||||
type: 'metadata_accumulator',
|
|
||||||
generation_mode: 'txt2img',
|
generation_mode: 'txt2img',
|
||||||
cfg_scale,
|
cfg_scale,
|
||||||
width: !isUsingScaledDimensions ? width : scaledBoundingBoxDimensions.width,
|
width: !isUsingScaledDimensions ? width : scaledBoundingBoxDimensions.width,
|
||||||
@ -305,21 +302,7 @@ export const buildCanvasTextToImageGraph = (
|
|||||||
steps,
|
steps,
|
||||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||||
scheduler,
|
scheduler,
|
||||||
vae: undefined, // option; set in addVAEToGraph
|
|
||||||
controlnets: [], // populated in addControlNetToLinearGraph
|
|
||||||
loras: [], // populated in addLoRAsToGraph
|
|
||||||
clip_skip: clipSkip,
|
clip_skip: clipSkip,
|
||||||
};
|
|
||||||
|
|
||||||
graph.edges.push({
|
|
||||||
source: {
|
|
||||||
node_id: METADATA_ACCUMULATOR,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
destination: {
|
|
||||||
node_id: CANVAS_OUTPUT,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add Seamless To Graph
|
// Add Seamless To Graph
|
||||||
|
@ -4,13 +4,20 @@ import { generateSeeds } from 'common/util/generateSeeds';
|
|||||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||||
import { range, unset } from 'lodash-es';
|
import { range, unset } from 'lodash-es';
|
||||||
import { components } from 'services/api/schema';
|
import { components } from 'services/api/schema';
|
||||||
import { Batch, BatchConfig } from 'services/api/types';
|
import { Batch, BatchConfig, MetadataItemInvocation } from 'services/api/types';
|
||||||
import {
|
import {
|
||||||
|
BATCH_PROMPT,
|
||||||
|
BATCH_SEED,
|
||||||
|
BATCH_STYLE_PROMPT,
|
||||||
CANVAS_COHERENCE_NOISE,
|
CANVAS_COHERENCE_NOISE,
|
||||||
METADATA_ACCUMULATOR,
|
METADATA_ACCUMULATOR,
|
||||||
NOISE,
|
NOISE,
|
||||||
POSITIVE_CONDITIONING,
|
POSITIVE_CONDITIONING,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import {
|
||||||
|
addBatchMetadataNodeToGraph,
|
||||||
|
removeMetadataFromMainMetadataNode,
|
||||||
|
} from './metadata';
|
||||||
|
|
||||||
export const prepareLinearUIBatch = (
|
export const prepareLinearUIBatch = (
|
||||||
state: RootState,
|
state: RootState,
|
||||||
@ -23,8 +30,27 @@ export const prepareLinearUIBatch = (
|
|||||||
|
|
||||||
const data: Batch['data'] = [];
|
const data: Batch['data'] = [];
|
||||||
|
|
||||||
|
const seedMetadataItemNode: MetadataItemInvocation = {
|
||||||
|
id: BATCH_SEED,
|
||||||
|
type: 'metadata_item',
|
||||||
|
label: 'seed',
|
||||||
|
};
|
||||||
|
|
||||||
|
const promptMetadataItemNode: MetadataItemInvocation = {
|
||||||
|
id: BATCH_PROMPT,
|
||||||
|
type: 'metadata_item',
|
||||||
|
label: 'positive_prompt',
|
||||||
|
};
|
||||||
|
|
||||||
|
const stylePromptMetadataItemNode: MetadataItemInvocation = {
|
||||||
|
id: BATCH_STYLE_PROMPT,
|
||||||
|
type: 'metadata_item',
|
||||||
|
label: 'positive_style_prompt',
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemNodesIds: string[] = [];
|
||||||
|
|
||||||
if (prompts.length === 1) {
|
if (prompts.length === 1) {
|
||||||
unset(graph.nodes[METADATA_ACCUMULATOR], 'seed');
|
|
||||||
const seeds = generateSeeds({
|
const seeds = generateSeeds({
|
||||||
count: iterations,
|
count: iterations,
|
||||||
start: shouldRandomizeSeed ? undefined : seed,
|
start: shouldRandomizeSeed ? undefined : seed,
|
||||||
@ -40,13 +66,15 @@ export const prepareLinearUIBatch = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (graph.nodes[METADATA_ACCUMULATOR]) {
|
// add to metadata
|
||||||
zipped.push({
|
removeMetadataFromMainMetadataNode(graph, 'seed');
|
||||||
node_path: METADATA_ACCUMULATOR,
|
itemNodesIds.push(BATCH_SEED);
|
||||||
field_name: 'seed',
|
graph.nodes[BATCH_SEED] = seedMetadataItemNode;
|
||||||
items: seeds,
|
zipped.push({
|
||||||
});
|
node_path: BATCH_SEED,
|
||||||
}
|
field_name: 'value',
|
||||||
|
items: seeds,
|
||||||
|
});
|
||||||
|
|
||||||
if (graph.nodes[CANVAS_COHERENCE_NOISE]) {
|
if (graph.nodes[CANVAS_COHERENCE_NOISE]) {
|
||||||
zipped.push({
|
zipped.push({
|
||||||
@ -77,13 +105,15 @@ export const prepareLinearUIBatch = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (graph.nodes[METADATA_ACCUMULATOR]) {
|
// add to metadata
|
||||||
firstBatchDatumList.push({
|
removeMetadataFromMainMetadataNode(graph, 'seed');
|
||||||
node_path: METADATA_ACCUMULATOR,
|
itemNodesIds.push(BATCH_SEED);
|
||||||
field_name: 'seed',
|
graph.nodes[BATCH_SEED] = seedMetadataItemNode;
|
||||||
items: seeds,
|
firstBatchDatumList.push({
|
||||||
});
|
node_path: BATCH_SEED,
|
||||||
}
|
field_name: 'value',
|
||||||
|
items: seeds,
|
||||||
|
});
|
||||||
|
|
||||||
if (graph.nodes[CANVAS_COHERENCE_NOISE]) {
|
if (graph.nodes[CANVAS_COHERENCE_NOISE]) {
|
||||||
firstBatchDatumList.push({
|
firstBatchDatumList.push({
|
||||||
@ -106,13 +136,17 @@ export const prepareLinearUIBatch = (
|
|||||||
items: seeds,
|
items: seeds,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (graph.nodes[METADATA_ACCUMULATOR]) {
|
|
||||||
secondBatchDatumList.push({
|
// add to metadata
|
||||||
node_path: METADATA_ACCUMULATOR,
|
removeMetadataFromMainMetadataNode(graph, 'seed');
|
||||||
field_name: 'seed',
|
itemNodesIds.push(BATCH_SEED);
|
||||||
items: seeds,
|
graph.nodes[BATCH_SEED] = seedMetadataItemNode;
|
||||||
});
|
secondBatchDatumList.push({
|
||||||
}
|
node_path: BATCH_SEED,
|
||||||
|
field_name: 'value',
|
||||||
|
items: seeds,
|
||||||
|
});
|
||||||
|
|
||||||
if (graph.nodes[CANVAS_COHERENCE_NOISE]) {
|
if (graph.nodes[CANVAS_COHERENCE_NOISE]) {
|
||||||
secondBatchDatumList.push({
|
secondBatchDatumList.push({
|
||||||
node_path: CANVAS_COHERENCE_NOISE,
|
node_path: CANVAS_COHERENCE_NOISE,
|
||||||
@ -137,13 +171,15 @@ export const prepareLinearUIBatch = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (graph.nodes[METADATA_ACCUMULATOR]) {
|
// add to metadata
|
||||||
firstBatchDatumList.push({
|
removeMetadataFromMainMetadataNode(graph, 'positive_prompt');
|
||||||
node_path: METADATA_ACCUMULATOR,
|
itemNodesIds.push(BATCH_PROMPT);
|
||||||
field_name: 'positive_prompt',
|
graph.nodes[BATCH_PROMPT] = promptMetadataItemNode;
|
||||||
items: extendedPrompts,
|
firstBatchDatumList.push({
|
||||||
});
|
node_path: BATCH_PROMPT,
|
||||||
}
|
field_name: 'value',
|
||||||
|
items: extendedPrompts,
|
||||||
|
});
|
||||||
|
|
||||||
if (shouldConcatSDXLStylePrompt && model?.base_model === 'sdxl') {
|
if (shouldConcatSDXLStylePrompt && model?.base_model === 'sdxl') {
|
||||||
unset(graph.nodes[METADATA_ACCUMULATOR], 'positive_style_prompt');
|
unset(graph.nodes[METADATA_ACCUMULATOR], 'positive_style_prompt');
|
||||||
@ -160,18 +196,22 @@ export const prepareLinearUIBatch = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (graph.nodes[METADATA_ACCUMULATOR]) {
|
// add to metadata
|
||||||
firstBatchDatumList.push({
|
removeMetadataFromMainMetadataNode(graph, 'positive_style_prompt');
|
||||||
node_path: METADATA_ACCUMULATOR,
|
itemNodesIds.push(BATCH_STYLE_PROMPT);
|
||||||
field_name: 'positive_style_prompt',
|
graph.nodes[BATCH_STYLE_PROMPT] = stylePromptMetadataItemNode;
|
||||||
items: stylePrompts,
|
firstBatchDatumList.push({
|
||||||
});
|
node_path: BATCH_STYLE_PROMPT,
|
||||||
}
|
field_name: 'value',
|
||||||
|
items: extendedPrompts,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
data.push(firstBatchDatumList);
|
data.push(firstBatchDatumList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addBatchMetadataNodeToGraph(graph, itemNodesIds);
|
||||||
|
|
||||||
const enqueueBatchArg: BatchConfig = {
|
const enqueueBatchArg: BatchConfig = {
|
||||||
prepend,
|
prepend,
|
||||||
batch: {
|
batch: {
|
||||||
|
@ -20,13 +20,13 @@ import {
|
|||||||
IMAGE_TO_LATENTS,
|
IMAGE_TO_LATENTS,
|
||||||
LATENTS_TO_IMAGE,
|
LATENTS_TO_IMAGE,
|
||||||
MAIN_MODEL_LOADER,
|
MAIN_MODEL_LOADER,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
NEGATIVE_CONDITIONING,
|
NEGATIVE_CONDITIONING,
|
||||||
NOISE,
|
NOISE,
|
||||||
POSITIVE_CONDITIONING,
|
POSITIVE_CONDITIONING,
|
||||||
RESIZE,
|
RESIZE,
|
||||||
SEAMLESS,
|
SEAMLESS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import { addMainMetadataNodeToGraph } from './metadata';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the Image to Image tab graph.
|
* Builds the Image to Image tab graph.
|
||||||
@ -310,10 +310,7 @@ export const buildLinearImageToImageGraph = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// add metadata accumulator, which is only mostly populated - some fields are added later
|
addMainMetadataNodeToGraph(graph, {
|
||||||
graph.nodes[METADATA_ACCUMULATOR] = {
|
|
||||||
id: METADATA_ACCUMULATOR,
|
|
||||||
type: 'metadata_accumulator',
|
|
||||||
generation_mode: 'img2img',
|
generation_mode: 'img2img',
|
||||||
cfg_scale,
|
cfg_scale,
|
||||||
height,
|
height,
|
||||||
@ -325,23 +322,9 @@ export const buildLinearImageToImageGraph = (
|
|||||||
steps,
|
steps,
|
||||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||||
scheduler,
|
scheduler,
|
||||||
vae: undefined, // option; set in addVAEToGraph
|
|
||||||
controlnets: [], // populated in addControlNetToLinearGraph
|
|
||||||
loras: [], // populated in addLoRAsToGraph
|
|
||||||
clip_skip: clipSkip,
|
clip_skip: clipSkip,
|
||||||
strength,
|
strength,
|
||||||
init_image: initialImage.imageName,
|
init_image: initialImage.imageName,
|
||||||
};
|
|
||||||
|
|
||||||
graph.edges.push({
|
|
||||||
source: {
|
|
||||||
node_id: METADATA_ACCUMULATOR,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
destination: {
|
|
||||||
node_id: LATENTS_TO_IMAGE,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add Seamless To Graph
|
// Add Seamless To Graph
|
||||||
|
@ -17,7 +17,6 @@ import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
|||||||
import {
|
import {
|
||||||
IMAGE_TO_LATENTS,
|
IMAGE_TO_LATENTS,
|
||||||
LATENTS_TO_IMAGE,
|
LATENTS_TO_IMAGE,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
NEGATIVE_CONDITIONING,
|
NEGATIVE_CONDITIONING,
|
||||||
NOISE,
|
NOISE,
|
||||||
POSITIVE_CONDITIONING,
|
POSITIVE_CONDITIONING,
|
||||||
@ -29,6 +28,7 @@ import {
|
|||||||
SEAMLESS,
|
SEAMLESS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
||||||
|
import { addMainMetadataNodeToGraph } from './metadata';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the Image to Image tab graph.
|
* Builds the Image to Image tab graph.
|
||||||
@ -330,10 +330,7 @@ export const buildLinearSDXLImageToImageGraph = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// add metadata accumulator, which is only mostly populated - some fields are added later
|
addMainMetadataNodeToGraph(graph, {
|
||||||
graph.nodes[METADATA_ACCUMULATOR] = {
|
|
||||||
id: METADATA_ACCUMULATOR,
|
|
||||||
type: 'metadata_accumulator',
|
|
||||||
generation_mode: 'sdxl_img2img',
|
generation_mode: 'sdxl_img2img',
|
||||||
cfg_scale,
|
cfg_scale,
|
||||||
height,
|
height,
|
||||||
@ -345,24 +342,10 @@ export const buildLinearSDXLImageToImageGraph = (
|
|||||||
steps,
|
steps,
|
||||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||||
scheduler,
|
scheduler,
|
||||||
vae: undefined,
|
strength,
|
||||||
controlnets: [],
|
|
||||||
loras: [],
|
|
||||||
strength: strength,
|
|
||||||
init_image: initialImage.imageName,
|
init_image: initialImage.imageName,
|
||||||
positive_style_prompt: positiveStylePrompt,
|
positive_style_prompt: positiveStylePrompt,
|
||||||
negative_style_prompt: negativeStylePrompt,
|
negative_style_prompt: negativeStylePrompt,
|
||||||
};
|
|
||||||
|
|
||||||
graph.edges.push({
|
|
||||||
source: {
|
|
||||||
node_id: METADATA_ACCUMULATOR,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
destination: {
|
|
||||||
node_id: LATENTS_TO_IMAGE,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add Seamless To Graph
|
// Add Seamless To Graph
|
||||||
|
@ -10,9 +10,9 @@ import { addSaveImageNode } from './addSaveImageNode';
|
|||||||
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
||||||
import { addVAEToGraph } from './addVAEToGraph';
|
import { addVAEToGraph } from './addVAEToGraph';
|
||||||
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
||||||
|
import { addMainMetadataNodeToGraph } from './metadata';
|
||||||
import {
|
import {
|
||||||
LATENTS_TO_IMAGE,
|
LATENTS_TO_IMAGE,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
NEGATIVE_CONDITIONING,
|
NEGATIVE_CONDITIONING,
|
||||||
NOISE,
|
NOISE,
|
||||||
POSITIVE_CONDITIONING,
|
POSITIVE_CONDITIONING,
|
||||||
@ -224,10 +224,7 @@ export const buildLinearSDXLTextToImageGraph = (
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// add metadata accumulator, which is only mostly populated - some fields are added later
|
addMainMetadataNodeToGraph(graph, {
|
||||||
graph.nodes[METADATA_ACCUMULATOR] = {
|
|
||||||
id: METADATA_ACCUMULATOR,
|
|
||||||
type: 'metadata_accumulator',
|
|
||||||
generation_mode: 'sdxl_txt2img',
|
generation_mode: 'sdxl_txt2img',
|
||||||
cfg_scale,
|
cfg_scale,
|
||||||
height,
|
height,
|
||||||
@ -239,22 +236,8 @@ export const buildLinearSDXLTextToImageGraph = (
|
|||||||
steps,
|
steps,
|
||||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||||
scheduler,
|
scheduler,
|
||||||
vae: undefined,
|
|
||||||
controlnets: [],
|
|
||||||
loras: [],
|
|
||||||
positive_style_prompt: positiveStylePrompt,
|
positive_style_prompt: positiveStylePrompt,
|
||||||
negative_style_prompt: negativeStylePrompt,
|
negative_style_prompt: negativeStylePrompt,
|
||||||
};
|
|
||||||
|
|
||||||
graph.edges.push({
|
|
||||||
source: {
|
|
||||||
node_id: METADATA_ACCUMULATOR,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
destination: {
|
|
||||||
node_id: LATENTS_TO_IMAGE,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add Seamless To Graph
|
// Add Seamless To Graph
|
||||||
|
@ -13,12 +13,12 @@ import { addSaveImageNode } from './addSaveImageNode';
|
|||||||
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
||||||
import { addVAEToGraph } from './addVAEToGraph';
|
import { addVAEToGraph } from './addVAEToGraph';
|
||||||
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
||||||
|
import { addMainMetadataNodeToGraph } from './metadata';
|
||||||
import {
|
import {
|
||||||
CLIP_SKIP,
|
CLIP_SKIP,
|
||||||
DENOISE_LATENTS,
|
DENOISE_LATENTS,
|
||||||
LATENTS_TO_IMAGE,
|
LATENTS_TO_IMAGE,
|
||||||
MAIN_MODEL_LOADER,
|
MAIN_MODEL_LOADER,
|
||||||
METADATA_ACCUMULATOR,
|
|
||||||
NEGATIVE_CONDITIONING,
|
NEGATIVE_CONDITIONING,
|
||||||
NOISE,
|
NOISE,
|
||||||
ONNX_MODEL_LOADER,
|
ONNX_MODEL_LOADER,
|
||||||
@ -232,10 +232,7 @@ export const buildLinearTextToImageGraph = (
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// add metadata accumulator, which is only mostly populated - some fields are added later
|
addMainMetadataNodeToGraph(graph, {
|
||||||
graph.nodes[METADATA_ACCUMULATOR] = {
|
|
||||||
id: METADATA_ACCUMULATOR,
|
|
||||||
type: 'metadata_accumulator',
|
|
||||||
generation_mode: 'txt2img',
|
generation_mode: 'txt2img',
|
||||||
cfg_scale,
|
cfg_scale,
|
||||||
height,
|
height,
|
||||||
@ -247,21 +244,7 @@ export const buildLinearTextToImageGraph = (
|
|||||||
steps,
|
steps,
|
||||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||||
scheduler,
|
scheduler,
|
||||||
vae: undefined, // option; set in addVAEToGraph
|
|
||||||
controlnets: [], // populated in addControlNetToLinearGraph
|
|
||||||
loras: [], // populated in addLoRAsToGraph
|
|
||||||
clip_skip: clipSkip,
|
clip_skip: clipSkip,
|
||||||
};
|
|
||||||
|
|
||||||
graph.edges.push({
|
|
||||||
source: {
|
|
||||||
node_id: METADATA_ACCUMULATOR,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
destination: {
|
|
||||||
node_id: LATENTS_TO_IMAGE,
|
|
||||||
field: 'metadata',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add Seamless To Graph
|
// Add Seamless To Graph
|
||||||
|
@ -50,7 +50,15 @@ export const IP_ADAPTER = 'ip_adapter';
|
|||||||
export const DYNAMIC_PROMPT = 'dynamic_prompt';
|
export const DYNAMIC_PROMPT = 'dynamic_prompt';
|
||||||
export const IMAGE_COLLECTION = 'image_collection';
|
export const IMAGE_COLLECTION = 'image_collection';
|
||||||
export const IMAGE_COLLECTION_ITERATE = 'image_collection_iterate';
|
export const IMAGE_COLLECTION_ITERATE = 'image_collection_iterate';
|
||||||
|
export const METADATA = 'metadata';
|
||||||
|
export const BATCH_METADATA = 'batch_metadata';
|
||||||
|
export const BATCH_METADATA_COLLECT = 'batch_metadata_collect';
|
||||||
|
export const BATCH_SEED = 'batch_seed';
|
||||||
|
export const BATCH_PROMPT = 'batch_prompt';
|
||||||
|
export const BATCH_STYLE_PROMPT = 'batch_style_prompt';
|
||||||
|
export const METADATA_COLLECT = 'metadata_collect';
|
||||||
export const METADATA_ACCUMULATOR = 'metadata_accumulator';
|
export const METADATA_ACCUMULATOR = 'metadata_accumulator';
|
||||||
|
export const MERGE_METADATA = 'merge_metadata';
|
||||||
export const REALESRGAN = 'esrgan';
|
export const REALESRGAN = 'esrgan';
|
||||||
export const DIVIDE = 'divide';
|
export const DIVIDE = 'divide';
|
||||||
export const SCALE = 'scale_image';
|
export const SCALE = 'scale_image';
|
||||||
|
@ -0,0 +1,151 @@
|
|||||||
|
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||||
|
import { map } from 'lodash-es';
|
||||||
|
import { MetadataInvocationAsCollection } from 'services/api/types';
|
||||||
|
import { JsonObject } from 'type-fest';
|
||||||
|
import {
|
||||||
|
BATCH_METADATA,
|
||||||
|
BATCH_METADATA_COLLECT,
|
||||||
|
MERGE_METADATA,
|
||||||
|
METADATA,
|
||||||
|
METADATA_COLLECT,
|
||||||
|
SAVE_IMAGE,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
export const addMainMetadataNodeToGraph = (
|
||||||
|
graph: NonNullableGraph,
|
||||||
|
metadata: JsonObject
|
||||||
|
): void => {
|
||||||
|
graph.nodes[METADATA] = {
|
||||||
|
id: METADATA,
|
||||||
|
type: 'metadata',
|
||||||
|
items: map(metadata, (value, label) => ({ label, value })),
|
||||||
|
};
|
||||||
|
|
||||||
|
graph.nodes[METADATA_COLLECT] = {
|
||||||
|
id: METADATA_COLLECT,
|
||||||
|
type: 'collect',
|
||||||
|
};
|
||||||
|
|
||||||
|
graph.nodes[MERGE_METADATA] = {
|
||||||
|
id: MERGE_METADATA,
|
||||||
|
type: 'merge_metadata_dict',
|
||||||
|
};
|
||||||
|
|
||||||
|
graph.edges.push({
|
||||||
|
source: {
|
||||||
|
node_id: METADATA,
|
||||||
|
field: 'metadata_dict',
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
node_id: METADATA_COLLECT,
|
||||||
|
field: 'item',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.edges.push({
|
||||||
|
source: {
|
||||||
|
node_id: METADATA_COLLECT,
|
||||||
|
field: 'collection',
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
node_id: MERGE_METADATA,
|
||||||
|
field: 'collection',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.edges.push({
|
||||||
|
source: {
|
||||||
|
node_id: MERGE_METADATA,
|
||||||
|
field: 'metadata_dict',
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
node_id: SAVE_IMAGE,
|
||||||
|
field: 'metadata',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addMainMetadata = (
|
||||||
|
graph: NonNullableGraph,
|
||||||
|
metadata: JsonObject
|
||||||
|
): void => {
|
||||||
|
const metadataNode = graph.nodes[METADATA] as
|
||||||
|
| MetadataInvocationAsCollection
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (!metadataNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataNode.items.push(
|
||||||
|
...map(metadata, (value, label) => ({ label, value }))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeMetadataFromMainMetadataNode = (
|
||||||
|
graph: NonNullableGraph,
|
||||||
|
label: string
|
||||||
|
): void => {
|
||||||
|
const metadataNode = graph.nodes[METADATA] as
|
||||||
|
| MetadataInvocationAsCollection
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (!metadataNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataNode.items = metadataNode.items.filter(
|
||||||
|
(item) => item.label !== label
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addBatchMetadataNodeToGraph = (
|
||||||
|
graph: NonNullableGraph,
|
||||||
|
itemNodeIds: string[]
|
||||||
|
) => {
|
||||||
|
graph.nodes[BATCH_METADATA] = {
|
||||||
|
id: BATCH_METADATA,
|
||||||
|
type: 'metadata',
|
||||||
|
};
|
||||||
|
graph.nodes[BATCH_METADATA_COLLECT] = {
|
||||||
|
id: BATCH_METADATA_COLLECT,
|
||||||
|
type: 'collect',
|
||||||
|
};
|
||||||
|
|
||||||
|
itemNodeIds.forEach((id) => {
|
||||||
|
graph.edges.push({
|
||||||
|
source: {
|
||||||
|
node_id: id,
|
||||||
|
field: 'item',
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
node_id: BATCH_METADATA_COLLECT,
|
||||||
|
field: 'item',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.edges.push({
|
||||||
|
source: {
|
||||||
|
node_id: BATCH_METADATA_COLLECT,
|
||||||
|
field: 'collection',
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
node_id: BATCH_METADATA,
|
||||||
|
field: 'items',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.edges.push({
|
||||||
|
source: {
|
||||||
|
node_id: BATCH_METADATA,
|
||||||
|
field: 'metadata_dict',
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
node_id: METADATA_COLLECT,
|
||||||
|
field: 'item',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -4,7 +4,6 @@ import { reduce } from 'lodash-es';
|
|||||||
import { OpenAPIV3 } from 'openapi-types';
|
import { OpenAPIV3 } from 'openapi-types';
|
||||||
import { AnyInvocationType } from 'services/events/types';
|
import { AnyInvocationType } from 'services/events/types';
|
||||||
import {
|
import {
|
||||||
FieldType,
|
|
||||||
InputFieldTemplate,
|
InputFieldTemplate,
|
||||||
InvocationSchemaObject,
|
InvocationSchemaObject,
|
||||||
InvocationTemplate,
|
InvocationTemplate,
|
||||||
@ -16,18 +15,11 @@ import {
|
|||||||
} from '../types/types';
|
} from '../types/types';
|
||||||
import { buildInputFieldTemplate, getFieldType } from './fieldTemplateBuilders';
|
import { buildInputFieldTemplate, getFieldType } from './fieldTemplateBuilders';
|
||||||
|
|
||||||
const RESERVED_INPUT_FIELD_NAMES = ['id', 'type', 'metadata', 'use_cache'];
|
const RESERVED_INPUT_FIELD_NAMES = ['id', 'type', 'use_cache'];
|
||||||
const RESERVED_OUTPUT_FIELD_NAMES = ['type'];
|
const RESERVED_OUTPUT_FIELD_NAMES = ['type'];
|
||||||
const RESERVED_FIELD_TYPES = [
|
const RESERVED_FIELD_TYPES = ['IsIntermediate', 'WorkflowField'];
|
||||||
'WorkflowField',
|
|
||||||
'MetadataField',
|
|
||||||
'IsIntermediate',
|
|
||||||
];
|
|
||||||
|
|
||||||
const invocationDenylist: AnyInvocationType[] = [
|
const invocationDenylist: AnyInvocationType[] = ['graph'];
|
||||||
'graph',
|
|
||||||
'metadata_accumulator',
|
|
||||||
];
|
|
||||||
|
|
||||||
const isReservedInputField = (nodeType: string, fieldName: string) => {
|
const isReservedInputField = (nodeType: string, fieldName: string) => {
|
||||||
if (RESERVED_INPUT_FIELD_NAMES.includes(fieldName)) {
|
if (RESERVED_INPUT_FIELD_NAMES.includes(fieldName)) {
|
||||||
@ -42,7 +34,7 @@ const isReservedInputField = (nodeType: string, fieldName: string) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isReservedFieldType = (fieldType: FieldType) => {
|
const isReservedFieldType = (fieldType: string) => {
|
||||||
if (RESERVED_FIELD_TYPES.includes(fieldType)) {
|
if (RESERVED_FIELD_TYPES.includes(fieldType)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -86,6 +78,7 @@ export const parseSchema = (
|
|||||||
const tags = schema.tags ?? [];
|
const tags = schema.tags ?? [];
|
||||||
const description = schema.description ?? '';
|
const description = schema.description ?? '';
|
||||||
const version = schema.version;
|
const version = schema.version;
|
||||||
|
let withWorkflow = false;
|
||||||
|
|
||||||
const inputs = reduce(
|
const inputs = reduce(
|
||||||
schema.properties,
|
schema.properties,
|
||||||
@ -112,7 +105,7 @@ export const parseSchema = (
|
|||||||
|
|
||||||
const fieldType = getFieldType(property);
|
const fieldType = getFieldType(property);
|
||||||
|
|
||||||
if (!isFieldType(fieldType)) {
|
if (!fieldType) {
|
||||||
logger('nodes').warn(
|
logger('nodes').warn(
|
||||||
{
|
{
|
||||||
node: type,
|
node: type,
|
||||||
@ -120,11 +113,16 @@ export const parseSchema = (
|
|||||||
fieldType,
|
fieldType,
|
||||||
field: parseify(property),
|
field: parseify(property),
|
||||||
},
|
},
|
||||||
'Skipping unknown input field type'
|
'Missing input field type'
|
||||||
);
|
);
|
||||||
return inputsAccumulator;
|
return inputsAccumulator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fieldType === 'WorkflowField') {
|
||||||
|
withWorkflow = true;
|
||||||
|
return inputsAccumulator;
|
||||||
|
}
|
||||||
|
|
||||||
if (isReservedFieldType(fieldType)) {
|
if (isReservedFieldType(fieldType)) {
|
||||||
logger('nodes').trace(
|
logger('nodes').trace(
|
||||||
{
|
{
|
||||||
@ -133,7 +131,20 @@ export const parseSchema = (
|
|||||||
fieldType,
|
fieldType,
|
||||||
field: parseify(property),
|
field: parseify(property),
|
||||||
},
|
},
|
||||||
'Skipping reserved field type'
|
`Skipping reserved input field type: ${fieldType}`
|
||||||
|
);
|
||||||
|
return inputsAccumulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isFieldType(fieldType)) {
|
||||||
|
logger('nodes').warn(
|
||||||
|
{
|
||||||
|
node: type,
|
||||||
|
fieldName: propertyName,
|
||||||
|
fieldType,
|
||||||
|
field: parseify(property),
|
||||||
|
},
|
||||||
|
`Skipping unknown input field type: ${fieldType}`
|
||||||
);
|
);
|
||||||
return inputsAccumulator;
|
return inputsAccumulator;
|
||||||
}
|
}
|
||||||
@ -146,7 +157,7 @@ export const parseSchema = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!field) {
|
if (!field) {
|
||||||
logger('nodes').debug(
|
logger('nodes').warn(
|
||||||
{
|
{
|
||||||
node: type,
|
node: type,
|
||||||
fieldName: propertyName,
|
fieldName: propertyName,
|
||||||
@ -247,6 +258,7 @@ export const parseSchema = (
|
|||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
useCache,
|
useCache,
|
||||||
|
withWorkflow,
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(invocationsAccumulator, { [type]: invocation });
|
Object.assign(invocationsAccumulator, { [type]: invocation });
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
import { setClipSkip } from 'features/parameters/store/generationSlice';
|
import { setClipSkip } from 'features/parameters/store/generationSlice';
|
||||||
import { clipSkipMap } from 'features/parameters/types/constants';
|
import { clipSkipMap } from 'features/parameters/types/constants';
|
||||||
@ -47,7 +47,7 @@ export default function ParamClipSkip() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="clipSkip">
|
<IAIInformationalPopover feature="clipSkip" placement="top">
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={t('parameters.clipSkip')}
|
label={t('parameters.clipSkip')}
|
||||||
aria-label={t('parameters.clipSkip')}
|
aria-label={t('parameters.clipSkip')}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Box, Flex, Spacer, Text } from '@chakra-ui/react';
|
import { Flex, FormControl, FormLabel, Spacer } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import { flipBoundingBoxAxes } from 'features/canvas/store/canvasSlice';
|
import { flipBoundingBoxAxes } from 'features/canvas/store/canvasSlice';
|
||||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||||
@ -18,7 +19,6 @@ import ParamAspectRatio, {
|
|||||||
} from '../../Core/ParamAspectRatio';
|
} from '../../Core/ParamAspectRatio';
|
||||||
import ParamBoundingBoxHeight from './ParamBoundingBoxHeight';
|
import ParamBoundingBoxHeight from './ParamBoundingBoxHeight';
|
||||||
import ParamBoundingBoxWidth from './ParamBoundingBoxWidth';
|
import ParamBoundingBoxWidth from './ParamBoundingBoxWidth';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
|
||||||
|
|
||||||
const sizeOptsSelector = createSelector(
|
const sizeOptsSelector = createSelector(
|
||||||
[generationSelector, canvasSelector],
|
[generationSelector, canvasSelector],
|
||||||
@ -93,42 +93,29 @@ export default function ParamBoundingBoxSize() {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex alignItems="center" gap={2}>
|
<IAIInformationalPopover feature="paramRatio">
|
||||||
<Box width="full">
|
<FormControl as={Flex} flexDir="row" alignItems="center" gap={2}>
|
||||||
<IAIInformationalPopover details="paramRatio">
|
<FormLabel>{t('parameters.aspectRatio')}</FormLabel>
|
||||||
<Text
|
<Spacer />
|
||||||
sx={{
|
<ParamAspectRatio />
|
||||||
fontSize: 'sm',
|
<IAIIconButton
|
||||||
width: 'full',
|
tooltip={t('ui.swapSizes')}
|
||||||
color: 'base.700',
|
aria-label={t('ui.swapSizes')}
|
||||||
_dark: {
|
size="sm"
|
||||||
color: 'base.300',
|
icon={<MdOutlineSwapVert />}
|
||||||
},
|
fontSize={20}
|
||||||
}}
|
onClick={handleToggleSize}
|
||||||
>
|
/>
|
||||||
{t('parameters.aspectRatio')}
|
<IAIIconButton
|
||||||
</Text>
|
tooltip={t('ui.lockRatio')}
|
||||||
</IAIInformationalPopover>
|
aria-label={t('ui.lockRatio')}
|
||||||
</Box>
|
size="sm"
|
||||||
<Spacer />
|
icon={<FaLock />}
|
||||||
<ParamAspectRatio />
|
isChecked={shouldLockAspectRatio}
|
||||||
<IAIIconButton
|
onClick={handleLockRatio}
|
||||||
tooltip={t('ui.swapSizes')}
|
/>
|
||||||
aria-label={t('ui.swapSizes')}
|
</FormControl>
|
||||||
size="sm"
|
</IAIInformationalPopover>
|
||||||
icon={<MdOutlineSwapVert />}
|
|
||||||
fontSize={20}
|
|
||||||
onClick={handleToggleSize}
|
|
||||||
/>
|
|
||||||
<IAIIconButton
|
|
||||||
tooltip={t('ui.lockRatio')}
|
|
||||||
aria-label={t('ui.lockRatio')}
|
|
||||||
size="sm"
|
|
||||||
icon={<FaLock />}
|
|
||||||
isChecked={shouldLockAspectRatio}
|
|
||||||
onClick={handleLockRatio}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<ParamBoundingBoxWidth />
|
<ParamBoundingBoxWidth />
|
||||||
<ParamBoundingBoxHeight />
|
<ParamBoundingBoxHeight />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { RootState } from 'app/store/store';
|
import type { RootState } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import { IAISelectDataType } from 'common/components/IAIMantineSearchableSelect';
|
import { IAISelectDataType } from 'common/components/IAIMantineSearchableSelect';
|
||||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||||
import { setCanvasCoherenceMode } from 'features/parameters/store/generationSlice';
|
import { setCanvasCoherenceMode } from 'features/parameters/store/generationSlice';
|
||||||
@ -31,7 +31,7 @@ const ParamCanvasCoherenceMode = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="compositingCoherenceMode">
|
<IAIInformationalPopover feature="compositingCoherenceMode">
|
||||||
<IAIMantineSelect
|
<IAIMantineSelect
|
||||||
label={t('parameters.coherenceMode')}
|
label={t('parameters.coherenceMode')}
|
||||||
data={coherenceModeSelectData}
|
data={coherenceModeSelectData}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { RootState } from 'app/store/store';
|
import type { RootState } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
import { setCanvasCoherenceSteps } from 'features/parameters/store/generationSlice';
|
import { setCanvasCoherenceSteps } from 'features/parameters/store/generationSlice';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
@ -14,7 +14,7 @@ const ParamCanvasCoherenceSteps = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="compositingCoherenceSteps">
|
<IAIInformationalPopover feature="compositingCoherenceSteps">
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={t('parameters.coherenceSteps')}
|
label={t('parameters.coherenceSteps')}
|
||||||
min={1}
|
min={1}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { RootState } from 'app/store/store';
|
import type { RootState } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
import { setCanvasCoherenceStrength } from 'features/parameters/store/generationSlice';
|
import { setCanvasCoherenceStrength } from 'features/parameters/store/generationSlice';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
@ -14,7 +14,7 @@ const ParamCanvasCoherenceStrength = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="compositingStrength">
|
<IAIInformationalPopover feature="compositingStrength">
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={t('parameters.coherenceStrength')}
|
label={t('parameters.coherenceStrength')}
|
||||||
min={0}
|
min={0}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { RootState } from 'app/store/store';
|
import type { RootState } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
import { setMaskBlur } from 'features/parameters/store/generationSlice';
|
import { setMaskBlur } from 'features/parameters/store/generationSlice';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -13,7 +13,7 @@ export default function ParamMaskBlur() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="compositingBlur">
|
<IAIInformationalPopover feature="compositingBlur">
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={t('parameters.maskBlur')}
|
label={t('parameters.maskBlur')}
|
||||||
min={0}
|
min={0}
|
||||||
|
@ -2,7 +2,7 @@ import { SelectItem } from '@mantine/core';
|
|||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||||
import { setMaskBlurMethod } from 'features/parameters/store/generationSlice';
|
import { setMaskBlurMethod } from 'features/parameters/store/generationSlice';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -29,7 +29,7 @@ export default function ParamMaskBlurMethod() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="compositingBlurMethod">
|
<IAIInformationalPopover feature="compositingBlurMethod">
|
||||||
<IAIMantineSelect
|
<IAIMantineSelect
|
||||||
value={maskBlurMethod}
|
value={maskBlurMethod}
|
||||||
onChange={handleMaskBlurMethodChange}
|
onChange={handleMaskBlurMethodChange}
|
||||||
|
@ -15,19 +15,13 @@ const ParamCompositingSettingsCollapse = () => {
|
|||||||
return (
|
return (
|
||||||
<IAICollapse label={t('parameters.compositingSettingsHeader')}>
|
<IAICollapse label={t('parameters.compositingSettingsHeader')}>
|
||||||
<Flex sx={{ flexDirection: 'column', gap: 2 }}>
|
<Flex sx={{ flexDirection: 'column', gap: 2 }}>
|
||||||
<SubParametersWrapper
|
<SubParametersWrapper label={t('parameters.coherencePassHeader')}>
|
||||||
label={t('parameters.coherencePassHeader')}
|
|
||||||
headerInfoPopover="compositingCoherencePass"
|
|
||||||
>
|
|
||||||
<ParamCanvasCoherenceMode />
|
<ParamCanvasCoherenceMode />
|
||||||
<ParamCanvasCoherenceSteps />
|
<ParamCanvasCoherenceSteps />
|
||||||
<ParamCanvasCoherenceStrength />
|
<ParamCanvasCoherenceStrength />
|
||||||
</SubParametersWrapper>
|
</SubParametersWrapper>
|
||||||
<Divider />
|
<Divider />
|
||||||
<SubParametersWrapper
|
<SubParametersWrapper label={t('parameters.maskAdjustmentsHeader')}>
|
||||||
label={t('parameters.maskAdjustmentsHeader')}
|
|
||||||
headerInfoPopover="compositingMaskAdjustments"
|
|
||||||
>
|
|
||||||
<ParamMaskBlur />
|
<ParamMaskBlur />
|
||||||
<ParamMaskBlurMethod />
|
<ParamMaskBlurMethod />
|
||||||
</SubParametersWrapper>
|
</SubParametersWrapper>
|
||||||
|
@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||||
import { setInfillMethod } from 'features/parameters/store/generationSlice';
|
import { setInfillMethod } from 'features/parameters/store/generationSlice';
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ const ParamInfillMethod = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="infillMethod">
|
<IAIInformationalPopover feature="infillMethod">
|
||||||
<IAIMantineSelect
|
<IAIMantineSelect
|
||||||
disabled={infill_methods?.length === 0}
|
disabled={infill_methods?.length === 0}
|
||||||
placeholder={isLoading ? 'Loading...' : undefined}
|
placeholder={isLoading ? 'Loading...' : undefined}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
||||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import { setBoundingBoxScaleMethod } from 'features/canvas/store/canvasSlice';
|
import { setBoundingBoxScaleMethod } from 'features/canvas/store/canvasSlice';
|
||||||
@ -36,7 +36,7 @@ const ParamScaleBeforeProcessing = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="scaleBeforeProcessing">
|
<IAIInformationalPopover feature="scaleBeforeProcessing">
|
||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
label={t('parameters.scaleBeforeProcessing')}
|
label={t('parameters.scaleBeforeProcessing')}
|
||||||
data={BOUNDING_BOX_SCALES_DICT}
|
data={BOUNDING_BOX_SCALES_DICT}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ButtonGroup, Flex } from '@chakra-ui/react';
|
import { ButtonGroup } from '@chakra-ui/react';
|
||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
@ -29,25 +29,23 @@ export default function ParamAspectRatio() {
|
|||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap={2} flexGrow={1}>
|
<ButtonGroup isAttached>
|
||||||
<ButtonGroup isAttached>
|
{aspectRatios.map((ratio) => (
|
||||||
{aspectRatios.map((ratio) => (
|
<IAIButton
|
||||||
<IAIButton
|
key={ratio.name}
|
||||||
key={ratio.name}
|
size="sm"
|
||||||
size="sm"
|
isChecked={aspectRatio === ratio.value}
|
||||||
isChecked={aspectRatio === ratio.value}
|
isDisabled={
|
||||||
isDisabled={
|
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
||||||
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
}
|
||||||
}
|
onClick={() => {
|
||||||
onClick={() => {
|
dispatch(setAspectRatio(ratio.value));
|
||||||
dispatch(setAspectRatio(ratio.value));
|
dispatch(setShouldLockAspectRatio(false));
|
||||||
dispatch(setShouldLockAspectRatio(false));
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{ratio.name}
|
||||||
{ratio.name}
|
</IAIButton>
|
||||||
</IAIButton>
|
))}
|
||||||
))}
|
</ButtonGroup>
|
||||||
</ButtonGroup>
|
|
||||||
</Flex>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAINumberInput from 'common/components/IAINumberInput';
|
import IAINumberInput from 'common/components/IAINumberInput';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
import { setCfgScale } from 'features/parameters/store/generationSlice';
|
import { setCfgScale } from 'features/parameters/store/generationSlice';
|
||||||
@ -54,7 +54,7 @@ const ParamCFGScale = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return shouldUseSliders ? (
|
return shouldUseSliders ? (
|
||||||
<IAIInformationalPopover details="paramCFGScale">
|
<IAIInformationalPopover feature="paramCFGScale">
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={t('parameters.cfgScale')}
|
label={t('parameters.cfgScale')}
|
||||||
step={shift ? 0.1 : 0.5}
|
step={shift ? 0.1 : 0.5}
|
||||||
@ -71,7 +71,7 @@ const ParamCFGScale = () => {
|
|||||||
/>
|
/>
|
||||||
</IAIInformationalPopover>
|
</IAIInformationalPopover>
|
||||||
) : (
|
) : (
|
||||||
<IAIInformationalPopover details="paramCFGScale">
|
<IAIInformationalPopover feature="paramCFGScale">
|
||||||
<IAINumberInput
|
<IAINumberInput
|
||||||
label={t('parameters.cfgScale')}
|
label={t('parameters.cfgScale')}
|
||||||
step={0.5}
|
step={0.5}
|
||||||
|
@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAINumberInput from 'common/components/IAINumberInput';
|
import IAINumberInput from 'common/components/IAINumberInput';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
import { setIterations } from 'features/parameters/store/generationSlice';
|
import { setIterations } from 'features/parameters/store/generationSlice';
|
||||||
@ -61,7 +61,7 @@ const ParamIterations = ({ asSlider }: Props) => {
|
|||||||
}, [dispatch, initial]);
|
}, [dispatch, initial]);
|
||||||
|
|
||||||
return asSlider || shouldUseSliders ? (
|
return asSlider || shouldUseSliders ? (
|
||||||
<IAIInformationalPopover details="paramIterations">
|
<IAIInformationalPopover feature="paramIterations">
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={t('parameters.iterations')}
|
label={t('parameters.iterations')}
|
||||||
step={step}
|
step={step}
|
||||||
@ -77,7 +77,7 @@ const ParamIterations = ({ asSlider }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</IAIInformationalPopover>
|
</IAIInformationalPopover>
|
||||||
) : (
|
) : (
|
||||||
<IAIInformationalPopover details="paramIterations">
|
<IAIInformationalPopover feature="paramIterations">
|
||||||
<IAINumberInput
|
<IAINumberInput
|
||||||
label={t('parameters.iterations')}
|
label={t('parameters.iterations')}
|
||||||
step={step}
|
step={step}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Box, FormControl, useDisclosure } from '@chakra-ui/react';
|
import { Box, FormControl, useDisclosure } from '@chakra-ui/react';
|
||||||
import type { RootState } from 'app/store/store';
|
import type { RootState } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAITextarea from 'common/components/IAITextarea';
|
import IAITextarea from 'common/components/IAITextarea';
|
||||||
import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton';
|
import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton';
|
||||||
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
|
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
|
||||||
@ -76,15 +76,15 @@ const ParamNegativeConditioning = () => {
|
|||||||
const isEmbeddingEnabled = useFeatureStatus('embedding').isFeatureEnabled;
|
const isEmbeddingEnabled = useFeatureStatus('embedding').isFeatureEnabled;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover
|
<FormControl>
|
||||||
placement="right"
|
<ParamEmbeddingPopover
|
||||||
details="paramNegativeConditioning"
|
isOpen={isOpen}
|
||||||
>
|
onClose={onClose}
|
||||||
<FormControl>
|
onSelect={handleSelectEmbedding}
|
||||||
<ParamEmbeddingPopover
|
>
|
||||||
isOpen={isOpen}
|
<IAIInformationalPopover
|
||||||
onClose={onClose}
|
feature="paramNegativeConditioning"
|
||||||
onSelect={handleSelectEmbedding}
|
placement="right"
|
||||||
>
|
>
|
||||||
<IAITextarea
|
<IAITextarea
|
||||||
id="negativePrompt"
|
id="negativePrompt"
|
||||||
@ -98,20 +98,20 @@ const ParamNegativeConditioning = () => {
|
|||||||
minH={16}
|
minH={16}
|
||||||
{...(isEmbeddingEnabled && { onKeyDown: handleKeyDown })}
|
{...(isEmbeddingEnabled && { onKeyDown: handleKeyDown })}
|
||||||
/>
|
/>
|
||||||
</ParamEmbeddingPopover>
|
</IAIInformationalPopover>
|
||||||
{!isOpen && isEmbeddingEnabled && (
|
</ParamEmbeddingPopover>
|
||||||
<Box
|
{!isOpen && isEmbeddingEnabled && (
|
||||||
sx={{
|
<Box
|
||||||
position: 'absolute',
|
sx={{
|
||||||
top: 0,
|
position: 'absolute',
|
||||||
insetInlineEnd: 0,
|
top: 0,
|
||||||
}}
|
insetInlineEnd: 0,
|
||||||
>
|
}}
|
||||||
<AddEmbeddingButton onClick={onOpen} />
|
>
|
||||||
</Box>
|
<AddEmbeddingButton onClick={onOpen} />
|
||||||
)}
|
</Box>
|
||||||
</FormControl>
|
)}
|
||||||
</IAIInformationalPopover>
|
</FormControl>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import { Box, FormControl, useDisclosure } from '@chakra-ui/react';
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAITextarea from 'common/components/IAITextarea';
|
import IAITextarea from 'common/components/IAITextarea';
|
||||||
import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton';
|
import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton';
|
||||||
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
|
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
|
||||||
@ -12,7 +13,6 @@ import { flushSync } from 'react-dom';
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
|
||||||
import IAIInformationalPopover from '../../../../../common/components/IAIInformationalPopover';
|
|
||||||
|
|
||||||
const promptInputSelector = createSelector(
|
const promptInputSelector = createSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
@ -104,15 +104,15 @@ const ParamPositiveConditioning = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box position="relative">
|
<Box position="relative">
|
||||||
<IAIInformationalPopover
|
<FormControl>
|
||||||
placement="right"
|
<ParamEmbeddingPopover
|
||||||
details="paramPositiveConditioning"
|
isOpen={isOpen}
|
||||||
>
|
onClose={onClose}
|
||||||
<FormControl>
|
onSelect={handleSelectEmbedding}
|
||||||
<ParamEmbeddingPopover
|
>
|
||||||
isOpen={isOpen}
|
<IAIInformationalPopover
|
||||||
onClose={onClose}
|
feature="paramPositiveConditioning"
|
||||||
onSelect={handleSelectEmbedding}
|
placement="right"
|
||||||
>
|
>
|
||||||
<IAITextarea
|
<IAITextarea
|
||||||
id="prompt"
|
id="prompt"
|
||||||
@ -125,9 +125,9 @@ const ParamPositiveConditioning = () => {
|
|||||||
resize="vertical"
|
resize="vertical"
|
||||||
minH={32}
|
minH={32}
|
||||||
/>
|
/>
|
||||||
</ParamEmbeddingPopover>
|
</IAIInformationalPopover>
|
||||||
</FormControl>
|
</ParamEmbeddingPopover>
|
||||||
</IAIInformationalPopover>
|
</FormControl>
|
||||||
{!isOpen && isEmbeddingEnabled && (
|
{!isOpen && isEmbeddingEnabled && (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
||||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||||
import { setScheduler } from 'features/parameters/store/generationSlice';
|
import { setScheduler } from 'features/parameters/store/generationSlice';
|
||||||
@ -52,7 +52,7 @@ const ParamScheduler = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover details="paramScheduler">
|
<IAIInformationalPopover feature="paramScheduler">
|
||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
label={t('parameters.scheduler')}
|
label={t('parameters.scheduler')}
|
||||||
value={scheduler}
|
value={scheduler}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { Box, Flex, Spacer, Text } from '@chakra-ui/react';
|
import { Flex, FormControl, FormLabel, Spacer } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||||
import {
|
import {
|
||||||
setAspectRatio,
|
setAspectRatio,
|
||||||
@ -16,8 +18,6 @@ import { activeTabNameSelector } from '../../../../ui/store/uiSelectors';
|
|||||||
import ParamAspectRatio, { mappedAspectRatios } from './ParamAspectRatio';
|
import ParamAspectRatio, { mappedAspectRatios } from './ParamAspectRatio';
|
||||||
import ParamHeight from './ParamHeight';
|
import ParamHeight from './ParamHeight';
|
||||||
import ParamWidth from './ParamWidth';
|
import ParamWidth from './ParamWidth';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|
||||||
|
|
||||||
const sizeOptsSelector = createSelector(
|
const sizeOptsSelector = createSelector(
|
||||||
[generationSelector, activeTabNameSelector],
|
[generationSelector, activeTabNameSelector],
|
||||||
@ -83,47 +83,35 @@ export default function ParamSize() {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex alignItems="center" gap={2}>
|
<IAIInformationalPopover feature="paramRatio">
|
||||||
<Box width="full">
|
<FormControl as={Flex} flexDir="row" alignItems="center" gap={2}>
|
||||||
<IAIInformationalPopover details="paramRatio">
|
<FormLabel>{t('parameters.aspectRatio')}</FormLabel>
|
||||||
<Text
|
<Spacer />
|
||||||
sx={{
|
<ParamAspectRatio />
|
||||||
fontSize: 'sm',
|
<IAIIconButton
|
||||||
color: 'base.700',
|
tooltip={t('ui.swapSizes')}
|
||||||
_dark: {
|
aria-label={t('ui.swapSizes')}
|
||||||
color: 'base.300',
|
size="sm"
|
||||||
},
|
icon={<MdOutlineSwapVert />}
|
||||||
}}
|
fontSize={20}
|
||||||
>
|
isDisabled={
|
||||||
{t('parameters.aspectRatio')}
|
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
||||||
</Text>
|
}
|
||||||
</IAIInformationalPopover>
|
onClick={handleToggleSize}
|
||||||
</Box>
|
/>
|
||||||
<Spacer />
|
<IAIIconButton
|
||||||
<ParamAspectRatio />
|
tooltip={t('ui.lockRatio')}
|
||||||
<IAIIconButton
|
aria-label={t('ui.lockRatio')}
|
||||||
tooltip={t('ui.swapSizes')}
|
size="sm"
|
||||||
aria-label={t('ui.swapSizes')}
|
icon={<FaLock />}
|
||||||
size="sm"
|
isChecked={shouldLockAspectRatio}
|
||||||
icon={<MdOutlineSwapVert />}
|
isDisabled={
|
||||||
fontSize={20}
|
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
||||||
isDisabled={
|
}
|
||||||
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
onClick={handleLockRatio}
|
||||||
}
|
/>
|
||||||
onClick={handleToggleSize}
|
</FormControl>
|
||||||
/>
|
</IAIInformationalPopover>
|
||||||
<IAIIconButton
|
|
||||||
tooltip={t('ui.lockRatio')}
|
|
||||||
aria-label={t('ui.lockRatio')}
|
|
||||||
size="sm"
|
|
||||||
icon={<FaLock />}
|
|
||||||
isChecked={shouldLockAspectRatio}
|
|
||||||
isDisabled={
|
|
||||||
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
|
||||||
}
|
|
||||||
onClick={handleLockRatio}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={2} alignItems="center">
|
<Flex gap={2} alignItems="center">
|
||||||
<Flex gap={2} flexDirection="column" width="full">
|
<Flex gap={2} flexDirection="column" width="full">
|
||||||
<ParamWidth
|
<ParamWidth
|
||||||
|
@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAINumberInput from 'common/components/IAINumberInput';
|
import IAINumberInput from 'common/components/IAINumberInput';
|
||||||
|
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
@ -57,7 +57,7 @@ const ParamSteps = () => {
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
return shouldUseSliders ? (
|
return shouldUseSliders ? (
|
||||||
<IAIInformationalPopover details="paramSteps">
|
<IAIInformationalPopover feature="paramSteps">
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={t('parameters.steps')}
|
label={t('parameters.steps')}
|
||||||
min={min}
|
min={min}
|
||||||
@ -73,7 +73,7 @@ const ParamSteps = () => {
|
|||||||
/>
|
/>
|
||||||
</IAIInformationalPopover>
|
</IAIInformationalPopover>
|
||||||
) : (
|
) : (
|
||||||
<IAIInformationalPopover details="paramSteps">
|
<IAIInformationalPopover feature="paramSteps">
|
||||||
<IAINumberInput
|
<IAINumberInput
|
||||||
label={t('parameters.steps')}
|
label={t('parameters.steps')}
|
||||||
min={min}
|
min={min}
|
||||||
|
@ -7,7 +7,7 @@ import { setImg2imgStrength } from 'features/parameters/store/generationSlice';
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import SubParametersWrapper from '../SubParametersWrapper';
|
import SubParametersWrapper from '../SubParametersWrapper';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
@ -46,8 +46,8 @@ const ImageToImageStrength = () => {
|
|||||||
}, [dispatch, initial]);
|
}, [dispatch, initial]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubParametersWrapper>
|
<IAIInformationalPopover feature="paramDenoisingStrength">
|
||||||
<IAIInformationalPopover details="paramDenoisingStrength">
|
<SubParametersWrapper>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={`${t('parameters.denoisingStrength')}`}
|
label={`${t('parameters.denoisingStrength')}`}
|
||||||
step={step}
|
step={step}
|
||||||
@ -62,8 +62,8 @@ const ImageToImageStrength = () => {
|
|||||||
withReset
|
withReset
|
||||||
sliderNumberInputProps={{ max: inputMax }}
|
sliderNumberInputProps={{ max: inputMax }}
|
||||||
/>
|
/>
|
||||||
</IAIInformationalPopover>
|
</SubParametersWrapper>
|
||||||
</SubParametersWrapper>
|
</IAIInformationalPopover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import {
|
|||||||
useGetOnnxModelsQuery,
|
useGetOnnxModelsQuery,
|
||||||
} from 'services/api/endpoints/models';
|
} from 'services/api/endpoints/models';
|
||||||
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
|
||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -120,7 +120,7 @@ const ParamMainModelSelect = () => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Flex w="100%" alignItems="center" gap={3}>
|
<Flex w="100%" alignItems="center" gap={3}>
|
||||||
<IAIInformationalPopover details="paramModel" placement="bottom">
|
<IAIInformationalPopover feature="paramModel">
|
||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
tooltip={selectedModel?.description}
|
tooltip={selectedModel?.description}
|
||||||
label={t('modelManager.model')}
|
label={t('modelManager.model')}
|
||||||
@ -136,7 +136,7 @@ const ParamMainModelSelect = () => {
|
|||||||
/>
|
/>
|
||||||
</IAIInformationalPopover>
|
</IAIInformationalPopover>
|
||||||
{isSyncModelEnabled && (
|
{isSyncModelEnabled && (
|
||||||
<Box mt={7}>
|
<Box mt={6}>
|
||||||
<SyncModelsButton iconMode />
|
<SyncModelsButton iconMode />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user