mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
merge with main
This commit is contained in:
commit
5f6f38074d
@ -3,6 +3,7 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from invokeai.app.invocations.util.choose_model import choose_model
|
||||
from .baseinvocation import BaseInvocation, BaseInvocationOutput, InvocationContext, InvocationConfig
|
||||
from ...backend.prompting.conditioning import try_parse_legacy_blend
|
||||
|
||||
from ...backend.util.devices import choose_torch_device, torch_dtype
|
||||
from ...backend.stable_diffusion.diffusion import InvokeAIDiffuserComponent
|
||||
@ -13,7 +14,7 @@ from compel.prompt_parser import (
|
||||
Blend,
|
||||
CrossAttentionControlSubstitute,
|
||||
FlattenedPrompt,
|
||||
Fragment,
|
||||
Fragment, Conjunction,
|
||||
)
|
||||
|
||||
|
||||
@ -93,25 +94,22 @@ class CompelInvocation(BaseInvocation):
|
||||
text_encoder=text_encoder,
|
||||
textual_inversion_manager=pipeline.textual_inversion_manager,
|
||||
dtype_for_device_getter=torch_dtype,
|
||||
truncate_long_prompts=True, # TODO:
|
||||
truncate_long_prompts=False,
|
||||
)
|
||||
|
||||
# TODO: support legacy blend?
|
||||
|
||||
conjunction = Compel.parse_prompt_string(prompt_str)
|
||||
prompt: Union[FlattenedPrompt, Blend] = conjunction.prompts[0]
|
||||
legacy_blend = try_parse_legacy_blend(prompt_str, skip_normalize=False)
|
||||
if legacy_blend is not None:
|
||||
conjunction = legacy_blend
|
||||
else:
|
||||
conjunction = Compel.parse_prompt_string(prompt_str)
|
||||
|
||||
if context.services.configuration.log_tokenization:
|
||||
log_tokenization_for_prompt_object(prompt, tokenizer)
|
||||
log_tokenization_for_conjunction(conjunction, tokenizer)
|
||||
|
||||
c, options = compel.build_conditioning_tensor_for_prompt_object(prompt)
|
||||
|
||||
# TODO: long prompt support
|
||||
#if not self.truncate_long_prompts:
|
||||
# [c, uc] = compel.pad_conditioning_tensors_to_same_length([c, uc])
|
||||
c, options = compel.build_conditioning_tensor_for_conjunction(conjunction)
|
||||
|
||||
ec = InvokeAIDiffuserComponent.ExtraConditioningInfo(
|
||||
tokens_count_including_eos_bos=get_max_token_count(tokenizer, prompt),
|
||||
tokens_count_including_eos_bos=get_max_token_count(tokenizer, conjunction),
|
||||
cross_attention_control_args=options.get("cross_attention_control", None),
|
||||
)
|
||||
|
||||
@ -128,14 +126,22 @@ class CompelInvocation(BaseInvocation):
|
||||
|
||||
|
||||
def get_max_token_count(
|
||||
tokenizer, prompt: Union[FlattenedPrompt, Blend], truncate_if_too_long=False
|
||||
tokenizer, prompt: Union[FlattenedPrompt, Blend, Conjunction], truncate_if_too_long=False
|
||||
) -> int:
|
||||
if type(prompt) is Blend:
|
||||
blend: Blend = prompt
|
||||
return max(
|
||||
[
|
||||
get_max_token_count(tokenizer, c, truncate_if_too_long)
|
||||
for c in blend.prompts
|
||||
get_max_token_count(tokenizer, p, truncate_if_too_long)
|
||||
for p in blend.prompts
|
||||
]
|
||||
)
|
||||
elif type(prompt) is Conjunction:
|
||||
conjunction: Conjunction = prompt
|
||||
return sum(
|
||||
[
|
||||
get_max_token_count(tokenizer, p, truncate_if_too_long)
|
||||
for p in conjunction.prompts
|
||||
]
|
||||
)
|
||||
else:
|
||||
@ -170,6 +176,22 @@ def get_tokens_for_prompt_object(
|
||||
return tokens
|
||||
|
||||
|
||||
def log_tokenization_for_conjunction(
|
||||
c: Conjunction, tokenizer, display_label_prefix=None
|
||||
):
|
||||
display_label_prefix = display_label_prefix or ""
|
||||
for i, p in enumerate(c.prompts):
|
||||
if len(c.prompts)>1:
|
||||
this_display_label_prefix = f"{display_label_prefix}(conjunction part {i + 1}, weight={c.weights[i]})"
|
||||
else:
|
||||
this_display_label_prefix = display_label_prefix
|
||||
log_tokenization_for_prompt_object(
|
||||
p,
|
||||
tokenizer,
|
||||
display_label_prefix=this_display_label_prefix
|
||||
)
|
||||
|
||||
|
||||
def log_tokenization_for_prompt_object(
|
||||
p: Union[Blend, FlattenedPrompt], tokenizer, display_label_prefix=None
|
||||
):
|
||||
|
@ -94,13 +94,13 @@ CONTROLNET_DEFAULT_MODELS = [
|
||||
CONTROLNET_NAME_VALUES = Literal[tuple(CONTROLNET_DEFAULT_MODELS)]
|
||||
|
||||
class ControlField(BaseModel):
|
||||
image: ImageField = Field(default=None, description="processed image")
|
||||
control_model: Optional[str] = Field(default=None, description="control model used")
|
||||
control_weight: Optional[float] = Field(default=1, description="weight given to controlnet")
|
||||
image: ImageField = Field(default=None, description="The control image")
|
||||
control_model: Optional[str] = Field(default=None, description="The ControlNet model to use")
|
||||
control_weight: Optional[float] = Field(default=1, description="The weight given to the ControlNet")
|
||||
begin_step_percent: float = Field(default=0, ge=0, le=1,
|
||||
description="% of total steps at which controlnet is first applied")
|
||||
description="When the ControlNet is first applied (% of total steps)")
|
||||
end_step_percent: float = Field(default=1, ge=0, le=1,
|
||||
description="% of total steps at which controlnet is last applied")
|
||||
description="When the ControlNet is last applied (% of total steps)")
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
@ -112,7 +112,7 @@ class ControlOutput(BaseInvocationOutput):
|
||||
"""node output for ControlNet info"""
|
||||
# fmt: off
|
||||
type: Literal["control_output"] = "control_output"
|
||||
control: ControlField = Field(default=None, description="The control info dict")
|
||||
control: ControlField = Field(default=None, description="The output control image")
|
||||
# fmt: on
|
||||
|
||||
|
||||
@ -121,15 +121,15 @@ class ControlNetInvocation(BaseInvocation):
|
||||
# fmt: off
|
||||
type: Literal["controlnet"] = "controlnet"
|
||||
# Inputs
|
||||
image: ImageField = Field(default=None, description="image to process")
|
||||
image: ImageField = Field(default=None, description="The control image")
|
||||
control_model: CONTROLNET_NAME_VALUES = Field(default="lllyasviel/sd-controlnet-canny",
|
||||
description="control model used")
|
||||
control_weight: float = Field(default=1.0, ge=0, le=1, description="weight given to controlnet")
|
||||
description="The ControlNet model to use")
|
||||
control_weight: float = Field(default=1.0, ge=0, le=1, description="The weight given to the ControlNet")
|
||||
# TODO: add support in backend core for begin_step_percent, end_step_percent, guess_mode
|
||||
begin_step_percent: float = Field(default=0, ge=0, le=1,
|
||||
description="% of total steps at which controlnet is first applied")
|
||||
description="When the ControlNet is first applied (% of total steps)")
|
||||
end_step_percent: float = Field(default=1, ge=0, le=1,
|
||||
description="% of total steps at which controlnet is last applied")
|
||||
description="When the ControlNet is last applied (% of total steps)")
|
||||
# fmt: on
|
||||
|
||||
|
||||
@ -152,7 +152,7 @@ class ImageProcessorInvocation(BaseInvocation, PILInvocationConfig):
|
||||
# fmt: off
|
||||
type: Literal["image_processor"] = "image_processor"
|
||||
# Inputs
|
||||
image: ImageField = Field(default=None, description="image to process")
|
||||
image: ImageField = Field(default=None, description="The image to process")
|
||||
# fmt: on
|
||||
|
||||
|
||||
@ -204,8 +204,8 @@ class CannyImageProcessorInvocation(ImageProcessorInvocation, PILInvocationConfi
|
||||
# fmt: off
|
||||
type: Literal["canny_image_processor"] = "canny_image_processor"
|
||||
# Input
|
||||
low_threshold: float = Field(default=100, ge=0, description="low threshold of Canny pixel gradient")
|
||||
high_threshold: float = Field(default=200, ge=0, description="high threshold of Canny pixel gradient")
|
||||
low_threshold: int = Field(default=100, ge=0, le=255, description="The low threshold of the Canny pixel gradient (0-255)")
|
||||
high_threshold: int = Field(default=200, ge=0, le=255, description="The high threshold of the Canny pixel gradient (0-255)")
|
||||
# fmt: on
|
||||
|
||||
def run_processor(self, image):
|
||||
@ -214,16 +214,16 @@ class CannyImageProcessorInvocation(ImageProcessorInvocation, PILInvocationConfi
|
||||
return processed_image
|
||||
|
||||
|
||||
class HedImageprocessorInvocation(ImageProcessorInvocation, PILInvocationConfig):
|
||||
class HedImageProcessorInvocation(ImageProcessorInvocation, PILInvocationConfig):
|
||||
"""Applies HED edge detection to image"""
|
||||
# fmt: off
|
||||
type: Literal["hed_image_processor"] = "hed_image_processor"
|
||||
# Inputs
|
||||
detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image")
|
||||
detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image")
|
||||
# safe not supported in controlnet_aux v0.0.3
|
||||
# safe: bool = Field(default=False, description="whether to use safe mode")
|
||||
scribble: bool = Field(default=False, description="whether to use scribble mode")
|
||||
scribble: bool = Field(default=False, description="Whether to use scribble mode")
|
||||
# fmt: on
|
||||
|
||||
def run_processor(self, image):
|
||||
@ -243,9 +243,9 @@ class LineartImageProcessorInvocation(ImageProcessorInvocation, PILInvocationCon
|
||||
# fmt: off
|
||||
type: Literal["lineart_image_processor"] = "lineart_image_processor"
|
||||
# Inputs
|
||||
detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image")
|
||||
coarse: bool = Field(default=False, description="whether to use coarse mode")
|
||||
detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image")
|
||||
coarse: bool = Field(default=False, description="Whether to use coarse mode")
|
||||
# fmt: on
|
||||
|
||||
def run_processor(self, image):
|
||||
@ -262,8 +262,8 @@ class LineartAnimeImageProcessorInvocation(ImageProcessorInvocation, PILInvocati
|
||||
# fmt: off
|
||||
type: Literal["lineart_anime_image_processor"] = "lineart_anime_image_processor"
|
||||
# Inputs
|
||||
detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image")
|
||||
detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image")
|
||||
# fmt: on
|
||||
|
||||
def run_processor(self, image):
|
||||
@ -280,9 +280,9 @@ class OpenposeImageProcessorInvocation(ImageProcessorInvocation, PILInvocationCo
|
||||
# fmt: off
|
||||
type: Literal["openpose_image_processor"] = "openpose_image_processor"
|
||||
# Inputs
|
||||
hand_and_face: bool = Field(default=False, description="whether to use hands and face mode")
|
||||
detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image")
|
||||
hand_and_face: bool = Field(default=False, description="Whether to use hands and face mode")
|
||||
detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image")
|
||||
# fmt: on
|
||||
|
||||
def run_processor(self, image):
|
||||
@ -300,8 +300,8 @@ class MidasDepthImageProcessorInvocation(ImageProcessorInvocation, PILInvocation
|
||||
# fmt: off
|
||||
type: Literal["midas_depth_image_processor"] = "midas_depth_image_processor"
|
||||
# Inputs
|
||||
a_mult: float = Field(default=2.0, ge=0, description="Midas parameter a = amult * PI")
|
||||
bg_th: float = Field(default=0.1, ge=0, description="Midas parameter bg_th")
|
||||
a_mult: float = Field(default=2.0, ge=0, description="Midas parameter `a_mult` (a = a_mult * PI)")
|
||||
bg_th: float = Field(default=0.1, ge=0, description="Midas parameter `bg_th`")
|
||||
# depth_and_normal not supported in controlnet_aux v0.0.3
|
||||
# depth_and_normal: bool = Field(default=False, description="whether to use depth and normal mode")
|
||||
# fmt: on
|
||||
@ -322,8 +322,8 @@ class NormalbaeImageProcessorInvocation(ImageProcessorInvocation, PILInvocationC
|
||||
# fmt: off
|
||||
type: Literal["normalbae_image_processor"] = "normalbae_image_processor"
|
||||
# Inputs
|
||||
detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image")
|
||||
detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image")
|
||||
# fmt: on
|
||||
|
||||
def run_processor(self, image):
|
||||
@ -339,10 +339,10 @@ class MlsdImageProcessorInvocation(ImageProcessorInvocation, PILInvocationConfig
|
||||
# fmt: off
|
||||
type: Literal["mlsd_image_processor"] = "mlsd_image_processor"
|
||||
# Inputs
|
||||
detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image")
|
||||
thr_v: float = Field(default=0.1, ge=0, description="MLSD parameter thr_v")
|
||||
thr_d: float = Field(default=0.1, ge=0, description="MLSD parameter thr_d")
|
||||
detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image")
|
||||
thr_v: float = Field(default=0.1, ge=0, description="MLSD parameter `thr_v`")
|
||||
thr_d: float = Field(default=0.1, ge=0, description="MLSD parameter `thr_d`")
|
||||
# fmt: on
|
||||
|
||||
def run_processor(self, image):
|
||||
@ -360,10 +360,10 @@ class PidiImageProcessorInvocation(ImageProcessorInvocation, PILInvocationConfig
|
||||
# fmt: off
|
||||
type: Literal["pidi_image_processor"] = "pidi_image_processor"
|
||||
# Inputs
|
||||
detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image")
|
||||
safe: bool = Field(default=False, description="whether to use safe mode")
|
||||
scribble: bool = Field(default=False, description="whether to use scribble mode")
|
||||
detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image")
|
||||
safe: bool = Field(default=False, description="Whether to use safe mode")
|
||||
scribble: bool = Field(default=False, description="Whether to use scribble mode")
|
||||
# fmt: on
|
||||
|
||||
def run_processor(self, image):
|
||||
@ -381,11 +381,11 @@ class ContentShuffleImageProcessorInvocation(ImageProcessorInvocation, PILInvoca
|
||||
# fmt: off
|
||||
type: Literal["content_shuffle_image_processor"] = "content_shuffle_image_processor"
|
||||
# Inputs
|
||||
detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image")
|
||||
h: Union[int | None] = Field(default=512, ge=0, description="content shuffle h parameter")
|
||||
w: Union[int | None] = Field(default=512, ge=0, description="content shuffle w parameter")
|
||||
f: Union[int | None] = Field(default=256, ge=0, description="cont")
|
||||
detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection")
|
||||
image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image")
|
||||
h: Union[int, None] = Field(default=512, ge=0, description="Content shuffle `h` parameter")
|
||||
w: Union[int, None] = Field(default=512, ge=0, description="Content shuffle `w` parameter")
|
||||
f: Union[int, None] = Field(default=256, ge=0, description="Content shuffle `f` parameter")
|
||||
# fmt: on
|
||||
|
||||
def run_processor(self, image):
|
||||
@ -418,8 +418,8 @@ class MediapipeFaceProcessorInvocation(ImageProcessorInvocation, PILInvocationCo
|
||||
# fmt: off
|
||||
type: Literal["mediapipe_face_processor"] = "mediapipe_face_processor"
|
||||
# Inputs
|
||||
max_faces: int = Field(default=1, ge=1, description="maximum number of faces to detect")
|
||||
min_confidence: float = Field(default=0.5, ge=0, le=1, description="minimum confidence for face detection")
|
||||
max_faces: int = Field(default=1, ge=1, description="Maximum number of faces to detect")
|
||||
min_confidence: float = Field(default=0.5, ge=0, le=1, description="Minimum confidence for face detection")
|
||||
# fmt: on
|
||||
|
||||
def run_processor(self, image):
|
||||
|
@ -4,6 +4,7 @@ import random
|
||||
import einops
|
||||
from typing import Literal, Optional, Union, List
|
||||
|
||||
from compel import Compel
|
||||
from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_controlnet import MultiControlNetModel
|
||||
|
||||
from pydantic import BaseModel, Field, validator
|
||||
@ -233,6 +234,15 @@ class TextToLatentsInvocation(BaseInvocation):
|
||||
c, extra_conditioning_info = context.services.latents.get(self.positive_conditioning.conditioning_name)
|
||||
uc, _ = context.services.latents.get(self.negative_conditioning.conditioning_name)
|
||||
|
||||
compel = Compel(
|
||||
tokenizer=model.tokenizer,
|
||||
text_encoder=model.text_encoder,
|
||||
textual_inversion_manager=model.textual_inversion_manager,
|
||||
dtype_for_device_getter=torch_dtype,
|
||||
truncate_long_prompts=False,
|
||||
)
|
||||
[c, uc] = compel.pad_conditioning_tensors_to_same_length([c, uc])
|
||||
|
||||
conditioning_data = ConditioningData(
|
||||
uc,
|
||||
c,
|
||||
|
@ -38,7 +38,7 @@ def get_uc_and_c_and_ec(prompt_string,
|
||||
dtype_for_device_getter=torch_dtype,
|
||||
truncate_long_prompts=False,
|
||||
)
|
||||
|
||||
|
||||
config = get_invokeai_config()
|
||||
|
||||
# get rid of any newline characters
|
||||
@ -282,6 +282,8 @@ def split_weighted_subprompts(text, skip_normalize=False) -> list:
|
||||
(match.group("prompt").replace("\\:", ":"), float(match.group("weight") or 1))
|
||||
for match in re.finditer(prompt_parser, text)
|
||||
]
|
||||
if len(parsed_prompts) == 0:
|
||||
return []
|
||||
if skip_normalize:
|
||||
return parsed_prompts
|
||||
weight_sum = sum(map(lambda x: x[1], parsed_prompts))
|
||||
|
@ -26,10 +26,10 @@ We need to start the nodes web server, which serves the OpenAPI schema to the ge
|
||||
|
||||
```bash
|
||||
# from the repo root
|
||||
python scripts/invoke-new.py --web
|
||||
python scripts/invokeai-web.py
|
||||
```
|
||||
|
||||
2. Generate the API client.
|
||||
2. Generate the API client.
|
||||
|
||||
```bash
|
||||
# from invokeai/frontend/web/
|
||||
|
@ -12,7 +12,14 @@ Code in `invokeai/frontend/web/` if you want to have a look.
|
||||
|
||||
## Stack
|
||||
|
||||
State management is Redux via [Redux Toolkit](https://github.com/reduxjs/redux-toolkit). Communication with server is a mix of HTTP and [socket.io](https://github.com/socketio/socket.io-client) (with a custom redux middleware to help).
|
||||
State management is Redux via [Redux Toolkit](https://github.com/reduxjs/redux-toolkit). We lean heavily on RTK:
|
||||
- `createAsyncThunk` for HTTP requests
|
||||
- `createEntityAdapter` for fetching images and models
|
||||
- `createListenerMiddleware` for workflows
|
||||
|
||||
The API client and associated types are generated from the OpenAPI schema. See API_CLIENT.md.
|
||||
|
||||
Communication with server is a mix of HTTP and [socket.io](https://github.com/socketio/socket.io-client) (with a simple socket.io redux middleware to help).
|
||||
|
||||
[Chakra-UI](https://github.com/chakra-ui/chakra-ui) for components and styling.
|
||||
|
||||
@ -37,9 +44,15 @@ From `invokeai/frontend/web/` run `yarn install` to get everything set up.
|
||||
Start everything in dev mode:
|
||||
|
||||
1. Start the dev server: `yarn dev`
|
||||
2. Start the InvokeAI Nodes backend: `python scripts/invokeai-new.py --web # run from the repo root`
|
||||
2. Start the InvokeAI Nodes backend: `python scripts/invokeai-web.py # run from the repo root`
|
||||
3. Point your browser to the dev server address e.g. <http://localhost:5173/>
|
||||
|
||||
#### VSCode Remote Dev
|
||||
|
||||
We've noticed an intermittent issue with the VSCode Remote Dev port forwarding. If you use this feature of VSCode, you may intermittently click the Invoke button and then get nothing until the request times out. Suggest disabling the IDE's port forwarding feature and doing it manually via SSH:
|
||||
|
||||
`ssh -L 9090:localhost:9090 -L 5173:localhost:5173 user@host`
|
||||
|
||||
### Production builds
|
||||
|
||||
For a number of technical and logistical reasons, we need to commit UI build artefacts to the repo.
|
||||
|
@ -60,6 +60,8 @@
|
||||
"@chakra-ui/styled-system": "^2.9.0",
|
||||
"@chakra-ui/theme-tools": "^2.0.16",
|
||||
"@dagrejs/graphlib": "^2.1.12",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/modifiers": "^6.0.1",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@floating-ui/react-dom": "^2.0.0",
|
||||
@ -87,7 +89,7 @@
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hotkeys-hook": "4.4.0",
|
||||
"react-i18next": "^12.2.2",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-icons": "^4.9.0",
|
||||
"react-konva": "^18.2.7",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-resizable-panels": "^0.0.42",
|
||||
|
@ -0,0 +1,68 @@
|
||||
import {
|
||||
DndContext,
|
||||
DragEndEvent,
|
||||
DragOverlay,
|
||||
DragStartEvent,
|
||||
KeyboardSensor,
|
||||
MouseSensor,
|
||||
TouchSensor,
|
||||
pointerWithin,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import { PropsWithChildren, memo, useCallback, useState } from 'react';
|
||||
import OverlayDragImage from './OverlayDragImage';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { isImageDTO } from 'services/types/guards';
|
||||
import { snapCenterToCursor } from '@dnd-kit/modifiers';
|
||||
|
||||
type ImageDndContextProps = PropsWithChildren;
|
||||
|
||||
const ImageDndContext = (props: ImageDndContextProps) => {
|
||||
const [draggedImage, setDraggedImage] = useState<ImageDTO | null>(null);
|
||||
|
||||
const handleDragStart = useCallback((event: DragStartEvent) => {
|
||||
const dragData = event.active.data.current;
|
||||
if (dragData && 'image' in dragData && isImageDTO(dragData.image)) {
|
||||
setDraggedImage(dragData.image);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleDragEnd = useCallback(
|
||||
(event: DragEndEvent) => {
|
||||
const handleDrop = event.over?.data.current?.handleDrop;
|
||||
if (handleDrop && typeof handleDrop === 'function' && draggedImage) {
|
||||
handleDrop(draggedImage);
|
||||
}
|
||||
setDraggedImage(null);
|
||||
},
|
||||
[draggedImage]
|
||||
);
|
||||
|
||||
const mouseSensor = useSensor(MouseSensor, {
|
||||
activationConstraint: { distance: 15 },
|
||||
});
|
||||
|
||||
const touchSensor = useSensor(TouchSensor, {
|
||||
activationConstraint: { distance: 15 },
|
||||
});
|
||||
const keyboardSensor = useSensor(KeyboardSensor);
|
||||
|
||||
const sensors = useSensors(mouseSensor, touchSensor, keyboardSensor);
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
sensors={sensors}
|
||||
collisionDetection={pointerWithin}
|
||||
>
|
||||
{props.children}
|
||||
<DragOverlay dropAnimation={null} modifiers={[snapCenterToCursor]}>
|
||||
{draggedImage && <OverlayDragImage image={draggedImage} />}
|
||||
</DragOverlay>
|
||||
</DndContext>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ImageDndContext);
|
@ -0,0 +1,36 @@
|
||||
import { Box, Image } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import { ImageDTO } from 'services/api';
|
||||
|
||||
type OverlayDragImageProps = {
|
||||
image: ImageDTO;
|
||||
};
|
||||
|
||||
const OverlayDragImage = (props: OverlayDragImageProps) => {
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
userSelect: 'none',
|
||||
cursor: 'grabbing',
|
||||
opacity: 0.5,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
sx={{
|
||||
maxW: 36,
|
||||
maxH: 36,
|
||||
borderRadius: 'base',
|
||||
shadow: 'dark-lg',
|
||||
}}
|
||||
src={props.image.thumbnail_url}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(OverlayDragImage);
|
@ -16,6 +16,7 @@ import { PartialAppConfig } from 'app/types/invokeai';
|
||||
import '../../i18n';
|
||||
import { socketMiddleware } from 'services/events/middleware';
|
||||
import { Middleware } from '@reduxjs/toolkit';
|
||||
import ImageDndContext from './ImageDnd/ImageDndContext';
|
||||
|
||||
const App = lazy(() => import('./App'));
|
||||
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
||||
@ -69,11 +70,13 @@ const InvokeAIUI = ({
|
||||
<Provider store={store}>
|
||||
<React.Suspense fallback={<Loading />}>
|
||||
<ThemeLocaleProvider>
|
||||
<App
|
||||
config={config}
|
||||
headerComponent={headerComponent}
|
||||
setIsReady={setIsReady}
|
||||
/>
|
||||
<ImageDndContext>
|
||||
<App
|
||||
config={config}
|
||||
headerComponent={headerComponent}
|
||||
setIsReady={setIsReady}
|
||||
/>
|
||||
</ImageDndContext>
|
||||
</ThemeLocaleProvider>
|
||||
</React.Suspense>
|
||||
</Provider>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { canvasPersistDenylist } from 'features/canvas/store/canvasPersistDenylist';
|
||||
import { controlNetDenylist } from 'features/controlNet/store/controlNetDenylist';
|
||||
import { galleryPersistDenylist } from 'features/gallery/store/galleryPersistDenylist';
|
||||
import { lightboxPersistDenylist } from 'features/lightbox/store/lightboxPersistDenylist';
|
||||
import { nodesPersistDenylist } from 'features/nodes/store/nodesPersistDenylist';
|
||||
@ -23,6 +24,7 @@ const serializationDenylist: {
|
||||
system: systemPersistDenylist,
|
||||
// config: configPersistDenyList,
|
||||
ui: uiPersistDenylist,
|
||||
controlNet: controlNetDenylist,
|
||||
// hotkeys: hotkeysPersistDenylist,
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { initialCanvasState } from 'features/canvas/store/canvasSlice';
|
||||
import { initialControlNetState } from 'features/controlNet/store/controlNetSlice';
|
||||
import { initialGalleryState } from 'features/gallery/store/gallerySlice';
|
||||
import { initialImagesState } from 'features/gallery/store/imagesSlice';
|
||||
import { initialLightboxState } from 'features/lightbox/store/lightboxSlice';
|
||||
@ -28,6 +29,7 @@ const initialStates: {
|
||||
ui: initialUIState,
|
||||
hotkeys: initialHotkeysState,
|
||||
images: initialImagesState,
|
||||
controlNet: initialControlNetState,
|
||||
};
|
||||
|
||||
export const unserialize: UnserializeFunction = (data, key) => {
|
||||
|
@ -70,6 +70,8 @@ import {
|
||||
import { addStagingAreaImageSavedListener } from './listeners/stagingAreaImageSaved';
|
||||
import { addCommitStagingAreaImageListener } from './listeners/addCommitStagingAreaImageListener';
|
||||
import { addImageCategoriesChangedListener } from './listeners/imageCategoriesChanged';
|
||||
import { addControlNetImageProcessedListener } from './listeners/controlNetImageProcessed';
|
||||
import { addControlNetAutoProcessListener } from './listeners/controlNetAutoProcess';
|
||||
|
||||
export const listenerMiddleware = createListenerMiddleware();
|
||||
|
||||
@ -173,3 +175,7 @@ addReceivedPageOfImagesRejectedListener();
|
||||
|
||||
// Gallery
|
||||
addImageCategoriesChangedListener();
|
||||
|
||||
// ControlNet
|
||||
addControlNetImageProcessedListener();
|
||||
addControlNetAutoProcessListener();
|
||||
|
@ -0,0 +1,59 @@
|
||||
import { AnyAction } from '@reduxjs/toolkit';
|
||||
import { startAppListening } from '..';
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { controlNetImageProcessed } from 'features/controlNet/store/actions';
|
||||
import {
|
||||
controlNetImageChanged,
|
||||
controlNetProcessorParamsChanged,
|
||||
controlNetProcessorTypeChanged,
|
||||
} from 'features/controlNet/store/controlNetSlice';
|
||||
import { RootState } from 'app/store/store';
|
||||
|
||||
const moduleLog = log.child({ namespace: 'controlNet' });
|
||||
|
||||
const predicate = (action: AnyAction, state: RootState) => {
|
||||
const isActionMatched =
|
||||
controlNetProcessorParamsChanged.match(action) ||
|
||||
controlNetImageChanged.match(action) ||
|
||||
controlNetProcessorTypeChanged.match(action);
|
||||
|
||||
if (!isActionMatched) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { controlImage, processorType } =
|
||||
state.controlNet.controlNets[action.payload.controlNetId];
|
||||
|
||||
const isProcessorSelected = processorType !== 'none';
|
||||
|
||||
const isBusy = state.system.isProcessing;
|
||||
|
||||
const hasControlImage = Boolean(controlImage);
|
||||
|
||||
return isProcessorSelected && !isBusy && hasControlImage;
|
||||
};
|
||||
|
||||
/**
|
||||
* Listener that automatically processes a ControlNet image when its processor parameters are changed.
|
||||
*
|
||||
* The network request is debounced by 1 second.
|
||||
*/
|
||||
export const addControlNetAutoProcessListener = () => {
|
||||
startAppListening({
|
||||
predicate,
|
||||
effect: async (
|
||||
action,
|
||||
{ dispatch, getState, cancelActiveListeners, delay }
|
||||
) => {
|
||||
const { controlNetId } = action.payload;
|
||||
|
||||
// Cancel any in-progress instances of this listener
|
||||
cancelActiveListeners();
|
||||
|
||||
// Delay before starting actual work
|
||||
await delay(300);
|
||||
|
||||
dispatch(controlNetImageProcessed({ controlNetId }));
|
||||
},
|
||||
});
|
||||
};
|
@ -0,0 +1,93 @@
|
||||
import { startAppListening } from '..';
|
||||
import { imageMetadataReceived } from 'services/thunks/image';
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { controlNetImageProcessed } from 'features/controlNet/store/actions';
|
||||
import { Graph } from 'services/api';
|
||||
import { sessionCreated } from 'services/thunks/session';
|
||||
import { sessionReadyToInvoke } from 'features/system/store/actions';
|
||||
import { socketInvocationComplete } from 'services/events/actions';
|
||||
import { isImageOutput } from 'services/types/guards';
|
||||
import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice';
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
const moduleLog = log.child({ namespace: 'controlNet' });
|
||||
|
||||
export const addControlNetImageProcessedListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: controlNetImageProcessed,
|
||||
effect: async (
|
||||
action,
|
||||
{ dispatch, getState, take, unsubscribe, subscribe }
|
||||
) => {
|
||||
const { controlNetId } = action.payload;
|
||||
const controlNet = getState().controlNet.controlNets[controlNetId];
|
||||
|
||||
if (!controlNet.controlImage) {
|
||||
moduleLog.error('Unable to process ControlNet image');
|
||||
return;
|
||||
}
|
||||
|
||||
// ControlNet one-off procressing graph is just the processor node, no edges.
|
||||
// Also we need to grab the image.
|
||||
const graph: Graph = {
|
||||
nodes: {
|
||||
[controlNet.processorNode.id]: {
|
||||
...controlNet.processorNode,
|
||||
is_intermediate: true,
|
||||
image: pick(controlNet.controlImage, [
|
||||
'image_name',
|
||||
'image_origin',
|
||||
]),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Create a session to run the graph & wait til it's ready to invoke
|
||||
const sessionCreatedAction = dispatch(sessionCreated({ graph }));
|
||||
const [sessionCreatedFulfilledAction] = await take(
|
||||
(action): action is ReturnType<typeof sessionCreated.fulfilled> =>
|
||||
sessionCreated.fulfilled.match(action) &&
|
||||
action.meta.requestId === sessionCreatedAction.requestId
|
||||
);
|
||||
|
||||
const sessionId = sessionCreatedFulfilledAction.payload.id;
|
||||
|
||||
// Invoke the session & wait til it's complete
|
||||
dispatch(sessionReadyToInvoke());
|
||||
const [invocationCompleteAction] = await take(
|
||||
(action): action is ReturnType<typeof socketInvocationComplete> =>
|
||||
socketInvocationComplete.match(action) &&
|
||||
action.payload.data.graph_execution_state_id === sessionId
|
||||
);
|
||||
|
||||
// We still have to check the output type
|
||||
if (isImageOutput(invocationCompleteAction.payload.data.result)) {
|
||||
const { image_name } =
|
||||
invocationCompleteAction.payload.data.result.image;
|
||||
|
||||
// Wait for the ImageDTO to be received
|
||||
const [imageMetadataReceivedAction] = await take(
|
||||
(
|
||||
action
|
||||
): action is ReturnType<typeof imageMetadataReceived.fulfilled> =>
|
||||
imageMetadataReceived.fulfilled.match(action) &&
|
||||
action.payload.image_name === image_name
|
||||
);
|
||||
const processedControlImage = imageMetadataReceivedAction.payload;
|
||||
|
||||
moduleLog.debug(
|
||||
{ data: { arg: action.payload, processedControlImage } },
|
||||
'ControlNet image processed'
|
||||
);
|
||||
|
||||
// Update the processed image in the store
|
||||
dispatch(
|
||||
controlNetProcessedImageChanged({
|
||||
controlNetId,
|
||||
processedControlImage,
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
@ -2,12 +2,10 @@ import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
||||
import { t } from 'i18next';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { startAppListening } from '..';
|
||||
import {
|
||||
initialImageSelected,
|
||||
isImageDTO,
|
||||
} from 'features/parameters/store/actions';
|
||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||
import { makeToast } from 'app/components/Toaster';
|
||||
import { selectImagesById } from 'features/gallery/store/imagesSlice';
|
||||
import { isImageDTO } from 'services/types/guards';
|
||||
|
||||
export const addInitialImageSelectedListener = () => {
|
||||
startAppListening({
|
||||
|
@ -13,6 +13,7 @@ import galleryReducer from 'features/gallery/store/gallerySlice';
|
||||
import imagesReducer from 'features/gallery/store/imagesSlice';
|
||||
import lightboxReducer from 'features/lightbox/store/lightboxSlice';
|
||||
import generationReducer from 'features/parameters/store/generationSlice';
|
||||
import controlNetReducer from 'features/controlNet/store/controlNetSlice';
|
||||
import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
|
||||
import systemReducer from 'features/system/store/systemSlice';
|
||||
// import sessionReducer from 'features/system/store/sessionSlice';
|
||||
@ -45,6 +46,7 @@ const allReducers = {
|
||||
ui: uiReducer,
|
||||
hotkeys: hotkeysReducer,
|
||||
images: imagesReducer,
|
||||
controlNet: controlNetReducer,
|
||||
// session: sessionReducer,
|
||||
};
|
||||
|
||||
@ -62,6 +64,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [
|
||||
'postprocessing',
|
||||
'system',
|
||||
'ui',
|
||||
'controlNet',
|
||||
// 'hotkeys',
|
||||
// 'config',
|
||||
];
|
||||
|
@ -95,6 +95,7 @@ export type AppFeature =
|
||||
* A disable-able Stable Diffusion feature
|
||||
*/
|
||||
export type SDFeature =
|
||||
| 'controlNet'
|
||||
| 'noise'
|
||||
| 'variation'
|
||||
| 'symmetry'
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { Checkbox, CheckboxProps } from '@chakra-ui/react';
|
||||
import { memo, ReactNode } from 'react';
|
||||
|
||||
type IAICheckboxProps = CheckboxProps & {
|
||||
label: string | ReactNode;
|
||||
};
|
||||
|
||||
const IAICheckbox = (props: IAICheckboxProps) => {
|
||||
const { label, ...rest } = props;
|
||||
return (
|
||||
<Checkbox colorScheme="accent" {...rest}>
|
||||
{label}
|
||||
</Checkbox>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(IAICheckbox);
|
@ -49,7 +49,7 @@ const IAICollapse = (props: IAIToggleCollapseProps) => {
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Collapse in={isOpen} animateOpacity>
|
||||
<Collapse in={isOpen} animateOpacity style={{ overflow: 'unset' }}>
|
||||
<Box sx={{ p: 4, borderBottomRadius: 'base', bg: 'base.800' }}>
|
||||
{children}
|
||||
</Box>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CheckIcon } from '@chakra-ui/icons';
|
||||
import { CheckIcon, ChevronUpIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
@ -10,7 +10,6 @@ import {
|
||||
GridItem,
|
||||
List,
|
||||
ListItem,
|
||||
Select,
|
||||
Text,
|
||||
Tooltip,
|
||||
TooltipProps,
|
||||
@ -19,7 +18,8 @@ import { autoUpdate, offset, shift, useFloating } from '@floating-ui/react-dom';
|
||||
import { useSelect } from 'downshift';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
|
||||
import { memo } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { getInputOutlineStyles } from 'theme/util/getInputOutlineStyles';
|
||||
|
||||
export type ItemTooltips = { [key: string]: string };
|
||||
|
||||
@ -34,6 +34,7 @@ type IAICustomSelectProps = {
|
||||
buttonProps?: FlexProps;
|
||||
tooltip?: string;
|
||||
tooltipProps?: Omit<TooltipProps, 'children'>;
|
||||
ellipsisPosition?: 'start' | 'end';
|
||||
};
|
||||
|
||||
const IAICustomSelect = (props: IAICustomSelectProps) => {
|
||||
@ -48,6 +49,7 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
|
||||
tooltip,
|
||||
buttonProps,
|
||||
tooltipProps,
|
||||
ellipsisPosition = 'end',
|
||||
} = props;
|
||||
|
||||
const {
|
||||
@ -69,6 +71,14 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
|
||||
middleware: [offset(4), shift({ crossAxis: true, padding: 8 })],
|
||||
});
|
||||
|
||||
const labelTextDirection = useMemo(() => {
|
||||
if (ellipsisPosition === 'start') {
|
||||
return document.dir === 'rtl' ? 'ltr' : 'rtl';
|
||||
}
|
||||
|
||||
return document.dir;
|
||||
}, [ellipsisPosition]);
|
||||
|
||||
return (
|
||||
<FormControl sx={{ w: 'full' }} {...formControlProps}>
|
||||
{label && (
|
||||
@ -82,20 +92,44 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
|
||||
</FormLabel>
|
||||
)}
|
||||
<Tooltip label={tooltip} {...tooltipProps}>
|
||||
<Select
|
||||
<Flex
|
||||
{...getToggleButtonProps({ ref: refs.setReference })}
|
||||
{...buttonProps}
|
||||
as={Flex}
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer',
|
||||
overflow: 'hidden',
|
||||
width: 'full',
|
||||
py: 1,
|
||||
px: 2,
|
||||
gap: 2,
|
||||
justifyContent: 'space-between',
|
||||
...getInputOutlineStyles(),
|
||||
}}
|
||||
>
|
||||
<Text sx={{ fontSize: 'sm', fontWeight: 500, color: 'base.100' }}>
|
||||
<Text
|
||||
sx={{
|
||||
fontSize: 'sm',
|
||||
fontWeight: 500,
|
||||
color: 'base.100',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
direction: labelTextDirection,
|
||||
}}
|
||||
>
|
||||
{selectedItem}
|
||||
</Text>
|
||||
</Select>
|
||||
<ChevronUpIcon
|
||||
sx={{
|
||||
color: 'base.300',
|
||||
transform: isOpen ? 'rotate(0deg)' : 'rotate(180deg)',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: 'normal',
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
<Box {...getMenuProps()}>
|
||||
{isOpen && (
|
||||
@ -104,11 +138,10 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
|
||||
ref={refs.setFloating}
|
||||
sx={{
|
||||
...floatingStyles,
|
||||
width: 'max-content',
|
||||
top: 0,
|
||||
left: 0,
|
||||
insetInlineStart: 0,
|
||||
flexDirection: 'column',
|
||||
zIndex: 1,
|
||||
zIndex: 2,
|
||||
bg: 'base.800',
|
||||
borderRadius: 'base',
|
||||
border: '1px',
|
||||
@ -118,61 +151,72 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
|
||||
px: 0,
|
||||
h: 'fit-content',
|
||||
maxH: 64,
|
||||
minW: 48,
|
||||
}}
|
||||
>
|
||||
<OverlayScrollbarsComponent>
|
||||
{items.map((item, index) => (
|
||||
<Tooltip
|
||||
isDisabled={!itemTooltips}
|
||||
key={`${item}${index}`}
|
||||
label={itemTooltips?.[item]}
|
||||
hasArrow
|
||||
placement="right"
|
||||
>
|
||||
<ListItem
|
||||
sx={{
|
||||
bg: highlightedIndex === index ? 'base.700' : undefined,
|
||||
py: 1,
|
||||
paddingInlineStart: 3,
|
||||
paddingInlineEnd: 6,
|
||||
cursor: 'pointer',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: '0.15s',
|
||||
}}
|
||||
{items.map((item, index) => {
|
||||
const isSelected = selectedItem === item;
|
||||
const isHighlighted = highlightedIndex === index;
|
||||
const fontWeight = isSelected ? 700 : 500;
|
||||
const bg = isHighlighted
|
||||
? 'base.700'
|
||||
: isSelected
|
||||
? 'base.750'
|
||||
: undefined;
|
||||
return (
|
||||
<Tooltip
|
||||
isDisabled={!itemTooltips}
|
||||
key={`${item}${index}`}
|
||||
{...getItemProps({ item, index })}
|
||||
label={itemTooltips?.[item]}
|
||||
hasArrow
|
||||
placement="right"
|
||||
>
|
||||
{withCheckIcon ? (
|
||||
<Grid gridTemplateColumns="1.25rem auto">
|
||||
<GridItem>
|
||||
{selectedItem === item && <CheckIcon boxSize={2} />}
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Text
|
||||
sx={{
|
||||
fontSize: 'sm',
|
||||
color: 'base.100',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Text>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
) : (
|
||||
<Text
|
||||
sx={{
|
||||
fontSize: 'sm',
|
||||
color: 'base.100',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Text>
|
||||
)}
|
||||
</ListItem>
|
||||
</Tooltip>
|
||||
))}
|
||||
<ListItem
|
||||
sx={{
|
||||
bg,
|
||||
py: 1,
|
||||
paddingInlineStart: 3,
|
||||
paddingInlineEnd: 6,
|
||||
cursor: 'pointer',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: '0.15s',
|
||||
}}
|
||||
key={`${item}${index}`}
|
||||
{...getItemProps({ item, index })}
|
||||
>
|
||||
{withCheckIcon ? (
|
||||
<Grid gridTemplateColumns="1.25rem auto">
|
||||
<GridItem>
|
||||
{isSelected && <CheckIcon boxSize={2} />}
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Text
|
||||
sx={{
|
||||
fontSize: 'sm',
|
||||
color: 'base.100',
|
||||
fontWeight,
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Text>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
) : (
|
||||
<Text
|
||||
sx={{
|
||||
fontSize: 'sm',
|
||||
color: 'base.50',
|
||||
fontWeight,
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Text>
|
||||
)}
|
||||
</ListItem>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</OverlayScrollbarsComponent>
|
||||
</List>
|
||||
)}
|
||||
|
258
invokeai/frontend/web/src/common/components/IAIDndImage.tsx
Normal file
258
invokeai/frontend/web/src/common/components/IAIDndImage.tsx
Normal file
@ -0,0 +1,258 @@
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Icon,
|
||||
IconButtonProps,
|
||||
Image,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import { useDraggable, useDroppable } from '@dnd-kit/core';
|
||||
import { useCombinedRefs } from '@dnd-kit/utilities';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
||||
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
||||
import { useGetUrl } from 'common/util/getUrl';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { ReactElement, SyntheticEvent } from 'react';
|
||||
import { memo, useRef } from 'react';
|
||||
import { FaImage, FaTimes } from 'react-icons/fa';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
type IAIDndImageProps = {
|
||||
image: ImageDTO | null | undefined;
|
||||
onDrop: (droppedImage: ImageDTO) => void;
|
||||
onReset?: () => void;
|
||||
onError?: (event: SyntheticEvent<HTMLImageElement>) => void;
|
||||
onLoad?: (event: SyntheticEvent<HTMLImageElement>) => void;
|
||||
resetIconSize?: IconButtonProps['size'];
|
||||
withResetIcon?: boolean;
|
||||
withMetadataOverlay?: boolean;
|
||||
isDragDisabled?: boolean;
|
||||
isDropDisabled?: boolean;
|
||||
fallback?: ReactElement;
|
||||
payloadImage?: ImageDTO | null | undefined;
|
||||
minSize?: number;
|
||||
};
|
||||
|
||||
const IAIDndImage = (props: IAIDndImageProps) => {
|
||||
const {
|
||||
image,
|
||||
onDrop,
|
||||
onReset,
|
||||
onError,
|
||||
resetIconSize = 'md',
|
||||
withResetIcon = false,
|
||||
withMetadataOverlay = false,
|
||||
isDropDisabled = false,
|
||||
isDragDisabled = false,
|
||||
fallback = <IAIImageFallback />,
|
||||
payloadImage,
|
||||
minSize = 24,
|
||||
} = props;
|
||||
const dndId = useRef(uuidv4());
|
||||
const { getUrl } = useGetUrl();
|
||||
const {
|
||||
isOver,
|
||||
setNodeRef: setDroppableRef,
|
||||
active,
|
||||
} = useDroppable({
|
||||
id: dndId.current,
|
||||
disabled: isDropDisabled,
|
||||
data: {
|
||||
handleDrop: onDrop,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef: setDraggableRef,
|
||||
} = useDraggable({
|
||||
id: dndId.current,
|
||||
data: {
|
||||
image: payloadImage ? payloadImage : image,
|
||||
},
|
||||
disabled: isDragDisabled,
|
||||
});
|
||||
|
||||
const setNodeRef = useCombinedRefs(setDroppableRef, setDraggableRef);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
width: 'full',
|
||||
height: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
minW: minSize,
|
||||
minH: minSize,
|
||||
userSelect: 'none',
|
||||
cursor: 'grab',
|
||||
}}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
ref={setNodeRef}
|
||||
>
|
||||
{image && (
|
||||
<Flex
|
||||
sx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
position: 'relative',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={getUrl(image.image_url)}
|
||||
fallbackStrategy="beforeLoadOrError"
|
||||
fallback={fallback}
|
||||
onError={onError}
|
||||
objectFit="contain"
|
||||
draggable={false}
|
||||
sx={{
|
||||
maxW: 'full',
|
||||
maxH: 'full',
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
/>
|
||||
{withMetadataOverlay && <ImageMetadataOverlay image={image} />}
|
||||
{onReset && withResetIcon && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<IAIIconButton
|
||||
size={resetIconSize}
|
||||
tooltip="Reset Image"
|
||||
aria-label="Reset Image"
|
||||
icon={<FaTimes />}
|
||||
onClick={onReset}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<AnimatePresence>
|
||||
{active && <DropOverlay isOver={isOver} />}
|
||||
</AnimatePresence>
|
||||
</Flex>
|
||||
)}
|
||||
{!image && (
|
||||
<>
|
||||
<Flex
|
||||
sx={{
|
||||
minH: minSize,
|
||||
bg: 'base.850',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
as={FaImage}
|
||||
sx={{
|
||||
boxSize: 24,
|
||||
color: 'base.500',
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<AnimatePresence>
|
||||
{active && <DropOverlay isOver={isOver} />}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(IAIDndImage);
|
||||
|
||||
type DropOverlayProps = {
|
||||
isOver: boolean;
|
||||
};
|
||||
|
||||
const DropOverlay = (props: DropOverlayProps) => {
|
||||
const { isOver } = props;
|
||||
return (
|
||||
<motion.div
|
||||
key="statusText"
|
||||
initial={{
|
||||
opacity: 0,
|
||||
}}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
transition: { duration: 0.1 },
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
transition: { duration: 0.1 },
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
bg: 'base.900',
|
||||
opacity: 0.7,
|
||||
borderRadius: 'base',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: '0.1s',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
opacity: 1,
|
||||
borderWidth: 2,
|
||||
borderColor: isOver ? 'base.200' : 'base.500',
|
||||
borderRadius: 'base',
|
||||
borderStyle: 'dashed',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: '0.1s',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
sx={{
|
||||
fontSize: '2xl',
|
||||
fontWeight: 600,
|
||||
transform: isOver ? 'scale(1.1)' : 'scale(1)',
|
||||
color: isOver ? 'base.100' : 'base.500',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: '0.1s',
|
||||
}}
|
||||
>
|
||||
Drop
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
@ -0,0 +1,25 @@
|
||||
import {
|
||||
Checkbox,
|
||||
CheckboxProps,
|
||||
FormControl,
|
||||
FormControlProps,
|
||||
FormLabel,
|
||||
} from '@chakra-ui/react';
|
||||
import { memo, ReactNode } from 'react';
|
||||
|
||||
type IAIFullCheckboxProps = CheckboxProps & {
|
||||
label: string | ReactNode;
|
||||
formControlProps?: FormControlProps;
|
||||
};
|
||||
|
||||
const IAIFullCheckbox = (props: IAIFullCheckboxProps) => {
|
||||
const { label, formControlProps, ...rest } = props;
|
||||
return (
|
||||
<FormControl {...formControlProps}>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
<Checkbox colorScheme="accent" {...rest} />
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(IAIFullCheckbox);
|
@ -0,0 +1,27 @@
|
||||
import { Flex, FlexProps, Spinner, SpinnerProps } from '@chakra-ui/react';
|
||||
|
||||
type Props = FlexProps & {
|
||||
spinnerProps?: SpinnerProps;
|
||||
};
|
||||
|
||||
export const IAIImageFallback = (props: Props) => {
|
||||
const { spinnerProps, ...rest } = props;
|
||||
const { sx, ...restFlexProps } = rest;
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
bg: 'base.900',
|
||||
opacity: 0.7,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 'base',
|
||||
...sx,
|
||||
}}
|
||||
{...restFlexProps}
|
||||
>
|
||||
<Spinner size="xl" {...spinnerProps} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
@ -0,0 +1,19 @@
|
||||
import { Checkbox, CheckboxProps, Text } from '@chakra-ui/react';
|
||||
import { memo, ReactElement } from 'react';
|
||||
|
||||
type IAISimpleCheckboxProps = CheckboxProps & {
|
||||
label: string | ReactElement;
|
||||
};
|
||||
|
||||
const IAISimpleCheckbox = (props: IAISimpleCheckboxProps) => {
|
||||
const { label, ...rest } = props;
|
||||
return (
|
||||
<Checkbox colorScheme="accent" {...rest}>
|
||||
<Text color="base.200" fontSize="md">
|
||||
{label}
|
||||
</Text>
|
||||
</Checkbox>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(IAISimpleCheckbox);
|
@ -40,7 +40,7 @@ import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton';
|
||||
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
|
||||
|
||||
export type IAIFullSliderProps = {
|
||||
label: string;
|
||||
label?: string;
|
||||
value: number;
|
||||
min?: number;
|
||||
max?: number;
|
||||
@ -178,9 +178,11 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
||||
isDisabled={isDisabled}
|
||||
{...sliderFormControlProps}
|
||||
>
|
||||
<FormLabel {...sliderFormLabelProps} mb={-1}>
|
||||
{label}
|
||||
</FormLabel>
|
||||
{label && (
|
||||
<FormLabel {...sliderFormLabelProps} mb={-1}>
|
||||
{label}
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<HStack w="100%" gap={2} alignItems="center">
|
||||
<Slider
|
||||
@ -203,6 +205,7 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
||||
sx={{
|
||||
insetInlineStart: '0 !important',
|
||||
insetInlineEnd: 'unset !important',
|
||||
mt: 1.5,
|
||||
}}
|
||||
{...sliderMarkProps}
|
||||
>
|
||||
@ -213,6 +216,7 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
||||
sx={{
|
||||
insetInlineStart: 'unset !important',
|
||||
insetInlineEnd: '0 !important',
|
||||
mt: 1.5,
|
||||
}}
|
||||
{...sliderMarkProps}
|
||||
>
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
FormLabelProps,
|
||||
Switch,
|
||||
SwitchProps,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
|
||||
@ -13,6 +14,7 @@ interface Props extends SwitchProps {
|
||||
width?: string | number;
|
||||
formControlProps?: FormControlProps;
|
||||
formLabelProps?: FormLabelProps;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,22 +27,27 @@ const IAISwitch = (props: Props) => {
|
||||
width = 'auto',
|
||||
formControlProps,
|
||||
formLabelProps,
|
||||
tooltip,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<FormControl
|
||||
isDisabled={isDisabled}
|
||||
width={width}
|
||||
display="flex"
|
||||
gap={4}
|
||||
alignItems="center"
|
||||
{...formControlProps}
|
||||
>
|
||||
<FormLabel my={1} flexGrow={1} {...formLabelProps}>
|
||||
{label}
|
||||
</FormLabel>
|
||||
<Switch {...rest} />
|
||||
</FormControl>
|
||||
<Tooltip label={tooltip} hasArrow placement="top" isDisabled={!tooltip}>
|
||||
<FormControl
|
||||
isDisabled={isDisabled}
|
||||
width={width}
|
||||
display="flex"
|
||||
gap={4}
|
||||
alignItems="center"
|
||||
{...formControlProps}
|
||||
>
|
||||
{label && (
|
||||
<FormLabel my={1} flexGrow={1} {...formLabelProps}>
|
||||
{label}
|
||||
</FormLabel>
|
||||
)}
|
||||
<Switch {...rest} />
|
||||
</FormControl>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Badge, Flex } from '@chakra-ui/react';
|
||||
import { isNumber, isString } from 'lodash-es';
|
||||
import { isString } from 'lodash-es';
|
||||
import { useMemo } from 'react';
|
||||
import { ImageDTO } from 'services/api';
|
||||
|
||||
@ -8,14 +8,6 @@ type ImageMetadataOverlayProps = {
|
||||
};
|
||||
|
||||
const ImageMetadataOverlay = ({ image }: ImageMetadataOverlayProps) => {
|
||||
const dimensions = useMemo(() => {
|
||||
if (!isNumber(image.metadata?.width) || isNumber(!image.metadata?.height)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return `${image.metadata?.width} × ${image.metadata?.height}`;
|
||||
}, [image.metadata]);
|
||||
|
||||
const model = useMemo(() => {
|
||||
if (!isString(image.metadata?.model)) {
|
||||
return;
|
||||
@ -31,17 +23,15 @@ const ImageMetadataOverlay = ({ image }: ImageMetadataOverlayProps) => {
|
||||
flexDirection: 'column',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
insetInlineStart: 0,
|
||||
p: 2,
|
||||
alignItems: 'flex-end',
|
||||
alignItems: 'flex-start',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{dimensions && (
|
||||
<Badge variant="solid" colorScheme="base">
|
||||
{dimensions}
|
||||
</Badge>
|
||||
)}
|
||||
<Badge variant="solid" colorScheme="base">
|
||||
{image.width} × {image.height}
|
||||
</Badge>
|
||||
{model && (
|
||||
<Badge variant="solid" colorScheme="base">
|
||||
{model}
|
||||
|
@ -1,42 +0,0 @@
|
||||
import { ButtonGroup, Flex, Spacer, Text } from '@chakra-ui/react';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaUndo, FaUpload } from 'react-icons/fa';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useCallback } from 'react';
|
||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||
import useImageUploader from 'common/hooks/useImageUploader';
|
||||
|
||||
const InitialImageButtons = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { openUploader } = useImageUploader();
|
||||
|
||||
const handleResetInitialImage = useCallback(() => {
|
||||
dispatch(clearInitialImage());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Flex w="full" alignItems="center">
|
||||
<Text size="sm" fontWeight={500} color="base.300">
|
||||
{t('parameters.initialImage')}
|
||||
</Text>
|
||||
<Spacer />
|
||||
<ButtonGroup>
|
||||
<IAIIconButton
|
||||
icon={<FaUndo />}
|
||||
aria-label={t('accessibility.reset')}
|
||||
onClick={handleResetInitialImage}
|
||||
/>
|
||||
<IAIIconButton
|
||||
icon={<FaUpload />}
|
||||
onClick={openUploader}
|
||||
aria-label={t('common.upload')}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default InitialImageButtons;
|
@ -1,12 +1,12 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { validateSeedWeights } from 'common/util/seedWeightPairs';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
export const readinessSelector = createSelector(
|
||||
const readinessSelector = createSelector(
|
||||
[generationSelector, systemSelector, activeTabNameSelector],
|
||||
(generation, system, activeTabName) => {
|
||||
const {
|
||||
@ -60,3 +60,8 @@ export const readinessSelector = createSelector(
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
export const useIsReadyToInvoke = () => {
|
||||
const { isReady } = useAppSelector(readinessSelector);
|
||||
return isReady;
|
||||
};
|
@ -2,7 +2,7 @@ import { ButtonGroup, Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIPopover from 'common/components/IAIPopover';
|
||||
@ -117,12 +117,12 @@ const IAICanvasMaskOptions = () => {
|
||||
}
|
||||
>
|
||||
<Flex direction="column" gap={2}>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={`${t('unifiedCanvas.enableMask')} (H)`}
|
||||
isChecked={isMaskEnabled}
|
||||
onChange={handleToggleEnableMask}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.preserveMaskedArea')}
|
||||
isChecked={shouldPreserveMaskedArea}
|
||||
onChange={(e) =>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIPopover from 'common/components/IAIPopover';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
@ -102,50 +102,50 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
}
|
||||
>
|
||||
<Flex direction="column" gap={2}>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showIntermediates')}
|
||||
isChecked={shouldShowIntermediates}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldShowIntermediates(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showGrid')}
|
||||
isChecked={shouldShowGrid}
|
||||
onChange={(e) => dispatch(setShouldShowGrid(e.target.checked))}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.snapToGrid')}
|
||||
isChecked={shouldSnapToGrid}
|
||||
onChange={handleChangeShouldSnapToGrid}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.darkenOutsideSelection')}
|
||||
isChecked={shouldDarkenOutsideBoundingBox}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.autoSaveToGallery')}
|
||||
isChecked={shouldAutoSave}
|
||||
onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.saveBoxRegionOnly')}
|
||||
isChecked={shouldCropToBoundingBoxOnSave}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.limitStrokesToBox')}
|
||||
isChecked={shouldRestrictStrokesToBox}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldRestrictStrokesToBox(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showCanvasDebugInfo')}
|
||||
isChecked={shouldShowCanvasDebugInfo}
|
||||
onChange={(e) =>
|
||||
@ -153,7 +153,7 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
}
|
||||
/>
|
||||
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.antialiasing')}
|
||||
isChecked={shouldAntialias}
|
||||
onChange={(e) => dispatch(setShouldAntialias(e.target.checked))}
|
||||
|
@ -0,0 +1,258 @@
|
||||
import { memo, useCallback } from 'react';
|
||||
import {
|
||||
ControlNetConfig,
|
||||
controlNetAdded,
|
||||
controlNetRemoved,
|
||||
controlNetToggled,
|
||||
} from '../store/controlNetSlice';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import ParamControlNetModel from './parameters/ParamControlNetModel';
|
||||
import ParamControlNetWeight from './parameters/ParamControlNetWeight';
|
||||
import {
|
||||
Checkbox,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
HStack,
|
||||
TabList,
|
||||
TabPanels,
|
||||
Tabs,
|
||||
Tab,
|
||||
TabPanel,
|
||||
Box,
|
||||
} from '@chakra-ui/react';
|
||||
import { FaCopy, FaPlus, FaTrash, FaWrench } from 'react-icons/fa';
|
||||
|
||||
import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd';
|
||||
import ControlNetImagePreview from './ControlNetImagePreview';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useToggle } from 'react-use';
|
||||
import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect';
|
||||
import ControlNetProcessorComponent from './ControlNetProcessorComponent';
|
||||
import ControlNetPreprocessButton from './ControlNetPreprocessButton';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons';
|
||||
|
||||
type ControlNetProps = {
|
||||
controlNet: ControlNetConfig;
|
||||
};
|
||||
|
||||
const ControlNet = (props: ControlNetProps) => {
|
||||
const {
|
||||
controlNetId,
|
||||
isEnabled,
|
||||
model,
|
||||
weight,
|
||||
beginStepPct,
|
||||
endStepPct,
|
||||
controlImage,
|
||||
processedControlImage,
|
||||
processorNode,
|
||||
processorType,
|
||||
} = props.controlNet;
|
||||
const dispatch = useAppDispatch();
|
||||
const [shouldShowAdvanced, onToggleAdvanced] = useToggle(false);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
dispatch(controlNetRemoved({ controlNetId }));
|
||||
}, [controlNetId, dispatch]);
|
||||
|
||||
const handleDuplicate = useCallback(() => {
|
||||
dispatch(
|
||||
controlNetAdded({ controlNetId: uuidv4(), controlNet: props.controlNet })
|
||||
);
|
||||
}, [dispatch, props.controlNet]);
|
||||
|
||||
const handleToggleIsEnabled = useCallback(() => {
|
||||
dispatch(controlNetToggled({ controlNetId }));
|
||||
}, [controlNetId, dispatch]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
flexDir: 'column',
|
||||
gap: 2,
|
||||
p: 3,
|
||||
bg: 'base.850',
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
>
|
||||
<Flex sx={{ gap: 2 }}>
|
||||
<IAISwitch
|
||||
tooltip="Toggle"
|
||||
aria-label="Toggle"
|
||||
isChecked={isEnabled}
|
||||
onChange={handleToggleIsEnabled}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
w: 'full',
|
||||
minW: 0,
|
||||
opacity: isEnabled ? 1 : 0.5,
|
||||
pointerEvents: isEnabled ? 'auto' : 'none',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: '0.1s',
|
||||
}}
|
||||
>
|
||||
<ParamControlNetModel controlNetId={controlNetId} model={model} />
|
||||
</Box>
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
tooltip="Duplicate"
|
||||
aria-label="Duplicate"
|
||||
onClick={handleDuplicate}
|
||||
icon={<FaCopy />}
|
||||
/>
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
tooltip="Delete"
|
||||
aria-label="Delete"
|
||||
colorScheme="error"
|
||||
onClick={handleDelete}
|
||||
icon={<FaTrash />}
|
||||
/>
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
aria-label="Expand"
|
||||
onClick={onToggleAdvanced}
|
||||
variant="link"
|
||||
icon={
|
||||
<ChevronUpIcon
|
||||
sx={{
|
||||
boxSize: 4,
|
||||
color: 'base.300',
|
||||
transform: shouldShowAdvanced
|
||||
? 'rotate(0deg)'
|
||||
: 'rotate(180deg)',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: 'normal',
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
{isEnabled && (
|
||||
<>
|
||||
<Flex sx={{ gap: 4 }}>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDir: 'column',
|
||||
gap: 2,
|
||||
w: 'full',
|
||||
h: 24,
|
||||
paddingInlineStart: 1,
|
||||
paddingInlineEnd: shouldShowAdvanced ? 1 : 0,
|
||||
pb: 2,
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<ParamControlNetWeight
|
||||
controlNetId={controlNetId}
|
||||
weight={weight}
|
||||
mini
|
||||
/>
|
||||
<ParamControlNetBeginEnd
|
||||
controlNetId={controlNetId}
|
||||
beginStepPct={beginStepPct}
|
||||
endStepPct={endStepPct}
|
||||
mini
|
||||
/>
|
||||
</Flex>
|
||||
{!shouldShowAdvanced && (
|
||||
<Flex
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
h: 24,
|
||||
w: 24,
|
||||
aspectRatio: '1/1',
|
||||
}}
|
||||
>
|
||||
<ControlNetImagePreview controlNet={props.controlNet} />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
{shouldShowAdvanced && (
|
||||
<>
|
||||
<Box pt={2}>
|
||||
<ControlNetImagePreview controlNet={props.controlNet} />
|
||||
</Box>
|
||||
<ParamControlNetProcessorSelect
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
<ControlNetProcessorComponent
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex sx={{ flexDir: 'column', gap: 3 }}>
|
||||
<ControlNetImagePreview controlNet={props.controlNet} />
|
||||
<ParamControlNetModel controlNetId={controlNetId} model={model} />
|
||||
<Tabs
|
||||
isFitted
|
||||
orientation="horizontal"
|
||||
variant="enclosed"
|
||||
size="sm"
|
||||
colorScheme="accent"
|
||||
>
|
||||
<TabList>
|
||||
<Tab
|
||||
sx={{ 'button&': { _selected: { borderBottomColor: 'base.800' } } }}
|
||||
>
|
||||
Model Config
|
||||
</Tab>
|
||||
<Tab
|
||||
sx={{ 'button&': { _selected: { borderBottomColor: 'base.800' } } }}
|
||||
>
|
||||
Preprocess
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanels sx={{ pt: 2 }}>
|
||||
<TabPanel sx={{ p: 0 }}>
|
||||
<ParamControlNetWeight
|
||||
controlNetId={controlNetId}
|
||||
weight={weight}
|
||||
/>
|
||||
<ParamControlNetBeginEnd
|
||||
controlNetId={controlNetId}
|
||||
beginStepPct={beginStepPct}
|
||||
endStepPct={endStepPct}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel sx={{ p: 0 }}>
|
||||
<ParamControlNetProcessorSelect
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
<ControlNetProcessorComponent
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
<ControlNetPreprocessButton controlNet={props.controlNet} />
|
||||
{/* <IAIButton
|
||||
size="sm"
|
||||
leftIcon={<FaUndo />}
|
||||
onClick={handleReset}
|
||||
isDisabled={Boolean(!processedControlImage)}
|
||||
>
|
||||
Reset Processing
|
||||
</IAIButton> */}
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
<IAIButton onClick={handleDelete}>Remove ControlNet</IAIButton>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ControlNet);
|
@ -0,0 +1,141 @@
|
||||
import { memo, useCallback, useRef, useState } from 'react';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import {
|
||||
ControlNetConfig,
|
||||
controlNetImageChanged,
|
||||
controlNetSelector,
|
||||
} from '../store/controlNetSlice';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
||||
import { useHoverDirty } from 'react-use';
|
||||
|
||||
const selector = createSelector(
|
||||
controlNetSelector,
|
||||
(controlNet) => {
|
||||
const { isProcessingControlImage } = controlNet;
|
||||
return { isProcessingControlImage };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
type Props = {
|
||||
controlNet: ControlNetConfig;
|
||||
};
|
||||
|
||||
const ControlNetImagePreview = (props: Props) => {
|
||||
const { controlNetId, controlImage, processedControlImage, processorType } =
|
||||
props.controlNet;
|
||||
const dispatch = useAppDispatch();
|
||||
const { isProcessingControlImage } = useAppSelector(selector);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const isMouseOverImage = useHoverDirty(containerRef);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(droppedImage: ImageDTO) => {
|
||||
if (controlImage?.image_name === droppedImage.image_name) {
|
||||
return;
|
||||
}
|
||||
dispatch(
|
||||
controlNetImageChanged({ controlNetId, controlImage: droppedImage })
|
||||
);
|
||||
},
|
||||
[controlImage, controlNetId, dispatch]
|
||||
);
|
||||
|
||||
const shouldShowProcessedImageBackdrop =
|
||||
Number(controlImage?.width) > Number(processedControlImage?.width) ||
|
||||
Number(controlImage?.height) > Number(processedControlImage?.height);
|
||||
|
||||
const shouldShowProcessedImage =
|
||||
controlImage &&
|
||||
processedControlImage &&
|
||||
!isMouseOverImage &&
|
||||
!isProcessingControlImage &&
|
||||
processorType !== 'none';
|
||||
|
||||
return (
|
||||
<Box ref={containerRef} sx={{ position: 'relative', w: 'full', h: 'full' }}>
|
||||
<IAIDndImage
|
||||
image={controlImage}
|
||||
onDrop={handleDrop}
|
||||
isDropDisabled={Boolean(
|
||||
processedControlImage && processorType !== 'none'
|
||||
)}
|
||||
/>
|
||||
<AnimatePresence>
|
||||
{shouldShowProcessedImage && (
|
||||
<motion.div
|
||||
initial={{
|
||||
opacity: 0,
|
||||
}}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
transition: { duration: 0.1 },
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
transition: { duration: 0.1 },
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
top: 0,
|
||||
insetInlineStart: 0,
|
||||
}}
|
||||
>
|
||||
{shouldShowProcessedImageBackdrop && (
|
||||
<Box
|
||||
sx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
bg: 'base.900',
|
||||
opacity: 0.7,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineStart: 0,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
}}
|
||||
>
|
||||
<IAIDndImage
|
||||
image={processedControlImage}
|
||||
onDrop={handleDrop}
|
||||
payloadImage={controlImage}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{isProcessingControlImage && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineStart: 0,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
}}
|
||||
>
|
||||
<IAIImageFallback />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ControlNetImagePreview);
|
@ -0,0 +1,36 @@
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { ControlNetConfig } from '../store/controlNetSlice';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { controlNetImageProcessed } from '../store/actions';
|
||||
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
|
||||
|
||||
type Props = {
|
||||
controlNet: ControlNetConfig;
|
||||
};
|
||||
|
||||
const ControlNetPreprocessButton = (props: Props) => {
|
||||
const { controlNetId, controlImage } = props.controlNet;
|
||||
const dispatch = useAppDispatch();
|
||||
const isReady = useIsReadyToInvoke();
|
||||
|
||||
const handleProcess = useCallback(() => {
|
||||
dispatch(
|
||||
controlNetImageProcessed({
|
||||
controlNetId,
|
||||
})
|
||||
);
|
||||
}, [controlNetId, dispatch]);
|
||||
|
||||
return (
|
||||
<IAIButton
|
||||
size="sm"
|
||||
onClick={handleProcess}
|
||||
isDisabled={Boolean(!controlImage) || !isReady}
|
||||
>
|
||||
Preprocess
|
||||
</IAIButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ControlNetPreprocessButton);
|
@ -0,0 +1,131 @@
|
||||
import { memo } from 'react';
|
||||
import { RequiredControlNetProcessorNode } from '../store/types';
|
||||
import CannyProcessor from './processors/CannyProcessor';
|
||||
import HedProcessor from './processors/HedProcessor';
|
||||
import LineartProcessor from './processors/LineartProcessor';
|
||||
import LineartAnimeProcessor from './processors/LineartAnimeProcessor';
|
||||
import ContentShuffleProcessor from './processors/ContentShuffleProcessor';
|
||||
import MediapipeFaceProcessor from './processors/MediapipeFaceProcessor';
|
||||
import MidasDepthProcessor from './processors/MidasDepthProcessor';
|
||||
import MlsdImageProcessor from './processors/MlsdImageProcessor';
|
||||
import NormalBaeProcessor from './processors/NormalBaeProcessor';
|
||||
import OpenposeProcessor from './processors/OpenposeProcessor';
|
||||
import PidiProcessor from './processors/PidiProcessor';
|
||||
import ZoeDepthProcessor from './processors/ZoeDepthProcessor';
|
||||
|
||||
export type ControlNetProcessorProps = {
|
||||
controlNetId: string;
|
||||
processorNode: RequiredControlNetProcessorNode;
|
||||
};
|
||||
|
||||
const ControlNetProcessorComponent = (props: ControlNetProcessorProps) => {
|
||||
const { controlNetId, processorNode } = props;
|
||||
if (processorNode.type === 'canny_image_processor') {
|
||||
return (
|
||||
<CannyProcessor
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (processorNode.type === 'hed_image_processor') {
|
||||
return (
|
||||
<HedProcessor controlNetId={controlNetId} processorNode={processorNode} />
|
||||
);
|
||||
}
|
||||
|
||||
if (processorNode.type === 'lineart_image_processor') {
|
||||
return (
|
||||
<LineartProcessor
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (processorNode.type === 'content_shuffle_image_processor') {
|
||||
return (
|
||||
<ContentShuffleProcessor
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (processorNode.type === 'lineart_anime_image_processor') {
|
||||
return (
|
||||
<LineartAnimeProcessor
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (processorNode.type === 'mediapipe_face_processor') {
|
||||
return (
|
||||
<MediapipeFaceProcessor
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (processorNode.type === 'midas_depth_image_processor') {
|
||||
return (
|
||||
<MidasDepthProcessor
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (processorNode.type === 'mlsd_image_processor') {
|
||||
return (
|
||||
<MlsdImageProcessor
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (processorNode.type === 'normalbae_image_processor') {
|
||||
return (
|
||||
<NormalBaeProcessor
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (processorNode.type === 'openpose_image_processor') {
|
||||
return (
|
||||
<OpenposeProcessor
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (processorNode.type === 'pidi_image_processor') {
|
||||
return (
|
||||
<PidiProcessor
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (processorNode.type === 'zoe_depth_image_processor') {
|
||||
return (
|
||||
<ZoeDepthProcessor
|
||||
controlNetId={controlNetId}
|
||||
processorNode={processorNode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default memo(ControlNetProcessorComponent);
|
@ -0,0 +1,20 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { controlNetProcessorParamsChanged } from 'features/controlNet/store/controlNetSlice';
|
||||
import { ControlNetProcessorNode } from 'features/controlNet/store/types';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useProcessorNodeChanged = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const handleProcessorNodeChanged = useCallback(
|
||||
(controlNetId: string, changes: Partial<ControlNetProcessorNode>) => {
|
||||
dispatch(
|
||||
controlNetProcessorParamsChanged({
|
||||
controlNetId,
|
||||
changes,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
return handleProcessorNodeChanged;
|
||||
};
|
@ -0,0 +1,130 @@
|
||||
import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
HStack,
|
||||
RangeSlider,
|
||||
RangeSliderFilledTrack,
|
||||
RangeSliderMark,
|
||||
RangeSliderThumb,
|
||||
RangeSliderTrack,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import {
|
||||
controlNetBeginStepPctChanged,
|
||||
controlNetEndStepPctChanged,
|
||||
} from 'features/controlNet/store/controlNetSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { BiReset } from 'react-icons/bi';
|
||||
|
||||
type Props = {
|
||||
controlNetId: string;
|
||||
beginStepPct: number;
|
||||
endStepPct: number;
|
||||
mini?: boolean;
|
||||
};
|
||||
|
||||
const formatPct = (v: number) => `${Math.round(v * 100)}%`;
|
||||
|
||||
const ParamControlNetBeginEnd = (props: Props) => {
|
||||
const { controlNetId, beginStepPct, endStepPct, mini = false } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleStepPctChanged = useCallback(
|
||||
(v: number[]) => {
|
||||
dispatch(
|
||||
controlNetBeginStepPctChanged({ controlNetId, beginStepPct: v[0] })
|
||||
);
|
||||
dispatch(controlNetEndStepPctChanged({ controlNetId, endStepPct: v[1] }));
|
||||
},
|
||||
[controlNetId, dispatch]
|
||||
);
|
||||
|
||||
const handleStepPctReset = useCallback(() => {
|
||||
dispatch(controlNetBeginStepPctChanged({ controlNetId, beginStepPct: 0 }));
|
||||
dispatch(controlNetEndStepPctChanged({ controlNetId, endStepPct: 1 }));
|
||||
}, [controlNetId, dispatch]);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<FormLabel>Begin / End Step Percentage</FormLabel>
|
||||
<HStack w="100%" gap={2} alignItems="center">
|
||||
<RangeSlider
|
||||
aria-label={['Begin Step %', 'End Step %']}
|
||||
value={[beginStepPct, endStepPct]}
|
||||
onChange={handleStepPctChanged}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
minStepsBetweenThumbs={5}
|
||||
>
|
||||
<RangeSliderTrack>
|
||||
<RangeSliderFilledTrack />
|
||||
</RangeSliderTrack>
|
||||
<Tooltip label={formatPct(beginStepPct)} placement="top" hasArrow>
|
||||
<RangeSliderThumb index={0} />
|
||||
</Tooltip>
|
||||
<Tooltip label={formatPct(endStepPct)} placement="top" hasArrow>
|
||||
<RangeSliderThumb index={1} />
|
||||
</Tooltip>
|
||||
{!mini && (
|
||||
<>
|
||||
<RangeSliderMark
|
||||
value={0}
|
||||
sx={{
|
||||
fontSize: 'xs',
|
||||
fontWeight: '500',
|
||||
color: 'base.200',
|
||||
insetInlineStart: '0 !important',
|
||||
insetInlineEnd: 'unset !important',
|
||||
mt: 1.5,
|
||||
}}
|
||||
>
|
||||
0%
|
||||
</RangeSliderMark>
|
||||
<RangeSliderMark
|
||||
value={0.5}
|
||||
sx={{
|
||||
fontSize: 'xs',
|
||||
fontWeight: '500',
|
||||
color: 'base.200',
|
||||
mt: 1.5,
|
||||
}}
|
||||
>
|
||||
50%
|
||||
</RangeSliderMark>
|
||||
<RangeSliderMark
|
||||
value={1}
|
||||
sx={{
|
||||
fontSize: 'xs',
|
||||
fontWeight: '500',
|
||||
color: 'base.200',
|
||||
insetInlineStart: 'unset !important',
|
||||
insetInlineEnd: '0 !important',
|
||||
mt: 1.5,
|
||||
}}
|
||||
>
|
||||
100%
|
||||
</RangeSliderMark>
|
||||
</>
|
||||
)}
|
||||
</RangeSlider>
|
||||
|
||||
{!mini && (
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
aria-label={t('accessibility.reset')}
|
||||
tooltip={t('accessibility.reset')}
|
||||
icon={<BiReset />}
|
||||
onClick={handleStepPctReset}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamControlNetBeginEnd);
|
@ -0,0 +1,28 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { controlNetToggled } from 'features/controlNet/store/controlNetSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
type ParamControlNetIsEnabledProps = {
|
||||
controlNetId: string;
|
||||
isEnabled: boolean;
|
||||
};
|
||||
|
||||
const ParamControlNetIsEnabled = (props: ParamControlNetIsEnabledProps) => {
|
||||
const { controlNetId, isEnabled } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleIsEnabledChanged = useCallback(() => {
|
||||
dispatch(controlNetToggled({ controlNetId }));
|
||||
}, [dispatch, controlNetId]);
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
label="Enabled"
|
||||
isChecked={isEnabled}
|
||||
onChange={handleIsEnabledChanged}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamControlNetIsEnabled);
|
@ -0,0 +1,36 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIFullCheckbox from 'common/components/IAIFullCheckbox';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import {
|
||||
controlNetToggled,
|
||||
isControlNetImagePreprocessedToggled,
|
||||
} from 'features/controlNet/store/controlNetSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
type ParamControlNetIsEnabledProps = {
|
||||
controlNetId: string;
|
||||
isControlImageProcessed: boolean;
|
||||
};
|
||||
|
||||
const ParamControlNetIsEnabled = (props: ParamControlNetIsEnabledProps) => {
|
||||
const { controlNetId, isControlImageProcessed } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleIsControlImageProcessedToggled = useCallback(() => {
|
||||
dispatch(
|
||||
isControlNetImagePreprocessedToggled({
|
||||
controlNetId,
|
||||
})
|
||||
);
|
||||
}, [controlNetId, dispatch]);
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
label="Preprocess"
|
||||
isChecked={isControlImageProcessed}
|
||||
onChange={handleIsControlImageProcessedToggled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamControlNetIsEnabled);
|
@ -0,0 +1,41 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAICustomSelect from 'common/components/IAICustomSelect';
|
||||
import {
|
||||
CONTROLNET_MODELS,
|
||||
ControlNetModel,
|
||||
} from 'features/controlNet/store/constants';
|
||||
import { controlNetModelChanged } from 'features/controlNet/store/controlNetSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
type ParamIsControlNetModelProps = {
|
||||
controlNetId: string;
|
||||
model: ControlNetModel;
|
||||
};
|
||||
|
||||
const ParamIsControlNetModel = (props: ParamIsControlNetModelProps) => {
|
||||
const { controlNetId, model } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleModelChanged = useCallback(
|
||||
(val: string | null | undefined) => {
|
||||
// TODO: do not cast
|
||||
const model = val as ControlNetModel;
|
||||
dispatch(controlNetModelChanged({ controlNetId, model }));
|
||||
},
|
||||
[controlNetId, dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAICustomSelect
|
||||
tooltip={model}
|
||||
tooltipProps={{ placement: 'top', hasArrow: true }}
|
||||
items={CONTROLNET_MODELS}
|
||||
selectedItem={model}
|
||||
setSelectedItem={handleModelChanged}
|
||||
ellipsisPosition="start"
|
||||
withCheckIcon
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamIsControlNetModel);
|
@ -0,0 +1,47 @@
|
||||
import IAICustomSelect from 'common/components/IAICustomSelect';
|
||||
import { memo, useCallback } from 'react';
|
||||
import {
|
||||
ControlNetProcessorNode,
|
||||
ControlNetProcessorType,
|
||||
} from '../../store/types';
|
||||
import { controlNetProcessorTypeChanged } from '../../store/controlNetSlice';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { CONTROLNET_PROCESSORS } from '../../store/constants';
|
||||
|
||||
type ParamControlNetProcessorSelectProps = {
|
||||
controlNetId: string;
|
||||
processorNode: ControlNetProcessorNode;
|
||||
};
|
||||
|
||||
const CONTROLNET_PROCESSOR_TYPES = Object.keys(
|
||||
CONTROLNET_PROCESSORS
|
||||
) as ControlNetProcessorType[];
|
||||
|
||||
const ParamControlNetProcessorSelect = (
|
||||
props: ParamControlNetProcessorSelectProps
|
||||
) => {
|
||||
const { controlNetId, processorNode } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const handleProcessorTypeChanged = useCallback(
|
||||
(v: string | null | undefined) => {
|
||||
dispatch(
|
||||
controlNetProcessorTypeChanged({
|
||||
controlNetId,
|
||||
processorType: v as ControlNetProcessorType,
|
||||
})
|
||||
);
|
||||
},
|
||||
[controlNetId, dispatch]
|
||||
);
|
||||
return (
|
||||
<IAICustomSelect
|
||||
label="Processor"
|
||||
items={CONTROLNET_PROCESSOR_TYPES}
|
||||
selectedItem={processorNode.type ?? 'canny_image_processor'}
|
||||
setSelectedItem={handleProcessorTypeChanged}
|
||||
withCheckIcon
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamControlNetProcessorSelect);
|
@ -0,0 +1,57 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { controlNetWeightChanged } from 'features/controlNet/store/controlNetSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
type ParamControlNetWeightProps = {
|
||||
controlNetId: string;
|
||||
weight: number;
|
||||
mini?: boolean;
|
||||
};
|
||||
|
||||
const ParamControlNetWeight = (props: ParamControlNetWeightProps) => {
|
||||
const { controlNetId, weight, mini = false } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleWeightChanged = useCallback(
|
||||
(weight: number) => {
|
||||
dispatch(controlNetWeightChanged({ controlNetId, weight }));
|
||||
},
|
||||
[controlNetId, dispatch]
|
||||
);
|
||||
|
||||
const handleWeightReset = () => {
|
||||
dispatch(controlNetWeightChanged({ controlNetId, weight: 1 }));
|
||||
};
|
||||
|
||||
if (mini) {
|
||||
return (
|
||||
<IAISlider
|
||||
label={'Weight'}
|
||||
sliderFormLabelProps={{ pb: 1 }}
|
||||
value={weight}
|
||||
onChange={handleWeightChanged}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
label="Weight"
|
||||
value={weight}
|
||||
onChange={handleWeightChanged}
|
||||
withInput
|
||||
withReset
|
||||
handleReset={handleWeightReset}
|
||||
withSliderMarks
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamControlNetWeight);
|
@ -0,0 +1,72 @@
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredCannyImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.canny_image_processor.default;
|
||||
|
||||
type CannyProcessorProps = {
|
||||
controlNetId: string;
|
||||
processorNode: RequiredCannyImageProcessorInvocation;
|
||||
};
|
||||
|
||||
const CannyProcessor = (props: CannyProcessorProps) => {
|
||||
const { controlNetId, processorNode } = props;
|
||||
const { low_threshold, high_threshold } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
|
||||
const handleLowThresholdChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { low_threshold: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleLowThresholdReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
low_threshold: DEFAULTS.low_threshold,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleHighThresholdChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { high_threshold: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleHighThresholdReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
high_threshold: DEFAULTS.high_threshold,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<IAISlider
|
||||
label="Low Threshold"
|
||||
value={low_threshold}
|
||||
onChange={handleLowThresholdChanged}
|
||||
handleReset={handleLowThresholdReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={255}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="High Threshold"
|
||||
value={high_threshold}
|
||||
onChange={handleHighThresholdChanged}
|
||||
handleReset={handleHighThresholdReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={255}
|
||||
withInput
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(CannyProcessor);
|
@ -0,0 +1,141 @@
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredContentShuffleImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.content_shuffle_image_processor.default;
|
||||
|
||||
type Props = {
|
||||
controlNetId: string;
|
||||
processorNode: RequiredContentShuffleImageProcessorInvocation;
|
||||
};
|
||||
|
||||
const ContentShuffleProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode } = props;
|
||||
const { image_resolution, detect_resolution, w, h, f } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { detect_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleDetectResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
detect_resolution: DEFAULTS.detect_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleImageResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { image_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleImageResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
image_resolution: DEFAULTS.image_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleWChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { w: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleWReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
w: DEFAULTS.w,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleHChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { h: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleHReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
h: DEFAULTS.h,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleFChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { f: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleFReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
f: DEFAULTS.f,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<IAISlider
|
||||
label="Detect Resolution"
|
||||
value={detect_resolution}
|
||||
onChange={handleDetectResolutionChanged}
|
||||
handleReset={handleDetectResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="Image Resolution"
|
||||
value={image_resolution}
|
||||
onChange={handleImageResolutionChanged}
|
||||
handleReset={handleImageResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="W"
|
||||
value={w}
|
||||
onChange={handleWChanged}
|
||||
handleReset={handleWReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="H"
|
||||
value={h}
|
||||
onChange={handleHChanged}
|
||||
handleReset={handleHReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="F"
|
||||
value={f}
|
||||
onChange={handleFChanged}
|
||||
handleReset={handleFReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ContentShuffleProcessor);
|
@ -0,0 +1,88 @@
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredHedImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.hed_image_processor.default;
|
||||
|
||||
type HedProcessorProps = {
|
||||
controlNetId: string;
|
||||
processorNode: RequiredHedImageProcessorInvocation;
|
||||
};
|
||||
|
||||
const HedPreprocessor = (props: HedProcessorProps) => {
|
||||
const {
|
||||
controlNetId,
|
||||
processorNode: { detect_resolution, image_resolution, scribble },
|
||||
} = props;
|
||||
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { detect_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleImageResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { image_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleScribbleChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
processorChanged(controlNetId, { scribble: e.target.checked });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleDetectResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
detect_resolution: DEFAULTS.detect_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleImageResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
image_resolution: DEFAULTS.image_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<IAISlider
|
||||
label="Detect Resolution"
|
||||
value={detect_resolution}
|
||||
onChange={handleDetectResolutionChanged}
|
||||
handleReset={handleDetectResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="Image Resolution"
|
||||
value={image_resolution}
|
||||
onChange={handleImageResolutionChanged}
|
||||
handleReset={handleImageResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISwitch
|
||||
label="Scribble"
|
||||
isChecked={scribble}
|
||||
onChange={handleScribbleChanged}
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(HedPreprocessor);
|
@ -0,0 +1,72 @@
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredLineartAnimeImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.lineart_anime_image_processor.default;
|
||||
|
||||
type Props = {
|
||||
controlNetId: string;
|
||||
processorNode: RequiredLineartAnimeImageProcessorInvocation;
|
||||
};
|
||||
|
||||
const LineartAnimeProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode } = props;
|
||||
const { image_resolution, detect_resolution } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { detect_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleImageResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { image_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleDetectResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
detect_resolution: DEFAULTS.detect_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleImageResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
image_resolution: DEFAULTS.image_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<IAISlider
|
||||
label="Detect Resolution"
|
||||
value={detect_resolution}
|
||||
onChange={handleDetectResolutionChanged}
|
||||
handleReset={handleDetectResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="Image Resolution"
|
||||
value={image_resolution}
|
||||
onChange={handleImageResolutionChanged}
|
||||
handleReset={handleImageResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(LineartAnimeProcessor);
|
@ -0,0 +1,85 @@
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredLineartImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.lineart_image_processor.default;
|
||||
|
||||
type LineartProcessorProps = {
|
||||
controlNetId: string;
|
||||
processorNode: RequiredLineartImageProcessorInvocation;
|
||||
};
|
||||
|
||||
const LineartProcessor = (props: LineartProcessorProps) => {
|
||||
const { controlNetId, processorNode } = props;
|
||||
const { image_resolution, detect_resolution, coarse } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { detect_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleImageResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { image_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleDetectResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
detect_resolution: DEFAULTS.detect_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleImageResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
image_resolution: DEFAULTS.image_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleCoarseChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
processorChanged(controlNetId, { coarse: e.target.checked });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<IAISlider
|
||||
label="Detect Resolution"
|
||||
value={detect_resolution}
|
||||
onChange={handleDetectResolutionChanged}
|
||||
handleReset={handleDetectResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="Image Resolution"
|
||||
value={image_resolution}
|
||||
onChange={handleImageResolutionChanged}
|
||||
handleReset={handleImageResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISwitch
|
||||
label="Coarse"
|
||||
isChecked={coarse}
|
||||
onChange={handleCoarseChanged}
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(LineartProcessor);
|
@ -0,0 +1,69 @@
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredMediapipeFaceProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.mediapipe_face_processor.default;
|
||||
|
||||
type Props = {
|
||||
controlNetId: string;
|
||||
processorNode: RequiredMediapipeFaceProcessorInvocation;
|
||||
};
|
||||
|
||||
const MediapipeFaceProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode } = props;
|
||||
const { max_faces, min_confidence } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
|
||||
const handleMaxFacesChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { max_faces: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleMinConfidenceChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { min_confidence: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleMaxFacesReset = useCallback(() => {
|
||||
processorChanged(controlNetId, { max_faces: DEFAULTS.max_faces });
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleMinConfidenceReset = useCallback(() => {
|
||||
processorChanged(controlNetId, { min_confidence: DEFAULTS.min_confidence });
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<IAISlider
|
||||
label="Max Faces"
|
||||
value={max_faces}
|
||||
onChange={handleMaxFacesChanged}
|
||||
handleReset={handleMaxFacesReset}
|
||||
withReset
|
||||
min={1}
|
||||
max={20}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="Min Confidence"
|
||||
value={min_confidence}
|
||||
onChange={handleMinConfidenceChanged}
|
||||
handleReset={handleMinConfidenceReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
withInput
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(MediapipeFaceProcessor);
|
@ -0,0 +1,70 @@
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredMidasDepthImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.midas_depth_image_processor.default;
|
||||
|
||||
type Props = {
|
||||
controlNetId: string;
|
||||
processorNode: RequiredMidasDepthImageProcessorInvocation;
|
||||
};
|
||||
|
||||
const MidasDepthProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode } = props;
|
||||
const { a_mult, bg_th } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
|
||||
const handleAMultChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { a_mult: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleBgThChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { bg_th: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleAMultReset = useCallback(() => {
|
||||
processorChanged(controlNetId, { a_mult: DEFAULTS.a_mult });
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleBgThReset = useCallback(() => {
|
||||
processorChanged(controlNetId, { bg_th: DEFAULTS.bg_th });
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<IAISlider
|
||||
label="a_mult"
|
||||
value={a_mult}
|
||||
onChange={handleAMultChanged}
|
||||
handleReset={handleAMultReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={20}
|
||||
step={0.01}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="bg_th"
|
||||
value={bg_th}
|
||||
onChange={handleBgThChanged}
|
||||
handleReset={handleBgThReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={20}
|
||||
step={0.01}
|
||||
withInput
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(MidasDepthProcessor);
|
@ -0,0 +1,116 @@
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredMlsdImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.mlsd_image_processor.default;
|
||||
|
||||
type Props = {
|
||||
controlNetId: string;
|
||||
processorNode: RequiredMlsdImageProcessorInvocation;
|
||||
};
|
||||
|
||||
const MlsdImageProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode } = props;
|
||||
const { image_resolution, detect_resolution, thr_d, thr_v } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { detect_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleImageResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { image_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleThrDChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { thr_d: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleThrVChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { thr_v: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleDetectResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
detect_resolution: DEFAULTS.detect_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleImageResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
image_resolution: DEFAULTS.image_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleThrDReset = useCallback(() => {
|
||||
processorChanged(controlNetId, { thr_d: DEFAULTS.thr_d });
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleThrVReset = useCallback(() => {
|
||||
processorChanged(controlNetId, { thr_v: DEFAULTS.thr_v });
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<IAISlider
|
||||
label="Detect Resolution"
|
||||
value={detect_resolution}
|
||||
onChange={handleDetectResolutionChanged}
|
||||
handleReset={handleDetectResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="Image Resolution"
|
||||
value={image_resolution}
|
||||
onChange={handleImageResolutionChanged}
|
||||
handleReset={handleImageResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="W"
|
||||
value={thr_d}
|
||||
onChange={handleThrDChanged}
|
||||
handleReset={handleThrDReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="H"
|
||||
value={thr_v}
|
||||
onChange={handleThrVChanged}
|
||||
handleReset={handleThrVReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
withInput
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(MlsdImageProcessor);
|
@ -0,0 +1,72 @@
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredNormalbaeImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.normalbae_image_processor.default;
|
||||
|
||||
type Props = {
|
||||
controlNetId: string;
|
||||
processorNode: RequiredNormalbaeImageProcessorInvocation;
|
||||
};
|
||||
|
||||
const NormalBaeProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode } = props;
|
||||
const { image_resolution, detect_resolution } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { detect_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleImageResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { image_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleDetectResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
detect_resolution: DEFAULTS.detect_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleImageResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
image_resolution: DEFAULTS.image_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<IAISlider
|
||||
label="Detect Resolution"
|
||||
value={detect_resolution}
|
||||
onChange={handleDetectResolutionChanged}
|
||||
handleReset={handleDetectResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="Image Resolution"
|
||||
value={image_resolution}
|
||||
onChange={handleImageResolutionChanged}
|
||||
handleReset={handleImageResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(NormalBaeProcessor);
|
@ -0,0 +1,85 @@
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredOpenposeImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.openpose_image_processor.default;
|
||||
|
||||
type Props = {
|
||||
controlNetId: string;
|
||||
processorNode: RequiredOpenposeImageProcessorInvocation;
|
||||
};
|
||||
|
||||
const OpenposeProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode } = props;
|
||||
const { image_resolution, detect_resolution, hand_and_face } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { detect_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleImageResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { image_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleDetectResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
detect_resolution: DEFAULTS.detect_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleImageResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
image_resolution: DEFAULTS.image_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleHandAndFaceChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
processorChanged(controlNetId, { hand_and_face: e.target.checked });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<IAISlider
|
||||
label="Detect Resolution"
|
||||
value={detect_resolution}
|
||||
onChange={handleDetectResolutionChanged}
|
||||
handleReset={handleDetectResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="Image Resolution"
|
||||
value={image_resolution}
|
||||
onChange={handleImageResolutionChanged}
|
||||
handleReset={handleImageResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISwitch
|
||||
label="Hand and Face"
|
||||
isChecked={hand_and_face}
|
||||
onChange={handleHandAndFaceChanged}
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(OpenposeProcessor);
|
@ -0,0 +1,93 @@
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredPidiImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.pidi_image_processor.default;
|
||||
|
||||
type Props = {
|
||||
controlNetId: string;
|
||||
processorNode: RequiredPidiImageProcessorInvocation;
|
||||
};
|
||||
|
||||
const PidiProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode } = props;
|
||||
const { image_resolution, detect_resolution, scribble, safe } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { detect_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleImageResolutionChanged = useCallback(
|
||||
(v: number) => {
|
||||
processorChanged(controlNetId, { image_resolution: v });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleDetectResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
detect_resolution: DEFAULTS.detect_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleImageResolutionReset = useCallback(() => {
|
||||
processorChanged(controlNetId, {
|
||||
image_resolution: DEFAULTS.image_resolution,
|
||||
});
|
||||
}, [controlNetId, processorChanged]);
|
||||
|
||||
const handleScribbleChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
processorChanged(controlNetId, { scribble: e.target.checked });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
const handleSafeChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
processorChanged(controlNetId, { safe: e.target.checked });
|
||||
},
|
||||
[controlNetId, processorChanged]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<IAISlider
|
||||
label="Detect Resolution"
|
||||
value={detect_resolution}
|
||||
onChange={handleDetectResolutionChanged}
|
||||
handleReset={handleDetectResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISlider
|
||||
label="Image Resolution"
|
||||
value={image_resolution}
|
||||
onChange={handleImageResolutionChanged}
|
||||
handleReset={handleImageResolutionReset}
|
||||
withReset
|
||||
min={0}
|
||||
max={4096}
|
||||
withInput
|
||||
/>
|
||||
<IAISwitch
|
||||
label="Scribble"
|
||||
isChecked={scribble}
|
||||
onChange={handleScribbleChanged}
|
||||
/>
|
||||
<IAISwitch label="Safe" isChecked={safe} onChange={handleSafeChanged} />
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(PidiProcessor);
|
@ -0,0 +1,14 @@
|
||||
import { RequiredZoeDepthImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { memo } from 'react';
|
||||
|
||||
type Props = {
|
||||
controlNetId: string;
|
||||
processorNode: RequiredZoeDepthImageProcessorInvocation;
|
||||
};
|
||||
|
||||
const ZoeDepthProcessor = (props: Props) => {
|
||||
// Has no parameters?
|
||||
return null;
|
||||
};
|
||||
|
||||
export default memo(ZoeDepthProcessor);
|
@ -0,0 +1,8 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
type Props = PropsWithChildren;
|
||||
|
||||
export default function ProcessorWrapper(props: Props) {
|
||||
return <Flex sx={{ flexDirection: 'column', gap: 2 }}>{props.children}</Flex>;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
|
||||
export const controlNetImageProcessed = createAction<{
|
||||
controlNetId: string;
|
||||
}>('controlNet/imageProcessed');
|
212
invokeai/frontend/web/src/features/controlNet/store/constants.ts
Normal file
212
invokeai/frontend/web/src/features/controlNet/store/constants.ts
Normal file
@ -0,0 +1,212 @@
|
||||
import {
|
||||
ControlNetProcessorType,
|
||||
RequiredCannyImageProcessorInvocation,
|
||||
RequiredControlNetProcessorNode,
|
||||
} from './types';
|
||||
|
||||
type ControlNetProcessorsDict = Record<
|
||||
ControlNetProcessorType,
|
||||
{
|
||||
type: ControlNetProcessorType;
|
||||
label: string;
|
||||
description: string;
|
||||
default: RequiredControlNetProcessorNode;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* A dict of ControlNet processors, including:
|
||||
* - type
|
||||
* - label
|
||||
* - description
|
||||
* - default values
|
||||
*
|
||||
* TODO: Generate from the OpenAPI schema
|
||||
*/
|
||||
export const CONTROLNET_PROCESSORS = {
|
||||
none: {
|
||||
type: 'none',
|
||||
label: 'None',
|
||||
description: '',
|
||||
default: {
|
||||
type: 'none',
|
||||
},
|
||||
},
|
||||
canny_image_processor: {
|
||||
type: 'canny_image_processor',
|
||||
label: 'Canny',
|
||||
description: '',
|
||||
default: {
|
||||
id: 'canny_image_processor',
|
||||
type: 'canny_image_processor',
|
||||
low_threshold: 100,
|
||||
high_threshold: 200,
|
||||
},
|
||||
},
|
||||
content_shuffle_image_processor: {
|
||||
type: 'content_shuffle_image_processor',
|
||||
label: 'Content Shuffle',
|
||||
description: '',
|
||||
default: {
|
||||
id: 'content_shuffle_image_processor',
|
||||
type: 'content_shuffle_image_processor',
|
||||
detect_resolution: 512,
|
||||
image_resolution: 512,
|
||||
h: 512,
|
||||
w: 512,
|
||||
f: 256,
|
||||
},
|
||||
},
|
||||
hed_image_processor: {
|
||||
type: 'hed_image_processor',
|
||||
label: 'HED',
|
||||
description: '',
|
||||
default: {
|
||||
id: 'hed_image_processor',
|
||||
type: 'hed_image_processor',
|
||||
detect_resolution: 512,
|
||||
image_resolution: 512,
|
||||
scribble: false,
|
||||
},
|
||||
},
|
||||
lineart_anime_image_processor: {
|
||||
type: 'lineart_anime_image_processor',
|
||||
label: 'Lineart Anime',
|
||||
description: '',
|
||||
default: {
|
||||
id: 'lineart_anime_image_processor',
|
||||
type: 'lineart_anime_image_processor',
|
||||
detect_resolution: 512,
|
||||
image_resolution: 512,
|
||||
},
|
||||
},
|
||||
lineart_image_processor: {
|
||||
type: 'lineart_image_processor',
|
||||
label: 'Lineart',
|
||||
description: '',
|
||||
default: {
|
||||
id: 'lineart_image_processor',
|
||||
type: 'lineart_image_processor',
|
||||
detect_resolution: 512,
|
||||
image_resolution: 512,
|
||||
coarse: false,
|
||||
},
|
||||
},
|
||||
mediapipe_face_processor: {
|
||||
type: 'mediapipe_face_processor',
|
||||
label: 'Mediapipe Face',
|
||||
description: '',
|
||||
default: {
|
||||
id: 'mediapipe_face_processor',
|
||||
type: 'mediapipe_face_processor',
|
||||
max_faces: 1,
|
||||
min_confidence: 0.5,
|
||||
},
|
||||
},
|
||||
midas_depth_image_processor: {
|
||||
type: 'midas_depth_image_processor',
|
||||
label: 'Depth (Midas)',
|
||||
description: '',
|
||||
default: {
|
||||
id: 'midas_depth_image_processor',
|
||||
type: 'midas_depth_image_processor',
|
||||
a_mult: 2,
|
||||
bg_th: 0.1,
|
||||
},
|
||||
},
|
||||
mlsd_image_processor: {
|
||||
type: 'mlsd_image_processor',
|
||||
label: 'MLSD',
|
||||
description: '',
|
||||
default: {
|
||||
id: 'mlsd_image_processor',
|
||||
type: 'mlsd_image_processor',
|
||||
detect_resolution: 512,
|
||||
image_resolution: 512,
|
||||
thr_d: 0.1,
|
||||
thr_v: 0.1,
|
||||
},
|
||||
},
|
||||
normalbae_image_processor: {
|
||||
type: 'normalbae_image_processor',
|
||||
label: 'NormalBae',
|
||||
description: '',
|
||||
default: {
|
||||
id: 'normalbae_image_processor',
|
||||
type: 'normalbae_image_processor',
|
||||
detect_resolution: 512,
|
||||
image_resolution: 512,
|
||||
},
|
||||
},
|
||||
openpose_image_processor: {
|
||||
type: 'openpose_image_processor',
|
||||
label: 'Openpose',
|
||||
description: '',
|
||||
default: {
|
||||
id: 'openpose_image_processor',
|
||||
type: 'openpose_image_processor',
|
||||
detect_resolution: 512,
|
||||
image_resolution: 512,
|
||||
hand_and_face: false,
|
||||
},
|
||||
},
|
||||
pidi_image_processor: {
|
||||
type: 'pidi_image_processor',
|
||||
label: 'PIDI',
|
||||
description: '',
|
||||
default: {
|
||||
id: 'pidi_image_processor',
|
||||
type: 'pidi_image_processor',
|
||||
detect_resolution: 512,
|
||||
image_resolution: 512,
|
||||
scribble: false,
|
||||
safe: false,
|
||||
},
|
||||
},
|
||||
zoe_depth_image_processor: {
|
||||
type: 'zoe_depth_image_processor',
|
||||
label: 'Depth (Zoe)',
|
||||
description: '',
|
||||
default: {
|
||||
id: 'zoe_depth_image_processor',
|
||||
type: 'zoe_depth_image_processor',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const CONTROLNET_MODELS = [
|
||||
'lllyasviel/control_v11p_sd15_canny',
|
||||
'lllyasviel/control_v11p_sd15_inpaint',
|
||||
'lllyasviel/control_v11p_sd15_mlsd',
|
||||
'lllyasviel/control_v11f1p_sd15_depth',
|
||||
'lllyasviel/control_v11p_sd15_normalbae',
|
||||
'lllyasviel/control_v11p_sd15_seg',
|
||||
'lllyasviel/control_v11p_sd15_lineart',
|
||||
'lllyasviel/control_v11p_sd15s2_lineart_anime',
|
||||
'lllyasviel/control_v11p_sd15_scribble',
|
||||
'lllyasviel/control_v11p_sd15_softedge',
|
||||
'lllyasviel/control_v11e_sd15_shuffle',
|
||||
'lllyasviel/control_v11p_sd15_openpose',
|
||||
'lllyasviel/control_v11f1e_sd15_tile',
|
||||
'lllyasviel/control_v11e_sd15_ip2p',
|
||||
'CrucibleAI/ControlNetMediaPipeFace',
|
||||
];
|
||||
|
||||
export type ControlNetModel = (typeof CONTROLNET_MODELS)[number];
|
||||
|
||||
export const CONTROLNET_MODEL_MAP: Record<
|
||||
ControlNetModel,
|
||||
ControlNetProcessorType
|
||||
> = {
|
||||
'lllyasviel/control_v11p_sd15_canny': 'canny_image_processor',
|
||||
'lllyasviel/control_v11p_sd15_mlsd': 'mlsd_image_processor',
|
||||
'lllyasviel/control_v11f1p_sd15_depth': 'midas_depth_image_processor',
|
||||
'lllyasviel/control_v11p_sd15_normalbae': 'normalbae_image_processor',
|
||||
'lllyasviel/control_v11p_sd15_lineart': 'lineart_image_processor',
|
||||
'lllyasviel/control_v11p_sd15s2_lineart_anime':
|
||||
'lineart_anime_image_processor',
|
||||
'lllyasviel/control_v11p_sd15_softedge': 'hed_image_processor',
|
||||
'lllyasviel/control_v11e_sd15_shuffle': 'content_shuffle_image_processor',
|
||||
'lllyasviel/control_v11p_sd15_openpose': 'openpose_image_processor',
|
||||
'CrucibleAI/ControlNetMediaPipeFace': 'mediapipe_face_processor',
|
||||
};
|
@ -0,0 +1,8 @@
|
||||
import { ControlNetState } from './controlNetSlice';
|
||||
|
||||
/**
|
||||
* ControlNet slice persist denylist
|
||||
*/
|
||||
export const controlNetDenylist: (keyof ControlNetState)[] = [
|
||||
'isProcessingControlImage',
|
||||
];
|
@ -0,0 +1,218 @@
|
||||
import { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import {
|
||||
ControlNetProcessorType,
|
||||
RequiredCannyImageProcessorInvocation,
|
||||
RequiredControlNetProcessorNode,
|
||||
} from './types';
|
||||
import {
|
||||
CONTROLNET_MODELS,
|
||||
CONTROLNET_PROCESSORS,
|
||||
ControlNetModel,
|
||||
} from './constants';
|
||||
import { controlNetImageProcessed } from './actions';
|
||||
|
||||
export const initialControlNet: Omit<ControlNetConfig, 'controlNetId'> = {
|
||||
isEnabled: true,
|
||||
model: CONTROLNET_MODELS[0],
|
||||
weight: 1,
|
||||
beginStepPct: 0,
|
||||
endStepPct: 1,
|
||||
controlImage: null,
|
||||
processedControlImage: null,
|
||||
processorType: 'canny_image_processor',
|
||||
processorNode: CONTROLNET_PROCESSORS.canny_image_processor
|
||||
.default as RequiredCannyImageProcessorInvocation,
|
||||
};
|
||||
|
||||
export type ControlNetConfig = {
|
||||
controlNetId: string;
|
||||
isEnabled: boolean;
|
||||
model: ControlNetModel;
|
||||
weight: number;
|
||||
beginStepPct: number;
|
||||
endStepPct: number;
|
||||
controlImage: ImageDTO | null;
|
||||
processedControlImage: ImageDTO | null;
|
||||
processorType: ControlNetProcessorType;
|
||||
processorNode: RequiredControlNetProcessorNode;
|
||||
};
|
||||
|
||||
export type ControlNetState = {
|
||||
controlNets: Record<string, ControlNetConfig>;
|
||||
isEnabled: boolean;
|
||||
isProcessingControlImage: boolean;
|
||||
};
|
||||
|
||||
export const initialControlNetState: ControlNetState = {
|
||||
controlNets: {},
|
||||
isEnabled: false,
|
||||
isProcessingControlImage: false,
|
||||
};
|
||||
|
||||
export const controlNetSlice = createSlice({
|
||||
name: 'controlNet',
|
||||
initialState: initialControlNetState,
|
||||
reducers: {
|
||||
isControlNetEnabledToggled: (state) => {
|
||||
state.isEnabled = !state.isEnabled;
|
||||
},
|
||||
controlNetAdded: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
controlNetId: string;
|
||||
controlNet?: ControlNetConfig;
|
||||
}>
|
||||
) => {
|
||||
const { controlNetId, controlNet } = action.payload;
|
||||
state.controlNets[controlNetId] = {
|
||||
...(controlNet ?? initialControlNet),
|
||||
controlNetId,
|
||||
};
|
||||
},
|
||||
controlNetAddedFromImage: (
|
||||
state,
|
||||
action: PayloadAction<{ controlNetId: string; controlImage: ImageDTO }>
|
||||
) => {
|
||||
const { controlNetId, controlImage } = action.payload;
|
||||
state.controlNets[controlNetId] = {
|
||||
...initialControlNet,
|
||||
controlNetId,
|
||||
controlImage,
|
||||
};
|
||||
},
|
||||
controlNetRemoved: (
|
||||
state,
|
||||
action: PayloadAction<{ controlNetId: string }>
|
||||
) => {
|
||||
const { controlNetId } = action.payload;
|
||||
delete state.controlNets[controlNetId];
|
||||
},
|
||||
controlNetToggled: (
|
||||
state,
|
||||
action: PayloadAction<{ controlNetId: string }>
|
||||
) => {
|
||||
const { controlNetId } = action.payload;
|
||||
state.controlNets[controlNetId].isEnabled =
|
||||
!state.controlNets[controlNetId].isEnabled;
|
||||
},
|
||||
controlNetImageChanged: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
controlNetId: string;
|
||||
controlImage: ImageDTO | null;
|
||||
}>
|
||||
) => {
|
||||
const { controlNetId, controlImage } = action.payload;
|
||||
state.controlNets[controlNetId].controlImage = controlImage;
|
||||
state.controlNets[controlNetId].processedControlImage = null;
|
||||
if (
|
||||
controlImage !== null &&
|
||||
state.controlNets[controlNetId].processorType !== 'none'
|
||||
) {
|
||||
state.isProcessingControlImage = true;
|
||||
}
|
||||
},
|
||||
controlNetProcessedImageChanged: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
controlNetId: string;
|
||||
processedControlImage: ImageDTO | null;
|
||||
}>
|
||||
) => {
|
||||
const { controlNetId, processedControlImage } = action.payload;
|
||||
state.controlNets[controlNetId].processedControlImage =
|
||||
processedControlImage;
|
||||
state.isProcessingControlImage = false;
|
||||
},
|
||||
controlNetModelChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ controlNetId: string; model: ControlNetModel }>
|
||||
) => {
|
||||
const { controlNetId, model } = action.payload;
|
||||
state.controlNets[controlNetId].model = model;
|
||||
},
|
||||
controlNetWeightChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ controlNetId: string; weight: number }>
|
||||
) => {
|
||||
const { controlNetId, weight } = action.payload;
|
||||
state.controlNets[controlNetId].weight = weight;
|
||||
},
|
||||
controlNetBeginStepPctChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ controlNetId: string; beginStepPct: number }>
|
||||
) => {
|
||||
const { controlNetId, beginStepPct } = action.payload;
|
||||
state.controlNets[controlNetId].beginStepPct = beginStepPct;
|
||||
},
|
||||
controlNetEndStepPctChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ controlNetId: string; endStepPct: number }>
|
||||
) => {
|
||||
const { controlNetId, endStepPct } = action.payload;
|
||||
state.controlNets[controlNetId].endStepPct = endStepPct;
|
||||
},
|
||||
controlNetProcessorParamsChanged: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
controlNetId: string;
|
||||
changes: Omit<
|
||||
Partial<RequiredControlNetProcessorNode>,
|
||||
'id' | 'type' | 'is_intermediate'
|
||||
>;
|
||||
}>
|
||||
) => {
|
||||
const { controlNetId, changes } = action.payload;
|
||||
const processorNode = state.controlNets[controlNetId].processorNode;
|
||||
state.controlNets[controlNetId].processorNode = {
|
||||
...processorNode,
|
||||
...changes,
|
||||
};
|
||||
},
|
||||
controlNetProcessorTypeChanged: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
controlNetId: string;
|
||||
processorType: ControlNetProcessorType;
|
||||
}>
|
||||
) => {
|
||||
const { controlNetId, processorType } = action.payload;
|
||||
state.controlNets[controlNetId].processorType = processorType;
|
||||
state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS[
|
||||
processorType
|
||||
].default as RequiredControlNetProcessorNode;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(controlNetImageProcessed, (state, action) => {
|
||||
if (
|
||||
state.controlNets[action.payload.controlNetId].controlImage !== null
|
||||
) {
|
||||
state.isProcessingControlImage = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
isControlNetEnabledToggled,
|
||||
controlNetAdded,
|
||||
controlNetAddedFromImage,
|
||||
controlNetRemoved,
|
||||
controlNetImageChanged,
|
||||
controlNetProcessedImageChanged,
|
||||
controlNetToggled,
|
||||
controlNetModelChanged,
|
||||
controlNetWeightChanged,
|
||||
controlNetBeginStepPctChanged,
|
||||
controlNetEndStepPctChanged,
|
||||
controlNetProcessorParamsChanged,
|
||||
controlNetProcessorTypeChanged,
|
||||
} = controlNetSlice.actions;
|
||||
|
||||
export default controlNetSlice.reducer;
|
||||
|
||||
export const controlNetSelector = (state: RootState) => state.controlNet;
|
329
invokeai/frontend/web/src/features/controlNet/store/types.ts
Normal file
329
invokeai/frontend/web/src/features/controlNet/store/types.ts
Normal file
@ -0,0 +1,329 @@
|
||||
import { isObject } from 'lodash-es';
|
||||
import {
|
||||
CannyImageProcessorInvocation,
|
||||
ContentShuffleImageProcessorInvocation,
|
||||
HedImageProcessorInvocation,
|
||||
LineartAnimeImageProcessorInvocation,
|
||||
LineartImageProcessorInvocation,
|
||||
MediapipeFaceProcessorInvocation,
|
||||
MidasDepthImageProcessorInvocation,
|
||||
MlsdImageProcessorInvocation,
|
||||
NormalbaeImageProcessorInvocation,
|
||||
OpenposeImageProcessorInvocation,
|
||||
PidiImageProcessorInvocation,
|
||||
ZoeDepthImageProcessorInvocation,
|
||||
} from 'services/api';
|
||||
import { O } from 'ts-toolbelt';
|
||||
|
||||
/**
|
||||
* Any ControlNet processor node
|
||||
*/
|
||||
export type ControlNetProcessorNode =
|
||||
| CannyImageProcessorInvocation
|
||||
| ContentShuffleImageProcessorInvocation
|
||||
| HedImageProcessorInvocation
|
||||
| LineartAnimeImageProcessorInvocation
|
||||
| LineartImageProcessorInvocation
|
||||
| MediapipeFaceProcessorInvocation
|
||||
| MidasDepthImageProcessorInvocation
|
||||
| MlsdImageProcessorInvocation
|
||||
| NormalbaeImageProcessorInvocation
|
||||
| OpenposeImageProcessorInvocation
|
||||
| PidiImageProcessorInvocation
|
||||
| ZoeDepthImageProcessorInvocation;
|
||||
|
||||
/**
|
||||
* Any ControlNet processor type
|
||||
*/
|
||||
export type ControlNetProcessorType = NonNullable<
|
||||
ControlNetProcessorNode['type'] | 'none'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The Canny processor node, with parameters flagged as required
|
||||
*/
|
||||
export type RequiredCannyImageProcessorInvocation = O.Required<
|
||||
CannyImageProcessorInvocation,
|
||||
'type' | 'low_threshold' | 'high_threshold'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The ContentShuffle processor node, with parameters flagged as required
|
||||
*/
|
||||
export type RequiredContentShuffleImageProcessorInvocation = O.Required<
|
||||
ContentShuffleImageProcessorInvocation,
|
||||
'type' | 'detect_resolution' | 'image_resolution' | 'w' | 'h' | 'f'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The HED processor node, with parameters flagged as required
|
||||
*/
|
||||
export type RequiredHedImageProcessorInvocation = O.Required<
|
||||
HedImageProcessorInvocation,
|
||||
'type' | 'detect_resolution' | 'image_resolution' | 'scribble'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The Lineart Anime processor node, with parameters flagged as required
|
||||
*/
|
||||
export type RequiredLineartAnimeImageProcessorInvocation = O.Required<
|
||||
LineartAnimeImageProcessorInvocation,
|
||||
'type' | 'detect_resolution' | 'image_resolution'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The Lineart processor node, with parameters flagged as required
|
||||
*/
|
||||
export type RequiredLineartImageProcessorInvocation = O.Required<
|
||||
LineartImageProcessorInvocation,
|
||||
'type' | 'detect_resolution' | 'image_resolution' | 'coarse'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The MediapipeFace processor node, with parameters flagged as required
|
||||
*/
|
||||
export type RequiredMediapipeFaceProcessorInvocation = O.Required<
|
||||
MediapipeFaceProcessorInvocation,
|
||||
'type' | 'max_faces' | 'min_confidence'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The MidasDepth processor node, with parameters flagged as required
|
||||
*/
|
||||
export type RequiredMidasDepthImageProcessorInvocation = O.Required<
|
||||
MidasDepthImageProcessorInvocation,
|
||||
'type' | 'a_mult' | 'bg_th'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The MLSD processor node, with parameters flagged as required
|
||||
*/
|
||||
export type RequiredMlsdImageProcessorInvocation = O.Required<
|
||||
MlsdImageProcessorInvocation,
|
||||
'type' | 'detect_resolution' | 'image_resolution' | 'thr_v' | 'thr_d'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The NormalBae processor node, with parameters flagged as required
|
||||
*/
|
||||
export type RequiredNormalbaeImageProcessorInvocation = O.Required<
|
||||
NormalbaeImageProcessorInvocation,
|
||||
'type' | 'detect_resolution' | 'image_resolution'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The Openpose processor node, with parameters flagged as required
|
||||
*/
|
||||
export type RequiredOpenposeImageProcessorInvocation = O.Required<
|
||||
OpenposeImageProcessorInvocation,
|
||||
'type' | 'detect_resolution' | 'image_resolution' | 'hand_and_face'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The Pidi processor node, with parameters flagged as required
|
||||
*/
|
||||
export type RequiredPidiImageProcessorInvocation = O.Required<
|
||||
PidiImageProcessorInvocation,
|
||||
'type' | 'detect_resolution' | 'image_resolution' | 'safe' | 'scribble'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The ZoeDepth processor node, with parameters flagged as required
|
||||
*/
|
||||
export type RequiredZoeDepthImageProcessorInvocation = O.Required<
|
||||
ZoeDepthImageProcessorInvocation,
|
||||
'type'
|
||||
>;
|
||||
|
||||
/**
|
||||
* Any ControlNet Processor node, with its parameters flagged as required
|
||||
*/
|
||||
export type RequiredControlNetProcessorNode =
|
||||
| RequiredCannyImageProcessorInvocation
|
||||
| RequiredContentShuffleImageProcessorInvocation
|
||||
| RequiredHedImageProcessorInvocation
|
||||
| RequiredLineartAnimeImageProcessorInvocation
|
||||
| RequiredLineartImageProcessorInvocation
|
||||
| RequiredMediapipeFaceProcessorInvocation
|
||||
| RequiredMidasDepthImageProcessorInvocation
|
||||
| RequiredMlsdImageProcessorInvocation
|
||||
| RequiredNormalbaeImageProcessorInvocation
|
||||
| RequiredOpenposeImageProcessorInvocation
|
||||
| RequiredPidiImageProcessorInvocation
|
||||
| RequiredZoeDepthImageProcessorInvocation;
|
||||
|
||||
/**
|
||||
* Type guard for CannyImageProcessorInvocation
|
||||
*/
|
||||
export const isCannyImageProcessorInvocation = (
|
||||
obj: unknown
|
||||
): obj is CannyImageProcessorInvocation => {
|
||||
if (isObject(obj) && 'type' in obj && obj.type === 'canny_image_processor') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard for ContentShuffleImageProcessorInvocation
|
||||
*/
|
||||
export const isContentShuffleImageProcessorInvocation = (
|
||||
obj: unknown
|
||||
): obj is ContentShuffleImageProcessorInvocation => {
|
||||
if (
|
||||
isObject(obj) &&
|
||||
'type' in obj &&
|
||||
obj.type === 'content_shuffle_image_processor'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard for HedImageprocessorInvocation
|
||||
*/
|
||||
export const isHedImageprocessorInvocation = (
|
||||
obj: unknown
|
||||
): obj is HedImageProcessorInvocation => {
|
||||
if (isObject(obj) && 'type' in obj && obj.type === 'hed_image_processor') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard for LineartAnimeImageProcessorInvocation
|
||||
*/
|
||||
export const isLineartAnimeImageProcessorInvocation = (
|
||||
obj: unknown
|
||||
): obj is LineartAnimeImageProcessorInvocation => {
|
||||
if (
|
||||
isObject(obj) &&
|
||||
'type' in obj &&
|
||||
obj.type === 'lineart_anime_image_processor'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard for LineartImageProcessorInvocation
|
||||
*/
|
||||
export const isLineartImageProcessorInvocation = (
|
||||
obj: unknown
|
||||
): obj is LineartImageProcessorInvocation => {
|
||||
if (
|
||||
isObject(obj) &&
|
||||
'type' in obj &&
|
||||
obj.type === 'lineart_image_processor'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard for MediapipeFaceProcessorInvocation
|
||||
*/
|
||||
export const isMediapipeFaceProcessorInvocation = (
|
||||
obj: unknown
|
||||
): obj is MediapipeFaceProcessorInvocation => {
|
||||
if (
|
||||
isObject(obj) &&
|
||||
'type' in obj &&
|
||||
obj.type === 'mediapipe_face_processor'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard for MidasDepthImageProcessorInvocation
|
||||
*/
|
||||
export const isMidasDepthImageProcessorInvocation = (
|
||||
obj: unknown
|
||||
): obj is MidasDepthImageProcessorInvocation => {
|
||||
if (
|
||||
isObject(obj) &&
|
||||
'type' in obj &&
|
||||
obj.type === 'midas_depth_image_processor'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard for MlsdImageProcessorInvocation
|
||||
*/
|
||||
export const isMlsdImageProcessorInvocation = (
|
||||
obj: unknown
|
||||
): obj is MlsdImageProcessorInvocation => {
|
||||
if (isObject(obj) && 'type' in obj && obj.type === 'mlsd_image_processor') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard for NormalbaeImageProcessorInvocation
|
||||
*/
|
||||
export const isNormalbaeImageProcessorInvocation = (
|
||||
obj: unknown
|
||||
): obj is NormalbaeImageProcessorInvocation => {
|
||||
if (
|
||||
isObject(obj) &&
|
||||
'type' in obj &&
|
||||
obj.type === 'normalbae_image_processor'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard for OpenposeImageProcessorInvocation
|
||||
*/
|
||||
export const isOpenposeImageProcessorInvocation = (
|
||||
obj: unknown
|
||||
): obj is OpenposeImageProcessorInvocation => {
|
||||
if (
|
||||
isObject(obj) &&
|
||||
'type' in obj &&
|
||||
obj.type === 'openpose_image_processor'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard for PidiImageProcessorInvocation
|
||||
*/
|
||||
export const isPidiImageProcessorInvocation = (
|
||||
obj: unknown
|
||||
): obj is PidiImageProcessorInvocation => {
|
||||
if (isObject(obj) && 'type' in obj && obj.type === 'pidi_image_processor') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard for ZoeDepthImageProcessorInvocation
|
||||
*/
|
||||
export const isZoeDepthImageProcessorInvocation = (
|
||||
obj: unknown
|
||||
): obj is ZoeDepthImageProcessorInvocation => {
|
||||
if (
|
||||
isObject(obj) &&
|
||||
'type' in obj &&
|
||||
obj.type === 'zoe_depth_image_processor'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { Flex, Icon } from '@chakra-ui/react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
@ -7,7 +7,7 @@ import { isEqual } from 'lodash-es';
|
||||
import { gallerySelector } from '../store/gallerySelectors';
|
||||
import CurrentImageButtons from './CurrentImageButtons';
|
||||
import CurrentImagePreview from './CurrentImagePreview';
|
||||
import { FaImage } from 'react-icons/fa';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
|
||||
export const currentImageDisplaySelector = createSelector(
|
||||
[systemSelector, gallerySelector],
|
||||
@ -15,21 +15,20 @@ export const currentImageDisplaySelector = createSelector(
|
||||
const { progressImage } = system;
|
||||
|
||||
return {
|
||||
hasAnImageToDisplay: gallery.selectedImage || progressImage,
|
||||
hasSelectedImage: Boolean(gallery.selectedImage),
|
||||
hasProgressImage: Boolean(progressImage),
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: isEqual,
|
||||
},
|
||||
}
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
/**
|
||||
* Displays the current image if there is one, plus associated actions.
|
||||
*/
|
||||
const CurrentImageDisplay = () => {
|
||||
const { hasAnImageToDisplay } = useAppSelector(currentImageDisplaySelector);
|
||||
const { hasSelectedImage, hasProgressImage } = useAppSelector(
|
||||
currentImageDisplaySelector
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@ -54,21 +53,13 @@ const CurrentImageDisplay = () => {
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
{hasAnImageToDisplay ? (
|
||||
<>
|
||||
<CurrentImageButtons />
|
||||
<CurrentImagePreview />
|
||||
</>
|
||||
) : (
|
||||
<Icon
|
||||
as={FaImage}
|
||||
sx={{
|
||||
boxSize: 24,
|
||||
color: 'base.500',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<CurrentImagePreview />
|
||||
</Flex>
|
||||
{hasSelectedImage && (
|
||||
<Box sx={{ position: 'absolute', top: 0 }}>
|
||||
<CurrentImageButtons />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -1,20 +1,20 @@
|
||||
import { Box, Flex, Image } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useGetUrl } from 'common/util/getUrl';
|
||||
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import { gallerySelector } from '../store/gallerySelectors';
|
||||
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
|
||||
import NextPrevImageButtons from './NextPrevImageButtons';
|
||||
import { DragEvent, memo, useCallback } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import ImageFallbackSpinner from './ImageFallbackSpinner';
|
||||
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
||||
import { configSelector } from '../../system/store/configSelectors';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { imageSelected } from '../store/gallerySlice';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
||||
|
||||
export const imagesSelector = createSelector(
|
||||
[uiSelector, gallerySelector, systemSelector],
|
||||
@ -46,27 +46,14 @@ const CurrentImagePreview = () => {
|
||||
const {
|
||||
shouldShowImageDetails,
|
||||
image,
|
||||
shouldHidePreview,
|
||||
progressImage,
|
||||
shouldShowProgressInViewer,
|
||||
shouldAntialiasProgressImage,
|
||||
} = useAppSelector(imagesSelector);
|
||||
const { shouldFetchImages } = useAppSelector(configSelector);
|
||||
const { getUrl } = useGetUrl();
|
||||
const toaster = useAppToaster();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
e.dataTransfer.setData('invokeai/imageName', image.image_name);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
},
|
||||
[image]
|
||||
);
|
||||
|
||||
const handleError = useCallback(() => {
|
||||
dispatch(imageSelected());
|
||||
if (shouldFetchImages) {
|
||||
@ -78,11 +65,21 @@ const CurrentImagePreview = () => {
|
||||
}
|
||||
}, [dispatch, toaster, shouldFetchImages]);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(droppedImage: ImageDTO) => {
|
||||
if (droppedImage.image_name === image?.image_name) {
|
||||
return;
|
||||
}
|
||||
dispatch(imageSelected(droppedImage));
|
||||
},
|
||||
[dispatch, image?.image_name]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
width: 'full',
|
||||
height: 'full',
|
||||
position: 'relative',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
@ -95,8 +92,8 @@ const CurrentImagePreview = () => {
|
||||
height={progressImage.height}
|
||||
sx={{
|
||||
objectFit: 'contain',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
maxWidth: 'full',
|
||||
maxHeight: 'full',
|
||||
height: 'auto',
|
||||
position: 'absolute',
|
||||
borderRadius: 'base',
|
||||
@ -104,34 +101,29 @@ const CurrentImagePreview = () => {
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
image && (
|
||||
<>
|
||||
<Image
|
||||
src={getUrl(image.image_url)}
|
||||
fallbackStrategy="beforeLoadOrError"
|
||||
fallback={<ImageFallbackSpinner />}
|
||||
onDragStart={handleDragStart}
|
||||
sx={{
|
||||
objectFit: 'contain',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
height: 'auto',
|
||||
position: 'absolute',
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
onError={handleError}
|
||||
/>
|
||||
<ImageMetadataOverlay image={image} />
|
||||
</>
|
||||
)
|
||||
<Flex
|
||||
sx={{
|
||||
width: 'full',
|
||||
height: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<IAIDndImage
|
||||
image={image}
|
||||
onDrop={handleDrop}
|
||||
onError={handleError}
|
||||
fallback={<IAIImageFallback sx={{ bg: 'none' }} />}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{shouldShowImageDetails && image && 'metadata' in image && (
|
||||
{shouldShowImageDetails && image && image.metadata && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '0',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
width: 'full',
|
||||
height: 'full',
|
||||
borderRadius: 'base',
|
||||
overflow: 'scroll',
|
||||
}}
|
||||
@ -139,7 +131,19 @@ const CurrentImagePreview = () => {
|
||||
<ImageMetadataViewer image={image} />
|
||||
</Box>
|
||||
)}
|
||||
{!shouldShowImageDetails && <NextPrevImageButtons />}
|
||||
{!shouldShowImageDetails && image && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '0',
|
||||
width: 'full',
|
||||
height: 'full',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
>
|
||||
<NextPrevImageButtons />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -39,6 +39,7 @@ import {
|
||||
} from '../store/actions';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { useDraggable } from '@dnd-kit/core';
|
||||
|
||||
export const selector = createSelector(
|
||||
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
||||
@ -117,6 +118,13 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
||||
useRecallParameters();
|
||||
|
||||
const { attributes, listeners, setNodeRef } = useDraggable({
|
||||
id: `galleryImage_${image_name}`,
|
||||
data: {
|
||||
image,
|
||||
},
|
||||
});
|
||||
|
||||
const handleMouseOver = () => setIsHovered(true);
|
||||
const handleMouseOut = () => setIsHovered(false);
|
||||
|
||||
@ -144,14 +152,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
dispatch(imageSelected(image));
|
||||
}, [image, dispatch]);
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
e.dataTransfer.setData('invokeai/imageName', image.image_name);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
},
|
||||
[image]
|
||||
);
|
||||
|
||||
// Recall parameters handlers
|
||||
const handleRecallPrompt = useCallback(() => {
|
||||
recallBothPrompts(
|
||||
@ -212,7 +212,12 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
ref={setNodeRef}
|
||||
{...listeners}
|
||||
{...attributes}
|
||||
sx={{ w: 'full', h: 'full', touchAction: 'none' }}
|
||||
>
|
||||
<ContextMenu<HTMLDivElement>
|
||||
menuProps={{ size: 'sm', isLazy: true }}
|
||||
renderMenu={() => (
|
||||
@ -291,8 +296,8 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
userSelect="none"
|
||||
draggable={true}
|
||||
onDragStart={handleDragStart}
|
||||
// draggable={true}
|
||||
// onDragStart={handleDragStart}
|
||||
onClick={handleSelectImage}
|
||||
ref={ref}
|
||||
sx={{
|
||||
@ -373,7 +378,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
onClose={onDeleteDialogClose}
|
||||
handleDelete={handleDelete}
|
||||
/>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
}, memoEqualityCheck);
|
||||
|
||||
|
@ -14,6 +14,8 @@ const ImageFallbackSpinner = (props: ImageFallbackSpinnerProps) => {
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
color: 'base.400',
|
||||
minH: 36,
|
||||
minW: 36,
|
||||
}}
|
||||
>
|
||||
<Spinner size={size} {...rest} />
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIPopover from 'common/components/IAIPopover';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
@ -233,7 +233,7 @@ const ImageGalleryContent = () => {
|
||||
withReset
|
||||
handleReset={() => dispatch(setGalleryImageMinimumWidth(64))}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('gallery.maintainAspectRatio')}
|
||||
isChecked={galleryImageObjectFit === 'contain'}
|
||||
onChange={() =>
|
||||
@ -244,14 +244,14 @@ const ImageGalleryContent = () => {
|
||||
)
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('gallery.autoSwitchNewImages')}
|
||||
isChecked={shouldAutoSwitchToNewImages}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldAutoSwitchToNewImages(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('gallery.singleColumnLayout')}
|
||||
isChecked={shouldUseSingleGalleryColumn}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -50,7 +50,10 @@ export const gallerySlice = createSlice({
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(imageUpserted, (state, action) => {
|
||||
if (state.shouldAutoSwitchToNewImages) {
|
||||
if (
|
||||
state.shouldAutoSwitchToNewImages &&
|
||||
action.payload.image_category === 'general'
|
||||
) {
|
||||
state.selectedImage = action.payload;
|
||||
}
|
||||
});
|
||||
|
@ -1,54 +1,67 @@
|
||||
import { Box, Image } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import SelectImagePlaceholder from 'common/components/SelectImagePlaceholder';
|
||||
import { useGetUrl } from 'common/util/getUrl';
|
||||
import useGetImageByName from 'features/gallery/hooks/useGetImageByName';
|
||||
|
||||
import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
ImageInputFieldTemplate,
|
||||
ImageInputFieldValue,
|
||||
} from 'features/nodes/types/types';
|
||||
import { DragEvent, memo, useCallback, useState } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
import { FieldComponentProps } from './types';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
|
||||
const ImageInputFieldComponent = (
|
||||
props: FieldComponentProps<ImageInputFieldValue, ImageInputFieldTemplate>
|
||||
) => {
|
||||
const { nodeId, field } = props;
|
||||
|
||||
const getImageByName = useGetImageByName();
|
||||
const dispatch = useAppDispatch();
|
||||
const [url, setUrl] = useState<string | undefined>(field.value?.image_url);
|
||||
const { getUrl } = useGetUrl();
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
const name = e.dataTransfer.getData('invokeai/imageName');
|
||||
const image = getImageByName(name);
|
||||
|
||||
if (!image) {
|
||||
(droppedImage: ImageDTO) => {
|
||||
if (field.value?.image_name === droppedImage.image_name) {
|
||||
return;
|
||||
}
|
||||
|
||||
setUrl(image.image_url);
|
||||
|
||||
dispatch(
|
||||
fieldValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: image,
|
||||
value: droppedImage,
|
||||
})
|
||||
);
|
||||
},
|
||||
[getImageByName, dispatch, field.name, nodeId]
|
||||
[dispatch, field.name, field.value?.image_name, nodeId]
|
||||
);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
dispatch(
|
||||
fieldValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: undefined,
|
||||
})
|
||||
);
|
||||
}, [dispatch, field.name, nodeId]);
|
||||
|
||||
return (
|
||||
<Box onDrop={handleDrop}>
|
||||
<Image src={getUrl(url)} fallback={<SelectImagePlaceholder />} />
|
||||
</Box>
|
||||
<Flex
|
||||
sx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<IAIDndImage
|
||||
image={field.value}
|
||||
onDrop={handleDrop}
|
||||
onReset={handleReset}
|
||||
resetIconSize="sm"
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { readinessSelector } from 'app/selectors/readinessSelector';
|
||||
import { userInvoked } from 'app/store/actions';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIButton, { IAIButtonProps } from 'common/components/IAIButton';
|
||||
import IAIIconButton, {
|
||||
IAIIconButtonProps,
|
||||
} from 'common/components/IAIIconButton';
|
||||
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
|
||||
import ProgressBar from 'features/system/components/ProgressBar';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { useCallback } from 'react';
|
||||
@ -21,9 +21,8 @@ interface InvokeButton
|
||||
export default function NodeInvokeButton(props: InvokeButton) {
|
||||
const { iconButton = false, ...rest } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const { isReady } = useAppSelector(readinessSelector);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
|
||||
const isReady = useIsReadyToInvoke();
|
||||
const handleInvoke = useCallback(() => {
|
||||
dispatch(userInvoked('nodes'));
|
||||
}, [dispatch]);
|
||||
|
@ -0,0 +1,99 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { forEach, size } from 'lodash-es';
|
||||
import { CollectInvocation, ControlNetInvocation } from 'services/api';
|
||||
import { NonNullableGraph } from '../types/types';
|
||||
|
||||
const CONTROL_NET_COLLECT = 'control_net_collect';
|
||||
|
||||
export const addControlNetToLinearGraph = (
|
||||
graph: NonNullableGraph,
|
||||
baseNodeId: string,
|
||||
state: RootState
|
||||
): void => {
|
||||
const { isEnabled: isControlNetEnabled, controlNets } = state.controlNet;
|
||||
|
||||
// Add ControlNet
|
||||
if (isControlNetEnabled) {
|
||||
if (size(controlNets) > 1) {
|
||||
const controlNetIterateNode: CollectInvocation = {
|
||||
id: CONTROL_NET_COLLECT,
|
||||
type: 'collect',
|
||||
};
|
||||
graph.nodes[controlNetIterateNode.id] = controlNetIterateNode;
|
||||
graph.edges.push({
|
||||
source: { node_id: controlNetIterateNode.id, field: 'collection' },
|
||||
destination: {
|
||||
node_id: baseNodeId,
|
||||
field: 'control',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
forEach(controlNets, (controlNet, index) => {
|
||||
const {
|
||||
controlNetId,
|
||||
isEnabled,
|
||||
controlImage,
|
||||
processedControlImage,
|
||||
beginStepPct,
|
||||
endStepPct,
|
||||
model,
|
||||
processorType,
|
||||
weight,
|
||||
} = controlNet;
|
||||
|
||||
if (!isEnabled) {
|
||||
// Skip disabled ControlNets
|
||||
return;
|
||||
}
|
||||
|
||||
const controlNetNode: ControlNetInvocation = {
|
||||
id: `control_net_${controlNetId}`,
|
||||
type: 'controlnet',
|
||||
begin_step_percent: beginStepPct,
|
||||
end_step_percent: endStepPct,
|
||||
control_model: model as ControlNetInvocation['control_model'],
|
||||
control_weight: weight,
|
||||
};
|
||||
|
||||
if (processedControlImage && processorType !== 'none') {
|
||||
// We've already processed the image in the app, so we can just use the processed image
|
||||
const { image_name, image_origin } = processedControlImage;
|
||||
controlNetNode.image = {
|
||||
image_name,
|
||||
image_origin,
|
||||
};
|
||||
} else if (controlImage && processorType !== 'none') {
|
||||
// The control image is preprocessed
|
||||
const { image_name, image_origin } = controlImage;
|
||||
controlNetNode.image = {
|
||||
image_name,
|
||||
image_origin,
|
||||
};
|
||||
} else {
|
||||
// Skip ControlNets without an unprocessed image - should never happen if everything is working correctly
|
||||
return;
|
||||
}
|
||||
|
||||
graph.nodes[controlNetNode.id] = controlNetNode;
|
||||
|
||||
if (size(controlNets) > 1) {
|
||||
graph.edges.push({
|
||||
source: { node_id: controlNetNode.id, field: 'control' },
|
||||
destination: {
|
||||
node_id: CONTROL_NET_COLLECT,
|
||||
field: 'item',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
graph.edges.push({
|
||||
source: { node_id: controlNetNode.id, field: 'control' },
|
||||
destination: {
|
||||
node_id: baseNodeId,
|
||||
field: 'control',
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
@ -14,6 +14,7 @@ import {
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { set } from 'lodash-es';
|
||||
import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph';
|
||||
|
||||
const moduleLog = log.child({ namespace: 'nodes' });
|
||||
|
||||
@ -408,5 +409,7 @@ export const buildImageToImageGraph = (state: RootState): Graph => {
|
||||
});
|
||||
}
|
||||
|
||||
addControlNetToLinearGraph(graph, LATENTS_TO_LATENTS, state);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
TextToLatentsInvocation,
|
||||
} from 'services/api';
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph';
|
||||
|
||||
const POSITIVE_CONDITIONING = 'positive_conditioning';
|
||||
const NEGATIVE_CONDITIONING = 'negative_conditioning';
|
||||
@ -308,5 +309,8 @@ export const buildTextToImageGraph = (state: RootState): Graph => {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
addControlNetToLinearGraph(graph, TEXT_TO_LATENTS, state);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -0,0 +1,69 @@
|
||||
import { Divider, Flex } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import IAICollapse from 'common/components/IAICollapse';
|
||||
import { Fragment, memo, useCallback } from 'react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import {
|
||||
controlNetAdded,
|
||||
controlNetSelector,
|
||||
isControlNetEnabledToggled,
|
||||
} from 'features/controlNet/store/controlNetSlice';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { map } from 'lodash-es';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import ControlNet from 'features/controlNet/components/ControlNet';
|
||||
|
||||
const selector = createSelector(
|
||||
controlNetSelector,
|
||||
(controlNet) => {
|
||||
const { controlNets, isEnabled } = controlNet;
|
||||
|
||||
return { controlNetsArray: map(controlNets), isEnabled };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const ParamControlNetCollapse = () => {
|
||||
const { t } = useTranslation();
|
||||
const { controlNetsArray, isEnabled } = useAppSelector(selector);
|
||||
const isControlNetDisabled = useFeatureStatus('controlNet').isFeatureDisabled;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleClickControlNetToggle = useCallback(() => {
|
||||
dispatch(isControlNetEnabledToggled());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleClickedAddControlNet = useCallback(() => {
|
||||
dispatch(controlNetAdded({ controlNetId: uuidv4() }));
|
||||
}, [dispatch]);
|
||||
|
||||
if (isControlNetDisabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<IAICollapse
|
||||
label={'ControlNet'}
|
||||
isOpen={isEnabled}
|
||||
onToggle={handleClickControlNetToggle}
|
||||
withSwitch
|
||||
>
|
||||
<Flex sx={{ flexDir: 'column', gap: 3 }}>
|
||||
{controlNetsArray.map((c, i) => (
|
||||
<Fragment key={c.controlNetId}>
|
||||
{i > 0 && <Divider />}
|
||||
<ControlNet controlNet={c} />
|
||||
</Fragment>
|
||||
))}
|
||||
<IAIButton flexGrow={1} onClick={handleClickedAddControlNet}>
|
||||
Add ControlNet
|
||||
</IAIButton>
|
||||
</Flex>
|
||||
</IAICollapse>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamControlNetCollapse);
|
@ -4,7 +4,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { ChangeEvent, KeyboardEvent, useCallback, useRef } from 'react';
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { readinessSelector } from 'app/selectors/readinessSelector';
|
||||
import {
|
||||
GenerationState,
|
||||
clampSymmetrySteps,
|
||||
@ -17,6 +16,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { userInvoked } from 'app/store/actions';
|
||||
import IAITextarea from 'common/components/IAITextarea';
|
||||
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
|
||||
|
||||
const promptInputSelector = createSelector(
|
||||
[(state: RootState) => state.generation, activeTabNameSelector],
|
||||
@ -39,7 +39,7 @@ const promptInputSelector = createSelector(
|
||||
const ParamPositiveConditioning = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { prompt, activeTabName } = useAppSelector(promptInputSelector);
|
||||
const { isReady } = useAppSelector(readinessSelector);
|
||||
const isReady = useIsReadyToInvoke();
|
||||
|
||||
const promptRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import InitialImagePreview from './InitialImagePreview';
|
||||
import InitialImageButtons from 'common/components/InitialImageButtons';
|
||||
|
||||
const InitialImageDisplay = () => {
|
||||
return (
|
||||
@ -28,7 +27,6 @@ const InitialImageDisplay = () => {
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<InitialImageButtons />
|
||||
<InitialImagePreview />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
@ -1,18 +1,20 @@
|
||||
import { Flex, Icon, Image } from '@chakra-ui/react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useGetUrl } from 'common/util/getUrl';
|
||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||
import { DragEvent, useCallback } from 'react';
|
||||
import {
|
||||
clearInitialImage,
|
||||
initialImageChanged,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import ImageFallbackSpinner from 'features/gallery/components/ImageFallbackSpinner';
|
||||
import { FaImage } from 'react-icons/fa';
|
||||
import { configSelector } from '../../../../system/store/configSelectors';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
||||
|
||||
const selector = createSelector(
|
||||
[generationSelector],
|
||||
@ -52,13 +54,19 @@ const InitialImagePreview = () => {
|
||||
}, [dispatch, t, toaster, shouldFetchImages]);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
const name = e.dataTransfer.getData('invokeai/imageName');
|
||||
dispatch(initialImageSelected(name));
|
||||
(droppedImage: ImageDTO) => {
|
||||
if (droppedImage.image_name === initialImage?.image_name) {
|
||||
return;
|
||||
}
|
||||
dispatch(initialImageChanged(droppedImage));
|
||||
},
|
||||
[dispatch]
|
||||
[dispatch, initialImage?.image_name]
|
||||
);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
dispatch(clearInitialImage());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
@ -68,36 +76,13 @@ const InitialImagePreview = () => {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
{initialImage?.image_url && (
|
||||
<>
|
||||
<Image
|
||||
src={getUrl(initialImage?.image_url)}
|
||||
fallbackStrategy="beforeLoadOrError"
|
||||
fallback={<ImageFallbackSpinner />}
|
||||
onError={handleError}
|
||||
sx={{
|
||||
objectFit: 'contain',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
height: 'auto',
|
||||
position: 'absolute',
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
/>
|
||||
<ImageMetadataOverlay image={initialImage} />
|
||||
</>
|
||||
)}
|
||||
{!initialImage?.image_url && (
|
||||
<Icon
|
||||
as={FaImage}
|
||||
sx={{
|
||||
boxSize: 24,
|
||||
color: 'base.500',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<IAIDndImage
|
||||
image={initialImage}
|
||||
onDrop={handleDrop}
|
||||
onReset={handleReset}
|
||||
fallback={<IAIImageFallback sx={{ bg: 'none' }} />}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,17 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import ParamSeed from './ParamSeed';
|
||||
import ParamSeedShuffle from './ParamSeedShuffle';
|
||||
import ParamSeedRandomize from './ParamSeedRandomize';
|
||||
|
||||
const ParamSeedFull = () => {
|
||||
return (
|
||||
<Flex sx={{ gap: 4, alignItems: 'center' }}>
|
||||
<ParamSeed />
|
||||
<ParamSeedShuffle />
|
||||
<ParamSeedRandomize />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamSeedFull);
|
@ -2,30 +2,10 @@ import { ChangeEvent, memo } from 'react';
|
||||
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { setShouldRandomizeSeed } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FormControl, FormLabel, Switch } from '@chakra-ui/react';
|
||||
|
||||
// export default function RandomizeSeed() {
|
||||
// const dispatch = useAppDispatch();
|
||||
// const { t } = useTranslation();
|
||||
|
||||
// const shouldRandomizeSeed = useAppSelector(
|
||||
// (state: RootState) => state.generation.shouldRandomizeSeed
|
||||
// );
|
||||
|
||||
// const handleChangeShouldRandomizeSeed = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
// dispatch(setShouldRandomizeSeed(e.target.checked));
|
||||
|
||||
// return (
|
||||
// <Switch
|
||||
// aria-label={t('parameters.randomizeSeed')}
|
||||
// isChecked={shouldRandomizeSeed}
|
||||
// onChange={handleChangeShouldRandomizeSeed}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
import { FormControl, FormLabel, Switch, Tooltip } from '@chakra-ui/react';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
|
||||
const ParamSeedRandomize = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
@ -38,6 +18,14 @@ const ParamSeedRandomize = () => {
|
||||
const handleChangeShouldRandomizeSeed = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldRandomizeSeed(e.target.checked));
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
label={t('common.random')}
|
||||
isChecked={shouldRandomizeSeed}
|
||||
onChange={handleChangeShouldRandomizeSeed}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
sx={{
|
||||
|
@ -3,9 +3,11 @@ import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import randomInt from 'common/util/randomInt';
|
||||
import { setSeed } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaRandom } from 'react-icons/fa';
|
||||
|
||||
export default function ParamSeedShuffle() {
|
||||
const dispatch = useAppDispatch();
|
||||
@ -17,6 +19,17 @@ export default function ParamSeedShuffle() {
|
||||
const handleClickRandomizeSeed = () =>
|
||||
dispatch(setSeed(randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX)));
|
||||
|
||||
return (
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
isDisabled={shouldRandomizeSeed}
|
||||
aria-label={t('parameters.shuffle')}
|
||||
tooltip={t('parameters.shuffle')}
|
||||
onClick={handleClickRandomizeSeed}
|
||||
icon={<FaRandom />}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIButton
|
||||
size="sm"
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { readinessSelector } from 'app/selectors/readinessSelector';
|
||||
import { userInvoked } from 'app/store/actions';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIButton, { IAIButtonProps } from 'common/components/IAIButton';
|
||||
import IAIIconButton, {
|
||||
IAIIconButtonProps,
|
||||
} from 'common/components/IAIIconButton';
|
||||
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
|
||||
import { clampSymmetrySteps } from 'features/parameters/store/generationSlice';
|
||||
import ProgressBar from 'features/system/components/ProgressBar';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
@ -22,7 +22,7 @@ interface InvokeButton
|
||||
export default function InvokeButton(props: InvokeButton) {
|
||||
const { iconButton = false, ...rest } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const { isReady } = useAppSelector(readinessSelector);
|
||||
const isReady = useIsReadyToInvoke();
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
|
||||
const handleInvoke = useCallback(() => {
|
||||
|
@ -7,25 +7,6 @@ export type ImageNameAndOrigin = {
|
||||
image_origin: ResourceOrigin;
|
||||
};
|
||||
|
||||
export const isImageDTO = (image: any): image is ImageDTO => {
|
||||
return (
|
||||
image &&
|
||||
isObject(image) &&
|
||||
'image_name' in image &&
|
||||
image?.image_name !== undefined &&
|
||||
'image_origin' in image &&
|
||||
image?.image_origin !== undefined &&
|
||||
'image_url' in image &&
|
||||
image?.image_url !== undefined &&
|
||||
'thumbnail_url' in image &&
|
||||
image?.thumbnail_url !== undefined &&
|
||||
'image_category' in image &&
|
||||
image?.image_category !== undefined &&
|
||||
'created_at' in image &&
|
||||
image?.created_at !== undefined
|
||||
);
|
||||
};
|
||||
|
||||
export const initialImageSelected = createAction<ImageDTO | string | undefined>(
|
||||
'generation/initialImageSelected'
|
||||
);
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import IAIInput from 'common/components/IAIInput';
|
||||
import IAINumberInput from 'common/components/IAINumberInput';
|
||||
import React from 'react';
|
||||
@ -74,12 +74,12 @@ export default function AddCheckpointModel() {
|
||||
return (
|
||||
<VStack gap={2} alignItems="flex-start">
|
||||
<Flex columnGap={4}>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
isChecked={!addManually}
|
||||
label={t('modelManager.scanForModels')}
|
||||
onChange={() => setAddmanually(!addManually)}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('modelManager.addManually')}
|
||||
isChecked={addManually}
|
||||
onChange={() => setAddmanually(!addManually)}
|
||||
|
@ -24,7 +24,7 @@ import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import * as InvokeAI from 'app/types/invokeai';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
|
||||
export default function MergeModels() {
|
||||
const dispatch = useAppDispatch();
|
||||
@ -286,7 +286,7 @@ export default function MergeModels() {
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('modelManager.ignoreMismatch')}
|
||||
isChecked={modelMergeForce}
|
||||
onChange={(e) => setModelMergeForce(e.target.checked)}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import React from 'react';
|
||||
|
||||
@ -81,13 +81,13 @@ function SearchModelEntry({
|
||||
borderRadius={4}
|
||||
>
|
||||
<Flex gap={4} alignItems="center" justifyContent="space-between">
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
value={model.name}
|
||||
label={<Text fontWeight={500}>{model.name}</Text>}
|
||||
isChecked={modelsToAdd.includes(model.name)}
|
||||
isDisabled={existingModels.includes(model.location)}
|
||||
onChange={foundModelsChangeHandler}
|
||||
></IAICheckbox>
|
||||
></IAISimpleCheckbox>
|
||||
{existingModels.includes(model.location) && (
|
||||
<Badge colorScheme="accent">{t('modelManager.modelExists')}</Badge>
|
||||
)}
|
||||
@ -324,7 +324,7 @@ export default function SearchModels() {
|
||||
>
|
||||
{t('modelManager.deselectAll')}
|
||||
</IAIButton>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('modelManager.showExisting')}
|
||||
isChecked={shouldShowExistingModelsInSearch}
|
||||
onChange={() =>
|
||||
|
@ -19,6 +19,9 @@ const isApplicationReadySelector = createSelector(
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Checks if the application is ready to be used, i.e. if the initial startup process is finished.
|
||||
*/
|
||||
export const useIsApplicationReady = () => {
|
||||
const { disabledTabs, wereModelsReceived, wasSchemaParsed } = useAppSelector(
|
||||
isApplicationReadySelector
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { memo } from 'react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { Box, Flex, useDisclosure } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
@ -13,6 +13,8 @@ import ImageToImageStrength from 'features/parameters/components/Parameters/Imag
|
||||
import ImageToImageFit from 'features/parameters/components/Parameters/ImageToImage/ImageToImageFit';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import ParamSchedulerAndModel from 'features/parameters/components/Parameters/Core/ParamSchedulerAndModel';
|
||||
import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull';
|
||||
import IAICollapse from 'common/components/IAICollapse';
|
||||
|
||||
const selector = createSelector(
|
||||
[uiSelector, generationSelector],
|
||||
@ -27,43 +29,47 @@ const selector = createSelector(
|
||||
|
||||
const ImageToImageTabCoreParameters = () => {
|
||||
const { shouldUseSliders, shouldFitToWidthHeight } = useAppSelector(selector);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
bg: 'base.800',
|
||||
p: 4,
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
>
|
||||
{shouldUseSliders ? (
|
||||
<Flex sx={{ gap: 3, flexDirection: 'column' }}>
|
||||
<ParamIterations />
|
||||
<ParamSteps />
|
||||
<ParamCFGScale />
|
||||
<ParamWidth isDisabled={!shouldFitToWidthHeight} />
|
||||
<ParamHeight isDisabled={!shouldFitToWidthHeight} />
|
||||
<ImageToImageStrength />
|
||||
<ImageToImageFit />
|
||||
<ParamSchedulerAndModel />
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
|
||||
<Flex gap={3}>
|
||||
<IAICollapse label={'General'} isOpen={isOpen} onToggle={onToggle}>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
gap: 3,
|
||||
}}
|
||||
>
|
||||
{shouldUseSliders ? (
|
||||
<>
|
||||
<ParamSchedulerAndModel />
|
||||
<Box pt={2}>
|
||||
<ParamSeedFull />
|
||||
</Box>
|
||||
<ParamIterations />
|
||||
<ParamSteps />
|
||||
<ParamCFGScale />
|
||||
</Flex>
|
||||
<ParamSchedulerAndModel />
|
||||
<ParamWidth isDisabled={!shouldFitToWidthHeight} />
|
||||
<ParamHeight isDisabled={!shouldFitToWidthHeight} />
|
||||
<ImageToImageStrength />
|
||||
<ImageToImageFit />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<ParamWidth isDisabled={!shouldFitToWidthHeight} />
|
||||
<ParamHeight isDisabled={!shouldFitToWidthHeight} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Flex gap={3}>
|
||||
<ParamIterations />
|
||||
<ParamSteps />
|
||||
<ParamCFGScale />
|
||||
</Flex>
|
||||
<ParamSchedulerAndModel />
|
||||
<Box pt={2}>
|
||||
<ParamSeedFull />
|
||||
</Box>
|
||||
<ParamWidth isDisabled={!shouldFitToWidthHeight} />
|
||||
<ParamHeight isDisabled={!shouldFitToWidthHeight} />
|
||||
</>
|
||||
)}
|
||||
<ImageToImageStrength />
|
||||
<ImageToImageFit />
|
||||
</Flex>
|
||||
</IAICollapse>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -2,12 +2,12 @@ import { memo } from 'react';
|
||||
import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons';
|
||||
import ParamPositiveConditioning from 'features/parameters/components/Parameters/Core/ParamPositiveConditioning';
|
||||
import ParamNegativeConditioning from 'features/parameters/components/Parameters/Core/ParamNegativeConditioning';
|
||||
import ParamSeedCollapse from 'features/parameters/components/Parameters/Seed/ParamSeedCollapse';
|
||||
import ParamVariationCollapse from 'features/parameters/components/Parameters/Variations/ParamVariationCollapse';
|
||||
import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/ParamNoiseCollapse';
|
||||
import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse';
|
||||
import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse';
|
||||
import ImageToImageTabCoreParameters from './ImageToImageTabCoreParameters';
|
||||
import ParamControlNetCollapse from 'features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse';
|
||||
|
||||
const ImageToImageTabParameters = () => {
|
||||
return (
|
||||
@ -16,7 +16,7 @@ const ImageToImageTabParameters = () => {
|
||||
<ParamNegativeConditioning />
|
||||
<ProcessButtons />
|
||||
<ImageToImageTabCoreParameters />
|
||||
<ParamSeedCollapse />
|
||||
<ParamControlNetCollapse />
|
||||
<ParamVariationCollapse />
|
||||
<ParamNoiseCollapse />
|
||||
<ParamSymmetryCollapse />
|
||||
|
@ -3,13 +3,15 @@ import ParamSteps from 'features/parameters/components/Parameters/Core/ParamStep
|
||||
import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale';
|
||||
import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth';
|
||||
import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { Box, Flex, useDisclosure } from '@chakra-ui/react';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { memo } from 'react';
|
||||
import ParamSchedulerAndModel from 'features/parameters/components/Parameters/Core/ParamSchedulerAndModel';
|
||||
import IAICollapse from 'common/components/IAICollapse';
|
||||
import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull';
|
||||
|
||||
const selector = createSelector(
|
||||
uiSelector,
|
||||
@ -23,39 +25,45 @@ const selector = createSelector(
|
||||
|
||||
const TextToImageTabCoreParameters = () => {
|
||||
const { shouldUseSliders } = useAppSelector(selector);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
bg: 'base.800',
|
||||
p: 4,
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
>
|
||||
{shouldUseSliders ? (
|
||||
<Flex sx={{ gap: 3, flexDirection: 'column' }}>
|
||||
<ParamIterations />
|
||||
<ParamSteps />
|
||||
<ParamCFGScale />
|
||||
<ParamWidth />
|
||||
<ParamHeight />
|
||||
<ParamSchedulerAndModel />
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
|
||||
<Flex gap={3}>
|
||||
<IAICollapse label={'General'} isOpen={isOpen} onToggle={onToggle}>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
gap: 3,
|
||||
}}
|
||||
>
|
||||
{shouldUseSliders ? (
|
||||
<>
|
||||
<ParamSchedulerAndModel />
|
||||
<Box pt={2}>
|
||||
<ParamSeedFull />
|
||||
</Box>
|
||||
<ParamIterations />
|
||||
<ParamSteps />
|
||||
<ParamCFGScale />
|
||||
</Flex>
|
||||
<ParamSchedulerAndModel />
|
||||
<ParamWidth />
|
||||
<ParamHeight />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<ParamWidth />
|
||||
<ParamHeight />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Flex gap={3}>
|
||||
<ParamIterations />
|
||||
<ParamSteps />
|
||||
<ParamCFGScale />
|
||||
</Flex>
|
||||
<ParamSchedulerAndModel />
|
||||
<Box pt={2}>
|
||||
<ParamSeedFull />
|
||||
</Box>
|
||||
<ParamWidth />
|
||||
<ParamHeight />
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</IAICollapse>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -2,13 +2,13 @@ import ProcessButtons from 'features/parameters/components/ProcessButtons/Proces
|
||||
import { memo } from 'react';
|
||||
import ParamPositiveConditioning from 'features/parameters/components/Parameters/Core/ParamPositiveConditioning';
|
||||
import ParamNegativeConditioning from 'features/parameters/components/Parameters/Core/ParamNegativeConditioning';
|
||||
import ParamSeedCollapse from 'features/parameters/components/Parameters/Seed/ParamSeedCollapse';
|
||||
import ParamVariationCollapse from 'features/parameters/components/Parameters/Variations/ParamVariationCollapse';
|
||||
import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/ParamNoiseCollapse';
|
||||
import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse';
|
||||
import ParamHiresCollapse from 'features/parameters/components/Parameters/Hires/ParamHiresCollapse';
|
||||
import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse';
|
||||
import TextToImageTabCoreParameters from './TextToImageTabCoreParameters';
|
||||
import ParamControlNetCollapse from 'features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse';
|
||||
|
||||
const TextToImageTabParameters = () => {
|
||||
return (
|
||||
@ -17,7 +17,7 @@ const TextToImageTabParameters = () => {
|
||||
<ParamNegativeConditioning />
|
||||
<ProcessButtons />
|
||||
<TextToImageTabCoreParameters />
|
||||
<ParamSeedCollapse />
|
||||
<ParamControlNetCollapse />
|
||||
<ParamVariationCollapse />
|
||||
<ParamNoiseCollapse />
|
||||
<ParamSymmetryCollapse />
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import { setShouldDarkenOutsideBoundingBox } from 'features/canvas/store/canvasSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -14,7 +14,7 @@ export default function UnifiedCanvasDarkenOutsideSelection() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.betaDarkenOutside')}
|
||||
isChecked={shouldDarkenOutsideBoundingBox}
|
||||
onChange={(e) =>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import { setIsMaskEnabled } from 'features/canvas/store/canvasSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -16,7 +16,7 @@ export default function UnifiedCanvasEnableMask() {
|
||||
dispatch(setIsMaskEnabled(!isMaskEnabled));
|
||||
|
||||
return (
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={`${t('unifiedCanvas.enableMask')} (H)`}
|
||||
isChecked={isMaskEnabled}
|
||||
onChange={handleToggleEnableMask}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import { setShouldRestrictStrokesToBox } from 'features/canvas/store/canvasSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -14,7 +14,7 @@ export default function UnifiedCanvasLimitStrokesToBox() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.betaLimitToBox')}
|
||||
isChecked={shouldRestrictStrokesToBox}
|
||||
onChange={(e) =>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import { setShouldPreserveMaskedArea } from 'features/canvas/store/canvasSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -13,7 +13,7 @@ export default function UnifiedCanvasPreserveMask() {
|
||||
);
|
||||
|
||||
return (
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.betaPreserveMasked')}
|
||||
isChecked={shouldPreserveMaskedArea}
|
||||
onChange={(e) => dispatch(setShouldPreserveMaskedArea(e.target.checked))}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIPopover from 'common/components/IAIPopover';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
@ -73,33 +73,33 @@ const UnifiedCanvasSettings = () => {
|
||||
}
|
||||
>
|
||||
<Flex direction="column" gap={2}>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showIntermediates')}
|
||||
isChecked={shouldShowIntermediates}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldShowIntermediates(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.autoSaveToGallery')}
|
||||
isChecked={shouldAutoSave}
|
||||
onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.saveBoxRegionOnly')}
|
||||
isChecked={shouldCropToBoundingBoxOnSave}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showCanvasDebugInfo')}
|
||||
isChecked={shouldShowCanvasDebugInfo}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldShowCanvasDebugInfo(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.antialiasing')}
|
||||
isChecked={shouldAntialias}
|
||||
onChange={(e) => dispatch(setShouldAntialias(e.target.checked))}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import { setShouldShowGrid } from 'features/canvas/store/canvasSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -13,7 +13,7 @@ export default function UnifiedCanvasShowGrid() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showGrid')}
|
||||
isChecked={shouldShowGrid}
|
||||
onChange={(e) => dispatch(setShouldShowGrid(e.target.checked))}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import { setShouldSnapToGrid } from 'features/canvas/store/canvasSlice';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -17,7 +17,7 @@ export default function UnifiedCanvasSnapToGrid() {
|
||||
dispatch(setShouldSnapToGrid(e.target.checked));
|
||||
|
||||
return (
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={`${t('unifiedCanvas.snapToGrid')} (N)`}
|
||||
isChecked={shouldSnapToGrid}
|
||||
onChange={handleChangeShouldSnapToGrid}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { memo } from 'react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { Box, Flex, useDisclosure } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
@ -8,10 +8,11 @@ import ParamIterations from 'features/parameters/components/Parameters/Core/Para
|
||||
import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps';
|
||||
import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale';
|
||||
import ImageToImageStrength from 'features/parameters/components/Parameters/ImageToImage/ImageToImageStrength';
|
||||
import ImageToImageFit from 'features/parameters/components/Parameters/ImageToImage/ImageToImageFit';
|
||||
import ParamSchedulerAndModel from 'features/parameters/components/Parameters/Core/ParamSchedulerAndModel';
|
||||
import ParamBoundingBoxWidth from 'features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth';
|
||||
import ParamBoundingBoxHeight from 'features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight';
|
||||
import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull';
|
||||
import IAICollapse from 'common/components/IAICollapse';
|
||||
|
||||
const selector = createSelector(
|
||||
uiSelector,
|
||||
@ -25,42 +26,46 @@ const selector = createSelector(
|
||||
|
||||
const UnifiedCanvasCoreParameters = () => {
|
||||
const { shouldUseSliders } = useAppSelector(selector);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
bg: 'base.800',
|
||||
p: 4,
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
>
|
||||
{shouldUseSliders ? (
|
||||
<Flex sx={{ gap: 3, flexDirection: 'column' }}>
|
||||
<ParamIterations />
|
||||
<ParamSteps />
|
||||
<ParamCFGScale />
|
||||
<ParamBoundingBoxWidth />
|
||||
<ParamBoundingBoxHeight />
|
||||
<ImageToImageStrength />
|
||||
<ImageToImageFit />
|
||||
<ParamSchedulerAndModel />
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
|
||||
<Flex gap={3}>
|
||||
<IAICollapse label={'General'} isOpen={isOpen} onToggle={onToggle}>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
gap: 3,
|
||||
}}
|
||||
>
|
||||
{shouldUseSliders ? (
|
||||
<>
|
||||
<ParamSchedulerAndModel />
|
||||
<Box pt={2}>
|
||||
<ParamSeedFull />
|
||||
</Box>
|
||||
<ParamIterations />
|
||||
<ParamSteps />
|
||||
<ParamCFGScale />
|
||||
</Flex>
|
||||
<ParamSchedulerAndModel />
|
||||
<ParamBoundingBoxWidth />
|
||||
<ParamBoundingBoxHeight />
|
||||
<ImageToImageStrength />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<ParamBoundingBoxWidth />
|
||||
<ParamBoundingBoxHeight />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Flex gap={3}>
|
||||
<ParamIterations />
|
||||
<ParamSteps />
|
||||
<ParamCFGScale />
|
||||
</Flex>
|
||||
<ParamSchedulerAndModel />
|
||||
<Box pt={2}>
|
||||
<ParamSeedFull />
|
||||
</Box>
|
||||
<ParamBoundingBoxWidth />
|
||||
<ParamBoundingBoxHeight />
|
||||
</>
|
||||
)}
|
||||
<ImageToImageStrength />
|
||||
</Flex>
|
||||
</IAICollapse>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -16,7 +16,6 @@ const UnifiedCanvasParameters = () => {
|
||||
<ParamNegativeConditioning />
|
||||
<ProcessButtons />
|
||||
<UnifiedCanvasCoreParameters />
|
||||
<ParamSeedCollapse />
|
||||
<ParamVariationCollapse />
|
||||
<ParamSymmetryCollapse />
|
||||
<ParamSeamCorrectionCollapse />
|
||||
|
@ -32,7 +32,7 @@ export type { Graph } from './models/Graph';
|
||||
export type { GraphExecutionState } from './models/GraphExecutionState';
|
||||
export type { GraphInvocation } from './models/GraphInvocation';
|
||||
export type { GraphInvocationOutput } from './models/GraphInvocationOutput';
|
||||
export type { HedImageprocessorInvocation } from './models/HedImageprocessorInvocation';
|
||||
export type { HedImageProcessorInvocation } from './models/HedImageProcessorInvocation';
|
||||
export type { HTTPValidationError } from './models/HTTPValidationError';
|
||||
export type { ImageBlurInvocation } from './models/ImageBlurInvocation';
|
||||
export type { ImageCategory } from './models/ImageCategory';
|
||||
|
@ -18,15 +18,15 @@ export type CannyImageProcessorInvocation = {
|
||||
is_intermediate?: boolean;
|
||||
type?: 'canny_image_processor';
|
||||
/**
|
||||
* image to process
|
||||
* The image to process
|
||||
*/
|
||||
image?: ImageField;
|
||||
/**
|
||||
* low threshold of Canny pixel gradient
|
||||
* The low threshold of the Canny pixel gradient (0-255)
|
||||
*/
|
||||
low_threshold?: number;
|
||||
/**
|
||||
* high threshold of Canny pixel gradient
|
||||
* The high threshold of the Canny pixel gradient (0-255)
|
||||
*/
|
||||
high_threshold?: number;
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user