mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into lstein/feat/simple-mm2-api
This commit is contained in:
commit
8e5e9b53d6
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
Invoke is a leading creative engine built to empower professionals and enthusiasts alike. Generate and create stunning visual media using the latest AI-driven technologies. Invoke offers an industry leading web-based UI, and serves as the foundation for multiple commercial products.
|
Invoke is a leading creative engine built to empower professionals and enthusiasts alike. Generate and create stunning visual media using the latest AI-driven technologies. Invoke offers an industry leading web-based UI, and serves as the foundation for multiple commercial products.
|
||||||
|
|
||||||
[Installation][installation docs] - [Documentation and Tutorials][docs home] - [Bug Reports][github issues] - [Contributing][contributing docs]
|
[Installation and Updates][installation docs] - [Documentation and Tutorials][docs home] - [Bug Reports][github issues] - [Contributing][contributing docs]
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
|
@ -4,278 +4,6 @@ title: Training
|
|||||||
|
|
||||||
# :material-file-document: Training
|
# :material-file-document: Training
|
||||||
|
|
||||||
# Textual Inversion Training
|
Invoke Training has moved to its own repository, with a dedicated UI for accessing common scripts like Textual Inversion and LoRA training.
|
||||||
## **Personalizing Text-to-Image Generation**
|
|
||||||
|
|
||||||
You may personalize the generated images to provide your own styles or objects
|
You can find more by visiting the repo at https://github.com/invoke-ai/invoke-training
|
||||||
by training a new LDM checkpoint and introducing a new vocabulary to the fixed
|
|
||||||
model as a (.pt) embeddings file. Alternatively, you may use or train
|
|
||||||
HuggingFace Concepts embeddings files (.bin) from
|
|
||||||
<https://huggingface.co/sd-concepts-library> and its associated
|
|
||||||
notebooks.
|
|
||||||
|
|
||||||
## **Hardware and Software Requirements**
|
|
||||||
|
|
||||||
You will need a GPU to perform training in a reasonable length of
|
|
||||||
time, and at least 12 GB of VRAM. We recommend using the [`xformers`
|
|
||||||
library](../installation/070_INSTALL_XFORMERS.md) to accelerate the
|
|
||||||
training process further. During training, about ~8 GB is temporarily
|
|
||||||
needed in order to store intermediate models, checkpoints and logs.
|
|
||||||
|
|
||||||
## **Preparing for Training**
|
|
||||||
|
|
||||||
To train, prepare a folder that contains 3-5 images that illustrate
|
|
||||||
the object or concept. It is good to provide a variety of examples or
|
|
||||||
poses to avoid overtraining the system. Format these images as PNG
|
|
||||||
(preferred) or JPG. You do not need to resize or crop the images in
|
|
||||||
advance, but for more control you may wish to do so.
|
|
||||||
|
|
||||||
Place the training images in a directory on the machine InvokeAI runs
|
|
||||||
on. We recommend placing them in a subdirectory of the
|
|
||||||
`text-inversion-training-data` folder located in the InvokeAI root
|
|
||||||
directory, ordinarily `~/invokeai` (Linux/Mac), or
|
|
||||||
`C:\Users\your_name\invokeai` (Windows). For example, to create an
|
|
||||||
embedding for the "psychedelic" style, you'd place the training images
|
|
||||||
into the directory
|
|
||||||
`~invokeai/text-inversion-training-data/psychedelic`.
|
|
||||||
|
|
||||||
## **Launching Training Using the Console Front End**
|
|
||||||
|
|
||||||
InvokeAI 2.3 and higher comes with a text console-based training front
|
|
||||||
end. From within the `invoke.sh`/`invoke.bat` Invoke launcher script,
|
|
||||||
start training tool selecting choice (3):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
1 "Generate images with a browser-based interface"
|
|
||||||
2 "Explore InvokeAI nodes using a command-line interface"
|
|
||||||
3 "Textual inversion training"
|
|
||||||
4 "Merge models (diffusers type only)"
|
|
||||||
5 "Download and install models"
|
|
||||||
6 "Change InvokeAI startup options"
|
|
||||||
7 "Re-run the configure script to fix a broken install or to complete a major upgrade"
|
|
||||||
8 "Open the developer console"
|
|
||||||
9 "Update InvokeAI"
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can select option (8) or from the command line, with the InvokeAI virtual environment active,
|
|
||||||
you can then launch the front end with the command `invokeai-ti --gui`.
|
|
||||||
|
|
||||||
This will launch a text-based front end that will look like this:
|
|
||||||
|
|
||||||
<figure markdown>
|
|
||||||

|
|
||||||
</figure>
|
|
||||||
|
|
||||||
The interface is keyboard-based. Move from field to field using
|
|
||||||
control-N (^N) to move to the next field and control-P (^P) to the
|
|
||||||
previous one. <Tab> and <shift-TAB> work as well. Once a field is
|
|
||||||
active, use the cursor keys. In a checkbox group, use the up and down
|
|
||||||
cursor keys to move from choice to choice, and <space> to select a
|
|
||||||
choice. In a scrollbar, use the left and right cursor keys to increase
|
|
||||||
and decrease the value of the scroll. In textfields, type the desired
|
|
||||||
values.
|
|
||||||
|
|
||||||
The number of parameters may look intimidating, but in most cases the
|
|
||||||
predefined defaults work fine. The red circled fields in the above
|
|
||||||
illustration are the ones you will adjust most frequently.
|
|
||||||
|
|
||||||
### Model Name
|
|
||||||
|
|
||||||
This will list all the diffusers models that are currently
|
|
||||||
installed. Select the one you wish to use as the basis for your
|
|
||||||
embedding. Be aware that if you use a SD-1.X-based model for your
|
|
||||||
training, you will only be able to use this embedding with other
|
|
||||||
SD-1.X-based models. Similarly, if you train on SD-2.X, you will only
|
|
||||||
be able to use the embeddings with models based on SD-2.X.
|
|
||||||
|
|
||||||
### Trigger Term
|
|
||||||
|
|
||||||
This is the prompt term you will use to trigger the embedding. Type a
|
|
||||||
single word or phrase you wish to use as the trigger, example
|
|
||||||
"psychedelic" (without angle brackets). Within InvokeAI, you will then
|
|
||||||
be able to activate the trigger using the syntax `<psychedelic>`.
|
|
||||||
|
|
||||||
### Initializer
|
|
||||||
|
|
||||||
This is a single character that is used internally during the training
|
|
||||||
process as a placeholder for the trigger term. It defaults to "*" and
|
|
||||||
can usually be left alone.
|
|
||||||
|
|
||||||
### Resume from last saved checkpoint
|
|
||||||
|
|
||||||
As training proceeds, textual inversion will write a series of
|
|
||||||
intermediate files that can be used to resume training from where it
|
|
||||||
was left off in the case of an interruption. This checkbox will be
|
|
||||||
automatically selected if you provide a previously used trigger term
|
|
||||||
and at least one checkpoint file is found on disk.
|
|
||||||
|
|
||||||
Note that as of 20 January 2023, resume does not seem to be working
|
|
||||||
properly due to an issue with the upstream code.
|
|
||||||
|
|
||||||
### Data Training Directory
|
|
||||||
|
|
||||||
This is the location of the images to be used for training. When you
|
|
||||||
select a trigger term like "my-trigger", the frontend will prepopulate
|
|
||||||
this field with `~/invokeai/text-inversion-training-data/my-trigger`,
|
|
||||||
but you can change the path to wherever you want.
|
|
||||||
|
|
||||||
### Output Destination Directory
|
|
||||||
|
|
||||||
This is the location of the logs, checkpoint files, and embedding
|
|
||||||
files created during training. When you select a trigger term like
|
|
||||||
"my-trigger", the frontend will prepopulate this field with
|
|
||||||
`~/invokeai/text-inversion-output/my-trigger`, but you can change the
|
|
||||||
path to wherever you want.
|
|
||||||
|
|
||||||
### Image resolution
|
|
||||||
|
|
||||||
The images in the training directory will be automatically scaled to
|
|
||||||
the value you use here. For best results, you will want to use the
|
|
||||||
same default resolution of the underlying model (512 pixels for
|
|
||||||
SD-1.5, 768 for the larger version of SD-2.1).
|
|
||||||
|
|
||||||
### Center crop images
|
|
||||||
|
|
||||||
If this is selected, your images will be center cropped to make them
|
|
||||||
square before resizing them to the desired resolution. Center cropping
|
|
||||||
can indiscriminately cut off the top of subjects' heads for portrait
|
|
||||||
aspect images, so if you have images like this, you may wish to use a
|
|
||||||
photoeditor to manually crop them to a square aspect ratio.
|
|
||||||
|
|
||||||
### Mixed precision
|
|
||||||
|
|
||||||
Select the floating point precision for the embedding. "no" will
|
|
||||||
result in a full 32-bit precision, "fp16" will provide 16-bit
|
|
||||||
precision, and "bf16" will provide mixed precision (only available
|
|
||||||
when XFormers is used).
|
|
||||||
|
|
||||||
### Max training steps
|
|
||||||
|
|
||||||
How many steps the training will take before the model converges. Most
|
|
||||||
training sets will converge with 2000-3000 steps.
|
|
||||||
|
|
||||||
### Batch size
|
|
||||||
|
|
||||||
This adjusts how many training images are processed simultaneously in
|
|
||||||
each step. Higher values will cause the training process to run more
|
|
||||||
quickly, but use more memory. The default size will run with GPUs with
|
|
||||||
as little as 12 GB.
|
|
||||||
|
|
||||||
### Learning rate
|
|
||||||
|
|
||||||
The rate at which the system adjusts its internal weights during
|
|
||||||
training. Higher values risk overtraining (getting the same image each
|
|
||||||
time), and lower values will take more steps to train a good
|
|
||||||
model. The default of 0.0005 is conservative; you may wish to increase
|
|
||||||
it to 0.005 to speed up training.
|
|
||||||
|
|
||||||
### Scale learning rate by number of GPUs, steps and batch size
|
|
||||||
|
|
||||||
If this is selected (the default) the system will adjust the provided
|
|
||||||
learning rate to improve performance.
|
|
||||||
|
|
||||||
### Use xformers acceleration
|
|
||||||
|
|
||||||
This will activate XFormers memory-efficient attention. You need to
|
|
||||||
have XFormers installed for this to have an effect.
|
|
||||||
|
|
||||||
### Learning rate scheduler
|
|
||||||
|
|
||||||
This adjusts how the learning rate changes over the course of
|
|
||||||
training. The default "constant" means to use a constant learning rate
|
|
||||||
for the entire training session. The other values scale the learning
|
|
||||||
rate according to various formulas.
|
|
||||||
|
|
||||||
Only "constant" is supported by the XFormers library.
|
|
||||||
|
|
||||||
### Gradient accumulation steps
|
|
||||||
|
|
||||||
This is a parameter that allows you to use bigger batch sizes than
|
|
||||||
your GPU's VRAM would ordinarily accommodate, at the cost of some
|
|
||||||
performance.
|
|
||||||
|
|
||||||
### Warmup steps
|
|
||||||
|
|
||||||
If "constant_with_warmup" is selected in the learning rate scheduler,
|
|
||||||
then this provides the number of warmup steps. Warmup steps have a
|
|
||||||
very low learning rate, and are one way of preventing early
|
|
||||||
overtraining.
|
|
||||||
|
|
||||||
## The training run
|
|
||||||
|
|
||||||
Start the training run by advancing to the OK button (bottom right)
|
|
||||||
and pressing <enter>. A series of progress messages will be displayed
|
|
||||||
as the training process proceeds. This may take an hour or two,
|
|
||||||
depending on settings and the speed of your system. Various log and
|
|
||||||
checkpoint files will be written into the output directory (ordinarily
|
|
||||||
`~/invokeai/text-inversion-output/my-model/`)
|
|
||||||
|
|
||||||
At the end of successful training, the system will copy the file
|
|
||||||
`learned_embeds.bin` into the InvokeAI root directory's `embeddings`
|
|
||||||
directory, using a subdirectory named after the trigger token. For
|
|
||||||
example, if the trigger token was `psychedelic`, then look for the
|
|
||||||
embeddings file in
|
|
||||||
`~/invokeai/embeddings/psychedelic/learned_embeds.bin`
|
|
||||||
|
|
||||||
You may now launch InvokeAI and try out a prompt that uses the trigger
|
|
||||||
term. For example `a plate of banana sushi in <psychedelic> style`.
|
|
||||||
|
|
||||||
## **Training with the Command-Line Script**
|
|
||||||
|
|
||||||
Training can also be done using a traditional command-line script. It
|
|
||||||
can be launched from within the "developer's console", or from the
|
|
||||||
command line after activating InvokeAI's virtual environment.
|
|
||||||
|
|
||||||
It accepts a large number of arguments, which can be summarized by
|
|
||||||
passing the `--help` argument:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
invokeai-ti --help
|
|
||||||
```
|
|
||||||
|
|
||||||
Typical usage is shown here:
|
|
||||||
```sh
|
|
||||||
invokeai-ti \
|
|
||||||
--model=stable-diffusion-1.5 \
|
|
||||||
--resolution=512 \
|
|
||||||
--learnable_property=style \
|
|
||||||
--initializer_token='*' \
|
|
||||||
--placeholder_token='<psychedelic>' \
|
|
||||||
--train_data_dir=/home/lstein/invokeai/training-data/psychedelic \
|
|
||||||
--output_dir=/home/lstein/invokeai/text-inversion-training/psychedelic \
|
|
||||||
--scale_lr \
|
|
||||||
--train_batch_size=8 \
|
|
||||||
--gradient_accumulation_steps=4 \
|
|
||||||
--max_train_steps=3000 \
|
|
||||||
--learning_rate=0.0005 \
|
|
||||||
--resume_from_checkpoint=latest \
|
|
||||||
--lr_scheduler=constant \
|
|
||||||
--mixed_precision=fp16 \
|
|
||||||
--only_save_embeds
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### `Cannot load embedding for <trigger>. It was trained on a model with token dimension 1024, but the current model has token dimension 768`
|
|
||||||
|
|
||||||
Messages like this indicate you trained the embedding on a different base model than the currently selected one.
|
|
||||||
|
|
||||||
For example, in the error above, the training was done on SD2.1 (768x768) but it was used on SD1.5 (512x512).
|
|
||||||
|
|
||||||
## Reading
|
|
||||||
|
|
||||||
For more information on textual inversion, please see the following
|
|
||||||
resources:
|
|
||||||
|
|
||||||
* The [textual inversion repository](https://github.com/rinongal/textual_inversion) and
|
|
||||||
associated paper for details and limitations.
|
|
||||||
* [HuggingFace's textual inversion training
|
|
||||||
page](https://huggingface.co/docs/diffusers/training/text_inversion)
|
|
||||||
* [HuggingFace example script
|
|
||||||
documentation](https://github.com/huggingface/diffusers/tree/main/examples/textual_inversion)
|
|
||||||
(Note that this script is similar to, but not identical, to
|
|
||||||
`textual_inversion`, but produces embed files that are completely compatible.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
copyright (c) 2023, Lincoln Stein and the InvokeAI Development Team
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
# Automatic Install
|
# Automatic Install & Updates
|
||||||
|
|
||||||
The installer is used for both new installs and updates.
|
**The same packaged installer file can be used for both new installs and updates.**
|
||||||
|
Using the installer for updates will leave everything you've added since installation, and just update the core libraries used to run Invoke.
|
||||||
|
Simply use the same path you installed to originally.
|
||||||
|
|
||||||
Both release and pre-release versions can be installed using it. It also supports install a wheel if needed.
|
Both release and pre-release versions can be installed using the installer. It also supports install through a wheel if needed.
|
||||||
|
|
||||||
Be sure to review the [installation requirements] and ensure your system has everything it needs to install Invoke.
|
Be sure to review the [installation requirements] and ensure your system has everything it needs to install Invoke.
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Installation Overview
|
# Installation and Updating Overview
|
||||||
|
|
||||||
Before installing, review the [installation requirements] to ensure your system is set up properly.
|
Before installing, review the [installation requirements] to ensure your system is set up properly.
|
||||||
|
|
||||||
@ -6,14 +6,21 @@ See the [FAQ] for frequently-encountered installation issues.
|
|||||||
|
|
||||||
If you need more help, join our [discord] or [create an issue].
|
If you need more help, join our [discord] or [create an issue].
|
||||||
|
|
||||||
<h2>Automatic Install</h2>
|
<h2>Automatic Install & Updates </h2>
|
||||||
|
|
||||||
✅ The automatic install is the best way to run InvokeAI. Check out the [installation guide] to get started.
|
✅ The automatic install is the best way to run InvokeAI. Check out the [installation guide] to get started.
|
||||||
|
|
||||||
|
⬆️ The same installer is also the best way to update InvokeAI - Simply rerun it for the same folder you installed to.
|
||||||
|
|
||||||
|
The installation process simply manages installation for the core libraries & application dependencies that run Invoke.
|
||||||
|
Any models, images, or other assets in the Invoke root folder won't be affected by the installation process.
|
||||||
|
|
||||||
<h2>Manual Install</h2>
|
<h2>Manual Install</h2>
|
||||||
|
|
||||||
If you are familiar with python and want more control over the packages that are installed, you can [install InvokeAI manually via PyPI].
|
If you are familiar with python and want more control over the packages that are installed, you can [install InvokeAI manually via PyPI].
|
||||||
|
|
||||||
|
Updates are managed by reinstalling the latest version through PyPi.
|
||||||
|
|
||||||
<h2>Developer Install</h2>
|
<h2>Developer Install</h2>
|
||||||
|
|
||||||
If you want to contribute to InvokeAI, consult the [developer install guide].
|
If you want to contribute to InvokeAI, consult the [developer install guide].
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
"@dnd-kit/sortable": "^8.0.0",
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@fontsource-variable/inter": "^5.0.17",
|
"@fontsource-variable/inter": "^5.0.17",
|
||||||
"@invoke-ai/ui-library": "^0.0.21",
|
"@invoke-ai/ui-library": "^0.0.25",
|
||||||
"@nanostores/react": "^0.7.2",
|
"@nanostores/react": "^0.7.2",
|
||||||
"@reduxjs/toolkit": "2.2.2",
|
"@reduxjs/toolkit": "2.2.2",
|
||||||
"@roarr/browser-log-writer": "^1.3.0",
|
"@roarr/browser-log-writer": "^1.3.0",
|
||||||
|
586
invokeai/frontend/web/pnpm-lock.yaml
generated
586
invokeai/frontend/web/pnpm-lock.yaml
generated
@ -7,7 +7,7 @@ settings:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/react':
|
'@chakra-ui/react':
|
||||||
specifier: ^2.8.2
|
specifier: ^2.8.2
|
||||||
version: 2.8.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(@types/react@18.2.59)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0)
|
version: 2.8.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.73)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@chakra-ui/react-use-size':
|
'@chakra-ui/react-use-size':
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0(react@18.2.0)
|
version: 2.1.0(react@18.2.0)
|
||||||
@ -30,8 +30,8 @@ dependencies:
|
|||||||
specifier: ^5.0.17
|
specifier: ^5.0.17
|
||||||
version: 5.0.17
|
version: 5.0.17
|
||||||
'@invoke-ai/ui-library':
|
'@invoke-ai/ui-library':
|
||||||
specifier: ^0.0.21
|
specifier: ^0.0.25
|
||||||
version: 0.0.21(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.17)(@internationalized/date@3.5.2)(@types/react@18.2.73)(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0)
|
version: 0.0.25(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.17)(@internationalized/date@3.5.3)(@types/react@18.2.73)(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@nanostores/react':
|
'@nanostores/react':
|
||||||
specifier: ^0.7.2
|
specifier: ^0.7.2
|
||||||
version: 0.7.2(nanostores@0.10.0)(react@18.2.0)
|
version: 0.7.2(nanostores@0.10.0)(react@18.2.0)
|
||||||
@ -306,7 +306,7 @@ packages:
|
|||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@ark-ui/anatomy@1.3.0(@internationalized/date@3.5.2):
|
/@ark-ui/anatomy@1.3.0(@internationalized/date@3.5.3):
|
||||||
resolution: {integrity: sha512-1yG2MrzUlix6KthjQMCNiHnkXrWwEdFAX6D+HqGJaNu0XvaGul2J+wDNtjsdX+gxiWu1nXXEEOAWlFVYMUf65w==}
|
resolution: {integrity: sha512-1yG2MrzUlix6KthjQMCNiHnkXrWwEdFAX6D+HqGJaNu0XvaGul2J+wDNtjsdX+gxiWu1nXXEEOAWlFVYMUf65w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@zag-js/accordion': 0.32.1
|
'@zag-js/accordion': 0.32.1
|
||||||
@ -318,7 +318,7 @@ packages:
|
|||||||
'@zag-js/color-utils': 0.32.1
|
'@zag-js/color-utils': 0.32.1
|
||||||
'@zag-js/combobox': 0.32.1
|
'@zag-js/combobox': 0.32.1
|
||||||
'@zag-js/date-picker': 0.32.1
|
'@zag-js/date-picker': 0.32.1
|
||||||
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.2)
|
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.3)
|
||||||
'@zag-js/dialog': 0.32.1
|
'@zag-js/dialog': 0.32.1
|
||||||
'@zag-js/editable': 0.32.1
|
'@zag-js/editable': 0.32.1
|
||||||
'@zag-js/file-upload': 0.32.1
|
'@zag-js/file-upload': 0.32.1
|
||||||
@ -345,13 +345,13 @@ packages:
|
|||||||
- '@internationalized/date'
|
- '@internationalized/date'
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@ark-ui/react@1.3.0(@internationalized/date@3.5.2)(react-dom@18.2.0)(react@18.2.0):
|
/@ark-ui/react@1.3.0(@internationalized/date@3.5.3)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-JHjNoIX50+mUCTaEGMjfGQWGGi31pKsV646jZJlR/1xohpYJigzg8BvO97cTsVk8fwtur+cm11gz3Nf7f5QUnA==}
|
resolution: {integrity: sha512-JHjNoIX50+mUCTaEGMjfGQWGGi31pKsV646jZJlR/1xohpYJigzg8BvO97cTsVk8fwtur+cm11gz3Nf7f5QUnA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=18.0.0'
|
react: '>=18.0.0'
|
||||||
react-dom: '>=18.0.0'
|
react-dom: '>=18.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ark-ui/anatomy': 1.3.0(@internationalized/date@3.5.2)
|
'@ark-ui/anatomy': 1.3.0(@internationalized/date@3.5.3)
|
||||||
'@zag-js/accordion': 0.32.1
|
'@zag-js/accordion': 0.32.1
|
||||||
'@zag-js/avatar': 0.32.1
|
'@zag-js/avatar': 0.32.1
|
||||||
'@zag-js/carousel': 0.32.1
|
'@zag-js/carousel': 0.32.1
|
||||||
@ -361,7 +361,7 @@ packages:
|
|||||||
'@zag-js/combobox': 0.32.1
|
'@zag-js/combobox': 0.32.1
|
||||||
'@zag-js/core': 0.32.1
|
'@zag-js/core': 0.32.1
|
||||||
'@zag-js/date-picker': 0.32.1
|
'@zag-js/date-picker': 0.32.1
|
||||||
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.2)
|
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.3)
|
||||||
'@zag-js/dialog': 0.32.1
|
'@zag-js/dialog': 0.32.1
|
||||||
'@zag-js/editable': 0.32.1
|
'@zag-js/editable': 0.32.1
|
||||||
'@zag-js/file-upload': 0.32.1
|
'@zag-js/file-upload': 0.32.1
|
||||||
@ -1681,7 +1681,7 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/accordion@2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0):
|
/@chakra-ui/accordion@2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-FSXRm8iClFyU+gVaXisOSEw0/4Q+qZbFRiuhIAkVU6Boj0FxAMrlo9a8AV5TuF77rgaHytCdHk0Ng+cyUijrag==}
|
resolution: {integrity: sha512-FSXRm8iClFyU+gVaXisOSEw0/4Q+qZbFRiuhIAkVU6Boj0FxAMrlo9a8AV5TuF77rgaHytCdHk0Ng+cyUijrag==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@chakra-ui/system': '>=2.0.0'
|
'@chakra-ui/system': '>=2.0.0'
|
||||||
@ -1694,9 +1694,9 @@ packages:
|
|||||||
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.2.0)
|
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.2.0)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||||
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.6)(react@18.2.0)
|
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.22)(react@18.2.0)
|
||||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1848,16 +1848,6 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/css-reset@2.3.0(@emotion/react@11.11.3)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==}
|
|
||||||
peerDependencies:
|
|
||||||
'@emotion/react': '>=10.0.35'
|
|
||||||
react: '>=18'
|
|
||||||
dependencies:
|
|
||||||
'@emotion/react': 11.11.3(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
react: 18.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@chakra-ui/css-reset@2.3.0(@emotion/react@11.11.4)(react@18.2.0):
|
/@chakra-ui/css-reset@2.3.0(@emotion/react@11.11.4)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==}
|
resolution: {integrity: sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1905,18 +1895,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-IGM/yGUHS+8TOQrZGpAKOJl/xGBrmRYJrmbHfUE7zrG3PpQyXvbLDP1M+RggkCFVgHlJi2wpYIf0QtQlU0XZfw==}
|
resolution: {integrity: sha512-IGM/yGUHS+8TOQrZGpAKOJl/xGBrmRYJrmbHfUE7zrG3PpQyXvbLDP1M+RggkCFVgHlJi2wpYIf0QtQlU0XZfw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/focus-lock@2.1.0(@types/react@18.2.59)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-EmGx4PhWGjm4dpjRqM4Aa+rCWBxP+Rq8Uc/nAVnD4YVqkEhBkrPTpui2lnjsuxqNaZ24fIAZ10cF1hlpemte/w==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=18'
|
|
||||||
dependencies:
|
|
||||||
'@chakra-ui/dom-utils': 2.1.0
|
|
||||||
react: 18.2.0
|
|
||||||
react-focus-lock: 2.11.1(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@chakra-ui/focus-lock@2.1.0(@types/react@18.2.73)(react@18.2.0):
|
/@chakra-ui/focus-lock@2.1.0(@types/react@18.2.73)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-EmGx4PhWGjm4dpjRqM4Aa+rCWBxP+Rq8Uc/nAVnD4YVqkEhBkrPTpui2lnjsuxqNaZ24fIAZ10cF1hlpemte/w==}
|
resolution: {integrity: sha512-EmGx4PhWGjm4dpjRqM4Aa+rCWBxP+Rq8Uc/nAVnD4YVqkEhBkrPTpui2lnjsuxqNaZ24fIAZ10cF1hlpemte/w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1924,7 +1902,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/dom-utils': 2.1.0
|
'@chakra-ui/dom-utils': 2.1.0
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-focus-lock: 2.11.2(@types/react@18.2.73)(react@18.2.0)
|
react-focus-lock: 2.11.1(@types/react@18.2.73)(react@18.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
dev: false
|
dev: false
|
||||||
@ -2100,59 +2078,6 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/menu@2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-lJS7XEObzJxsOwWQh7yfG4H8FzFPRP5hVPN/CL+JzytEINCSBvsCDHrYPQGp7jzpCi8vnTqQQGQe0f8dwnXd2g==}
|
|
||||||
peerDependencies:
|
|
||||||
'@chakra-ui/system': '>=2.0.0'
|
|
||||||
framer-motion: '>=4.0.0'
|
|
||||||
react: '>=18'
|
|
||||||
dependencies:
|
|
||||||
'@chakra-ui/clickable': 2.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/descendant': 3.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/lazy-utils': 2.0.5
|
|
||||||
'@chakra-ui/popper': 3.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/react-children-utils': 2.0.6(react@18.2.0)
|
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/react-use-animation-state': 2.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/react-use-disclosure': 2.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/react-use-focus-effect': 2.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/react-use-outside-click': 2.2.0(react@18.2.0)
|
|
||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
|
||||||
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.6)(react@18.2.0)
|
|
||||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
react: 18.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@chakra-ui/modal@2.3.1(@chakra-ui/system@2.6.2)(@types/react@18.2.59)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==}
|
|
||||||
peerDependencies:
|
|
||||||
'@chakra-ui/system': '>=2.0.0'
|
|
||||||
framer-motion: '>=4.0.0'
|
|
||||||
react: '>=18'
|
|
||||||
react-dom: '>=18'
|
|
||||||
dependencies:
|
|
||||||
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
'@chakra-ui/portal': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/react-types': 2.0.7(react@18.2.0)
|
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
|
||||||
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.6)(react@18.2.0)
|
|
||||||
aria-hidden: 1.2.3
|
|
||||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
react-remove-scroll: 2.5.7(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@chakra-ui/modal@2.3.1(@chakra-ui/system@2.6.2)(@types/react@18.2.73)(framer-motion@10.18.0)(react-dom@18.2.0)(react@18.2.0):
|
/@chakra-ui/modal@2.3.1(@chakra-ui/system@2.6.2)(@types/react@18.2.73)(framer-motion@10.18.0)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==}
|
resolution: {integrity: sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2170,11 +2095,37 @@ packages:
|
|||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||||
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.2.0)
|
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.2.0)
|
||||||
aria-hidden: 1.2.4
|
aria-hidden: 1.2.3
|
||||||
framer-motion: 10.18.0(react-dom@18.2.0)(react@18.2.0)
|
framer-motion: 10.18.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
react-remove-scroll: 2.5.9(@types/react@18.2.73)(react@18.2.0)
|
react-remove-scroll: 2.5.7(@types/react@18.2.73)(react@18.2.0)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@chakra-ui/modal@2.3.1(@chakra-ui/system@2.6.2)(@types/react@18.2.73)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@chakra-ui/system': '>=2.0.0'
|
||||||
|
framer-motion: '>=4.0.0'
|
||||||
|
react: '>=18'
|
||||||
|
react-dom: '>=18'
|
||||||
|
dependencies:
|
||||||
|
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.2.73)(react@18.2.0)
|
||||||
|
'@chakra-ui/portal': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@chakra-ui/react-context': 2.1.0(react@18.2.0)
|
||||||
|
'@chakra-ui/react-types': 2.0.7(react@18.2.0)
|
||||||
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
||||||
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||||
|
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.22)(react@18.2.0)
|
||||||
|
aria-hidden: 1.2.3
|
||||||
|
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
react-remove-scroll: 2.5.7(@types/react@18.2.73)(react@18.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
dev: false
|
dev: false
|
||||||
@ -2248,7 +2199,7 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/popover@2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0):
|
/@chakra-ui/popover@2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-K+2ai2dD0ljvJnlrzesCDT9mNzLifE3noGKZ3QwLqd/K34Ym1W/0aL1ERSynrcG78NKoXS54SdEzkhCZ4Gn/Zg==}
|
resolution: {integrity: sha512-K+2ai2dD0ljvJnlrzesCDT9mNzLifE3noGKZ3QwLqd/K34Ym1W/0aL1ERSynrcG78NKoXS54SdEzkhCZ4Gn/Zg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@chakra-ui/system': '>=2.0.0'
|
'@chakra-ui/system': '>=2.0.0'
|
||||||
@ -2266,8 +2217,8 @@ packages:
|
|||||||
'@chakra-ui/react-use-focus-on-pointer-down': 2.1.0(react@18.2.0)
|
'@chakra-ui/react-use-focus-on-pointer-down': 2.1.0(react@18.2.0)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2305,25 +2256,6 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/provider@2.4.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==}
|
|
||||||
peerDependencies:
|
|
||||||
'@emotion/react': ^11.0.0
|
|
||||||
'@emotion/styled': ^11.0.0
|
|
||||||
react: '>=18'
|
|
||||||
react-dom: '>=18'
|
|
||||||
dependencies:
|
|
||||||
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.11.3)(react@18.2.0)
|
|
||||||
'@chakra-ui/portal': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@chakra-ui/react-env': 3.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
|
||||||
'@chakra-ui/utils': 2.0.15
|
|
||||||
'@emotion/react': 11.11.3(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
'@emotion/styled': 11.11.0(@emotion/react@11.11.3)(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@chakra-ui/provider@2.4.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0):
|
/@chakra-ui/provider@2.4.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==}
|
resolution: {integrity: sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2554,77 +2486,6 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/react@2.8.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(@types/react@18.2.59)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==}
|
|
||||||
peerDependencies:
|
|
||||||
'@emotion/react': ^11.0.0
|
|
||||||
'@emotion/styled': ^11.0.0
|
|
||||||
framer-motion: '>=4.0.0'
|
|
||||||
react: '>=18'
|
|
||||||
react-dom: '>=18'
|
|
||||||
dependencies:
|
|
||||||
'@chakra-ui/accordion': 2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0)
|
|
||||||
'@chakra-ui/alert': 2.2.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/avatar': 2.3.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/breadcrumb': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/button': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/card': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/checkbox': 2.3.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/control-box': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/counter': 2.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.11.3)(react@18.2.0)
|
|
||||||
'@chakra-ui/editable': 3.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/hooks': 2.2.1(react@18.2.0)
|
|
||||||
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/image': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/input': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/live-region': 2.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/menu': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0)
|
|
||||||
'@chakra-ui/modal': 2.3.1(@chakra-ui/system@2.6.2)(@types/react@18.2.59)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@chakra-ui/number-input': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/pin-input': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/popover': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0)
|
|
||||||
'@chakra-ui/popper': 3.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/portal': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@chakra-ui/progress': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/provider': 2.4.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@chakra-ui/radio': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/react-env': 3.1.0(react@18.2.0)
|
|
||||||
'@chakra-ui/select': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/skeleton': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/skip-nav': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/slider': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/stat': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/stepper': 2.3.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/styled-system': 2.9.2
|
|
||||||
'@chakra-ui/switch': 2.1.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0)
|
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
|
||||||
'@chakra-ui/table': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/tabs': 3.0.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/tag': 3.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/textarea': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@chakra-ui/theme': 3.3.1(@chakra-ui/styled-system@2.9.2)
|
|
||||||
'@chakra-ui/theme-utils': 2.0.21
|
|
||||||
'@chakra-ui/toast': 7.0.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@chakra-ui/tooltip': 2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.6)(react@18.2.0)
|
|
||||||
'@chakra-ui/utils': 2.0.15
|
|
||||||
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
|
||||||
'@emotion/react': 11.11.3(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
'@emotion/styled': 11.11.0(@emotion/react@11.11.3)(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@chakra-ui/react@2.8.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.73)(framer-motion@10.18.0)(react-dom@18.2.0)(react@18.2.0):
|
/@chakra-ui/react@2.8.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.73)(framer-motion@10.18.0)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==}
|
resolution: {integrity: sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2696,6 +2557,77 @@ packages:
|
|||||||
- '@types/react'
|
- '@types/react'
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@chakra-ui/react@2.8.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.73)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@emotion/react': ^11.0.0
|
||||||
|
'@emotion/styled': ^11.0.0
|
||||||
|
framer-motion: '>=4.0.0'
|
||||||
|
react: '>=18'
|
||||||
|
react-dom: '>=18'
|
||||||
|
dependencies:
|
||||||
|
'@chakra-ui/accordion': 2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0)
|
||||||
|
'@chakra-ui/alert': 2.2.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/avatar': 2.3.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/breadcrumb': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/button': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/card': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/checkbox': 2.3.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/control-box': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/counter': 2.1.0(react@18.2.0)
|
||||||
|
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.11.4)(react@18.2.0)
|
||||||
|
'@chakra-ui/editable': 3.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.2.73)(react@18.2.0)
|
||||||
|
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/hooks': 2.2.1(react@18.2.0)
|
||||||
|
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/image': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/input': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/live-region': 2.1.0(react@18.2.0)
|
||||||
|
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/menu': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0)
|
||||||
|
'@chakra-ui/modal': 2.3.1(@chakra-ui/system@2.6.2)(@types/react@18.2.73)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@chakra-ui/number-input': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/pin-input': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/popover': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0)
|
||||||
|
'@chakra-ui/popper': 3.1.0(react@18.2.0)
|
||||||
|
'@chakra-ui/portal': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@chakra-ui/progress': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/provider': 2.4.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@chakra-ui/radio': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/react-env': 3.1.0(react@18.2.0)
|
||||||
|
'@chakra-ui/select': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/skeleton': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/skip-nav': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/slider': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/stat': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/stepper': 2.3.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/styled-system': 2.9.2
|
||||||
|
'@chakra-ui/switch': 2.1.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0)
|
||||||
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||||
|
'@chakra-ui/table': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/tabs': 3.0.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/tag': 3.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/textarea': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@chakra-ui/theme': 3.3.1(@chakra-ui/styled-system@2.9.2)
|
||||||
|
'@chakra-ui/theme-utils': 2.0.21
|
||||||
|
'@chakra-ui/toast': 7.0.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@chakra-ui/tooltip': 2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.22)(react@18.2.0)
|
||||||
|
'@chakra-ui/utils': 2.0.15
|
||||||
|
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
|
'@emotion/react': 11.11.4(@types/react@18.2.73)(react@18.2.0)
|
||||||
|
'@emotion/styled': 11.11.0(@emotion/react@11.11.4)(@types/react@18.2.73)(react@18.2.0)
|
||||||
|
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/select@2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0):
|
/@chakra-ui/select@2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-ZwCb7LqKCVLJhru3DXvKXpZ7Pbu1TDZ7N0PdQ0Zj1oyVLJyrpef1u9HR5u0amOpqcH++Ugt0f5JSmirjNlctjA==}
|
resolution: {integrity: sha512-ZwCb7LqKCVLJhru3DXvKXpZ7Pbu1TDZ7N0PdQ0Zj1oyVLJyrpef1u9HR5u0amOpqcH++Ugt0f5JSmirjNlctjA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2814,7 +2746,7 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/switch@2.1.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0):
|
/@chakra-ui/switch@2.1.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-pgmi/CC+E1v31FcnQhsSGjJnOE2OcND4cKPyTE+0F+bmGm48Q/b5UmKD9Y+CmZsrt/7V3h8KNczowupfuBfIHA==}
|
resolution: {integrity: sha512-pgmi/CC+E1v31FcnQhsSGjJnOE2OcND4cKPyTE+0F+bmGm48Q/b5UmKD9Y+CmZsrt/7V3h8KNczowupfuBfIHA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@chakra-ui/system': '>=2.0.0'
|
'@chakra-ui/system': '>=2.0.0'
|
||||||
@ -2823,30 +2755,11 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/checkbox': 2.3.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
'@chakra-ui/checkbox': 2.3.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/system@2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==}
|
|
||||||
peerDependencies:
|
|
||||||
'@emotion/react': ^11.0.0
|
|
||||||
'@emotion/styled': ^11.0.0
|
|
||||||
react: '>=18'
|
|
||||||
dependencies:
|
|
||||||
'@chakra-ui/color-mode': 2.2.0(react@18.2.0)
|
|
||||||
'@chakra-ui/object-utils': 2.1.0
|
|
||||||
'@chakra-ui/react-utils': 2.0.12(react@18.2.0)
|
|
||||||
'@chakra-ui/styled-system': 2.9.2
|
|
||||||
'@chakra-ui/theme-utils': 2.0.21
|
|
||||||
'@chakra-ui/utils': 2.0.15
|
|
||||||
'@emotion/react': 11.11.3(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
'@emotion/styled': 11.11.0(@emotion/react@11.11.3)(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
react: 18.2.0
|
|
||||||
react-fast-compare: 3.2.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@chakra-ui/system@2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0):
|
/@chakra-ui/system@2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==}
|
resolution: {integrity: sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2975,7 +2888,7 @@ packages:
|
|||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/toast@7.0.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0):
|
/@chakra-ui/toast@7.0.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-yvRP8jFKRs/YnkuE41BVTq9nB2v/KDRmje9u6dgDmE5+1bFt3bwjdf9gVbif4u5Ve7F7BGk5E093ARRVtvLvXA==}
|
resolution: {integrity: sha512-yvRP8jFKRs/YnkuE41BVTq9nB2v/KDRmje9u6dgDmE5+1bFt3bwjdf9gVbif4u5Ve7F7BGk5E093ARRVtvLvXA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@chakra-ui/system': 2.6.2
|
'@chakra-ui/system': 2.6.2
|
||||||
@ -2991,9 +2904,9 @@ packages:
|
|||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.2.0)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.2.0)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/styled-system': 2.9.2
|
'@chakra-ui/styled-system': 2.9.2
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||||
'@chakra-ui/theme': 3.3.1(@chakra-ui/styled-system@2.9.2)
|
'@chakra-ui/theme': 3.3.1(@chakra-ui/styled-system@2.9.2)
|
||||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
@ -3020,7 +2933,7 @@ packages:
|
|||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/tooltip@2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0):
|
/@chakra-ui/tooltip@2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-Rh39GBn/bL4kZpuEMPPRwYNnccRCL+w9OqamWHIB3Qboxs6h8cOyXfIdGxjo72lvhu1QI/a4KFqkM3St+WfC0A==}
|
resolution: {integrity: sha512-Rh39GBn/bL4kZpuEMPPRwYNnccRCL+w9OqamWHIB3Qboxs6h8cOyXfIdGxjo72lvhu1QI/a4KFqkM3St+WfC0A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@chakra-ui/system': '>=2.0.0'
|
'@chakra-ui/system': '>=2.0.0'
|
||||||
@ -3036,8 +2949,8 @@ packages:
|
|||||||
'@chakra-ui/react-use-event-listener': 2.1.0(react@18.2.0)
|
'@chakra-ui/react-use-event-listener': 2.1.0(react@18.2.0)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
@ -3064,17 +2977,6 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/transition@2.1.0(framer-motion@11.0.6)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-orkT6T/Dt+/+kVwJNy7zwJ+U2xAZ3EU7M3XCs45RBvUnZDr/u9vdmaM/3D/rOpmQJWgQBwKPJleUXrYWUagEDQ==}
|
|
||||||
peerDependencies:
|
|
||||||
framer-motion: '>=4.0.0'
|
|
||||||
react: '>=18'
|
|
||||||
dependencies:
|
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
|
||||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
react: 18.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@chakra-ui/utils@2.0.15:
|
/@chakra-ui/utils@2.0.15:
|
||||||
resolution: {integrity: sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==}
|
resolution: {integrity: sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -3198,12 +3100,6 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@emotion/is-prop-valid@1.2.1:
|
|
||||||
resolution: {integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==}
|
|
||||||
dependencies:
|
|
||||||
'@emotion/memoize': 0.8.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@emotion/is-prop-valid@1.2.2:
|
/@emotion/is-prop-valid@1.2.2:
|
||||||
resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==}
|
resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -3220,27 +3116,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==}
|
resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@emotion/react@11.11.3(@types/react@18.2.59)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==}
|
|
||||||
peerDependencies:
|
|
||||||
'@types/react': '*'
|
|
||||||
react: '>=16.8.0'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
'@babel/runtime': 7.23.9
|
|
||||||
'@emotion/babel-plugin': 11.11.0
|
|
||||||
'@emotion/cache': 11.11.0
|
|
||||||
'@emotion/serialize': 1.1.3
|
|
||||||
'@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
|
|
||||||
'@emotion/utils': 1.2.1
|
|
||||||
'@emotion/weak-memoize': 0.3.1
|
|
||||||
'@types/react': 18.2.59
|
|
||||||
hoist-non-react-statics: 3.3.2
|
|
||||||
react: 18.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@emotion/react@11.11.4(@types/react@18.2.73)(react@18.2.0):
|
/@emotion/react@11.11.4(@types/react@18.2.73)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==}
|
resolution: {integrity: sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -3276,27 +3151,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==}
|
resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@emotion/styled@11.11.0(@emotion/react@11.11.3)(@types/react@18.2.59)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==}
|
|
||||||
peerDependencies:
|
|
||||||
'@emotion/react': ^11.0.0-rc.0
|
|
||||||
'@types/react': '*'
|
|
||||||
react: '>=16.8.0'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
'@babel/runtime': 7.23.9
|
|
||||||
'@emotion/babel-plugin': 11.11.0
|
|
||||||
'@emotion/is-prop-valid': 1.2.1
|
|
||||||
'@emotion/react': 11.11.3(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
'@emotion/serialize': 1.1.3
|
|
||||||
'@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
|
|
||||||
'@emotion/utils': 1.2.1
|
|
||||||
'@types/react': 18.2.59
|
|
||||||
react: 18.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@emotion/styled@11.11.0(@emotion/react@11.11.4)(@types/react@18.2.73)(react@18.2.0):
|
/@emotion/styled@11.11.0(@emotion/react@11.11.4)(@types/react@18.2.73)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==}
|
resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -3663,16 +3517,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==}
|
resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@internationalized/date@3.5.2:
|
/@internationalized/date@3.5.3:
|
||||||
resolution: {integrity: sha512-vo1yOMUt2hzp63IutEaTUxROdvQg1qlMRsbCvbay2AK2Gai7wIgCyK5weEX3nHkiLgo4qCXHijFNC/ILhlRpOQ==}
|
resolution: {integrity: sha512-X9bi8NAEHAjD8yzmPYT2pdJsbe+tYSEBAfowtlxJVJdZR3aK8Vg7ZUT1Fm5M47KLzp/M1p1VwAaeSma3RT7biw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@swc/helpers': 0.5.7
|
'@swc/helpers': 0.5.11
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@internationalized/number@3.5.1:
|
/@internationalized/number@3.5.1:
|
||||||
resolution: {integrity: sha512-N0fPU/nz15SwR9IbfJ5xaS9Ss/O5h1sVXMZf43vc9mxEG48ovglvvzBjF53aHlq20uoR6c+88CrIXipU/LSzwg==}
|
resolution: {integrity: sha512-N0fPU/nz15SwR9IbfJ5xaS9Ss/O5h1sVXMZf43vc9mxEG48ovglvvzBjF53aHlq20uoR6c+88CrIXipU/LSzwg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@swc/helpers': 0.5.7
|
'@swc/helpers': 0.5.11
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@invoke-ai/eslint-config-react@0.0.14(eslint@8.57.0)(prettier@3.2.5)(typescript@5.4.3):
|
/@invoke-ai/eslint-config-react@0.0.14(eslint@8.57.0)(prettier@3.2.5)(typescript@5.4.3):
|
||||||
@ -3709,14 +3563,14 @@ packages:
|
|||||||
prettier: 3.2.5
|
prettier: 3.2.5
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@invoke-ai/ui-library@0.0.21(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.17)(@internationalized/date@3.5.2)(@types/react@18.2.73)(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0):
|
/@invoke-ai/ui-library@0.0.25(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.17)(@internationalized/date@3.5.3)(@types/react@18.2.73)(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-tCvgkBPDt0gNq+8IcR03e/Mw7R8Mb/SMXTqx3FEIxlTQEo93A/D38dKXeDCzTdx4sQ+sknfB+JLBbHs6sg5hhQ==}
|
resolution: {integrity: sha512-Fmjdlu62NXHgairYXGjcuCrxPEAl1G6Q6ban8g3excF6pDDdBeS7CmSNCyEDMxnSIOZrQlI04OhaMB17Imi9Uw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@fontsource-variable/inter': ^5.0.16
|
'@fontsource-variable/inter': ^5.0.16
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
react-dom: ^18.2.0
|
react-dom: ^18.2.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ark-ui/react': 1.3.0(@internationalized/date@3.5.2)(react-dom@18.2.0)(react@18.2.0)
|
'@ark-ui/react': 1.3.0(@internationalized/date@3.5.3)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@chakra-ui/anatomy': 2.2.2
|
'@chakra-ui/anatomy': 2.2.2
|
||||||
'@chakra-ui/icons': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
'@chakra-ui/icons': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||||
@ -5381,8 +5235,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@swc/helpers@0.5.7:
|
/@swc/helpers@0.5.11:
|
||||||
resolution: {integrity: sha512-BVvNZhx362+l2tSwSuyEUV4h7+jk9raNdoTSdLfwTshXJSaGmYKluGRJznziCI3KX02Z19DdsQrdfrpXAU3Hfg==}
|
resolution: {integrity: sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
@ -5844,10 +5698,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA==}
|
resolution: {integrity: sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/prop-types@15.7.11:
|
|
||||||
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/prop-types@15.7.12:
|
/@types/prop-types@15.7.12:
|
||||||
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
|
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
|
||||||
|
|
||||||
@ -5877,14 +5727,6 @@ packages:
|
|||||||
'@types/react': 18.2.73
|
'@types/react': 18.2.73
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@types/react@18.2.59:
|
|
||||||
resolution: {integrity: sha512-DE+F6BYEC8VtajY85Qr7mmhTd/79rJKIHCg99MU9SWPB4xvLb6D1za2vYflgZfmPqQVEr6UqJTnLXEwzpVPuOg==}
|
|
||||||
dependencies:
|
|
||||||
'@types/prop-types': 15.7.11
|
|
||||||
'@types/scheduler': 0.16.8
|
|
||||||
csstype: 3.1.3
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/react@18.2.73:
|
/@types/react@18.2.73:
|
||||||
resolution: {integrity: sha512-XcGdod0Jjv84HOC7N5ziY3x+qL0AfmubvKOZ9hJjJ2yd5EE+KYjWhdOjt387e9HPheHkdggF9atTifMRtyAaRA==}
|
resolution: {integrity: sha512-XcGdod0Jjv84HOC7N5ziY3x+qL0AfmubvKOZ9hJjJ2yd5EE+KYjWhdOjt387e9HPheHkdggF9atTifMRtyAaRA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -5895,10 +5737,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==}
|
resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/scheduler@0.16.8:
|
|
||||||
resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/semver@7.5.8:
|
/@types/semver@7.5.8:
|
||||||
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
|
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -6405,10 +6243,10 @@ packages:
|
|||||||
/@zag-js/date-picker@0.32.1:
|
/@zag-js/date-picker@0.32.1:
|
||||||
resolution: {integrity: sha512-n/hYmF+/R4+NuyfPRzCgeuLT6LJihKSuKzK29STPWy3sC/tBBHiqhNv1/4UKbatHUJXdBW2XF+N8Rw08RffcFQ==}
|
resolution: {integrity: sha512-n/hYmF+/R4+NuyfPRzCgeuLT6LJihKSuKzK29STPWy3sC/tBBHiqhNv1/4UKbatHUJXdBW2XF+N8Rw08RffcFQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.5.2
|
'@internationalized/date': 3.5.3
|
||||||
'@zag-js/anatomy': 0.32.1
|
'@zag-js/anatomy': 0.32.1
|
||||||
'@zag-js/core': 0.32.1
|
'@zag-js/core': 0.32.1
|
||||||
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.2)
|
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.3)
|
||||||
'@zag-js/dismissable': 0.32.1
|
'@zag-js/dismissable': 0.32.1
|
||||||
'@zag-js/dom-event': 0.32.1
|
'@zag-js/dom-event': 0.32.1
|
||||||
'@zag-js/dom-query': 0.32.1
|
'@zag-js/dom-query': 0.32.1
|
||||||
@ -6420,12 +6258,12 @@ packages:
|
|||||||
'@zag-js/utils': 0.32.1
|
'@zag-js/utils': 0.32.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@zag-js/date-utils@0.32.1(@internationalized/date@3.5.2):
|
/@zag-js/date-utils@0.32.1(@internationalized/date@3.5.3):
|
||||||
resolution: {integrity: sha512-dbBDRSVr5pRUw3rXndyGuSshZiWqQI5JQO4D2KIFGkXzorj6WzoOpcO910Z7AdM/9cCAMpCjUrka8d8o9BpJBg==}
|
resolution: {integrity: sha512-dbBDRSVr5pRUw3rXndyGuSshZiWqQI5JQO4D2KIFGkXzorj6WzoOpcO910Z7AdM/9cCAMpCjUrka8d8o9BpJBg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@internationalized/date': '>=3.0.0'
|
'@internationalized/date': '>=3.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.5.2
|
'@internationalized/date': 3.5.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@zag-js/dialog@0.32.1:
|
/@zag-js/dialog@0.32.1:
|
||||||
@ -6999,13 +6837,6 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/aria-hidden@1.2.4:
|
|
||||||
resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
dependencies:
|
|
||||||
tslib: 2.6.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/aria-query@5.1.3:
|
/aria-query@5.1.3:
|
||||||
resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==}
|
resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -9026,13 +8857,6 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/focus-lock@1.3.4:
|
|
||||||
resolution: {integrity: sha512-Gv0N3mvej3pD+HWkNryrF8sExzEHqhQ6OSFxD4DPxm9n5HGCaHme98ZMBZroNEAJcsdtHxk+skvThGKyUeoEGA==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
dependencies:
|
|
||||||
tslib: 2.6.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/focus-trap@7.5.4:
|
/focus-trap@7.5.4:
|
||||||
resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==}
|
resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -9095,24 +8919,6 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/framer-motion@11.0.6(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-BpO3mWF8UwxzO3Ca5AmSkrg14QYTeJa9vKgoLOoBdBdTPj0e81i1dMwnX6EQJXRieUx20uiDBXq8bA6y7N6b8Q==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^18.0.0
|
|
||||||
react-dom: ^18.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
react:
|
|
||||||
optional: true
|
|
||||||
react-dom:
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
tslib: 2.6.2
|
|
||||||
optionalDependencies:
|
|
||||||
'@emotion/is-prop-valid': 0.8.8
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/framesync@6.1.2:
|
/framesync@6.1.2:
|
||||||
resolution: {integrity: sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==}
|
resolution: {integrity: sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -11485,7 +11291,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
|
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-focus-lock@2.11.1(@types/react@18.2.59)(react@18.2.0):
|
/react-focus-lock@2.11.1(@types/react@18.2.73)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-IXLwnTBrLTlKTpASZXqqXJ8oymWrgAlOfuuDYN4XCuN1YJ72dwX198UCaF1QqGUk5C3QOnlMik//n3ufcfe8Ig==}
|
resolution: {integrity: sha512-IXLwnTBrLTlKTpASZXqqXJ8oymWrgAlOfuuDYN4XCuN1YJ72dwX198UCaF1QqGUk5C3QOnlMik//n3ufcfe8Ig==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
@ -11495,31 +11301,12 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.23.9
|
'@babel/runtime': 7.23.9
|
||||||
'@types/react': 18.2.59
|
'@types/react': 18.2.73
|
||||||
focus-lock: 1.3.3
|
focus-lock: 1.3.3
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-clientside-effect: 1.2.6(react@18.2.0)
|
react-clientside-effect: 1.2.6(react@18.2.0)
|
||||||
use-callback-ref: 1.3.1(@types/react@18.2.59)(react@18.2.0)
|
use-callback-ref: 1.3.1(@types/react@18.2.73)(react@18.2.0)
|
||||||
use-sidecar: 1.1.2(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react-focus-lock@2.11.2(@types/react@18.2.73)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-DDTbEiov0+RthESPVSTIdAWPPKic+op3sCcP+icbMRobvQNt7LuAlJ3KoarqQv5sCgKArru3kXmlmFTa27/CdQ==}
|
|
||||||
peerDependencies:
|
|
||||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
'@babel/runtime': 7.24.1
|
|
||||||
'@types/react': 18.2.73
|
|
||||||
focus-lock: 1.3.4
|
|
||||||
prop-types: 15.8.1
|
|
||||||
react: 18.2.0
|
|
||||||
react-clientside-effect: 1.2.6(react@18.2.0)
|
|
||||||
use-callback-ref: 1.3.2(@types/react@18.2.73)(react@18.2.0)
|
|
||||||
use-sidecar: 1.1.2(@types/react@18.2.73)(react@18.2.0)
|
use-sidecar: 1.1.2(@types/react@18.2.73)(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -11634,25 +11421,9 @@ packages:
|
|||||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-remove-scroll-bar@2.3.5(@types/react@18.2.59)(react@18.2.0):
|
/react-remove-scroll-bar@2.3.5(@types/react@18.2.73)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-3cqjOqg6s0XbOjWvmasmqHch+RLxIEk2r/70rzGXuz3iIGQsQheEQyqYCBb5EECoD01Vo2SIbDqW4paLeLTASw==}
|
resolution: {integrity: sha512-3cqjOqg6s0XbOjWvmasmqHch+RLxIEk2r/70rzGXuz3iIGQsQheEQyqYCBb5EECoD01Vo2SIbDqW4paLeLTASw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
peerDependencies:
|
|
||||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
'@types/react': 18.2.59
|
|
||||||
react: 18.2.0
|
|
||||||
react-style-singleton: 2.2.1(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
tslib: 2.6.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react-remove-scroll-bar@2.3.6(@types/react@18.2.73)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
@ -11666,28 +11437,9 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-remove-scroll@2.5.7(@types/react@18.2.59)(react@18.2.0):
|
/react-remove-scroll@2.5.7(@types/react@18.2.73)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==}
|
resolution: {integrity: sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
peerDependencies:
|
|
||||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
'@types/react': 18.2.59
|
|
||||||
react: 18.2.0
|
|
||||||
react-remove-scroll-bar: 2.3.5(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
react-style-singleton: 2.2.1(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
tslib: 2.6.2
|
|
||||||
use-callback-ref: 1.3.1(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
use-sidecar: 1.1.2(@types/react@18.2.59)(react@18.2.0)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react-remove-scroll@2.5.9(@types/react@18.2.73)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-bvHCLBrFfM2OgcrpPY2YW84sPdS2o2HKWJUf1xGyGLnSoEnOTOBpahIarjRuYtN0ryahCeP242yf+5TrBX/pZA==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
@ -11697,10 +11449,10 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 18.2.73
|
'@types/react': 18.2.73
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-remove-scroll-bar: 2.3.6(@types/react@18.2.73)(react@18.2.0)
|
react-remove-scroll-bar: 2.3.5(@types/react@18.2.73)(react@18.2.0)
|
||||||
react-style-singleton: 2.2.1(@types/react@18.2.73)(react@18.2.0)
|
react-style-singleton: 2.2.1(@types/react@18.2.73)(react@18.2.0)
|
||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
use-callback-ref: 1.3.2(@types/react@18.2.73)(react@18.2.0)
|
use-callback-ref: 1.3.1(@types/react@18.2.73)(react@18.2.0)
|
||||||
use-sidecar: 1.1.2(@types/react@18.2.73)(react@18.2.0)
|
use-sidecar: 1.1.2(@types/react@18.2.73)(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -11756,23 +11508,6 @@ packages:
|
|||||||
- '@types/react'
|
- '@types/react'
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-style-singleton@2.2.1(@types/react@18.2.59)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
peerDependencies:
|
|
||||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
'@types/react': 18.2.59
|
|
||||||
get-nonce: 1.0.1
|
|
||||||
invariant: 2.2.4
|
|
||||||
react: 18.2.0
|
|
||||||
tslib: 2.6.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react-style-singleton@2.2.1(@types/react@18.2.73)(react@18.2.0):
|
/react-style-singleton@2.2.1(@types/react@18.2.73)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
|
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -13288,24 +13023,9 @@ packages:
|
|||||||
punycode: 2.3.1
|
punycode: 2.3.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/use-callback-ref@1.3.1(@types/react@18.2.59)(react@18.2.0):
|
/use-callback-ref@1.3.1(@types/react@18.2.73)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==}
|
resolution: {integrity: sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
peerDependencies:
|
|
||||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
'@types/react': 18.2.59
|
|
||||||
react: 18.2.0
|
|
||||||
tslib: 2.6.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/use-callback-ref@1.3.2(@types/react@18.2.73)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
@ -13358,22 +13078,6 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/use-sidecar@1.1.2(@types/react@18.2.59)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
peerDependencies:
|
|
||||||
'@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
'@types/react': 18.2.59
|
|
||||||
detect-node-es: 1.1.0
|
|
||||||
react: 18.2.0
|
|
||||||
tslib: 2.6.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/use-sidecar@1.1.2(@types/react@18.2.73)(react@18.2.0):
|
/use-sidecar@1.1.2(@types/react@18.2.73)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
|
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
@ -88,11 +88,13 @@
|
|||||||
"negativePrompt": "Negative Prompt",
|
"negativePrompt": "Negative Prompt",
|
||||||
"discordLabel": "Discord",
|
"discordLabel": "Discord",
|
||||||
"dontAskMeAgain": "Don't ask me again",
|
"dontAskMeAgain": "Don't ask me again",
|
||||||
|
"editor": "Editor",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"file": "File",
|
"file": "File",
|
||||||
"folder": "Folder",
|
"folder": "Folder",
|
||||||
"format": "format",
|
"format": "format",
|
||||||
"githubLabel": "Github",
|
"githubLabel": "Github",
|
||||||
|
"goTo": "Go to",
|
||||||
"hotkeysLabel": "Hotkeys",
|
"hotkeysLabel": "Hotkeys",
|
||||||
"imageFailedToLoad": "Unable to Load Image",
|
"imageFailedToLoad": "Unable to Load Image",
|
||||||
"img2img": "Image To Image",
|
"img2img": "Image To Image",
|
||||||
@ -140,7 +142,8 @@
|
|||||||
"blue": "Blue",
|
"blue": "Blue",
|
||||||
"alpha": "Alpha",
|
"alpha": "Alpha",
|
||||||
"selected": "Selected",
|
"selected": "Selected",
|
||||||
"viewer": "Viewer"
|
"viewer": "Viewer",
|
||||||
|
"tab": "Tab"
|
||||||
},
|
},
|
||||||
"controlnet": {
|
"controlnet": {
|
||||||
"controlAdapter_one": "Control Adapter",
|
"controlAdapter_one": "Control Adapter",
|
||||||
@ -361,7 +364,8 @@
|
|||||||
"bulkDownloadRequestFailed": "Problem Preparing Download",
|
"bulkDownloadRequestFailed": "Problem Preparing Download",
|
||||||
"bulkDownloadFailed": "Download Failed",
|
"bulkDownloadFailed": "Download Failed",
|
||||||
"problemDeletingImages": "Problem Deleting Images",
|
"problemDeletingImages": "Problem Deleting Images",
|
||||||
"problemDeletingImagesDesc": "One or more images could not be deleted"
|
"problemDeletingImagesDesc": "One or more images could not be deleted",
|
||||||
|
"switchTo": "Switch to {{ tab }} (Z)"
|
||||||
},
|
},
|
||||||
"hotkeys": {
|
"hotkeys": {
|
||||||
"searchHotkeys": "Search Hotkeys",
|
"searchHotkeys": "Search Hotkeys",
|
||||||
@ -584,6 +588,14 @@
|
|||||||
"upscale": {
|
"upscale": {
|
||||||
"desc": "Upscale the current image",
|
"desc": "Upscale the current image",
|
||||||
"title": "Upscale"
|
"title": "Upscale"
|
||||||
|
},
|
||||||
|
"backToEditor": {
|
||||||
|
"desc": "Closes the Image Viewer and shows the Editor View (Text to Image tab only)",
|
||||||
|
"title": "Back to Editor"
|
||||||
|
},
|
||||||
|
"openImageViewer": {
|
||||||
|
"desc": "Opens the Image Viewer (Text to Image tab only)",
|
||||||
|
"title": "Open Image Viewer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
@ -1522,7 +1534,7 @@
|
|||||||
"moveForward": "Move Forward",
|
"moveForward": "Move Forward",
|
||||||
"moveBackward": "Move Backward",
|
"moveBackward": "Move Backward",
|
||||||
"brushSize": "Brush Size",
|
"brushSize": "Brush Size",
|
||||||
"controlLayers": "Control Layers (BETA)",
|
"controlLayers": "Control Layers",
|
||||||
"globalMaskOpacity": "Global Mask Opacity",
|
"globalMaskOpacity": "Global Mask Opacity",
|
||||||
"autoNegative": "Auto Negative",
|
"autoNegative": "Auto Negative",
|
||||||
"toggleVisibility": "Toggle Layer Visibility",
|
"toggleVisibility": "Toggle Layer Visibility",
|
||||||
@ -1543,8 +1555,25 @@
|
|||||||
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
|
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
|
||||||
"globalIPAdapter": "Global $t(common.ipAdapter)",
|
"globalIPAdapter": "Global $t(common.ipAdapter)",
|
||||||
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)",
|
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)",
|
||||||
|
"globalInitialImage": "Global Initial Image",
|
||||||
|
"globalInitialImageLayer": "$t(controlLayers.globalInitialImage) $t(unifiedCanvas.layer)",
|
||||||
"opacityFilter": "Opacity Filter",
|
"opacityFilter": "Opacity Filter",
|
||||||
"clearProcessor": "Clear Processor",
|
"clearProcessor": "Clear Processor",
|
||||||
"resetProcessor": "Reset Processor to Defaults"
|
"resetProcessor": "Reset Processor to Defaults",
|
||||||
|
"noLayersAdded": "No Layers Added"
|
||||||
|
},
|
||||||
|
"ui": {
|
||||||
|
"tabs": {
|
||||||
|
"generation": "Generation",
|
||||||
|
"generationTab": "$t(ui.tabs.generation) $t(common.tab)",
|
||||||
|
"canvas": "Canvas",
|
||||||
|
"canvasTab": "$t(ui.tabs.canvas) $t(common.tab)",
|
||||||
|
"workflows": "Workflows",
|
||||||
|
"workflowsTab": "$t(ui.tabs.workflows) $t(common.tab)",
|
||||||
|
"models": "Models",
|
||||||
|
"modelsTab": "$t(ui.tabs.models) $t(common.tab)",
|
||||||
|
"queue": "Queue",
|
||||||
|
"queueTab": "$t(ui.tabs.queue) $t(common.tab)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,7 @@ export type LoggerNamespace =
|
|||||||
| 'models'
|
| 'models'
|
||||||
| 'config'
|
| 'config'
|
||||||
| 'canvas'
|
| 'canvas'
|
||||||
| 'txt2img'
|
| 'generation'
|
||||||
| 'img2img'
|
|
||||||
| 'nodes'
|
| 'nodes'
|
||||||
| 'system'
|
| 'system'
|
||||||
| 'socketio'
|
| 'socketio'
|
||||||
|
@ -32,7 +32,6 @@ import { addImagesStarredListener } from 'app/store/middleware/listenerMiddlewar
|
|||||||
import { addImagesUnstarredListener } from 'app/store/middleware/listenerMiddleware/listeners/imagesUnstarred';
|
import { addImagesUnstarredListener } from 'app/store/middleware/listenerMiddleware/listeners/imagesUnstarred';
|
||||||
import { addImageToDeleteSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/imageToDeleteSelected';
|
import { addImageToDeleteSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/imageToDeleteSelected';
|
||||||
import { addImageUploadedFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/imageUploaded';
|
import { addImageUploadedFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/imageUploaded';
|
||||||
import { addInitialImageSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/initialImageSelected';
|
|
||||||
import { addModelSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelSelected';
|
import { addModelSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelSelected';
|
||||||
import { addModelsLoadedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelsLoaded';
|
import { addModelsLoadedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelsLoaded';
|
||||||
import { addDynamicPromptsListener } from 'app/store/middleware/listenerMiddleware/listeners/promptChanged';
|
import { addDynamicPromptsListener } from 'app/store/middleware/listenerMiddleware/listeners/promptChanged';
|
||||||
@ -73,9 +72,6 @@ const startAppListening = listenerMiddleware.startListening as AppStartListening
|
|||||||
// Image uploaded
|
// Image uploaded
|
||||||
addImageUploadedFulfilledListener(startAppListening);
|
addImageUploadedFulfilledListener(startAppListening);
|
||||||
|
|
||||||
// Image selected
|
|
||||||
addInitialImageSelectedListener(startAppListening);
|
|
||||||
|
|
||||||
// Image deleted
|
// Image deleted
|
||||||
addRequestedSingleImageDeletionListener(startAppListening);
|
addRequestedSingleImageDeletionListener(startAppListening);
|
||||||
addDeleteBoardAndImagesFulfilledListener(startAppListening);
|
addDeleteBoardAndImagesFulfilledListener(startAppListening);
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||||
import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice';
|
import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
|
import { allLayersDeleted } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { getImageUsage } from 'features/deleteImageModal/store/selectors';
|
import { getImageUsage } from 'features/deleteImageModal/store/selectors';
|
||||||
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => {
|
export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => {
|
||||||
@ -14,19 +14,14 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
|
|||||||
|
|
||||||
// Remove all deleted images from the UI
|
// Remove all deleted images from the UI
|
||||||
|
|
||||||
let wasInitialImageReset = false;
|
|
||||||
let wasCanvasReset = false;
|
let wasCanvasReset = false;
|
||||||
let wasNodeEditorReset = false;
|
let wasNodeEditorReset = false;
|
||||||
let wereControlAdaptersReset = false;
|
let wereControlAdaptersReset = false;
|
||||||
|
let wereControlLayersReset = false;
|
||||||
|
|
||||||
const { generation, canvas, nodes, controlAdapters } = getState();
|
const { canvas, nodes, controlAdapters, controlLayers } = getState();
|
||||||
deleted_images.forEach((image_name) => {
|
deleted_images.forEach((image_name) => {
|
||||||
const imageUsage = getImageUsage(generation, canvas, nodes, controlAdapters, image_name);
|
const imageUsage = getImageUsage(canvas, nodes, controlAdapters, controlLayers.present, image_name);
|
||||||
|
|
||||||
if (imageUsage.isInitialImage && !wasInitialImageReset) {
|
|
||||||
dispatch(clearInitialImage());
|
|
||||||
wasInitialImageReset = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageUsage.isCanvasImage && !wasCanvasReset) {
|
if (imageUsage.isCanvasImage && !wasCanvasReset) {
|
||||||
dispatch(resetCanvas());
|
dispatch(resetCanvas());
|
||||||
@ -42,6 +37,11 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
|
|||||||
dispatch(controlAdaptersReset());
|
dispatch(controlAdaptersReset());
|
||||||
wereControlAdaptersReset = true;
|
wereControlAdaptersReset = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (imageUsage.isControlLayerImage && !wereControlLayersReset) {
|
||||||
|
dispatch(allLayersDeleted());
|
||||||
|
wereControlLayersReset = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -48,10 +48,12 @@ export const addCanvasImageToControlNetListener = (startAppListening: AppStartLi
|
|||||||
})
|
})
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
|
const { image_name } = imageDTO;
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
controlAdapterImageChanged({
|
controlAdapterImageChanged({
|
||||||
id,
|
id,
|
||||||
controlImage: imageDTO,
|
controlImage: image_name,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -58,10 +58,12 @@ export const addCanvasMaskToControlNetListener = (startAppListening: AppStartLis
|
|||||||
})
|
})
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
|
const { image_name } = imageDTO;
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
controlAdapterImageChanged({
|
controlAdapterImageChanged({
|
||||||
id,
|
id,
|
||||||
controlImage: imageDTO,
|
controlImage: image_name,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
caLayerProcessorConfigChanged,
|
caLayerProcessorConfigChanged,
|
||||||
isControlAdapterLayer,
|
isControlAdapterLayer,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { CONTROLNET_PROCESSORS } from 'features/controlLayers/util/controlAdapters';
|
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { isImageOutput } from 'features/nodes/types/common';
|
import { isImageOutput } from 'features/nodes/types/common';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
@ -76,7 +76,7 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error: TS isn't able to narrow the typing of buildNode and `config` will error...
|
// @ts-expect-error: TS isn't able to narrow the typing of buildNode and `config` will error...
|
||||||
const processorNode = CONTROLNET_PROCESSORS[config.type].buildNode(image, config);
|
const processorNode = CA_PROCESSOR_DATA[config.type].buildNode(image, config);
|
||||||
const enqueueBatchArg: BatchConfig = {
|
const enqueueBatchArg: BatchConfig = {
|
||||||
prepend: true,
|
prepend: true,
|
||||||
batch: {
|
batch: {
|
||||||
|
@ -12,7 +12,6 @@ import {
|
|||||||
selectControlAdapterById,
|
selectControlAdapterById,
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
||||||
import { isEqual } from 'lodash-es';
|
|
||||||
|
|
||||||
type AnyControlAdapterParamChangeAction =
|
type AnyControlAdapterParamChangeAction =
|
||||||
| ReturnType<typeof controlAdapterProcessorParamsChanged>
|
| ReturnType<typeof controlAdapterProcessorParamsChanged>
|
||||||
@ -53,11 +52,6 @@ const predicate: AnyListenerPredicate<RootState> = (action, state, prevState) =>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevCA.controlImage === ca.controlImage && isEqual(prevCA.processorNode, ca.processorNode)) {
|
|
||||||
// Don't re-process if the processor hasn't changed
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isProcessorSelected = processorType !== 'none';
|
const isProcessorSelected = processorType !== 'none';
|
||||||
|
|
||||||
const hasControlImage = Boolean(controlImage);
|
const hasControlImage = Boolean(controlImage);
|
||||||
|
@ -91,7 +91,7 @@ export const addControlNetImageProcessedListener = (startAppListening: AppStartL
|
|||||||
dispatch(
|
dispatch(
|
||||||
controlAdapterProcessedImageChanged({
|
controlAdapterProcessedImageChanged({
|
||||||
id,
|
id,
|
||||||
processedControlImage,
|
processedControlImage: processedControlImage.image_name,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ import type { ImageDTO } from 'services/api/types';
|
|||||||
export const addEnqueueRequestedCanvasListener = (startAppListening: AppStartListening) => {
|
export const addEnqueueRequestedCanvasListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||||
enqueueRequested.match(action) && action.payload.tabName === 'unifiedCanvas',
|
enqueueRequested.match(action) && action.payload.tabName === 'canvas',
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
const log = logger('queue');
|
const log = logger('queue');
|
||||||
const { prepend } = action.payload;
|
const { prepend } = action.payload;
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
import { enqueueRequested } from 'app/store/actions';
|
import { enqueueRequested } from 'app/store/actions';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { buildGenerationTabGraph } from 'features/nodes/util/graph/buildGenerationTabGraph';
|
||||||
|
import { buildGenerationTabSDXLGraph } from 'features/nodes/util/graph/buildGenerationTabSDXLGraph';
|
||||||
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
||||||
import { buildLinearImageToImageGraph } from 'features/nodes/util/graph/buildLinearImageToImageGraph';
|
|
||||||
import { buildLinearSDXLImageToImageGraph } from 'features/nodes/util/graph/buildLinearSDXLImageToImageGraph';
|
|
||||||
import { buildLinearSDXLTextToImageGraph } from 'features/nodes/util/graph/buildLinearSDXLTextToImageGraph';
|
|
||||||
import { buildLinearTextToImageGraph } from 'features/nodes/util/graph/buildLinearTextToImageGraph';
|
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
|
|
||||||
export const addEnqueueRequestedLinear = (startAppListening: AppStartListening) => {
|
export const addEnqueueRequestedLinear = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||||
enqueueRequested.match(action) && (action.payload.tabName === 'txt2img' || action.payload.tabName === 'img2img'),
|
enqueueRequested.match(action) && action.payload.tabName === 'generation',
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const model = state.generation.model;
|
const model = state.generation.model;
|
||||||
@ -19,17 +17,9 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
|
|||||||
let graph;
|
let graph;
|
||||||
|
|
||||||
if (model && model.base === 'sdxl') {
|
if (model && model.base === 'sdxl') {
|
||||||
if (action.payload.tabName === 'txt2img') {
|
graph = await buildGenerationTabSDXLGraph(state);
|
||||||
graph = await buildLinearSDXLTextToImageGraph(state);
|
|
||||||
} else {
|
|
||||||
graph = await buildLinearSDXLImageToImageGraph(state);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (action.payload.tabName === 'txt2img') {
|
graph = await buildGenerationTabGraph(state);
|
||||||
graph = await buildLinearTextToImageGraph(state);
|
|
||||||
} else {
|
|
||||||
graph = await buildLinearImageToImageGraph(state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const batchConfig = prepareLinearUIBatch(state, graph, prepend);
|
const batchConfig = prepareLinearUIBatch(state, graph, prepend);
|
||||||
|
@ -8,7 +8,7 @@ import type { BatchConfig } from 'services/api/types';
|
|||||||
export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) => {
|
export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||||
enqueueRequested.match(action) && action.payload.tabName === 'nodes',
|
enqueueRequested.match(action) && action.payload.tabName === 'workflows',
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { nodes, edges } = state.nodes;
|
const { nodes, edges } = state.nodes;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||||
import { selectionChanged } from 'features/gallery/store/gallerySlice';
|
import { isImageViewerOpenChanged, selectionChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
import type { ImageDTO } from 'services/api/types';
|
import type { ImageDTO } from 'services/api/types';
|
||||||
import { imagesSelectors } from 'services/api/util';
|
import { imagesSelectors } from 'services/api/util';
|
||||||
@ -62,6 +62,7 @@ export const addGalleryImageClickedListener = (startAppListening: AppStartListen
|
|||||||
} else {
|
} else {
|
||||||
dispatch(selectionChanged([imageDTO]));
|
dispatch(selectionChanged([imageDTO]));
|
||||||
}
|
}
|
||||||
|
dispatch(isImageViewerOpenChanged(true));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import type { AppDispatch, RootState } from 'app/store/store';
|
||||||
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||||
import {
|
import {
|
||||||
controlAdapterImageChanged,
|
controlAdapterImageChanged,
|
||||||
@ -7,6 +8,13 @@ import {
|
|||||||
selectControlAdapterAll,
|
selectControlAdapterAll,
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
||||||
|
import {
|
||||||
|
isControlAdapterLayer,
|
||||||
|
isInitialImageLayer,
|
||||||
|
isIPAdapterLayer,
|
||||||
|
isRegionalGuidanceLayer,
|
||||||
|
layerDeleted,
|
||||||
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
||||||
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
||||||
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||||
@ -14,12 +22,82 @@ import { imageSelected } from 'features/gallery/store/gallerySlice';
|
|||||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { isImageFieldInputInstance } from 'features/nodes/types/field';
|
import { isImageFieldInputInstance } from 'features/nodes/types/field';
|
||||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
|
||||||
import { clamp, forEach } from 'lodash-es';
|
import { clamp, forEach } from 'lodash-es';
|
||||||
import { api } from 'services/api';
|
import { api } from 'services/api';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
import type { ImageDTO } from 'services/api/types';
|
||||||
import { imagesSelectors } from 'services/api/util';
|
import { imagesSelectors } from 'services/api/util';
|
||||||
|
|
||||||
|
const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||||
|
state.nodes.nodes.forEach((node) => {
|
||||||
|
if (!isInvocationNode(node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach(node.data.inputs, (input) => {
|
||||||
|
if (isImageFieldInputInstance(input) && input.value?.image_name === imageDTO.image_name) {
|
||||||
|
dispatch(
|
||||||
|
fieldImageValueChanged({
|
||||||
|
nodeId: node.data.id,
|
||||||
|
fieldName: input.name,
|
||||||
|
value: undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||||
|
forEach(selectControlAdapterAll(state.controlAdapters), (ca) => {
|
||||||
|
if (
|
||||||
|
ca.controlImage === imageDTO.image_name ||
|
||||||
|
(isControlNetOrT2IAdapter(ca) && ca.processedControlImage === imageDTO.image_name)
|
||||||
|
) {
|
||||||
|
dispatch(
|
||||||
|
controlAdapterImageChanged({
|
||||||
|
id: ca.id,
|
||||||
|
controlImage: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
controlAdapterProcessedImageChanged({
|
||||||
|
id: ca.id,
|
||||||
|
processedControlImage: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteControlLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||||
|
state.controlLayers.present.layers.forEach((l) => {
|
||||||
|
if (isRegionalGuidanceLayer(l)) {
|
||||||
|
if (l.ipAdapters.some((ipa) => ipa.image?.imageName === imageDTO.image_name)) {
|
||||||
|
dispatch(layerDeleted(l.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isControlAdapterLayer(l)) {
|
||||||
|
if (
|
||||||
|
l.controlAdapter.image?.imageName === imageDTO.image_name ||
|
||||||
|
l.controlAdapter.processedImage?.imageName === imageDTO.image_name
|
||||||
|
) {
|
||||||
|
dispatch(layerDeleted(l.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isIPAdapterLayer(l)) {
|
||||||
|
if (l.ipAdapter.image?.imageName === imageDTO.image_name) {
|
||||||
|
dispatch(layerDeleted(l.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isInitialImageLayer(l)) {
|
||||||
|
if (l.image?.imageName === imageDTO.image_name) {
|
||||||
|
dispatch(layerDeleted(l.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const addRequestedSingleImageDeletionListener = (startAppListening: AppStartListening) => {
|
export const addRequestedSingleImageDeletionListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: imageDeletionConfirmed,
|
actionCreator: imageDeletionConfirmed,
|
||||||
@ -73,50 +151,9 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
imageDTOs.forEach((imageDTO) => {
|
imageDTOs.forEach((imageDTO) => {
|
||||||
// reset init image if we deleted it
|
deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||||
if (getState().generation.initialImage?.imageName === imageDTO.image_name) {
|
deleteNodesImages(state, dispatch, imageDTO);
|
||||||
dispatch(clearInitialImage());
|
deleteControlLayerImages(state, dispatch, imageDTO);
|
||||||
}
|
|
||||||
|
|
||||||
// reset control adapters that use the deleted images
|
|
||||||
forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => {
|
|
||||||
if (
|
|
||||||
ca.controlImage === imageDTO.image_name ||
|
|
||||||
(isControlNetOrT2IAdapter(ca) && ca.processedControlImage === imageDTO.image_name)
|
|
||||||
) {
|
|
||||||
dispatch(
|
|
||||||
controlAdapterImageChanged({
|
|
||||||
id: ca.id,
|
|
||||||
controlImage: null,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
dispatch(
|
|
||||||
controlAdapterProcessedImageChanged({
|
|
||||||
id: ca.id,
|
|
||||||
processedControlImage: null,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// reset nodes that use the deleted images
|
|
||||||
getState().nodes.nodes.forEach((node) => {
|
|
||||||
if (!isInvocationNode(node)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach(node.data.inputs, (input) => {
|
|
||||||
if (isImageFieldInputInstance(input) && input.value?.image_name === imageDTO.image_name) {
|
|
||||||
dispatch(
|
|
||||||
fieldImageValueChanged({
|
|
||||||
nodeId: node.data.id,
|
|
||||||
fieldName: input.name,
|
|
||||||
value: undefined,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete from server
|
// Delete from server
|
||||||
@ -168,50 +205,9 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
imageDTOs.forEach((imageDTO) => {
|
imageDTOs.forEach((imageDTO) => {
|
||||||
// reset init image if we deleted it
|
deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||||
if (getState().generation.initialImage?.imageName === imageDTO.image_name) {
|
deleteNodesImages(state, dispatch, imageDTO);
|
||||||
dispatch(clearInitialImage());
|
deleteControlLayerImages(state, dispatch, imageDTO);
|
||||||
}
|
|
||||||
|
|
||||||
// reset control adapters that use the deleted images
|
|
||||||
forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => {
|
|
||||||
if (
|
|
||||||
ca.controlImage === imageDTO.image_name ||
|
|
||||||
(isControlNetOrT2IAdapter(ca) && ca.processedControlImage === imageDTO.image_name)
|
|
||||||
) {
|
|
||||||
dispatch(
|
|
||||||
controlAdapterImageChanged({
|
|
||||||
id: ca.id,
|
|
||||||
controlImage: null,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
dispatch(
|
|
||||||
controlAdapterProcessedImageChanged({
|
|
||||||
id: ca.id,
|
|
||||||
processedControlImage: null,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// reset nodes that use the deleted images
|
|
||||||
getState().nodes.nodes.forEach((node) => {
|
|
||||||
if (!isInvocationNode(node)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach(node.data.inputs, (input) => {
|
|
||||||
if (isImageFieldInputInstance(input) && input.value?.image_name === imageDTO.image_name) {
|
|
||||||
dispatch(
|
|
||||||
fieldImageValueChanged({
|
|
||||||
nodeId: node.data.id,
|
|
||||||
fieldName: input.name,
|
|
||||||
value: undefined,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// no-op
|
// no-op
|
||||||
|
@ -9,13 +9,14 @@ import {
|
|||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
import {
|
import {
|
||||||
caLayerImageChanged,
|
caLayerImageChanged,
|
||||||
|
iiLayerImageChanged,
|
||||||
ipaLayerImageChanged,
|
ipaLayerImageChanged,
|
||||||
rgLayerIPAdapterImageChanged,
|
rgLayerIPAdapterImageChanged,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { initialImageChanged, selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
export const dndDropped = createAction<{
|
export const dndDropped = createAction<{
|
||||||
@ -52,18 +53,6 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Image dropped on initial image
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
overData.actionType === 'SET_INITIAL_IMAGE' &&
|
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
|
||||||
activeData.payload.imageDTO
|
|
||||||
) {
|
|
||||||
dispatch(initialImageChanged(activeData.payload.imageDTO));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image dropped on ControlNet
|
* Image dropped on ControlNet
|
||||||
*/
|
*/
|
||||||
@ -76,7 +65,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
dispatch(
|
dispatch(
|
||||||
controlAdapterImageChanged({
|
controlAdapterImageChanged({
|
||||||
id,
|
id,
|
||||||
controlImage: activeData.payload.imageDTO,
|
controlImage: activeData.payload.imageDTO.image_name,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -143,6 +132,24 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image dropped on II Layer Image
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
overData.actionType === 'SET_II_LAYER_IMAGE' &&
|
||||||
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
|
activeData.payload.imageDTO
|
||||||
|
) {
|
||||||
|
const { layerId } = overData.context;
|
||||||
|
dispatch(
|
||||||
|
iiLayerImageChanged({
|
||||||
|
layerId,
|
||||||
|
imageDTO: activeData.payload.imageDTO,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image dropped on Canvas
|
* Image dropped on Canvas
|
||||||
*/
|
*/
|
||||||
|
@ -14,7 +14,6 @@ export const addImageToDeleteSelectedListener = (startAppListening: AppStartList
|
|||||||
|
|
||||||
const isImageInUse =
|
const isImageInUse =
|
||||||
imagesUsage.some((i) => i.isCanvasImage) ||
|
imagesUsage.some((i) => i.isCanvasImage) ||
|
||||||
imagesUsage.some((i) => i.isInitialImage) ||
|
|
||||||
imagesUsage.some((i) => i.isControlImage) ||
|
imagesUsage.some((i) => i.isControlImage) ||
|
||||||
imagesUsage.some((i) => i.isNodesImage);
|
imagesUsage.some((i) => i.isNodesImage);
|
||||||
|
|
||||||
|
@ -8,11 +8,12 @@ import {
|
|||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
import {
|
import {
|
||||||
caLayerImageChanged,
|
caLayerImageChanged,
|
||||||
|
iiLayerImageChanged,
|
||||||
ipaLayerImageChanged,
|
ipaLayerImageChanged,
|
||||||
rgLayerIPAdapterImageChanged,
|
rgLayerIPAdapterImageChanged,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { initialImageChanged, selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { omit } from 'lodash-es';
|
import { omit } from 'lodash-es';
|
||||||
@ -101,7 +102,7 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
|||||||
dispatch(
|
dispatch(
|
||||||
controlAdapterImageChanged({
|
controlAdapterImageChanged({
|
||||||
id,
|
id,
|
||||||
controlImage: imageDTO,
|
controlImage: imageDTO.image_name,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -146,15 +147,15 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_INITIAL_IMAGE') {
|
if (postUploadAction?.type === 'SET_II_LAYER_IMAGE') {
|
||||||
dispatch(initialImageChanged(imageDTO));
|
const { layerId } = postUploadAction;
|
||||||
|
dispatch(iiLayerImageChanged({ layerId, imageDTO }));
|
||||||
dispatch(
|
dispatch(
|
||||||
addToast({
|
addToast({
|
||||||
...DEFAULT_UPLOADED_TOAST,
|
...DEFAULT_UPLOADED_TOAST,
|
||||||
description: t('toast.setInitialImage'),
|
description: t('toast.setControlImage'),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_NODES_IMAGE') {
|
if (postUploadAction?.type === 'SET_NODES_IMAGE') {
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
|
||||||
import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
|
||||||
import { makeToast } from 'features/system/util/makeToast';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
|
|
||||||
export const addInitialImageSelectedListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: initialImageSelected,
|
|
||||||
effect: (action, { dispatch }) => {
|
|
||||||
if (!action.payload) {
|
|
||||||
dispatch(addToast(makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' })));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(initialImageChanged(action.payload));
|
|
||||||
dispatch(addToast(makeToast(t('toast.sentToImageToImage'))));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,7 +1,7 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import {
|
import {
|
||||||
controlAdapterModelChanged,
|
controlAdapterIsEnabledChanged,
|
||||||
selectControlAdapterAll,
|
selectControlAdapterAll,
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
import { loraRemoved } from 'features/lora/store/loraSlice';
|
import { loraRemoved } from 'features/lora/store/loraSlice';
|
||||||
@ -54,7 +54,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
|
|||||||
// handle incompatible controlnets
|
// handle incompatible controlnets
|
||||||
selectControlAdapterAll(state.controlAdapters).forEach((ca) => {
|
selectControlAdapterAll(state.controlAdapters).forEach((ca) => {
|
||||||
if (ca.model?.base !== newBaseModel) {
|
if (ca.model?.base !== newBaseModel) {
|
||||||
dispatch(controlAdapterModelChanged({ id: ca.id, modelConfig: null }));
|
dispatch(controlAdapterIsEnabledChanged({ id: ca.id, isEnabled: false }));
|
||||||
modelsCleared += 1;
|
modelsCleared += 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -96,16 +96,16 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
|
|||||||
dispatch(setScheduler(scheduler));
|
dispatch(setScheduler(scheduler));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const setSizeOptions = { updateAspectRatio: true, clamp: true };
|
||||||
if (width) {
|
if (width) {
|
||||||
if (isParameterWidth(width)) {
|
if (isParameterWidth(width)) {
|
||||||
dispatch(widthChanged({ width, updateAspectRatio: true }));
|
dispatch(widthChanged({ width, ...setSizeOptions }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (height) {
|
if (height) {
|
||||||
if (isParameterHeight(height)) {
|
if (isParameterHeight(height)) {
|
||||||
dispatch(heightChanged({ height, updateAspectRatio: true }));
|
dispatch(heightChanged({ height, ...setSizeOptions }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,14 +17,10 @@ const accept: Accept = {
|
|||||||
const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (activeTabName) => {
|
const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (activeTabName) => {
|
||||||
let postUploadAction: PostUploadAction = { type: 'TOAST' };
|
let postUploadAction: PostUploadAction = { type: 'TOAST' };
|
||||||
|
|
||||||
if (activeTabName === 'unifiedCanvas') {
|
if (activeTabName === 'canvas') {
|
||||||
postUploadAction = { type: 'SET_CANVAS_INITIAL_IMAGE' };
|
postUploadAction = { type: 'SET_CANVAS_INITIAL_IMAGE' };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTabName === 'img2img') {
|
|
||||||
postUploadAction = { type: 'SET_INITIAL_IMAGE' };
|
|
||||||
}
|
|
||||||
|
|
||||||
return postUploadAction;
|
return postUploadAction;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
'1',
|
'1',
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('txt2img'));
|
dispatch(setActiveTab('generation'));
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
@ -75,7 +75,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
'2',
|
'2',
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('img2img'));
|
dispatch(setActiveTab('canvas'));
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
@ -83,31 +83,23 @@ export const useGlobalHotkeys = () => {
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
'3',
|
'3',
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('unifiedCanvas'));
|
dispatch(setActiveTab('workflows'));
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'4',
|
'4',
|
||||||
() => {
|
|
||||||
dispatch(setActiveTab('nodes'));
|
|
||||||
},
|
|
||||||
[dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotkeys(
|
|
||||||
'5',
|
|
||||||
() => {
|
() => {
|
||||||
if (isModelManagerEnabled) {
|
if (isModelManagerEnabled) {
|
||||||
dispatch(setActiveTab('modelManager'));
|
dispatch(setActiveTab('models'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, isModelManagerEnabled]
|
[dispatch, isModelManagerEnabled]
|
||||||
);
|
);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
isModelManagerEnabled ? '6' : '5',
|
isModelManagerEnabled ? '5' : '4',
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('queue'));
|
dispatch(setActiveTab('queue'));
|
||||||
},
|
},
|
||||||
|
@ -16,7 +16,6 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
|||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import { forEach } from 'lodash-es';
|
import { forEach } from 'lodash-es';
|
||||||
import { getConnectedEdges } from 'reactflow';
|
import { getConnectedEdges } from 'reactflow';
|
||||||
import { assert } from 'tsafe';
|
|
||||||
|
|
||||||
const selector = createMemoizedSelector(
|
const selector = createMemoizedSelector(
|
||||||
[
|
[
|
||||||
@ -29,7 +28,7 @@ const selector = createMemoizedSelector(
|
|||||||
activeTabNameSelector,
|
activeTabNameSelector,
|
||||||
],
|
],
|
||||||
(controlAdapters, generation, system, nodes, dynamicPrompts, controlLayers, activeTabName) => {
|
(controlAdapters, generation, system, nodes, dynamicPrompts, controlLayers, activeTabName) => {
|
||||||
const { initialImage, model } = generation;
|
const { model } = generation;
|
||||||
const { positivePrompt } = controlLayers.present;
|
const { positivePrompt } = controlLayers.present;
|
||||||
|
|
||||||
const { isConnected } = system;
|
const { isConnected } = system;
|
||||||
@ -41,11 +40,7 @@ const selector = createMemoizedSelector(
|
|||||||
reasons.push(i18n.t('parameters.invoke.systemDisconnected'));
|
reasons.push(i18n.t('parameters.invoke.systemDisconnected'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTabName === 'img2img' && !initialImage) {
|
if (activeTabName === 'workflows') {
|
||||||
reasons.push(i18n.t('parameters.invoke.noInitialImageSelected'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeTabName === 'nodes') {
|
|
||||||
if (nodes.shouldValidateGraph) {
|
if (nodes.shouldValidateGraph) {
|
||||||
if (!nodes.nodes.length) {
|
if (!nodes.nodes.length) {
|
||||||
reasons.push(i18n.t('parameters.invoke.noNodesInGraph'));
|
reasons.push(i18n.t('parameters.invoke.noNodesInGraph'));
|
||||||
@ -98,8 +93,8 @@ const selector = createMemoizedSelector(
|
|||||||
reasons.push(i18n.t('parameters.invoke.noModelSelected'));
|
reasons.push(i18n.t('parameters.invoke.noModelSelected'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTabName === 'txt2img') {
|
if (activeTabName === 'generation') {
|
||||||
// Handling for Control Layers - only exists on txt2img tab now
|
// Handling for generation tab
|
||||||
controlLayers.present.layers
|
controlLayers.present.layers
|
||||||
.filter((l) => l.isEnabled)
|
.filter((l) => l.isEnabled)
|
||||||
.flatMap((l) => {
|
.flatMap((l) => {
|
||||||
@ -110,7 +105,7 @@ const selector = createMemoizedSelector(
|
|||||||
} else if (l.type === 'regional_guidance_layer') {
|
} else if (l.type === 'regional_guidance_layer') {
|
||||||
return l.ipAdapters;
|
return l.ipAdapters;
|
||||||
}
|
}
|
||||||
assert(false);
|
return [];
|
||||||
})
|
})
|
||||||
.forEach((ca, i) => {
|
.forEach((ca, i) => {
|
||||||
const hasNoModel = !ca.model;
|
const hasNoModel = !ca.model;
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import type { CanvasLayer } from 'features/canvas/store/canvasTypes';
|
import type { CanvasLayer } from 'features/canvas/store/canvasTypes';
|
||||||
import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes';
|
import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes';
|
||||||
|
import { ViewerButton } from 'features/gallery/components/ImageViewer/ViewerButton';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -219,97 +220,107 @@ const IAICanvasToolbar = () => {
|
|||||||
const value = useMemo(() => LAYER_NAMES_DICT.filter((o) => o.value === layer)[0], [layer]);
|
const value = useMemo(() => LAYER_NAMES_DICT.filter((o) => o.value === layer)[0], [layer]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex alignItems="center" gap={2} flexWrap="wrap">
|
<Flex w="full" gap={2} alignItems="center">
|
||||||
<Tooltip label={`${t('unifiedCanvas.layer')} (Q)`}>
|
<Flex flex={1} justifyContent="center">
|
||||||
<FormControl isDisabled={isStaging} w="5rem">
|
<Flex gap={2} marginInlineEnd="auto" />
|
||||||
<Combobox value={value} options={LAYER_NAMES_DICT} onChange={handleChangeLayer} />
|
</Flex>
|
||||||
</FormControl>
|
<Flex flex={1} gap={2} justifyContent="center">
|
||||||
</Tooltip>
|
<Tooltip label={`${t('unifiedCanvas.layer')} (Q)`}>
|
||||||
|
<FormControl isDisabled={isStaging} w="5rem">
|
||||||
|
<Combobox value={value} options={LAYER_NAMES_DICT} onChange={handleChangeLayer} />
|
||||||
|
</FormControl>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<IAICanvasMaskOptions />
|
<IAICanvasMaskOptions />
|
||||||
<IAICanvasToolChooserOptions />
|
<IAICanvasToolChooserOptions />
|
||||||
|
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<IconButton
|
|
||||||
aria-label={`${t('unifiedCanvas.move')} (V)`}
|
|
||||||
tooltip={`${t('unifiedCanvas.move')} (V)`}
|
|
||||||
icon={<PiHandGrabbingBold />}
|
|
||||||
isChecked={tool === 'move' || isStaging}
|
|
||||||
onClick={handleSelectMoveTool}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
aria-label={`${shouldShowBoundingBox ? t('unifiedCanvas.hideBoundingBox') : t('unifiedCanvas.showBoundingBox')} (Shift + H)`}
|
|
||||||
tooltip={`${shouldShowBoundingBox ? t('unifiedCanvas.hideBoundingBox') : t('unifiedCanvas.showBoundingBox')} (Shift + H)`}
|
|
||||||
icon={shouldShowBoundingBox ? <PiEyeBold /> : <PiEyeSlashBold />}
|
|
||||||
onClick={handleSetShouldShowBoundingBox}
|
|
||||||
isDisabled={isStaging}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
aria-label={`${t('unifiedCanvas.resetView')} (R)`}
|
|
||||||
tooltip={`${t('unifiedCanvas.resetView')} (R)`}
|
|
||||||
icon={<PiCrosshairSimpleBold />}
|
|
||||||
onClick={handleClickResetCanvasView}
|
|
||||||
/>
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
<ButtonGroup>
|
|
||||||
<IconButton
|
|
||||||
aria-label={`${t('unifiedCanvas.mergeVisible')} (Shift+M)`}
|
|
||||||
tooltip={`${t('unifiedCanvas.mergeVisible')} (Shift+M)`}
|
|
||||||
icon={<PiStackBold />}
|
|
||||||
onClick={handleMergeVisible}
|
|
||||||
isDisabled={isStaging}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
aria-label={`${t('unifiedCanvas.saveToGallery')} (Shift+S)`}
|
|
||||||
tooltip={`${t('unifiedCanvas.saveToGallery')} (Shift+S)`}
|
|
||||||
icon={<PiFloppyDiskBold />}
|
|
||||||
onClick={handleSaveToGallery}
|
|
||||||
isDisabled={isStaging}
|
|
||||||
/>
|
|
||||||
{isClipboardAPIAvailable && (
|
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={`${t('unifiedCanvas.copyToClipboard')} (Cmd/Ctrl+C)`}
|
aria-label={`${t('unifiedCanvas.move')} (V)`}
|
||||||
tooltip={`${t('unifiedCanvas.copyToClipboard')} (Cmd/Ctrl+C)`}
|
tooltip={`${t('unifiedCanvas.move')} (V)`}
|
||||||
icon={<PiCopyBold />}
|
icon={<PiHandGrabbingBold />}
|
||||||
onClick={handleCopyImageToClipboard}
|
isChecked={tool === 'move' || isStaging}
|
||||||
|
onClick={handleSelectMoveTool}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
aria-label={`${shouldShowBoundingBox ? t('unifiedCanvas.hideBoundingBox') : t('unifiedCanvas.showBoundingBox')} (Shift + H)`}
|
||||||
|
tooltip={`${shouldShowBoundingBox ? t('unifiedCanvas.hideBoundingBox') : t('unifiedCanvas.showBoundingBox')} (Shift + H)`}
|
||||||
|
icon={shouldShowBoundingBox ? <PiEyeBold /> : <PiEyeSlashBold />}
|
||||||
|
onClick={handleSetShouldShowBoundingBox}
|
||||||
isDisabled={isStaging}
|
isDisabled={isStaging}
|
||||||
/>
|
/>
|
||||||
)}
|
<IconButton
|
||||||
<IconButton
|
aria-label={`${t('unifiedCanvas.resetView')} (R)`}
|
||||||
aria-label={`${t('unifiedCanvas.downloadAsImage')} (Shift+D)`}
|
tooltip={`${t('unifiedCanvas.resetView')} (R)`}
|
||||||
tooltip={`${t('unifiedCanvas.downloadAsImage')} (Shift+D)`}
|
icon={<PiCrosshairSimpleBold />}
|
||||||
icon={<PiDownloadSimpleBold />}
|
onClick={handleClickResetCanvasView}
|
||||||
onClick={handleDownloadAsImage}
|
/>
|
||||||
isDisabled={isStaging}
|
</ButtonGroup>
|
||||||
/>
|
|
||||||
</ButtonGroup>
|
|
||||||
<ButtonGroup>
|
|
||||||
<IAICanvasUndoButton />
|
|
||||||
<IAICanvasRedoButton />
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={`${t('common.upload')}`}
|
aria-label={`${t('unifiedCanvas.mergeVisible')} (Shift+M)`}
|
||||||
tooltip={`${t('common.upload')}`}
|
tooltip={`${t('unifiedCanvas.mergeVisible')} (Shift+M)`}
|
||||||
icon={<PiUploadSimpleBold />}
|
icon={<PiStackBold />}
|
||||||
isDisabled={isStaging}
|
onClick={handleMergeVisible}
|
||||||
{...getUploadButtonProps()}
|
isDisabled={isStaging}
|
||||||
/>
|
/>
|
||||||
<input {...getUploadInputProps()} />
|
<IconButton
|
||||||
<IconButton
|
aria-label={`${t('unifiedCanvas.saveToGallery')} (Shift+S)`}
|
||||||
aria-label={`${t('unifiedCanvas.clearCanvas')}`}
|
tooltip={`${t('unifiedCanvas.saveToGallery')} (Shift+S)`}
|
||||||
tooltip={`${t('unifiedCanvas.clearCanvas')}`}
|
icon={<PiFloppyDiskBold />}
|
||||||
icon={<PiTrashSimpleBold />}
|
onClick={handleSaveToGallery}
|
||||||
onClick={handleResetCanvas}
|
isDisabled={isStaging}
|
||||||
colorScheme="error"
|
/>
|
||||||
isDisabled={isStaging}
|
{isClipboardAPIAvailable && (
|
||||||
/>
|
<IconButton
|
||||||
</ButtonGroup>
|
aria-label={`${t('unifiedCanvas.copyToClipboard')} (Cmd/Ctrl+C)`}
|
||||||
<ButtonGroup>
|
tooltip={`${t('unifiedCanvas.copyToClipboard')} (Cmd/Ctrl+C)`}
|
||||||
<IAICanvasSettingsButtonPopover />
|
icon={<PiCopyBold />}
|
||||||
</ButtonGroup>
|
onClick={handleCopyImageToClipboard}
|
||||||
|
isDisabled={isStaging}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<IconButton
|
||||||
|
aria-label={`${t('unifiedCanvas.downloadAsImage')} (Shift+D)`}
|
||||||
|
tooltip={`${t('unifiedCanvas.downloadAsImage')} (Shift+D)`}
|
||||||
|
icon={<PiDownloadSimpleBold />}
|
||||||
|
onClick={handleDownloadAsImage}
|
||||||
|
isDisabled={isStaging}
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
<ButtonGroup>
|
||||||
|
<IAICanvasUndoButton />
|
||||||
|
<IAICanvasRedoButton />
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<ButtonGroup>
|
||||||
|
<IconButton
|
||||||
|
aria-label={`${t('common.upload')}`}
|
||||||
|
tooltip={`${t('common.upload')}`}
|
||||||
|
icon={<PiUploadSimpleBold />}
|
||||||
|
isDisabled={isStaging}
|
||||||
|
{...getUploadButtonProps()}
|
||||||
|
/>
|
||||||
|
<input {...getUploadInputProps()} />
|
||||||
|
<IconButton
|
||||||
|
aria-label={`${t('unifiedCanvas.clearCanvas')}`}
|
||||||
|
tooltip={`${t('unifiedCanvas.clearCanvas')}`}
|
||||||
|
icon={<PiTrashSimpleBold />}
|
||||||
|
onClick={handleResetCanvas}
|
||||||
|
colorScheme="error"
|
||||||
|
isDisabled={isStaging}
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
<ButtonGroup>
|
||||||
|
<IAICanvasSettingsButtonPopover />
|
||||||
|
</ButtonGroup>
|
||||||
|
</Flex>
|
||||||
|
<Flex flex={1} justifyContent="center">
|
||||||
|
<Flex gap={2} marginInlineStart="auto">
|
||||||
|
<ViewerButton />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -75,7 +75,7 @@ const useInpaintingCanvasHotkeys = () => {
|
|||||||
|
|
||||||
const onKeyDown = useCallback(
|
const onKeyDown = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
if (e.repeat || e.key !== ' ' || isInteractiveTarget(e.target) || activeTabName !== 'unifiedCanvas') {
|
if (e.repeat || e.key !== ' ' || isInteractiveTarget(e.target) || activeTabName !== 'canvas') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($toolStash.get() || $tool.get() === 'move') {
|
if ($toolStash.get() || $tool.get() === 'move') {
|
||||||
@ -90,7 +90,7 @@ const useInpaintingCanvasHotkeys = () => {
|
|||||||
);
|
);
|
||||||
const onKeyUp = useCallback(
|
const onKeyUp = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
if (e.repeat || e.key !== ' ' || isInteractiveTarget(e.target) || activeTabName !== 'unifiedCanvas') {
|
if (e.repeat || e.key !== ' ' || isInteractiveTarget(e.target) || activeTabName !== 'canvas') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!$toolStash.get() || $tool.get() !== 'move') {
|
if (!$toolStash.get() || $tool.get() !== 'move') {
|
||||||
|
@ -76,7 +76,7 @@ const ControlAdapterConfig = (props: { id: string; number: number }) => {
|
|||||||
<Box minW={0} w="full" transitionProperty="common" transitionDuration="0.1s">
|
<Box minW={0} w="full" transitionProperty="common" transitionDuration="0.1s">
|
||||||
<ParamControlAdapterModel id={id} />
|
<ParamControlAdapterModel id={id} />
|
||||||
</Box>
|
</Box>
|
||||||
{activeTabName === 'unifiedCanvas' && <ControlNetCanvasImageImports id={id} />}
|
{activeTabName === 'canvas' && <ControlNetCanvasImageImports id={id} />}
|
||||||
<IconButton
|
<IconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
tooltip={t('controlnet.duplicate')}
|
tooltip={t('controlnet.duplicate')}
|
||||||
@ -113,7 +113,7 @@ const ControlAdapterConfig = (props: { id: string; number: number }) => {
|
|||||||
<Flex w="full" flexDir="column" gap={4}>
|
<Flex w="full" flexDir="column" gap={4}>
|
||||||
<Flex gap={8} w="full" alignItems="center">
|
<Flex gap={8} w="full" alignItems="center">
|
||||||
<Flex flexDir="column" gap={4} h={controlAdapterType === 'ip_adapter' ? 40 : 32} w="full">
|
<Flex flexDir="column" gap={4} h={controlAdapterType === 'ip_adapter' ? 40 : 32} w="full">
|
||||||
{controlAdapterType === 'ip_adapter' && <ParamControlAdapterIPMethod id={id} />}
|
<ParamControlAdapterIPMethod id={id} />
|
||||||
<ParamControlAdapterWeight id={id} />
|
<ParamControlAdapterWeight id={id} />
|
||||||
<ParamControlAdapterBeginEnd id={id} />
|
<ParamControlAdapterBeginEnd id={id} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -93,15 +93,16 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTabName === 'unifiedCanvas') {
|
if (activeTabName === 'canvas') {
|
||||||
dispatch(setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension));
|
dispatch(setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension));
|
||||||
} else {
|
} else {
|
||||||
|
const options = { updateAspectRatio: true, clamp: true };
|
||||||
const { width, height } = calculateNewSize(
|
const { width, height } = calculateNewSize(
|
||||||
controlImage.width / controlImage.height,
|
controlImage.width / controlImage.height,
|
||||||
optimalDimension * optimalDimension
|
optimalDimension * optimalDimension
|
||||||
);
|
);
|
||||||
dispatch(widthChanged({ width, updateAspectRatio: true }));
|
dispatch(widthChanged({ width, ...options }));
|
||||||
dispatch(heightChanged({ height, updateAspectRatio: true }));
|
dispatch(heightChanged({ height, ...options }));
|
||||||
}
|
}
|
||||||
}, [controlImage, activeTabName, dispatch, optimalDimension]);
|
}, [controlImage, activeTabName, dispatch, optimalDimension]);
|
||||||
|
|
||||||
|
@ -46,9 +46,13 @@ const ParamControlAdapterIPMethod = ({ id }: Props) => {
|
|||||||
|
|
||||||
const value = useMemo(() => options.find((o) => o.value === method), [options, method]);
|
const value = useMemo(() => options.find((o) => o.value === method), [options, method]);
|
||||||
|
|
||||||
|
if (!method) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<InformationalPopover feature="ipAdapterMethod">
|
<InformationalPopover feature="controlNetResizeMode">
|
||||||
<FormLabel>{t('controlnet.ipAdapterMethod')}</FormLabel>
|
<FormLabel>{t('controlnet.ipAdapterMethod')}</FormLabel>
|
||||||
</InformationalPopover>
|
</InformationalPopover>
|
||||||
<Combobox value={value} options={options} isDisabled={!isEnabled} onChange={handleIPMethodChanged} />
|
<Combobox value={value} options={options} isDisabled={!isEnabled} onChange={handleIPMethodChanged} />
|
||||||
|
@ -102,9 +102,13 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap={4}>
|
<Flex sx={{ gap: 2 }}>
|
||||||
<Tooltip label={selectedModel?.description}>
|
<Tooltip label={selectedModel?.description}>
|
||||||
<FormControl isDisabled={!isEnabled} isInvalid={!value || mainModel?.base !== modelConfig?.base} w="full">
|
<FormControl
|
||||||
|
isDisabled={!isEnabled}
|
||||||
|
isInvalid={!value || mainModel?.base !== modelConfig?.base}
|
||||||
|
sx={{ width: '100%' }}
|
||||||
|
>
|
||||||
<Combobox
|
<Combobox
|
||||||
options={options}
|
options={options}
|
||||||
placeholder={t('controlnet.selectModel')}
|
placeholder={t('controlnet.selectModel')}
|
||||||
@ -118,8 +122,7 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => {
|
|||||||
<FormControl
|
<FormControl
|
||||||
isDisabled={!isEnabled}
|
isDisabled={!isEnabled}
|
||||||
isInvalid={!value || mainModel?.base !== modelConfig?.base}
|
isInvalid={!value || mainModel?.base !== modelConfig?.base}
|
||||||
width="max-content"
|
sx={{ width: 'max-content', minWidth: 28 }}
|
||||||
minWidth={28}
|
|
||||||
>
|
>
|
||||||
<Combobox
|
<Combobox
|
||||||
options={clipVisionOptions}
|
options={clipVisionOptions}
|
||||||
|
@ -5,15 +5,15 @@ import {
|
|||||||
selectControlAdaptersSlice,
|
selectControlAdaptersSlice,
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { assert } from 'tsafe';
|
|
||||||
|
|
||||||
export const useControlAdapterIPMethod = (id: string) => {
|
export const useControlAdapterIPMethod = (id: string) => {
|
||||||
const selector = useMemo(
|
const selector = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => {
|
createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => {
|
||||||
const ca = selectControlAdapterById(controlAdapters, id);
|
const cn = selectControlAdapterById(controlAdapters, id);
|
||||||
assert(ca?.type === 'ip_adapter');
|
if (cn && cn?.type === 'ip_adapter') {
|
||||||
return ca.method;
|
return cn.method;
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
[id]
|
[id]
|
||||||
);
|
);
|
||||||
|
@ -7,7 +7,7 @@ import { buildControlAdapter } from 'features/controlAdapters/util/buildControlA
|
|||||||
import { buildControlAdapterProcessor } from 'features/controlAdapters/util/buildControlAdapterProcessor';
|
import { buildControlAdapterProcessor } from 'features/controlAdapters/util/buildControlAdapterProcessor';
|
||||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import { merge, uniq } from 'lodash-es';
|
import { merge, uniq } from 'lodash-es';
|
||||||
import type { ControlNetModelConfig, ImageDTO, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
import type { ControlNetModelConfig, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
||||||
import { socketInvocationError } from 'services/events/actions';
|
import { socketInvocationError } from 'services/events/actions';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
@ -134,46 +134,23 @@ export const controlAdaptersSlice = createSlice({
|
|||||||
const { id, isEnabled } = action.payload;
|
const { id, isEnabled } = action.payload;
|
||||||
caAdapter.updateOne(state, { id, changes: { isEnabled } });
|
caAdapter.updateOne(state, { id, changes: { isEnabled } });
|
||||||
},
|
},
|
||||||
controlAdapterImageChanged: (state, action: PayloadAction<{ id: string; controlImage: ImageDTO | null }>) => {
|
controlAdapterImageChanged: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{
|
||||||
|
id: string;
|
||||||
|
controlImage: string | null;
|
||||||
|
}>
|
||||||
|
) => {
|
||||||
const { id, controlImage } = action.payload;
|
const { id, controlImage } = action.payload;
|
||||||
const ca = selectControlAdapterById(state, id);
|
const ca = selectControlAdapterById(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isControlNetOrT2IAdapter(ca)) {
|
caAdapter.updateOne(state, {
|
||||||
if (controlImage) {
|
id,
|
||||||
const { image_name, width, height } = controlImage;
|
changes: { controlImage, processedControlImage: null },
|
||||||
const processorNode = deepClone(ca.processorNode);
|
});
|
||||||
const minDim = Math.min(controlImage.width, controlImage.height);
|
|
||||||
if ('detect_resolution' in processorNode) {
|
|
||||||
processorNode.detect_resolution = minDim;
|
|
||||||
}
|
|
||||||
if ('image_resolution' in processorNode) {
|
|
||||||
processorNode.image_resolution = minDim;
|
|
||||||
}
|
|
||||||
if ('resolution' in processorNode) {
|
|
||||||
processorNode.resolution = minDim;
|
|
||||||
}
|
|
||||||
caAdapter.updateOne(state, {
|
|
||||||
id,
|
|
||||||
changes: {
|
|
||||||
processorNode,
|
|
||||||
controlImage: image_name,
|
|
||||||
controlImageDimensions: { width, height },
|
|
||||||
processedControlImage: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
caAdapter.updateOne(state, {
|
|
||||||
id,
|
|
||||||
changes: { controlImage: null, controlImageDimensions: null, processedControlImage: null },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// ip adapter
|
|
||||||
caAdapter.updateOne(state, { id, changes: { controlImage: controlImage?.image_name ?? null } });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controlImage !== null && isControlNetOrT2IAdapter(ca) && ca.processorType !== 'none') {
|
if (controlImage !== null && isControlNetOrT2IAdapter(ca) && ca.processorType !== 'none') {
|
||||||
state.pendingControlImages.push(id);
|
state.pendingControlImages.push(id);
|
||||||
@ -183,7 +160,7 @@ export const controlAdaptersSlice = createSlice({
|
|||||||
state,
|
state,
|
||||||
action: PayloadAction<{
|
action: PayloadAction<{
|
||||||
id: string;
|
id: string;
|
||||||
processedControlImage: ImageDTO | null;
|
processedControlImage: string | null;
|
||||||
}>
|
}>
|
||||||
) => {
|
) => {
|
||||||
const { id, processedControlImage } = action.payload;
|
const { id, processedControlImage } = action.payload;
|
||||||
@ -196,24 +173,12 @@ export const controlAdaptersSlice = createSlice({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (processedControlImage) {
|
caAdapter.updateOne(state, {
|
||||||
const { image_name, width, height } = processedControlImage;
|
id,
|
||||||
caAdapter.updateOne(state, {
|
changes: {
|
||||||
id,
|
processedControlImage,
|
||||||
changes: {
|
},
|
||||||
processedControlImage: image_name,
|
});
|
||||||
processedControlImageDimensions: { width, height },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
caAdapter.updateOne(state, {
|
|
||||||
id,
|
|
||||||
changes: {
|
|
||||||
processedControlImage: null,
|
|
||||||
processedControlImageDimensions: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
state.pendingControlImages = state.pendingControlImages.filter((pendingId) => pendingId !== id);
|
state.pendingControlImages = state.pendingControlImages.filter((pendingId) => pendingId !== id);
|
||||||
},
|
},
|
||||||
@ -227,7 +192,7 @@ export const controlAdaptersSlice = createSlice({
|
|||||||
state,
|
state,
|
||||||
action: PayloadAction<{
|
action: PayloadAction<{
|
||||||
id: string;
|
id: string;
|
||||||
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | IPAdapterModelConfig | null;
|
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | IPAdapterModelConfig;
|
||||||
}>
|
}>
|
||||||
) => {
|
) => {
|
||||||
const { id, modelConfig } = action.payload;
|
const { id, modelConfig } = action.payload;
|
||||||
@ -236,11 +201,6 @@ export const controlAdaptersSlice = createSlice({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelConfig === null) {
|
|
||||||
caAdapter.updateOne(state, { id, changes: { model: null } });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const model = zModelIdentifierField.parse(modelConfig);
|
const model = zModelIdentifierField.parse(modelConfig);
|
||||||
|
|
||||||
if (!isControlNetOrT2IAdapter(cn)) {
|
if (!isControlNetOrT2IAdapter(cn)) {
|
||||||
@ -248,36 +208,22 @@ export const controlAdaptersSlice = createSlice({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const update: Update<ControlNetConfig | T2IAdapterConfig, string> = {
|
||||||
|
id,
|
||||||
|
changes: { model, shouldAutoConfig: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
update.changes.processedControlImage = null;
|
||||||
|
|
||||||
if (modelConfig.type === 'ip_adapter') {
|
if (modelConfig.type === 'ip_adapter') {
|
||||||
// should never happen...
|
// should never happen...
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We always update the model
|
|
||||||
const update: Update<ControlNetConfig | T2IAdapterConfig, string> = { id, changes: { model } };
|
|
||||||
|
|
||||||
// Build the default processor for this model
|
|
||||||
const processor = buildControlAdapterProcessor(modelConfig);
|
const processor = buildControlAdapterProcessor(modelConfig);
|
||||||
if (processor.processorType !== cn.processorNode.type) {
|
update.changes.processorType = processor.processorType;
|
||||||
// If the processor type has changed, update the processor node
|
update.changes.processorNode = processor.processorNode;
|
||||||
update.changes.shouldAutoConfig = true;
|
|
||||||
update.changes.processedControlImage = null;
|
|
||||||
update.changes.processorType = processor.processorType;
|
|
||||||
update.changes.processorNode = processor.processorNode;
|
|
||||||
|
|
||||||
if (cn.controlImageDimensions) {
|
|
||||||
const minDim = Math.min(cn.controlImageDimensions.width, cn.controlImageDimensions.height);
|
|
||||||
if ('detect_resolution' in update.changes.processorNode) {
|
|
||||||
update.changes.processorNode.detect_resolution = minDim;
|
|
||||||
}
|
|
||||||
if ('image_resolution' in update.changes.processorNode) {
|
|
||||||
update.changes.processorNode.image_resolution = minDim;
|
|
||||||
}
|
|
||||||
if ('resolution' in update.changes.processorNode) {
|
|
||||||
update.changes.processorNode.resolution = minDim;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
caAdapter.updateOne(state, update);
|
caAdapter.updateOne(state, update);
|
||||||
},
|
},
|
||||||
controlAdapterWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => {
|
controlAdapterWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => {
|
||||||
@ -394,23 +340,8 @@ export const controlAdaptersSlice = createSlice({
|
|||||||
|
|
||||||
if (update.changes.shouldAutoConfig && modelConfig) {
|
if (update.changes.shouldAutoConfig && modelConfig) {
|
||||||
const processor = buildControlAdapterProcessor(modelConfig);
|
const processor = buildControlAdapterProcessor(modelConfig);
|
||||||
if (processor.processorType !== cn.processorNode.type) {
|
update.changes.processorType = processor.processorType;
|
||||||
update.changes.processorType = processor.processorType;
|
update.changes.processorNode = processor.processorNode;
|
||||||
update.changes.processorNode = processor.processorNode;
|
|
||||||
// Copy image resolution settings, urgh
|
|
||||||
if (cn.controlImageDimensions) {
|
|
||||||
const minDim = Math.min(cn.controlImageDimensions.width, cn.controlImageDimensions.height);
|
|
||||||
if ('detect_resolution' in update.changes.processorNode) {
|
|
||||||
update.changes.processorNode.detect_resolution = minDim;
|
|
||||||
}
|
|
||||||
if ('image_resolution' in update.changes.processorNode) {
|
|
||||||
update.changes.processorNode.image_resolution = minDim;
|
|
||||||
}
|
|
||||||
if ('resolution' in update.changes.processorNode) {
|
|
||||||
update.changes.processorNode.resolution = minDim;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
caAdapter.updateOne(state, update);
|
caAdapter.updateOne(state, update);
|
||||||
|
@ -225,9 +225,7 @@ export type ControlNetConfig = {
|
|||||||
controlMode: ControlMode;
|
controlMode: ControlMode;
|
||||||
resizeMode: ResizeMode;
|
resizeMode: ResizeMode;
|
||||||
controlImage: string | null;
|
controlImage: string | null;
|
||||||
controlImageDimensions: { width: number; height: number } | null;
|
|
||||||
processedControlImage: string | null;
|
processedControlImage: string | null;
|
||||||
processedControlImageDimensions: { width: number; height: number } | null;
|
|
||||||
processorType: ControlAdapterProcessorType;
|
processorType: ControlAdapterProcessorType;
|
||||||
processorNode: RequiredControlAdapterProcessorNode;
|
processorNode: RequiredControlAdapterProcessorNode;
|
||||||
shouldAutoConfig: boolean;
|
shouldAutoConfig: boolean;
|
||||||
@ -243,9 +241,7 @@ export type T2IAdapterConfig = {
|
|||||||
endStepPct: number;
|
endStepPct: number;
|
||||||
resizeMode: ResizeMode;
|
resizeMode: ResizeMode;
|
||||||
controlImage: string | null;
|
controlImage: string | null;
|
||||||
controlImageDimensions: { width: number; height: number } | null;
|
|
||||||
processedControlImage: string | null;
|
processedControlImage: string | null;
|
||||||
processedControlImageDimensions: { width: number; height: number } | null;
|
|
||||||
processorType: ControlAdapterProcessorType;
|
processorType: ControlAdapterProcessorType;
|
||||||
processorNode: RequiredControlAdapterProcessorNode;
|
processorNode: RequiredControlAdapterProcessorNode;
|
||||||
shouldAutoConfig: boolean;
|
shouldAutoConfig: boolean;
|
||||||
|
@ -20,9 +20,7 @@ export const initialControlNet: Omit<ControlNetConfig, 'id'> = {
|
|||||||
controlMode: 'balanced',
|
controlMode: 'balanced',
|
||||||
resizeMode: 'just_resize',
|
resizeMode: 'just_resize',
|
||||||
controlImage: null,
|
controlImage: null,
|
||||||
controlImageDimensions: null,
|
|
||||||
processedControlImage: null,
|
processedControlImage: null,
|
||||||
processedControlImageDimensions: null,
|
|
||||||
processorType: 'canny_image_processor',
|
processorType: 'canny_image_processor',
|
||||||
processorNode: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults() as RequiredCannyImageProcessorInvocation,
|
processorNode: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults() as RequiredCannyImageProcessorInvocation,
|
||||||
shouldAutoConfig: true,
|
shouldAutoConfig: true,
|
||||||
@ -37,9 +35,7 @@ export const initialT2IAdapter: Omit<T2IAdapterConfig, 'id'> = {
|
|||||||
endStepPct: 1,
|
endStepPct: 1,
|
||||||
resizeMode: 'just_resize',
|
resizeMode: 'just_resize',
|
||||||
controlImage: null,
|
controlImage: null,
|
||||||
controlImageDimensions: null,
|
|
||||||
processedControlImage: null,
|
processedControlImage: null,
|
||||||
processedControlImageDimensions: null,
|
|
||||||
processorType: 'canny_image_processor',
|
processorType: 'canny_image_processor',
|
||||||
processorNode: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults() as RequiredCannyImageProcessorInvocation,
|
processorNode: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults() as RequiredCannyImageProcessorInvocation,
|
||||||
shouldAutoConfig: true,
|
shouldAutoConfig: true,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { useAddCALayer, useAddIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
import { useAddCALayer, useAddIILayer, useAddIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||||
import { rgLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
|
import { rgLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -11,6 +11,7 @@ export const AddLayerButton = memo(() => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [addCALayer, isAddCALayerDisabled] = useAddCALayer();
|
const [addCALayer, isAddCALayerDisabled] = useAddCALayer();
|
||||||
const [addIPALayer, isAddIPALayerDisabled] = useAddIPALayer();
|
const [addIPALayer, isAddIPALayerDisabled] = useAddIPALayer();
|
||||||
|
const [addIILayer, isAddIILayerDisabled] = useAddIILayer();
|
||||||
const addRGLayer = useCallback(() => {
|
const addRGLayer = useCallback(() => {
|
||||||
dispatch(rgLayerAdded());
|
dispatch(rgLayerAdded());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
@ -30,6 +31,9 @@ export const AddLayerButton = memo(() => {
|
|||||||
<MenuItem icon={<PiPlusBold />} onClick={addIPALayer} isDisabled={isAddIPALayerDisabled}>
|
<MenuItem icon={<PiPlusBold />} onClick={addIPALayer} isDisabled={isAddIPALayerDisabled}>
|
||||||
{t('controlLayers.globalIPAdapterLayer')}
|
{t('controlLayers.globalIPAdapterLayer')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem icon={<PiPlusBold />} onClick={addIILayer} isDisabled={isAddIILayerDisabled}>
|
||||||
|
{t('controlLayers.globalInitialImageLayer')}
|
||||||
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
@ -5,6 +5,7 @@ import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon
|
|||||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||||
|
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||||
import { layerSelected, selectCALayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
|
import { layerSelected, selectCALayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
|
||||||
@ -17,37 +18,28 @@ type Props = {
|
|||||||
export const CALayer = memo(({ layerId }: Props) => {
|
export const CALayer = memo(({ layerId }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isSelected = useAppSelector((s) => selectCALayerOrThrow(s.controlLayers.present, layerId).isSelected);
|
const isSelected = useAppSelector((s) => selectCALayerOrThrow(s.controlLayers.present, layerId).isSelected);
|
||||||
const onClickCapture = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
// Must be capture so that the layer is selected before deleting/resetting/etc
|
// Must be capture so that the layer is selected before deleting/resetting/etc
|
||||||
dispatch(layerSelected(layerId));
|
dispatch(layerSelected(layerId));
|
||||||
}, [dispatch, layerId]);
|
}, [dispatch, layerId]);
|
||||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
|
||||||
gap={2}
|
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||||
onClickCapture={onClickCapture}
|
<LayerVisibilityToggle layerId={layerId} />
|
||||||
bg={isSelected ? 'base.400' : 'base.800'}
|
<LayerTitle type="control_adapter_layer" />
|
||||||
px={2}
|
<Spacer />
|
||||||
borderRadius="base"
|
<CALayerOpacity layerId={layerId} />
|
||||||
py="1px"
|
<LayerMenu layerId={layerId} />
|
||||||
>
|
<LayerDeleteButton layerId={layerId} />
|
||||||
<Flex flexDir="column" w="full" bg="base.850" borderRadius="base">
|
|
||||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
|
||||||
<LayerVisibilityToggle layerId={layerId} />
|
|
||||||
<LayerTitle type="control_adapter_layer" />
|
|
||||||
<Spacer />
|
|
||||||
<CALayerOpacity layerId={layerId} />
|
|
||||||
<LayerMenu layerId={layerId} />
|
|
||||||
<LayerDeleteButton layerId={layerId} />
|
|
||||||
</Flex>
|
|
||||||
{isOpen && (
|
|
||||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
|
||||||
<CALayerControlAdapterWrapper layerId={layerId} />
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
{isOpen && (
|
||||||
|
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||||
|
<CALayerControlAdapterWrapper layerId={layerId} />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</LayerWrapper>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
caOrIPALayerWeightChanged,
|
caOrIPALayerWeightChanged,
|
||||||
selectCALayerOrThrow,
|
selectCALayerOrThrow,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { ControlMode, ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
import type { ControlModeV2, ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||||
import type { CALayerImageDropData } from 'features/dnd/types';
|
import type { CALayerImageDropData } from 'features/dnd/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import type {
|
import type {
|
||||||
@ -40,7 +40,7 @@ export const CALayerControlAdapterWrapper = memo(({ layerId }: Props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onChangeControlMode = useCallback(
|
const onChangeControlMode = useCallback(
|
||||||
(controlMode: ControlMode) => {
|
(controlMode: ControlModeV2) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
caLayerControlModeChanged({
|
caLayerControlModeChanged({
|
||||||
layerId,
|
layerId,
|
||||||
|
@ -55,7 +55,7 @@ const CALayerOpacity = ({ layerId }: Props) => {
|
|||||||
onDoubleClick={stopPropagation}
|
onDoubleClick={stopPropagation}
|
||||||
/>
|
/>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent>
|
<PopoverContent onDoubleClick={stopPropagation}>
|
||||||
<PopoverArrow />
|
<PopoverArrow />
|
||||||
<PopoverBody>
|
<PopoverBody>
|
||||||
<Flex direction="column" gap={2}>
|
<Flex direction="column" gap={2}>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Box, Divider, Flex, Icon, IconButton } from '@invoke-ai/ui-library';
|
import { Box, Divider, Flex, Icon, IconButton } from '@invoke-ai/ui-library';
|
||||||
import { ControlAdapterModelCombobox } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterModelCombobox';
|
import { ControlAdapterModelCombobox } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterModelCombobox';
|
||||||
import type {
|
import type {
|
||||||
ControlMode,
|
ControlModeV2,
|
||||||
ControlNetConfig,
|
ControlNetConfigV2,
|
||||||
ProcessorConfig,
|
ProcessorConfig,
|
||||||
T2IAdapterConfig,
|
T2IAdapterConfigV2,
|
||||||
} from 'features/controlLayers/util/controlAdapters';
|
} from 'features/controlLayers/util/controlAdapters';
|
||||||
import type { TypesafeDroppableData } from 'features/dnd/types';
|
import type { TypesafeDroppableData } from 'features/dnd/types';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
@ -21,9 +21,9 @@ import { ControlAdapterProcessorTypeSelect } from './ControlAdapterProcessorType
|
|||||||
import { ControlAdapterWeight } from './ControlAdapterWeight';
|
import { ControlAdapterWeight } from './ControlAdapterWeight';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
controlAdapter: ControlNetConfig | T2IAdapterConfig;
|
controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2;
|
||||||
onChangeBeginEndStepPct: (beginEndStepPct: [number, number]) => void;
|
onChangeBeginEndStepPct: (beginEndStepPct: [number, number]) => void;
|
||||||
onChangeControlMode: (controlMode: ControlMode) => void;
|
onChangeControlMode: (controlMode: ControlModeV2) => void;
|
||||||
onChangeWeight: (weight: number) => void;
|
onChangeWeight: (weight: number) => void;
|
||||||
onChangeProcessorConfig: (processorConfig: ProcessorConfig | null) => void;
|
onChangeProcessorConfig: (processorConfig: ProcessorConfig | null) => void;
|
||||||
onChangeModel: (modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => void;
|
onChangeModel: (modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => void;
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import type { ControlMode } from 'features/controlLayers/util/controlAdapters';
|
import type { ControlModeV2 } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { isControlMode } from 'features/controlLayers/util/controlAdapters';
|
import { isControlModeV2 } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
controlMode: ControlMode;
|
controlMode: ControlModeV2;
|
||||||
onChange: (controlMode: ControlMode) => void;
|
onChange: (controlMode: ControlModeV2) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ControlAdapterControlModeSelect = memo(({ controlMode, onChange }: Props) => {
|
export const ControlAdapterControlModeSelect = memo(({ controlMode, onChange }: Props) => {
|
||||||
@ -26,7 +26,7 @@ export const ControlAdapterControlModeSelect = memo(({ controlMode, onChange }:
|
|||||||
|
|
||||||
const handleControlModeChange = useCallback<ComboboxOnChange>(
|
const handleControlModeChange = useCallback<ComboboxOnChange>(
|
||||||
(v) => {
|
(v) => {
|
||||||
assert(isControlMode(v?.value));
|
assert(isControlModeV2(v?.value));
|
||||||
onChange(v.value);
|
onChange(v.value);
|
||||||
},
|
},
|
||||||
[onChange]
|
[onChange]
|
||||||
|
@ -6,7 +6,7 @@ import IAIDndImage from 'common/components/IAIDndImage';
|
|||||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||||
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { ControlNetConfig, T2IAdapterConfig } from 'features/controlLayers/util/controlAdapters';
|
import type { ControlNetConfigV2, T2IAdapterConfigV2 } from 'features/controlLayers/util/controlAdapters';
|
||||||
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||||
@ -23,7 +23,7 @@ import {
|
|||||||
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
controlAdapter: ControlNetConfig | T2IAdapterConfig;
|
controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2;
|
||||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||||
droppableData: TypesafeDroppableData;
|
droppableData: TypesafeDroppableData;
|
||||||
postUploadAction: PostUploadAction;
|
postUploadAction: PostUploadAction;
|
||||||
@ -80,22 +80,24 @@ export const ControlAdapterImagePreview = memo(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTabName === 'unifiedCanvas') {
|
if (activeTabName === 'canvas') {
|
||||||
dispatch(
|
dispatch(
|
||||||
setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension)
|
setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const options = { updateAspectRatio: true, clamp: true };
|
||||||
|
|
||||||
if (shift) {
|
if (shift) {
|
||||||
const { width, height } = controlImage;
|
const { width, height } = controlImage;
|
||||||
dispatch(widthChanged({ width, updateAspectRatio: true }));
|
dispatch(widthChanged({ width, ...options }));
|
||||||
dispatch(heightChanged({ height, updateAspectRatio: true }));
|
dispatch(heightChanged({ height, ...options }));
|
||||||
} else {
|
} else {
|
||||||
const { width, height } = calculateNewSize(
|
const { width, height } = calculateNewSize(
|
||||||
controlImage.width / controlImage.height,
|
controlImage.width / controlImage.height,
|
||||||
optimalDimension * optimalDimension
|
optimalDimension * optimalDimension
|
||||||
);
|
);
|
||||||
dispatch(widthChanged({ width, updateAspectRatio: true }));
|
dispatch(widthChanged({ width, ...options }));
|
||||||
dispatch(heightChanged({ height, updateAspectRatio: true }));
|
dispatch(heightChanged({ height, ...options }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [controlImage, activeTabName, dispatch, optimalDimension, shift]);
|
}, [controlImage, activeTabName, dispatch, optimalDimension, shift]);
|
||||||
|
@ -4,7 +4,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import type { ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
import type { ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { CONTROLNET_PROCESSORS, isProcessorType } from 'features/controlLayers/util/controlAdapters';
|
import { CA_PROCESSOR_DATA, isProcessorTypeV2 } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { configSelector } from 'features/system/store/configSelectors';
|
import { configSelector } from 'features/system/store/configSelectors';
|
||||||
import { includes, map } from 'lodash-es';
|
import { includes, map } from 'lodash-es';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
@ -26,7 +26,7 @@ export const ControlAdapterProcessorTypeSelect = memo(({ config, onChange }: Pro
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const disabledProcessors = useAppSelector(selectDisabledProcessors);
|
const disabledProcessors = useAppSelector(selectDisabledProcessors);
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
return map(CONTROLNET_PROCESSORS, ({ labelTKey }, type) => ({ value: type, label: t(labelTKey) })).filter(
|
return map(CA_PROCESSOR_DATA, ({ labelTKey }, type) => ({ value: type, label: t(labelTKey) })).filter(
|
||||||
(o) => !includes(disabledProcessors, o.value)
|
(o) => !includes(disabledProcessors, o.value)
|
||||||
);
|
);
|
||||||
}, [disabledProcessors, t]);
|
}, [disabledProcessors, t]);
|
||||||
@ -36,8 +36,8 @@ export const ControlAdapterProcessorTypeSelect = memo(({ config, onChange }: Pro
|
|||||||
if (!v) {
|
if (!v) {
|
||||||
onChange(null);
|
onChange(null);
|
||||||
} else {
|
} else {
|
||||||
assert(isProcessorType(v.value));
|
assert(isProcessorTypeV2(v.value));
|
||||||
onChange(CONTROLNET_PROCESSORS[v.value].buildDefaults());
|
onChange(CA_PROCESSOR_DATA[v.value].buildDefaults());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onChange]
|
[onChange]
|
||||||
|
@ -4,18 +4,18 @@ import { ControlAdapterWeight } from 'features/controlLayers/components/ControlA
|
|||||||
import { IPAdapterImagePreview } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterImagePreview';
|
import { IPAdapterImagePreview } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterImagePreview';
|
||||||
import { IPAdapterMethod } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterMethod';
|
import { IPAdapterMethod } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterMethod';
|
||||||
import { IPAdapterModelSelect } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterModelSelect';
|
import { IPAdapterModelSelect } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterModelSelect';
|
||||||
import type { CLIPVisionModel, IPAdapterConfig, IPMethod } from 'features/controlLayers/util/controlAdapters';
|
import type { CLIPVisionModelV2, IPAdapterConfigV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||||
import type { TypesafeDroppableData } from 'features/dnd/types';
|
import type { TypesafeDroppableData } from 'features/dnd/types';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import type { ImageDTO, IPAdapterModelConfig, PostUploadAction } from 'services/api/types';
|
import type { ImageDTO, IPAdapterModelConfig, PostUploadAction } from 'services/api/types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
ipAdapter: IPAdapterConfig;
|
ipAdapter: IPAdapterConfigV2;
|
||||||
onChangeBeginEndStepPct: (beginEndStepPct: [number, number]) => void;
|
onChangeBeginEndStepPct: (beginEndStepPct: [number, number]) => void;
|
||||||
onChangeWeight: (weight: number) => void;
|
onChangeWeight: (weight: number) => void;
|
||||||
onChangeIPMethod: (method: IPMethod) => void;
|
onChangeIPMethod: (method: IPMethodV2) => void;
|
||||||
onChangeModel: (modelConfig: IPAdapterModelConfig) => void;
|
onChangeModel: (modelConfig: IPAdapterModelConfig) => void;
|
||||||
onChangeCLIPVisionModel: (clipVisionModel: CLIPVisionModel) => void;
|
onChangeCLIPVisionModel: (clipVisionModel: CLIPVisionModelV2) => void;
|
||||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||||
droppableData: TypesafeDroppableData;
|
droppableData: TypesafeDroppableData;
|
||||||
postUploadAction: PostUploadAction;
|
postUploadAction: PostUploadAction;
|
||||||
|
@ -46,22 +46,23 @@ export const IPAdapterImagePreview = memo(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTabName === 'unifiedCanvas') {
|
if (activeTabName === 'canvas') {
|
||||||
dispatch(
|
dispatch(
|
||||||
setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension)
|
setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const options = { updateAspectRatio: true, clamp: true };
|
||||||
if (shift) {
|
if (shift) {
|
||||||
const { width, height } = controlImage;
|
const { width, height } = controlImage;
|
||||||
dispatch(widthChanged({ width, updateAspectRatio: true }));
|
dispatch(widthChanged({ width, ...options }));
|
||||||
dispatch(heightChanged({ height, updateAspectRatio: true }));
|
dispatch(heightChanged({ height, ...options }));
|
||||||
} else {
|
} else {
|
||||||
const { width, height } = calculateNewSize(
|
const { width, height } = calculateNewSize(
|
||||||
controlImage.width / controlImage.height,
|
controlImage.width / controlImage.height,
|
||||||
optimalDimension * optimalDimension
|
optimalDimension * optimalDimension
|
||||||
);
|
);
|
||||||
dispatch(widthChanged({ width, updateAspectRatio: true }));
|
dispatch(widthChanged({ width, ...options }));
|
||||||
dispatch(heightChanged({ height, updateAspectRatio: true }));
|
dispatch(heightChanged({ height, ...options }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [controlImage, activeTabName, dispatch, optimalDimension, shift]);
|
}, [controlImage, activeTabName, dispatch, optimalDimension, shift]);
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import type { IPMethod } from 'features/controlLayers/util/controlAdapters';
|
import type { IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { isIPMethod } from 'features/controlLayers/util/controlAdapters';
|
import { isIPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
method: IPMethod;
|
method: IPMethodV2;
|
||||||
onChange: (method: IPMethod) => void;
|
onChange: (method: IPMethodV2) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IPAdapterMethod = memo(({ method, onChange }: Props) => {
|
export const IPAdapterMethod = memo(({ method, onChange }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const options: { label: string; value: IPMethod }[] = useMemo(
|
const options: { label: string; value: IPMethodV2 }[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{ label: t('controlnet.full'), value: 'full' },
|
{ label: t('controlnet.full'), value: 'full' },
|
||||||
{ label: `${t('controlnet.style')} (${t('common.beta')})`, value: 'style' },
|
{ label: `${t('controlnet.style')} (${t('common.beta')})`, value: 'style' },
|
||||||
@ -24,7 +24,7 @@ export const IPAdapterMethod = memo(({ method, onChange }: Props) => {
|
|||||||
);
|
);
|
||||||
const _onChange = useCallback<ComboboxOnChange>(
|
const _onChange = useCallback<ComboboxOnChange>(
|
||||||
(v) => {
|
(v) => {
|
||||||
assert(isIPMethod(v?.value));
|
assert(isIPMethodV2(v?.value));
|
||||||
onChange(v.value);
|
onChange(v.value);
|
||||||
},
|
},
|
||||||
[onChange]
|
[onChange]
|
||||||
|
@ -2,8 +2,8 @@ import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
|||||||
import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library';
|
import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
||||||
import type { CLIPVisionModel } from 'features/controlLayers/util/controlAdapters';
|
import type { CLIPVisionModelV2 } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { isCLIPVisionModel } from 'features/controlLayers/util/controlAdapters';
|
import { isCLIPVisionModelV2 } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useIPAdapterModels } from 'services/api/hooks/modelsByType';
|
import { useIPAdapterModels } from 'services/api/hooks/modelsByType';
|
||||||
@ -18,8 +18,8 @@ const CLIP_VISION_OPTIONS = [
|
|||||||
type Props = {
|
type Props = {
|
||||||
modelKey: string | null;
|
modelKey: string | null;
|
||||||
onChangeModel: (modelConfig: IPAdapterModelConfig) => void;
|
onChangeModel: (modelConfig: IPAdapterModelConfig) => void;
|
||||||
clipVisionModel: CLIPVisionModel;
|
clipVisionModel: CLIPVisionModelV2;
|
||||||
onChangeCLIPVisionModel: (clipVisionModel: CLIPVisionModel) => void;
|
onChangeCLIPVisionModel: (clipVisionModel: CLIPVisionModelV2) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IPAdapterModelSelect = memo(
|
export const IPAdapterModelSelect = memo(
|
||||||
@ -41,7 +41,7 @@ export const IPAdapterModelSelect = memo(
|
|||||||
|
|
||||||
const _onChangeCLIPVisionModel = useCallback<ComboboxOnChange>(
|
const _onChangeCLIPVisionModel = useCallback<ComboboxOnChange>(
|
||||||
(v) => {
|
(v) => {
|
||||||
assert(isCLIPVisionModel(v?.value));
|
assert(isCLIPVisionModelV2(v?.value));
|
||||||
onChangeCLIPVisionModel(v.value);
|
onChangeCLIPVisionModel(v.value);
|
||||||
},
|
},
|
||||||
[onChangeCLIPVisionModel]
|
[onChangeCLIPVisionModel]
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||||
import { type CannyProcessorConfig, CONTROLNET_PROCESSORS } from 'features/controlLayers/util/controlAdapters';
|
import { CA_PROCESSOR_DATA, type CannyProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import ProcessorWrapper from './ProcessorWrapper';
|
import ProcessorWrapper from './ProcessorWrapper';
|
||||||
|
|
||||||
type Props = ProcessorComponentProps<CannyProcessorConfig>;
|
type Props = ProcessorComponentProps<CannyProcessorConfig>;
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS['canny_image_processor'].buildDefaults();
|
const DEFAULTS = CA_PROCESSOR_DATA['canny_image_processor'].buildDefaults();
|
||||||
|
|
||||||
export const CannyProcessor = ({ onChange, config }: Props) => {
|
export const CannyProcessor = ({ onChange, config }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||||
import { type ColorMapProcessorConfig, CONTROLNET_PROCESSORS } from 'features/controlLayers/util/controlAdapters';
|
import { CA_PROCESSOR_DATA, type ColorMapProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import ProcessorWrapper from './ProcessorWrapper';
|
import ProcessorWrapper from './ProcessorWrapper';
|
||||||
|
|
||||||
type Props = ProcessorComponentProps<ColorMapProcessorConfig>;
|
type Props = ProcessorComponentProps<ColorMapProcessorConfig>;
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS['color_map_image_processor'].buildDefaults();
|
const DEFAULTS = CA_PROCESSOR_DATA['color_map_image_processor'].buildDefaults();
|
||||||
|
|
||||||
export const ColorMapProcessor = memo(({ onChange, config }: Props) => {
|
export const ColorMapProcessor = memo(({ onChange, config }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||||
import type { ContentShuffleProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
import type { ContentShuffleProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { CONTROLNET_PROCESSORS } from 'features/controlLayers/util/controlAdapters';
|
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import ProcessorWrapper from './ProcessorWrapper';
|
import ProcessorWrapper from './ProcessorWrapper';
|
||||||
|
|
||||||
type Props = ProcessorComponentProps<ContentShuffleProcessorConfig>;
|
type Props = ProcessorComponentProps<ContentShuffleProcessorConfig>;
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS['content_shuffle_image_processor'].buildDefaults();
|
const DEFAULTS = CA_PROCESSOR_DATA['content_shuffle_image_processor'].buildDefaults();
|
||||||
|
|
||||||
export const ContentShuffleProcessor = memo(({ onChange, config }: Props) => {
|
export const ContentShuffleProcessor = memo(({ onChange, config }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Flex, FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
import { Flex, FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||||
import type { DWOpenposeProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
import type { DWOpenposeProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { CONTROLNET_PROCESSORS } from 'features/controlLayers/util/controlAdapters';
|
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import ProcessorWrapper from './ProcessorWrapper';
|
import ProcessorWrapper from './ProcessorWrapper';
|
||||||
|
|
||||||
type Props = ProcessorComponentProps<DWOpenposeProcessorConfig>;
|
type Props = ProcessorComponentProps<DWOpenposeProcessorConfig>;
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS['dw_openpose_image_processor'].buildDefaults();
|
const DEFAULTS = CA_PROCESSOR_DATA['dw_openpose_image_processor'].buildDefaults();
|
||||||
|
|
||||||
export const DWOpenposeProcessor = memo(({ onChange, config }: Props) => {
|
export const DWOpenposeProcessor = memo(({ onChange, config }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -2,14 +2,14 @@ import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
|||||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||||
import type { DepthAnythingModelSize, DepthAnythingProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
import type { DepthAnythingModelSize, DepthAnythingProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { CONTROLNET_PROCESSORS, isDepthAnythingModelSize } from 'features/controlLayers/util/controlAdapters';
|
import { CA_PROCESSOR_DATA, isDepthAnythingModelSize } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import ProcessorWrapper from './ProcessorWrapper';
|
import ProcessorWrapper from './ProcessorWrapper';
|
||||||
|
|
||||||
type Props = ProcessorComponentProps<DepthAnythingProcessorConfig>;
|
type Props = ProcessorComponentProps<DepthAnythingProcessorConfig>;
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS['depth_anything_image_processor'].buildDefaults();
|
const DEFAULTS = CA_PROCESSOR_DATA['depth_anything_image_processor'].buildDefaults();
|
||||||
|
|
||||||
export const DepthAnythingProcessor = memo(({ onChange, config }: Props) => {
|
export const DepthAnythingProcessor = memo(({ onChange, config }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||||
import { CONTROLNET_PROCESSORS, type MediapipeFaceProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
import { CA_PROCESSOR_DATA, type MediapipeFaceProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import ProcessorWrapper from './ProcessorWrapper';
|
import ProcessorWrapper from './ProcessorWrapper';
|
||||||
|
|
||||||
type Props = ProcessorComponentProps<MediapipeFaceProcessorConfig>;
|
type Props = ProcessorComponentProps<MediapipeFaceProcessorConfig>;
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS['mediapipe_face_processor'].buildDefaults();
|
const DEFAULTS = CA_PROCESSOR_DATA['mediapipe_face_processor'].buildDefaults();
|
||||||
|
|
||||||
export const MediapipeFaceProcessor = memo(({ onChange, config }: Props) => {
|
export const MediapipeFaceProcessor = memo(({ onChange, config }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||||
import type { MidasDepthProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
import type { MidasDepthProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { CONTROLNET_PROCESSORS } from 'features/controlLayers/util/controlAdapters';
|
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import ProcessorWrapper from './ProcessorWrapper';
|
import ProcessorWrapper from './ProcessorWrapper';
|
||||||
|
|
||||||
type Props = ProcessorComponentProps<MidasDepthProcessorConfig>;
|
type Props = ProcessorComponentProps<MidasDepthProcessorConfig>;
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS['midas_depth_image_processor'].buildDefaults();
|
const DEFAULTS = CA_PROCESSOR_DATA['midas_depth_image_processor'].buildDefaults();
|
||||||
|
|
||||||
export const MidasDepthProcessor = memo(({ onChange, config }: Props) => {
|
export const MidasDepthProcessor = memo(({ onChange, config }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||||
import type { MlsdProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
import type { MlsdProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { CONTROLNET_PROCESSORS } from 'features/controlLayers/util/controlAdapters';
|
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import ProcessorWrapper from './ProcessorWrapper';
|
import ProcessorWrapper from './ProcessorWrapper';
|
||||||
|
|
||||||
type Props = ProcessorComponentProps<MlsdProcessorConfig>;
|
type Props = ProcessorComponentProps<MlsdProcessorConfig>;
|
||||||
const DEFAULTS = CONTROLNET_PROCESSORS['mlsd_image_processor'].buildDefaults();
|
const DEFAULTS = CA_PROCESSOR_DATA['mlsd_image_processor'].buildDefaults();
|
||||||
|
|
||||||
export const MlsdImageProcessor = memo(({ onChange, config }: Props) => {
|
export const MlsdImageProcessor = memo(({ onChange, config }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -2,16 +2,19 @@
|
|||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||||
import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton';
|
import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton';
|
||||||
import { CALayer } from 'features/controlLayers/components/CALayer/CALayer';
|
import { CALayer } from 'features/controlLayers/components/CALayer/CALayer';
|
||||||
import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
|
import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
|
||||||
|
import { IILayer } from 'features/controlLayers/components/IILayer/IILayer';
|
||||||
import { IPALayer } from 'features/controlLayers/components/IPALayer/IPALayer';
|
import { IPALayer } from 'features/controlLayers/components/IPALayer/IPALayer';
|
||||||
import { RGLayer } from 'features/controlLayers/components/RGLayer/RGLayer';
|
import { RGLayer } from 'features/controlLayers/components/RGLayer/RGLayer';
|
||||||
import { isRenderableLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
import { isRenderableLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { Layer } from 'features/controlLayers/store/types';
|
import type { Layer } from 'features/controlLayers/store/types';
|
||||||
import { partition } from 'lodash-es';
|
import { partition } from 'lodash-es';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selectLayerIdTypePairs = createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
const selectLayerIdTypePairs = createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||||
const [renderableLayers, ipAdapterLayers] = partition(controlLayers.present.layers, isRenderableLayer);
|
const [renderableLayers, ipAdapterLayers] = partition(controlLayers.present.layers, isRenderableLayer);
|
||||||
@ -19,20 +22,24 @@ const selectLayerIdTypePairs = createMemoizedSelector(selectControlLayersSlice,
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const ControlLayersPanelContent = memo(() => {
|
export const ControlLayersPanelContent = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const layerIdTypePairs = useAppSelector(selectLayerIdTypePairs);
|
const layerIdTypePairs = useAppSelector(selectLayerIdTypePairs);
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" gap={4} w="full" h="full">
|
<Flex flexDir="column" gap={2} w="full" h="full">
|
||||||
<Flex justifyContent="space-around">
|
<Flex justifyContent="space-around">
|
||||||
<AddLayerButton />
|
<AddLayerButton />
|
||||||
<DeleteAllLayersButton />
|
<DeleteAllLayersButton />
|
||||||
</Flex>
|
</Flex>
|
||||||
<ScrollableContent>
|
{layerIdTypePairs.length > 0 && (
|
||||||
<Flex flexDir="column" gap={4}>
|
<ScrollableContent>
|
||||||
{layerIdTypePairs.map(({ id, type }) => (
|
<Flex flexDir="column" gap={2}>
|
||||||
<LayerWrapper key={id} id={id} type={type} />
|
{layerIdTypePairs.map(({ id, type }) => (
|
||||||
))}
|
<LayerWrapper key={id} id={id} type={type} />
|
||||||
</Flex>
|
))}
|
||||||
</ScrollableContent>
|
</Flex>
|
||||||
|
</ScrollableContent>
|
||||||
|
)}
|
||||||
|
{layerIdTypePairs.length === 0 && <IAINoContentFallback icon={null} label={t('controlLayers.noLayersAdded')} />}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -54,6 +61,9 @@ const LayerWrapper = memo(({ id, type }: LayerWrapperProps) => {
|
|||||||
if (type === 'ip_adapter_layer') {
|
if (type === 'ip_adapter_layer') {
|
||||||
return <IPALayer key={id} layerId={id} />;
|
return <IPALayer key={id} layerId={id} />;
|
||||||
}
|
}
|
||||||
|
if (type === 'initial_image_layer') {
|
||||||
|
return <IILayer key={id} layerId={id} />;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
LayerWrapper.displayName = 'LayerWrapper';
|
LayerWrapper.displayName = 'LayerWrapper';
|
||||||
|
@ -4,15 +4,26 @@ import { BrushSize } from 'features/controlLayers/components/BrushSize';
|
|||||||
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
|
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
|
||||||
import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
|
import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
|
||||||
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
|
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
|
||||||
|
import { ViewerButton } from 'features/gallery/components/ImageViewer/ViewerButton';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
export const ControlLayersToolbar = memo(() => {
|
export const ControlLayersToolbar = memo(() => {
|
||||||
return (
|
return (
|
||||||
<Flex gap={4}>
|
<Flex w="full" gap={2}>
|
||||||
<BrushSize />
|
<Flex flex={1} justifyContent="center">
|
||||||
<ToolChooser />
|
<Flex gap={2} marginInlineEnd="auto" />
|
||||||
<UndoRedoButtonGroup />
|
</Flex>
|
||||||
<ControlLayersSettingsPopover />
|
<Flex flex={1} gap={2} justifyContent="center">
|
||||||
|
<BrushSize />
|
||||||
|
<ToolChooser />
|
||||||
|
<UndoRedoButtonGroup />
|
||||||
|
<ControlLayersSettingsPopover />
|
||||||
|
</Flex>
|
||||||
|
<Flex flex={1} justifyContent="center">
|
||||||
|
<Flex gap={2} marginInlineStart="auto">
|
||||||
|
<ViewerButton />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import IILayerOpacity from 'features/controlLayers/components/IILayer/IILayerOpacity';
|
||||||
|
import { InitialImagePreview } from 'features/controlLayers/components/IILayer/InitialImagePreview';
|
||||||
|
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||||
|
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||||
|
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||||
|
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||||
|
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||||
|
import {
|
||||||
|
iiLayerImageChanged,
|
||||||
|
layerSelected,
|
||||||
|
selectIILayerOrThrow,
|
||||||
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import type { IILayerImageDropData } from 'features/dnd/types';
|
||||||
|
import ImageToImageStrength from 'features/parameters/components/ImageToImage/ImageToImageStrength';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
import type { IILayerImagePostUploadAction, ImageDTO } from 'services/api/types';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
layerId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IILayer = memo(({ layerId }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const layer = useAppSelector((s) => selectIILayerOrThrow(s.controlLayers.present, layerId));
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
dispatch(layerSelected(layerId));
|
||||||
|
}, [dispatch, layerId]);
|
||||||
|
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||||
|
|
||||||
|
const onChangeImage = useCallback(
|
||||||
|
(imageDTO: ImageDTO | null) => {
|
||||||
|
dispatch(iiLayerImageChanged({ layerId, imageDTO }));
|
||||||
|
},
|
||||||
|
[dispatch, layerId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const droppableData = useMemo<IILayerImageDropData>(
|
||||||
|
() => ({
|
||||||
|
actionType: 'SET_II_LAYER_IMAGE',
|
||||||
|
context: {
|
||||||
|
layerId,
|
||||||
|
},
|
||||||
|
id: layerId,
|
||||||
|
}),
|
||||||
|
[layerId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const postUploadAction = useMemo<IILayerImagePostUploadAction>(
|
||||||
|
() => ({
|
||||||
|
layerId,
|
||||||
|
type: 'SET_II_LAYER_IMAGE',
|
||||||
|
}),
|
||||||
|
[layerId]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LayerWrapper onClick={onClick} borderColor={layer.isSelected ? 'base.400' : 'base.800'}>
|
||||||
|
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||||
|
<LayerVisibilityToggle layerId={layerId} />
|
||||||
|
<LayerTitle type="initial_image_layer" />
|
||||||
|
<Spacer />
|
||||||
|
<IILayerOpacity layerId={layerId} />
|
||||||
|
<LayerMenu layerId={layerId} />
|
||||||
|
<LayerDeleteButton layerId={layerId} />
|
||||||
|
</Flex>
|
||||||
|
{isOpen && (
|
||||||
|
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||||
|
<ImageToImageStrength />
|
||||||
|
<InitialImagePreview
|
||||||
|
image={layer.image}
|
||||||
|
onChangeImage={onChangeImage}
|
||||||
|
droppableData={droppableData}
|
||||||
|
postUploadAction={postUploadAction}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</LayerWrapper>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
IILayer.displayName = 'IILayer';
|
@ -0,0 +1,98 @@
|
|||||||
|
import {
|
||||||
|
CompositeNumberInput,
|
||||||
|
CompositeSlider,
|
||||||
|
Flex,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
IconButton,
|
||||||
|
Popover,
|
||||||
|
PopoverArrow,
|
||||||
|
PopoverBody,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
|
import {
|
||||||
|
iiLayerOpacityChanged,
|
||||||
|
isInitialImageLayer,
|
||||||
|
selectControlLayersSlice,
|
||||||
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiDropHalfFill } from 'react-icons/pi';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
layerId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const marks = [0, 25, 50, 75, 100];
|
||||||
|
const formatPct = (v: number | string) => `${v} %`;
|
||||||
|
|
||||||
|
const IILayerOpacity = ({ layerId }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const selectOpacity = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||||
|
const layer = controlLayers.present.layers.filter(isInitialImageLayer).find((l) => l.id === layerId);
|
||||||
|
assert(layer, `Layer ${layerId} not found`);
|
||||||
|
return Math.round(layer.opacity * 100);
|
||||||
|
}),
|
||||||
|
[layerId]
|
||||||
|
);
|
||||||
|
const opacity = useAppSelector(selectOpacity);
|
||||||
|
const onChangeOpacity = useCallback(
|
||||||
|
(v: number) => {
|
||||||
|
dispatch(iiLayerOpacityChanged({ layerId, opacity: v / 100 }));
|
||||||
|
},
|
||||||
|
[dispatch, layerId]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Popover isLazy>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<IconButton
|
||||||
|
aria-label={t('controlLayers.opacity')}
|
||||||
|
size="sm"
|
||||||
|
icon={<PiDropHalfFill size={16} />}
|
||||||
|
variant="ghost"
|
||||||
|
onDoubleClick={stopPropagation}
|
||||||
|
/>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent onDoubleClick={stopPropagation}>
|
||||||
|
<PopoverArrow />
|
||||||
|
<PopoverBody>
|
||||||
|
<Flex direction="column" gap={2}>
|
||||||
|
<FormControl orientation="horizontal">
|
||||||
|
<FormLabel m={0}>{t('controlLayers.opacity')}</FormLabel>
|
||||||
|
<CompositeSlider
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
value={opacity}
|
||||||
|
defaultValue={100}
|
||||||
|
onChange={onChangeOpacity}
|
||||||
|
marks={marks}
|
||||||
|
w={48}
|
||||||
|
/>
|
||||||
|
<CompositeNumberInput
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
value={opacity}
|
||||||
|
defaultValue={100}
|
||||||
|
onChange={onChangeOpacity}
|
||||||
|
w={24}
|
||||||
|
format={formatPct}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Flex>
|
||||||
|
</PopoverBody>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(IILayerOpacity);
|
@ -0,0 +1,109 @@
|
|||||||
|
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||||
|
import { Flex, useShiftModifier } from '@invoke-ai/ui-library';
|
||||||
|
import { skipToken } from '@reduxjs/toolkit/query';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
|
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||||
|
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||||
|
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import type { ImageWithDims } from 'features/controlLayers/util/controlAdapters';
|
||||||
|
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||||
|
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||||
|
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||||
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
|
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiArrowCounterClockwiseBold, PiRulerBold } from 'react-icons/pi';
|
||||||
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
|
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
image: ImageWithDims | null;
|
||||||
|
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||||
|
droppableData: TypesafeDroppableData;
|
||||||
|
postUploadAction: PostUploadAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InitialImagePreview = memo(({ image, onChangeImage, droppableData, postUploadAction }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||||
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
|
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||||
|
const shift = useShiftModifier();
|
||||||
|
|
||||||
|
const { currentData: imageDTO, isError: isErrorControlImage } = useGetImageDTOQuery(image?.imageName ?? skipToken);
|
||||||
|
|
||||||
|
const onReset = useCallback(() => {
|
||||||
|
onChangeImage(null);
|
||||||
|
}, [onChangeImage]);
|
||||||
|
|
||||||
|
const onUseSize = useCallback(() => {
|
||||||
|
if (!imageDTO) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeTabName === 'canvas') {
|
||||||
|
dispatch(setBoundingBoxDimensions({ width: imageDTO.width, height: imageDTO.height }, optimalDimension));
|
||||||
|
} else {
|
||||||
|
const options = { updateAspectRatio: true, clamp: true };
|
||||||
|
if (shift) {
|
||||||
|
const { width, height } = imageDTO;
|
||||||
|
dispatch(widthChanged({ width, ...options }));
|
||||||
|
dispatch(heightChanged({ height, ...options }));
|
||||||
|
} else {
|
||||||
|
const { width, height } = calculateNewSize(
|
||||||
|
imageDTO.width / imageDTO.height,
|
||||||
|
optimalDimension * optimalDimension
|
||||||
|
);
|
||||||
|
dispatch(widthChanged({ width, ...options }));
|
||||||
|
dispatch(heightChanged({ height, ...options }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [imageDTO, activeTabName, dispatch, optimalDimension, shift]);
|
||||||
|
|
||||||
|
const draggableData = useMemo<ImageDraggableData | undefined>(() => {
|
||||||
|
if (imageDTO) {
|
||||||
|
return {
|
||||||
|
id: 'initial_image_layer',
|
||||||
|
payloadType: 'IMAGE_DTO',
|
||||||
|
payload: { imageDTO: imageDTO },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [imageDTO]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isConnected && isErrorControlImage) {
|
||||||
|
onReset();
|
||||||
|
}
|
||||||
|
}, [onReset, isConnected, isErrorControlImage]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex position="relative" w="full" h={36} alignItems="center" justifyContent="center">
|
||||||
|
<IAIDndImage
|
||||||
|
draggableData={draggableData}
|
||||||
|
droppableData={droppableData}
|
||||||
|
imageDTO={imageDTO}
|
||||||
|
postUploadAction={postUploadAction}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<>
|
||||||
|
<IAIDndImageIcon
|
||||||
|
onClick={onReset}
|
||||||
|
icon={imageDTO ? <PiArrowCounterClockwiseBold size={16} /> : undefined}
|
||||||
|
tooltip={t('controlnet.resetControlImage')}
|
||||||
|
/>
|
||||||
|
<IAIDndImageIcon
|
||||||
|
onClick={onUseSize}
|
||||||
|
icon={imageDTO ? <PiRulerBold size={16} /> : undefined}
|
||||||
|
tooltip={shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')}
|
||||||
|
styleOverrides={useSizeStyleOverrides}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
InitialImagePreview.displayName = 'InitialImagePreview';
|
||||||
|
|
||||||
|
const useSizeStyleOverrides: SystemStyleObject = { mt: 6 };
|
@ -3,6 +3,7 @@ import { IPALayerIPAdapterWrapper } from 'features/controlLayers/components/IPAL
|
|||||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||||
|
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -12,21 +13,19 @@ type Props = {
|
|||||||
export const IPALayer = memo(({ layerId }: Props) => {
|
export const IPALayer = memo(({ layerId }: Props) => {
|
||||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||||
return (
|
return (
|
||||||
<Flex gap={2} bg="base.800" borderRadius="base" p="1px" px={2}>
|
<LayerWrapper borderColor="base.800">
|
||||||
<Flex flexDir="column" w="full" bg="base.850" borderRadius="base">
|
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
<LayerVisibilityToggle layerId={layerId} />
|
||||||
<LayerVisibilityToggle layerId={layerId} />
|
<LayerTitle type="ip_adapter_layer" />
|
||||||
<LayerTitle type="ip_adapter_layer" />
|
<Spacer />
|
||||||
<Spacer />
|
<LayerDeleteButton layerId={layerId} />
|
||||||
<LayerDeleteButton layerId={layerId} />
|
|
||||||
</Flex>
|
|
||||||
{isOpen && (
|
|
||||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
|
||||||
<IPALayerIPAdapterWrapper layerId={layerId} />
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
{isOpen && (
|
||||||
|
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||||
|
<IPALayerIPAdapterWrapper layerId={layerId} />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</LayerWrapper>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
ipaLayerModelChanged,
|
ipaLayerModelChanged,
|
||||||
selectIPALayerOrThrow,
|
selectIPALayerOrThrow,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { CLIPVisionModel, IPMethod } from 'features/controlLayers/util/controlAdapters';
|
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||||
import type { IPALayerImageDropData } from 'features/dnd/types';
|
import type { IPALayerImageDropData } from 'features/dnd/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import type { ImageDTO, IPAdapterModelConfig, IPALayerImagePostUploadAction } from 'services/api/types';
|
import type { ImageDTO, IPAdapterModelConfig, IPALayerImagePostUploadAction } from 'services/api/types';
|
||||||
@ -42,7 +42,7 @@ export const IPALayerIPAdapterWrapper = memo(({ layerId }: Props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onChangeIPMethod = useCallback(
|
const onChangeIPMethod = useCallback(
|
||||||
(method: IPMethod) => {
|
(method: IPMethodV2) => {
|
||||||
dispatch(ipaLayerMethodChanged({ layerId, method }));
|
dispatch(ipaLayerMethodChanged({ layerId, method }));
|
||||||
},
|
},
|
||||||
[dispatch, layerId]
|
[dispatch, layerId]
|
||||||
@ -56,7 +56,7 @@ export const IPALayerIPAdapterWrapper = memo(({ layerId }: Props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onChangeCLIPVisionModel = useCallback(
|
const onChangeCLIPVisionModel = useCallback(
|
||||||
(clipVisionModel: CLIPVisionModel) => {
|
(clipVisionModel: CLIPVisionModelV2) => {
|
||||||
dispatch(ipaLayerCLIPVisionModelChanged({ layerId, clipVisionModel }));
|
dispatch(ipaLayerCLIPVisionModelChanged({ layerId, clipVisionModel }));
|
||||||
},
|
},
|
||||||
[dispatch, layerId]
|
[dispatch, layerId]
|
||||||
|
@ -37,7 +37,9 @@ export const LayerMenu = memo(({ layerId }: Props) => {
|
|||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{(layerType === 'regional_guidance_layer' || layerType === 'control_adapter_layer') && (
|
{(layerType === 'regional_guidance_layer' ||
|
||||||
|
layerType === 'control_adapter_layer' ||
|
||||||
|
layerType === 'initial_image_layer') && (
|
||||||
<>
|
<>
|
||||||
<LayerMenuArrangeActions layerId={layerId} />
|
<LayerMenuArrangeActions layerId={layerId} />
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
|
@ -16,6 +16,8 @@ export const LayerTitle = memo(({ type }: Props) => {
|
|||||||
return t('controlLayers.globalControlAdapter');
|
return t('controlLayers.globalControlAdapter');
|
||||||
} else if (type === 'ip_adapter_layer') {
|
} else if (type === 'ip_adapter_layer') {
|
||||||
return t('controlLayers.globalIPAdapter');
|
return t('controlLayers.globalIPAdapter');
|
||||||
|
} else if (type === 'initial_image_layer') {
|
||||||
|
return t('controlLayers.globalInitialImage');
|
||||||
}
|
}
|
||||||
}, [t, type]);
|
}, [t, type]);
|
||||||
|
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||||
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
|
import type { PropsWithChildren } from 'react';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
type Props = PropsWithChildren<{
|
||||||
|
onClick?: () => void;
|
||||||
|
borderColor: ChakraProps['bg'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const LayerWrapper = memo(({ onClick, borderColor, children }: Props) => {
|
||||||
|
return (
|
||||||
|
<Flex gap={2} onClick={onClick} bg={borderColor} px={2} borderRadius="base" py="1px">
|
||||||
|
<Flex flexDir="column" w="full" bg="base.850" borderRadius="base">
|
||||||
|
{children}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
LayerWrapper.displayName = 'LayerWrapper';
|
@ -7,6 +7,7 @@ import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon
|
|||||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||||
|
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||||
import {
|
import {
|
||||||
isRegionalGuidanceLayer,
|
isRegionalGuidanceLayer,
|
||||||
layerSelected,
|
layerSelected,
|
||||||
@ -52,32 +53,30 @@ export const RGLayer = memo(({ layerId }: Props) => {
|
|||||||
dispatch(layerSelected(layerId));
|
dispatch(layerSelected(layerId));
|
||||||
}, [dispatch, layerId]);
|
}, [dispatch, layerId]);
|
||||||
return (
|
return (
|
||||||
<Flex gap={2} onClick={onClick} bg={isSelected ? color : 'base.800'} px={2} borderRadius="base" py="1px">
|
<LayerWrapper onClick={onClick} borderColor={isSelected ? color : 'base.800'}>
|
||||||
<Flex flexDir="column" w="full" bg="base.850" borderRadius="base">
|
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
<LayerVisibilityToggle layerId={layerId} />
|
||||||
<LayerVisibilityToggle layerId={layerId} />
|
<LayerTitle type="regional_guidance_layer" />
|
||||||
<LayerTitle type="regional_guidance_layer" />
|
<Spacer />
|
||||||
<Spacer />
|
{autoNegative === 'invert' && (
|
||||||
{autoNegative === 'invert' && (
|
<Badge color="base.300" bg="transparent" borderWidth={1} userSelect="none">
|
||||||
<Badge color="base.300" bg="transparent" borderWidth={1} userSelect="none">
|
{t('controlLayers.autoNegative')}
|
||||||
{t('controlLayers.autoNegative')}
|
</Badge>
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
<RGLayerColorPicker layerId={layerId} />
|
|
||||||
<RGLayerSettingsPopover layerId={layerId} />
|
|
||||||
<LayerMenu layerId={layerId} />
|
|
||||||
<LayerDeleteButton layerId={layerId} />
|
|
||||||
</Flex>
|
|
||||||
{isOpen && (
|
|
||||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
|
||||||
{!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && <AddPromptButtons layerId={layerId} />}
|
|
||||||
{hasPositivePrompt && <RGLayerPositivePrompt layerId={layerId} />}
|
|
||||||
{hasNegativePrompt && <RGLayerNegativePrompt layerId={layerId} />}
|
|
||||||
{hasIPAdapters && <RGLayerIPAdapterList layerId={layerId} />}
|
|
||||||
</Flex>
|
|
||||||
)}
|
)}
|
||||||
|
<RGLayerColorPicker layerId={layerId} />
|
||||||
|
<RGLayerSettingsPopover layerId={layerId} />
|
||||||
|
<LayerMenu layerId={layerId} />
|
||||||
|
<LayerDeleteButton layerId={layerId} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
{isOpen && (
|
||||||
|
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||||
|
{!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && <AddPromptButtons layerId={layerId} />}
|
||||||
|
{hasPositivePrompt && <RGLayerPositivePrompt layerId={layerId} />}
|
||||||
|
{hasNegativePrompt && <RGLayerNegativePrompt layerId={layerId} />}
|
||||||
|
{hasIPAdapters && <RGLayerIPAdapterList layerId={layerId} />}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</LayerWrapper>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
rgLayerIPAdapterWeightChanged,
|
rgLayerIPAdapterWeightChanged,
|
||||||
selectRGLayerIPAdapterOrThrow,
|
selectRGLayerIPAdapterOrThrow,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { CLIPVisionModel, IPMethod } from 'features/controlLayers/util/controlAdapters';
|
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||||
import type { RGLayerIPAdapterImageDropData } from 'features/dnd/types';
|
import type { RGLayerIPAdapterImageDropData } from 'features/dnd/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||||
@ -51,7 +51,7 @@ export const RGLayerIPAdapterWrapper = memo(({ layerId, ipAdapterId, ipAdapterNu
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onChangeIPMethod = useCallback(
|
const onChangeIPMethod = useCallback(
|
||||||
(method: IPMethod) => {
|
(method: IPMethodV2) => {
|
||||||
dispatch(rgLayerIPAdapterMethodChanged({ layerId, ipAdapterId, method }));
|
dispatch(rgLayerIPAdapterMethodChanged({ layerId, ipAdapterId, method }));
|
||||||
},
|
},
|
||||||
[dispatch, ipAdapterId, layerId]
|
[dispatch, ipAdapterId, layerId]
|
||||||
@ -65,7 +65,7 @@ export const RGLayerIPAdapterWrapper = memo(({ layerId, ipAdapterId, ipAdapterNu
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onChangeCLIPVisionModel = useCallback(
|
const onChangeCLIPVisionModel = useCallback(
|
||||||
(clipVisionModel: CLIPVisionModel) => {
|
(clipVisionModel: CLIPVisionModelV2) => {
|
||||||
dispatch(rgLayerIPAdapterCLIPVisionModelChanged({ layerId, ipAdapterId, clipVisionModel }));
|
dispatch(rgLayerIPAdapterCLIPVisionModelChanged({ layerId, ipAdapterId, clipVisionModel }));
|
||||||
},
|
},
|
||||||
[dispatch, ipAdapterId, layerId]
|
[dispatch, ipAdapterId, layerId]
|
||||||
|
@ -6,8 +6,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useMouseEvents } from 'features/controlLayers/hooks/mouseEventHooks';
|
import { useMouseEvents } from 'features/controlLayers/hooks/mouseEventHooks';
|
||||||
import {
|
import {
|
||||||
$cursorPosition,
|
$lastCursorPos,
|
||||||
$isMouseOver,
|
|
||||||
$lastMouseDownPos,
|
$lastMouseDownPos,
|
||||||
$tool,
|
$tool,
|
||||||
isRegionalGuidanceLayer,
|
isRegionalGuidanceLayer,
|
||||||
@ -48,10 +47,9 @@ const useStageRenderer = (
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const state = useAppSelector((s) => s.controlLayers.present);
|
const state = useAppSelector((s) => s.controlLayers.present);
|
||||||
const tool = useStore($tool);
|
const tool = useStore($tool);
|
||||||
const { onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave, onMouseWheel } = useMouseEvents();
|
const mouseEventHandlers = useMouseEvents();
|
||||||
const cursorPosition = useStore($cursorPosition);
|
const lastCursorPos = useStore($lastCursorPos);
|
||||||
const lastMouseDownPos = useStore($lastMouseDownPos);
|
const lastMouseDownPos = useStore($lastMouseDownPos);
|
||||||
const isMouseOver = useStore($isMouseOver);
|
|
||||||
const selectedLayerIdColor = useAppSelector(selectSelectedLayerColor);
|
const selectedLayerIdColor = useAppSelector(selectSelectedLayerColor);
|
||||||
const selectedLayerType = useAppSelector(selectSelectedLayerType);
|
const selectedLayerType = useAppSelector(selectSelectedLayerType);
|
||||||
const layerIds = useMemo(() => state.layers.map((l) => l.id), [state.layers]);
|
const layerIds = useMemo(() => state.layers.map((l) => l.id), [state.layers]);
|
||||||
@ -90,23 +88,21 @@ const useStageRenderer = (
|
|||||||
if (asPreview) {
|
if (asPreview) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stage.on('mousedown', onMouseDown);
|
stage.on('mousedown', mouseEventHandlers.onMouseDown);
|
||||||
stage.on('mouseup', onMouseUp);
|
stage.on('mouseup', mouseEventHandlers.onMouseUp);
|
||||||
stage.on('mousemove', onMouseMove);
|
stage.on('mousemove', mouseEventHandlers.onMouseMove);
|
||||||
stage.on('mouseenter', onMouseEnter);
|
stage.on('mouseleave', mouseEventHandlers.onMouseLeave);
|
||||||
stage.on('mouseleave', onMouseLeave);
|
stage.on('wheel', mouseEventHandlers.onMouseWheel);
|
||||||
stage.on('wheel', onMouseWheel);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
log.trace('Cleaning up stage listeners');
|
log.trace('Cleaning up stage listeners');
|
||||||
stage.off('mousedown', onMouseDown);
|
stage.off('mousedown', mouseEventHandlers.onMouseDown);
|
||||||
stage.off('mouseup', onMouseUp);
|
stage.off('mouseup', mouseEventHandlers.onMouseUp);
|
||||||
stage.off('mousemove', onMouseMove);
|
stage.off('mousemove', mouseEventHandlers.onMouseMove);
|
||||||
stage.off('mouseenter', onMouseEnter);
|
stage.off('mouseleave', mouseEventHandlers.onMouseLeave);
|
||||||
stage.off('mouseleave', onMouseLeave);
|
stage.off('wheel', mouseEventHandlers.onMouseWheel);
|
||||||
stage.off('wheel', onMouseWheel);
|
|
||||||
};
|
};
|
||||||
}, [stage, asPreview, onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave, onMouseWheel]);
|
}, [stage, asPreview, mouseEventHandlers]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
log.trace('Updating stage dimensions');
|
log.trace('Updating stage dimensions');
|
||||||
@ -145,9 +141,8 @@ const useStageRenderer = (
|
|||||||
selectedLayerIdColor,
|
selectedLayerIdColor,
|
||||||
selectedLayerType,
|
selectedLayerType,
|
||||||
state.globalMaskLayerOpacity,
|
state.globalMaskLayerOpacity,
|
||||||
cursorPosition,
|
lastCursorPos,
|
||||||
lastMouseDownPos,
|
lastMouseDownPos,
|
||||||
isMouseOver,
|
|
||||||
state.brushSize
|
state.brushSize
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
@ -157,9 +152,8 @@ const useStageRenderer = (
|
|||||||
selectedLayerIdColor,
|
selectedLayerIdColor,
|
||||||
selectedLayerType,
|
selectedLayerType,
|
||||||
state.globalMaskLayerOpacity,
|
state.globalMaskLayerOpacity,
|
||||||
cursorPosition,
|
lastCursorPos,
|
||||||
lastMouseDownPos,
|
lastMouseDownPos,
|
||||||
isMouseOver,
|
|
||||||
state.brushSize,
|
state.brushSize,
|
||||||
renderers,
|
renderers,
|
||||||
]);
|
]);
|
||||||
|
@ -4,9 +4,9 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import {
|
||||||
$tool,
|
$tool,
|
||||||
|
layerReset,
|
||||||
selectControlLayersSlice,
|
selectControlLayersSlice,
|
||||||
selectedLayerDeleted,
|
selectedLayerDeleted,
|
||||||
selectedLayerReset,
|
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
@ -22,6 +22,7 @@ export const ToolChooser: React.FC = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isDisabled = useAppSelector(selectIsDisabled);
|
const isDisabled = useAppSelector(selectIsDisabled);
|
||||||
|
const selectedLayerId = useAppSelector((s) => s.controlLayers.present.selectedLayerId);
|
||||||
const tool = useStore($tool);
|
const tool = useStore($tool);
|
||||||
|
|
||||||
const setToolToBrush = useCallback(() => {
|
const setToolToBrush = useCallback(() => {
|
||||||
@ -42,8 +43,11 @@ export const ToolChooser: React.FC = () => {
|
|||||||
useHotkeys('v', setToolToMove, { enabled: !isDisabled }, [isDisabled]);
|
useHotkeys('v', setToolToMove, { enabled: !isDisabled }, [isDisabled]);
|
||||||
|
|
||||||
const resetSelectedLayer = useCallback(() => {
|
const resetSelectedLayer = useCallback(() => {
|
||||||
dispatch(selectedLayerReset());
|
if (selectedLayerId === null) {
|
||||||
}, [dispatch]);
|
return;
|
||||||
|
}
|
||||||
|
dispatch(layerReset(selectedLayerId));
|
||||||
|
}, [dispatch, selectedLayerId]);
|
||||||
useHotkeys('shift+c', resetSelectedLayer);
|
useHotkeys('shift+c', resetSelectedLayer);
|
||||||
|
|
||||||
const deleteSelectedLayer = useCallback(() => {
|
const deleteSelectedLayer = useCallback(() => {
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { caLayerAdded, ipaLayerAdded, rgLayerIPAdapterAdded } from 'features/controlLayers/store/controlLayersSlice';
|
import {
|
||||||
|
caLayerAdded,
|
||||||
|
iiLayerAdded,
|
||||||
|
ipaLayerAdded,
|
||||||
|
isInitialImageLayer,
|
||||||
|
rgLayerIPAdapterAdded,
|
||||||
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import {
|
import {
|
||||||
buildControlNet,
|
buildControlNet,
|
||||||
buildIPAdapter,
|
buildIPAdapter,
|
||||||
buildT2IAdapter,
|
buildT2IAdapter,
|
||||||
CONTROLNET_PROCESSORS,
|
CA_PROCESSOR_DATA,
|
||||||
isProcessorType,
|
isProcessorTypeV2,
|
||||||
} from 'features/controlLayers/util/controlAdapters';
|
} from 'features/controlLayers/util/controlAdapters';
|
||||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
@ -30,8 +36,8 @@ export const useAddCALayer = () => {
|
|||||||
|
|
||||||
const id = uuidv4();
|
const id = uuidv4();
|
||||||
const defaultPreprocessor = model.default_settings?.preprocessor;
|
const defaultPreprocessor = model.default_settings?.preprocessor;
|
||||||
const processorConfig = isProcessorType(defaultPreprocessor)
|
const processorConfig = isProcessorTypeV2(defaultPreprocessor)
|
||||||
? CONTROLNET_PROCESSORS[defaultPreprocessor].buildDefaults(baseModel)
|
? CA_PROCESSOR_DATA[defaultPreprocessor].buildDefaults(baseModel)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const builder = model.type === 'controlnet' ? buildControlNet : buildT2IAdapter;
|
const builder = model.type === 'controlnet' ? buildControlNet : buildT2IAdapter;
|
||||||
@ -93,3 +99,13 @@ export const useAddIPAdapterToIPALayer = (layerId: string) => {
|
|||||||
|
|
||||||
return [addIPAdapter, isDisabled] as const;
|
return [addIPAdapter, isDisabled] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useAddIILayer = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isDisabled = useAppSelector((s) => Boolean(s.controlLayers.present.layers.find(isInitialImageLayer)));
|
||||||
|
const addIILayer = useCallback(() => {
|
||||||
|
dispatch(iiLayerAdded(null));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return [addIILayer, isDisabled] as const;
|
||||||
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import {
|
||||||
isControlAdapterLayer,
|
isControlAdapterLayer,
|
||||||
@ -69,7 +70,7 @@ export const useLayerType = (layerId: string) => {
|
|||||||
export const useLayerOpacity = (layerId: string) => {
|
export const useLayerOpacity = (layerId: string) => {
|
||||||
const selectLayer = useMemo(
|
const selectLayer = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createSelector(selectControlLayersSlice, (controlLayers) => {
|
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||||
const layer = controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
const layer = controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
||||||
assert(layer, `Layer ${layerId} not found`);
|
assert(layer, `Layer ${layerId} not found`);
|
||||||
return { opacity: Math.round(layer.opacity * 100), isFilterEnabled: layer.isFilterEnabled };
|
return { opacity: Math.round(layer.opacity * 100), isFilterEnabled: layer.isFilterEnabled };
|
||||||
|
@ -3,9 +3,8 @@ import { useStore } from '@nanostores/react';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { calculateNewBrushSize } from 'features/canvas/hooks/useCanvasZoom';
|
import { calculateNewBrushSize } from 'features/canvas/hooks/useCanvasZoom';
|
||||||
import {
|
import {
|
||||||
$cursorPosition,
|
$isDrawing,
|
||||||
$isMouseDown,
|
$lastCursorPos,
|
||||||
$isMouseOver,
|
|
||||||
$lastMouseDownPos,
|
$lastMouseDownPos,
|
||||||
$tool,
|
$tool,
|
||||||
brushSizeChanged,
|
brushSizeChanged,
|
||||||
@ -16,16 +15,41 @@ import {
|
|||||||
import type Konva from 'konva';
|
import type Konva from 'konva';
|
||||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||||
import type { Vector2d } from 'konva/lib/types';
|
import type { Vector2d } from 'konva/lib/types';
|
||||||
import { useCallback, useRef } from 'react';
|
import { clamp } from 'lodash-es';
|
||||||
|
import { useCallback, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
const getIsFocused = (stage: Konva.Stage) => {
|
const getIsFocused = (stage: Konva.Stage) => {
|
||||||
return stage.container().contains(document.activeElement);
|
return stage.container().contains(document.activeElement);
|
||||||
};
|
};
|
||||||
|
const getIsMouseDown = (e: KonvaEventObject<MouseEvent>) => e.evt.buttons === 1;
|
||||||
|
|
||||||
|
const SNAP_PX = 10;
|
||||||
|
|
||||||
|
export const snapPosToStage = (pos: Vector2d, stage: Konva.Stage) => {
|
||||||
|
const snappedPos = { ...pos };
|
||||||
|
// Get the normalized threshold for snapping to the edge of the stage
|
||||||
|
const thresholdX = SNAP_PX / stage.scaleX();
|
||||||
|
const thresholdY = SNAP_PX / stage.scaleY();
|
||||||
|
const stageWidth = stage.width() / stage.scaleX();
|
||||||
|
const stageHeight = stage.height() / stage.scaleY();
|
||||||
|
// Snap to the edge of the stage if within threshold
|
||||||
|
if (pos.x - thresholdX < 0) {
|
||||||
|
snappedPos.x = 0;
|
||||||
|
} else if (pos.x + thresholdX > stageWidth) {
|
||||||
|
snappedPos.x = Math.floor(stageWidth);
|
||||||
|
}
|
||||||
|
if (pos.y - thresholdY < 0) {
|
||||||
|
snappedPos.y = 0;
|
||||||
|
} else if (pos.y + thresholdY > stageHeight) {
|
||||||
|
snappedPos.y = Math.floor(stageHeight);
|
||||||
|
}
|
||||||
|
return snappedPos;
|
||||||
|
};
|
||||||
|
|
||||||
export const getScaledFlooredCursorPosition = (stage: Konva.Stage) => {
|
export const getScaledFlooredCursorPosition = (stage: Konva.Stage) => {
|
||||||
const pointerPosition = stage.getPointerPosition();
|
const pointerPosition = stage.getPointerPosition();
|
||||||
const stageTransform = stage.getAbsoluteTransform().copy();
|
const stageTransform = stage.getAbsoluteTransform().copy();
|
||||||
if (!pointerPosition || !stageTransform) {
|
if (!pointerPosition) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const scaledCursorPosition = stageTransform.invert().point(pointerPosition);
|
const scaledCursorPosition = stageTransform.invert().point(pointerPosition);
|
||||||
@ -40,33 +64,41 @@ const syncCursorPos = (stage: Konva.Stage): Vector2d | null => {
|
|||||||
if (!pos) {
|
if (!pos) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$cursorPosition.set(pos);
|
$lastCursorPos.set(pos);
|
||||||
return pos;
|
return pos;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BRUSH_SPACING = 20;
|
const BRUSH_SPACING_PCT = 10;
|
||||||
|
const MIN_BRUSH_SPACING_PX = 5;
|
||||||
|
const MAX_BRUSH_SPACING_PX = 15;
|
||||||
|
|
||||||
export const useMouseEvents = () => {
|
export const useMouseEvents = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const selectedLayerId = useAppSelector((s) => s.controlLayers.present.selectedLayerId);
|
const selectedLayerId = useAppSelector((s) => s.controlLayers.present.selectedLayerId);
|
||||||
|
const selectedLayerType = useAppSelector((s) => {
|
||||||
|
const selectedLayer = s.controlLayers.present.layers.find((l) => l.id === s.controlLayers.present.selectedLayerId);
|
||||||
|
if (!selectedLayer) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return selectedLayer.type;
|
||||||
|
});
|
||||||
const tool = useStore($tool);
|
const tool = useStore($tool);
|
||||||
const lastCursorPosRef = useRef<[number, number] | null>(null);
|
const lastCursorPosRef = useRef<[number, number] | null>(null);
|
||||||
const shouldInvertBrushSizeScrollDirection = useAppSelector((s) => s.canvas.shouldInvertBrushSizeScrollDirection);
|
const shouldInvertBrushSizeScrollDirection = useAppSelector((s) => s.canvas.shouldInvertBrushSizeScrollDirection);
|
||||||
const brushSize = useAppSelector((s) => s.controlLayers.present.brushSize);
|
const brushSize = useAppSelector((s) => s.controlLayers.present.brushSize);
|
||||||
|
const brushSpacingPx = useMemo(
|
||||||
|
() => clamp(brushSize / BRUSH_SPACING_PCT, MIN_BRUSH_SPACING_PX, MAX_BRUSH_SPACING_PX),
|
||||||
|
[brushSize]
|
||||||
|
);
|
||||||
|
|
||||||
const onMouseDown = useCallback(
|
const onMouseDown = useCallback(
|
||||||
(e: KonvaEventObject<MouseEvent | TouchEvent>) => {
|
(e: KonvaEventObject<MouseEvent>) => {
|
||||||
const stage = e.target.getStage();
|
const stage = e.target.getStage();
|
||||||
if (!stage) {
|
if (!stage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const pos = syncCursorPos(stage);
|
const pos = syncCursorPos(stage);
|
||||||
if (!pos) {
|
if (!pos || !selectedLayerId || selectedLayerType !== 'regional_guidance_layer') {
|
||||||
return;
|
|
||||||
}
|
|
||||||
$isMouseDown.set(true);
|
|
||||||
$lastMouseDownPos.set(pos);
|
|
||||||
if (!selectedLayerId) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (tool === 'brush' || tool === 'eraser') {
|
if (tool === 'brush' || tool === 'eraser') {
|
||||||
@ -77,126 +109,105 @@ export const useMouseEvents = () => {
|
|||||||
tool,
|
tool,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
$isDrawing.set(true);
|
||||||
|
$lastMouseDownPos.set(pos);
|
||||||
|
} else if (tool === 'rect') {
|
||||||
|
$lastMouseDownPos.set(snapPosToStage(pos, stage));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, selectedLayerId, tool]
|
[dispatch, selectedLayerId, selectedLayerType, tool]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onMouseUp = useCallback(
|
const onMouseUp = useCallback(
|
||||||
(e: KonvaEventObject<MouseEvent | TouchEvent>) => {
|
|
||||||
const stage = e.target.getStage();
|
|
||||||
if (!stage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$isMouseDown.set(false);
|
|
||||||
const pos = $cursorPosition.get();
|
|
||||||
const lastPos = $lastMouseDownPos.get();
|
|
||||||
const tool = $tool.get();
|
|
||||||
if (pos && lastPos && selectedLayerId && tool === 'rect') {
|
|
||||||
dispatch(
|
|
||||||
rgLayerRectAdded({
|
|
||||||
layerId: selectedLayerId,
|
|
||||||
rect: {
|
|
||||||
x: Math.min(pos.x, lastPos.x),
|
|
||||||
y: Math.min(pos.y, lastPos.y),
|
|
||||||
width: Math.abs(pos.x - lastPos.x),
|
|
||||||
height: Math.abs(pos.y - lastPos.y),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$lastMouseDownPos.set(null);
|
|
||||||
},
|
|
||||||
[dispatch, selectedLayerId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onMouseMove = useCallback(
|
|
||||||
(e: KonvaEventObject<MouseEvent | TouchEvent>) => {
|
|
||||||
const stage = e.target.getStage();
|
|
||||||
if (!stage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pos = syncCursorPos(stage);
|
|
||||||
if (!pos || !selectedLayerId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (getIsFocused(stage) && $isMouseOver.get() && $isMouseDown.get() && (tool === 'brush' || tool === 'eraser')) {
|
|
||||||
if (lastCursorPosRef.current) {
|
|
||||||
// Dispatching redux events impacts perf substantially - using brush spacing keeps dispatches to a reasonable number
|
|
||||||
if (Math.hypot(lastCursorPosRef.current[0] - pos.x, lastCursorPosRef.current[1] - pos.y) < BRUSH_SPACING) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastCursorPosRef.current = [pos.x, pos.y];
|
|
||||||
dispatch(rgLayerPointsAdded({ layerId: selectedLayerId, point: lastCursorPosRef.current }));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[dispatch, selectedLayerId, tool]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onMouseLeave = useCallback(
|
|
||||||
(e: KonvaEventObject<MouseEvent | TouchEvent>) => {
|
|
||||||
const stage = e.target.getStage();
|
|
||||||
if (!stage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pos = syncCursorPos(stage);
|
|
||||||
if (
|
|
||||||
pos &&
|
|
||||||
selectedLayerId &&
|
|
||||||
getIsFocused(stage) &&
|
|
||||||
$isMouseOver.get() &&
|
|
||||||
$isMouseDown.get() &&
|
|
||||||
(tool === 'brush' || tool === 'eraser')
|
|
||||||
) {
|
|
||||||
dispatch(rgLayerPointsAdded({ layerId: selectedLayerId, point: [pos.x, pos.y] }));
|
|
||||||
}
|
|
||||||
$isMouseOver.set(false);
|
|
||||||
$isMouseDown.set(false);
|
|
||||||
$cursorPosition.set(null);
|
|
||||||
},
|
|
||||||
[selectedLayerId, tool, dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onMouseEnter = useCallback(
|
|
||||||
(e: KonvaEventObject<MouseEvent>) => {
|
(e: KonvaEventObject<MouseEvent>) => {
|
||||||
const stage = e.target.getStage();
|
const stage = e.target.getStage();
|
||||||
if (!stage) {
|
if (!stage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$isMouseOver.set(true);
|
const pos = $lastCursorPos.get();
|
||||||
|
if (!pos || !selectedLayerId || selectedLayerType !== 'regional_guidance_layer') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lastPos = $lastMouseDownPos.get();
|
||||||
|
const tool = $tool.get();
|
||||||
|
if (lastPos && selectedLayerId && tool === 'rect') {
|
||||||
|
const snappedPos = snapPosToStage(pos, stage);
|
||||||
|
dispatch(
|
||||||
|
rgLayerRectAdded({
|
||||||
|
layerId: selectedLayerId,
|
||||||
|
rect: {
|
||||||
|
x: Math.min(snappedPos.x, lastPos.x),
|
||||||
|
y: Math.min(snappedPos.y, lastPos.y),
|
||||||
|
width: Math.abs(snappedPos.x - lastPos.x),
|
||||||
|
height: Math.abs(snappedPos.y - lastPos.y),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$isDrawing.set(false);
|
||||||
|
$lastMouseDownPos.set(null);
|
||||||
|
},
|
||||||
|
[dispatch, selectedLayerId, selectedLayerType]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onMouseMove = useCallback(
|
||||||
|
(e: KonvaEventObject<MouseEvent>) => {
|
||||||
|
const stage = e.target.getStage();
|
||||||
|
if (!stage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const pos = syncCursorPos(stage);
|
const pos = syncCursorPos(stage);
|
||||||
if (!pos) {
|
if (!pos || !selectedLayerId || selectedLayerType !== 'regional_guidance_layer') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!getIsFocused(stage)) {
|
if (getIsFocused(stage) && getIsMouseDown(e) && (tool === 'brush' || tool === 'eraser')) {
|
||||||
return;
|
if ($isDrawing.get()) {
|
||||||
}
|
// Continue the last line
|
||||||
if (e.evt.buttons !== 1) {
|
if (lastCursorPosRef.current) {
|
||||||
$isMouseDown.set(false);
|
// Dispatching redux events impacts perf substantially - using brush spacing keeps dispatches to a reasonable number
|
||||||
} else {
|
if (Math.hypot(lastCursorPosRef.current[0] - pos.x, lastCursorPosRef.current[1] - pos.y) < brushSpacingPx) {
|
||||||
$isMouseDown.set(true);
|
return;
|
||||||
if (!selectedLayerId) {
|
}
|
||||||
return;
|
}
|
||||||
}
|
lastCursorPosRef.current = [pos.x, pos.y];
|
||||||
if (tool === 'brush' || tool === 'eraser') {
|
dispatch(rgLayerPointsAdded({ layerId: selectedLayerId, point: lastCursorPosRef.current }));
|
||||||
dispatch(
|
} else {
|
||||||
rgLayerLineAdded({
|
// Start a new line
|
||||||
layerId: selectedLayerId,
|
dispatch(rgLayerLineAdded({ layerId: selectedLayerId, points: [pos.x, pos.y, pos.x, pos.y], tool }));
|
||||||
points: [pos.x, pos.y, pos.x, pos.y],
|
|
||||||
tool,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
$isDrawing.set(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, selectedLayerId, tool]
|
[brushSpacingPx, dispatch, selectedLayerId, selectedLayerType, tool]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onMouseLeave = useCallback(
|
||||||
|
(e: KonvaEventObject<MouseEvent>) => {
|
||||||
|
const stage = e.target.getStage();
|
||||||
|
if (!stage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pos = syncCursorPos(stage);
|
||||||
|
if (!pos || !selectedLayerId || selectedLayerType !== 'regional_guidance_layer') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (getIsFocused(stage) && getIsMouseDown(e) && (tool === 'brush' || tool === 'eraser')) {
|
||||||
|
dispatch(rgLayerPointsAdded({ layerId: selectedLayerId, point: [pos.x, pos.y] }));
|
||||||
|
}
|
||||||
|
$isDrawing.set(false);
|
||||||
|
$lastCursorPos.set(null);
|
||||||
|
$lastMouseDownPos.set(null);
|
||||||
|
},
|
||||||
|
[selectedLayerId, selectedLayerType, tool, dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onMouseWheel = useCallback(
|
const onMouseWheel = useCallback(
|
||||||
(e: KonvaEventObject<WheelEvent>) => {
|
(e: KonvaEventObject<WheelEvent>) => {
|
||||||
e.evt.preventDefault();
|
e.evt.preventDefault();
|
||||||
|
|
||||||
|
if (selectedLayerType !== 'regional_guidance_layer' || (tool !== 'brush' && tool !== 'eraser')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// checking for ctrl key is pressed or not,
|
// checking for ctrl key is pressed or not,
|
||||||
// so that brush size can be controlled using ctrl + scroll up/down
|
// so that brush size can be controlled using ctrl + scroll up/down
|
||||||
|
|
||||||
@ -210,8 +221,8 @@ export const useMouseEvents = () => {
|
|||||||
dispatch(brushSizeChanged(calculateNewBrushSize(brushSize, delta)));
|
dispatch(brushSizeChanged(calculateNewBrushSize(brushSize, delta)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[shouldInvertBrushSizeScrollDirection, brushSize, dispatch]
|
[selectedLayerType, tool, shouldInvertBrushSizeScrollDirection, dispatch, brushSize]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave, onMouseWheel };
|
return { onMouseDown, onMouseUp, onMouseMove, onMouseLeave, onMouseWheel };
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,43 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { isRegionalGuidanceLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
import {
|
||||||
|
isControlAdapterLayer,
|
||||||
|
isInitialImageLayer,
|
||||||
|
isIPAdapterLayer,
|
||||||
|
isRegionalGuidanceLayer,
|
||||||
|
selectControlLayersSlice,
|
||||||
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selectValidLayerCount = createSelector(selectControlLayersSlice, (controlLayers) => {
|
const selectValidLayerCount = createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||||
const validLayers = controlLayers.present.layers
|
let count = 0;
|
||||||
.filter(isRegionalGuidanceLayer)
|
controlLayers.present.layers.forEach((l) => {
|
||||||
.filter((l) => l.isEnabled)
|
if (isRegionalGuidanceLayer(l)) {
|
||||||
.filter((l) => {
|
|
||||||
const hasTextPrompt = Boolean(l.positivePrompt || l.negativePrompt);
|
const hasTextPrompt = Boolean(l.positivePrompt || l.negativePrompt);
|
||||||
const hasAtLeastOneImagePrompt = l.ipAdapters.length > 0;
|
const hasAtLeastOneImagePrompt = l.ipAdapters.filter((ipa) => Boolean(ipa.image)).length > 0;
|
||||||
return hasTextPrompt || hasAtLeastOneImagePrompt;
|
if (hasTextPrompt || hasAtLeastOneImagePrompt) {
|
||||||
});
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isControlAdapterLayer(l)) {
|
||||||
|
if (l.controlAdapter.image || l.controlAdapter.processedImage) {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isIPAdapterLayer(l)) {
|
||||||
|
if (l.ipAdapter.image) {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isInitialImageLayer(l)) {
|
||||||
|
if (l.image) {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return validLayers.length;
|
return count;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useControlLayersTitle = () => {
|
export const useControlLayersTitle = () => {
|
||||||
|
@ -3,17 +3,18 @@ import { createSlice, isAnyOf } from '@reduxjs/toolkit';
|
|||||||
import type { PersistConfig, RootState } from 'app/store/store';
|
import type { PersistConfig, RootState } from 'app/store/store';
|
||||||
import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils';
|
import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
|
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
|
||||||
import type {
|
import type {
|
||||||
CLIPVisionModel,
|
CLIPVisionModelV2,
|
||||||
ControlMode,
|
ControlModeV2,
|
||||||
ControlNetConfig,
|
ControlNetConfigV2,
|
||||||
IPAdapterConfig,
|
IPAdapterConfigV2,
|
||||||
IPMethod,
|
IPMethodV2,
|
||||||
ProcessorConfig,
|
ProcessorConfig,
|
||||||
T2IAdapterConfig,
|
T2IAdapterConfigV2,
|
||||||
} from 'features/controlLayers/util/controlAdapters';
|
} from 'features/controlLayers/util/controlAdapters';
|
||||||
import {
|
import {
|
||||||
buildControlAdapterProcessor,
|
buildControlAdapterProcessorV2,
|
||||||
controlNetToT2IAdapter,
|
controlNetToT2IAdapter,
|
||||||
imageDTOToImageWithDims,
|
imageDTOToImageWithDims,
|
||||||
t2iAdapterToControlNet,
|
t2iAdapterToControlNet,
|
||||||
@ -38,6 +39,7 @@ import type {
|
|||||||
ControlAdapterLayer,
|
ControlAdapterLayer,
|
||||||
ControlLayersState,
|
ControlLayersState,
|
||||||
DrawingTool,
|
DrawingTool,
|
||||||
|
InitialImageLayer,
|
||||||
IPAdapterLayer,
|
IPAdapterLayer,
|
||||||
Layer,
|
Layer,
|
||||||
RegionalGuidanceLayer,
|
RegionalGuidanceLayer,
|
||||||
@ -70,18 +72,13 @@ export const isRegionalGuidanceLayer = (layer?: Layer): layer is RegionalGuidanc
|
|||||||
export const isControlAdapterLayer = (layer?: Layer): layer is ControlAdapterLayer =>
|
export const isControlAdapterLayer = (layer?: Layer): layer is ControlAdapterLayer =>
|
||||||
layer?.type === 'control_adapter_layer';
|
layer?.type === 'control_adapter_layer';
|
||||||
export const isIPAdapterLayer = (layer?: Layer): layer is IPAdapterLayer => layer?.type === 'ip_adapter_layer';
|
export const isIPAdapterLayer = (layer?: Layer): layer is IPAdapterLayer => layer?.type === 'ip_adapter_layer';
|
||||||
export const isRenderableLayer = (layer?: Layer): layer is RegionalGuidanceLayer | ControlAdapterLayer =>
|
export const isInitialImageLayer = (layer?: Layer): layer is InitialImageLayer => layer?.type === 'initial_image_layer';
|
||||||
layer?.type === 'regional_guidance_layer' || layer?.type === 'control_adapter_layer';
|
export const isRenderableLayer = (
|
||||||
const resetLayer = (layer: Layer) => {
|
layer?: Layer
|
||||||
if (layer.type === 'regional_guidance_layer') {
|
): layer is RegionalGuidanceLayer | ControlAdapterLayer | InitialImageLayer =>
|
||||||
layer.maskObjects = [];
|
layer?.type === 'regional_guidance_layer' ||
|
||||||
layer.bbox = null;
|
layer?.type === 'control_adapter_layer' ||
|
||||||
layer.isEnabled = true;
|
layer?.type === 'initial_image_layer';
|
||||||
layer.needsPixelBbox = false;
|
|
||||||
layer.bboxNeedsUpdate = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const selectCALayerOrThrow = (state: ControlLayersState, layerId: string): ControlAdapterLayer => {
|
export const selectCALayerOrThrow = (state: ControlLayersState, layerId: string): ControlAdapterLayer => {
|
||||||
const layer = state.layers.find((l) => l.id === layerId);
|
const layer = state.layers.find((l) => l.id === layerId);
|
||||||
@ -93,6 +90,11 @@ export const selectIPALayerOrThrow = (state: ControlLayersState, layerId: string
|
|||||||
assert(isIPAdapterLayer(layer));
|
assert(isIPAdapterLayer(layer));
|
||||||
return layer;
|
return layer;
|
||||||
};
|
};
|
||||||
|
export const selectIILayerOrThrow = (state: ControlLayersState, layerId: string): InitialImageLayer => {
|
||||||
|
const layer = state.layers.find((l) => l.id === layerId);
|
||||||
|
assert(isInitialImageLayer(layer));
|
||||||
|
return layer;
|
||||||
|
};
|
||||||
const selectCAOrIPALayerOrThrow = (
|
const selectCAOrIPALayerOrThrow = (
|
||||||
state: ControlLayersState,
|
state: ControlLayersState,
|
||||||
layerId: string
|
layerId: string
|
||||||
@ -110,7 +112,7 @@ export const selectRGLayerIPAdapterOrThrow = (
|
|||||||
state: ControlLayersState,
|
state: ControlLayersState,
|
||||||
layerId: string,
|
layerId: string,
|
||||||
ipAdapterId: string
|
ipAdapterId: string
|
||||||
): IPAdapterConfig => {
|
): IPAdapterConfigV2 => {
|
||||||
const layer = state.layers.find((l) => l.id === layerId);
|
const layer = state.layers.find((l) => l.id === layerId);
|
||||||
assert(isRegionalGuidanceLayer(layer));
|
assert(isRegionalGuidanceLayer(layer));
|
||||||
const ipAdapter = layer.ipAdapters.find((ipAdapter) => ipAdapter.id === ipAdapterId);
|
const ipAdapter = layer.ipAdapters.find((ipAdapter) => ipAdapter.id === ipAdapterId);
|
||||||
@ -151,6 +153,9 @@ export const controlLayersSlice = createSlice({
|
|||||||
layer.x = x;
|
layer.x = x;
|
||||||
layer.y = y;
|
layer.y = y;
|
||||||
}
|
}
|
||||||
|
if (isRegionalGuidanceLayer(layer)) {
|
||||||
|
layer.uploadedMaskImage = null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
layerBboxChanged: (state, action: PayloadAction<{ layerId: string; bbox: IRect | null }>) => {
|
layerBboxChanged: (state, action: PayloadAction<{ layerId: string; bbox: IRect | null }>) => {
|
||||||
const { layerId, bbox } = action.payload;
|
const { layerId, bbox } = action.payload;
|
||||||
@ -161,14 +166,21 @@ export const controlLayersSlice = createSlice({
|
|||||||
if (bbox === null && layer.type === 'regional_guidance_layer') {
|
if (bbox === null && layer.type === 'regional_guidance_layer') {
|
||||||
// The layer was fully erased, empty its objects to prevent accumulation of invisible objects
|
// The layer was fully erased, empty its objects to prevent accumulation of invisible objects
|
||||||
layer.maskObjects = [];
|
layer.maskObjects = [];
|
||||||
|
layer.uploadedMaskImage = null;
|
||||||
layer.needsPixelBbox = false;
|
layer.needsPixelBbox = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
layerReset: (state, action: PayloadAction<string>) => {
|
layerReset: (state, action: PayloadAction<string>) => {
|
||||||
const layer = state.layers.find((l) => l.id === action.payload);
|
const layer = state.layers.find((l) => l.id === action.payload);
|
||||||
if (layer) {
|
// TODO(psyche): Should other layer types also have reset functionality?
|
||||||
resetLayer(layer);
|
if (isRegionalGuidanceLayer(layer)) {
|
||||||
|
layer.maskObjects = [];
|
||||||
|
layer.bbox = null;
|
||||||
|
layer.isEnabled = true;
|
||||||
|
layer.needsPixelBbox = false;
|
||||||
|
layer.bboxNeedsUpdate = false;
|
||||||
|
layer.uploadedMaskImage = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
layerDeleted: (state, action: PayloadAction<string>) => {
|
layerDeleted: (state, action: PayloadAction<string>) => {
|
||||||
@ -201,12 +213,6 @@ export const controlLayersSlice = createSlice({
|
|||||||
moveToFront(renderableLayers, cb);
|
moveToFront(renderableLayers, cb);
|
||||||
state.layers = [...ipAdapterLayers, ...renderableLayers];
|
state.layers = [...ipAdapterLayers, ...renderableLayers];
|
||||||
},
|
},
|
||||||
selectedLayerReset: (state) => {
|
|
||||||
const layer = state.layers.find((l) => l.id === state.selectedLayerId);
|
|
||||||
if (layer) {
|
|
||||||
resetLayer(layer);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectedLayerDeleted: (state) => {
|
selectedLayerDeleted: (state) => {
|
||||||
state.layers = state.layers.filter((l) => l.id !== state.selectedLayerId);
|
state.layers = state.layers.filter((l) => l.id !== state.selectedLayerId);
|
||||||
state.selectedLayerId = state.layers[0]?.id ?? null;
|
state.selectedLayerId = state.layers[0]?.id ?? null;
|
||||||
@ -221,7 +227,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
caLayerAdded: {
|
caLayerAdded: {
|
||||||
reducer: (
|
reducer: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ layerId: string; controlAdapter: ControlNetConfig | T2IAdapterConfig }>
|
action: PayloadAction<{ layerId: string; controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2 }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, controlAdapter } = action.payload;
|
const { layerId, controlAdapter } = action.payload;
|
||||||
const layer: ControlAdapterLayer = {
|
const layer: ControlAdapterLayer = {
|
||||||
@ -245,7 +251,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
prepare: (controlAdapter: ControlNetConfig | T2IAdapterConfig) => ({
|
prepare: (controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2) => ({
|
||||||
payload: { layerId: uuidv4(), controlAdapter },
|
payload: { layerId: uuidv4(), controlAdapter },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -297,7 +303,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
layer.controlAdapter = controlNetToT2IAdapter(layer.controlAdapter);
|
layer.controlAdapter = controlNetToT2IAdapter(layer.controlAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
const candidateProcessorConfig = buildControlAdapterProcessor(modelConfig);
|
const candidateProcessorConfig = buildControlAdapterProcessorV2(modelConfig);
|
||||||
if (candidateProcessorConfig?.type !== layer.controlAdapter.processorConfig?.type) {
|
if (candidateProcessorConfig?.type !== layer.controlAdapter.processorConfig?.type) {
|
||||||
// The processor has changed. For example, the previous model was a Canny model and the new model is a Depth
|
// The processor has changed. For example, the previous model was a Canny model and the new model is a Depth
|
||||||
// model. We need to use the new processor.
|
// model. We need to use the new processor.
|
||||||
@ -305,7 +311,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
layer.controlAdapter.processorConfig = candidateProcessorConfig;
|
layer.controlAdapter.processorConfig = candidateProcessorConfig;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
caLayerControlModeChanged: (state, action: PayloadAction<{ layerId: string; controlMode: ControlMode }>) => {
|
caLayerControlModeChanged: (state, action: PayloadAction<{ layerId: string; controlMode: ControlModeV2 }>) => {
|
||||||
const { layerId, controlMode } = action.payload;
|
const { layerId, controlMode } = action.payload;
|
||||||
const layer = selectCALayerOrThrow(state, layerId);
|
const layer = selectCALayerOrThrow(state, layerId);
|
||||||
assert(layer.controlAdapter.type === 'controlnet');
|
assert(layer.controlAdapter.type === 'controlnet');
|
||||||
@ -340,11 +346,17 @@ export const controlLayersSlice = createSlice({
|
|||||||
const layer = selectCALayerOrThrow(state, layerId);
|
const layer = selectCALayerOrThrow(state, layerId);
|
||||||
layer.controlAdapter.isProcessingImage = isProcessingImage;
|
layer.controlAdapter.isProcessingImage = isProcessingImage;
|
||||||
},
|
},
|
||||||
|
caLayerControlNetsDeleted: (state) => {
|
||||||
|
state.layers = state.layers.filter((l) => !isControlAdapterLayer(l) || l.controlAdapter.type !== 'controlnet');
|
||||||
|
},
|
||||||
|
caLayerT2IAdaptersDeleted: (state) => {
|
||||||
|
state.layers = state.layers.filter((l) => !isControlAdapterLayer(l) || l.controlAdapter.type !== 't2i_adapter');
|
||||||
|
},
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region IP Adapter Layers
|
//#region IP Adapter Layers
|
||||||
ipaLayerAdded: {
|
ipaLayerAdded: {
|
||||||
reducer: (state, action: PayloadAction<{ layerId: string; ipAdapter: IPAdapterConfig }>) => {
|
reducer: (state, action: PayloadAction<{ layerId: string; ipAdapter: IPAdapterConfigV2 }>) => {
|
||||||
const { layerId, ipAdapter } = action.payload;
|
const { layerId, ipAdapter } = action.payload;
|
||||||
const layer: IPAdapterLayer = {
|
const layer: IPAdapterLayer = {
|
||||||
id: getIPALayerId(layerId),
|
id: getIPALayerId(layerId),
|
||||||
@ -354,14 +366,14 @@ export const controlLayersSlice = createSlice({
|
|||||||
};
|
};
|
||||||
state.layers.push(layer);
|
state.layers.push(layer);
|
||||||
},
|
},
|
||||||
prepare: (ipAdapter: IPAdapterConfig) => ({ payload: { layerId: uuidv4(), ipAdapter } }),
|
prepare: (ipAdapter: IPAdapterConfigV2) => ({ payload: { layerId: uuidv4(), ipAdapter } }),
|
||||||
},
|
},
|
||||||
ipaLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
ipaLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||||
const { layerId, imageDTO } = action.payload;
|
const { layerId, imageDTO } = action.payload;
|
||||||
const layer = selectIPALayerOrThrow(state, layerId);
|
const layer = selectIPALayerOrThrow(state, layerId);
|
||||||
layer.ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
layer.ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
},
|
},
|
||||||
ipaLayerMethodChanged: (state, action: PayloadAction<{ layerId: string; method: IPMethod }>) => {
|
ipaLayerMethodChanged: (state, action: PayloadAction<{ layerId: string; method: IPMethodV2 }>) => {
|
||||||
const { layerId, method } = action.payload;
|
const { layerId, method } = action.payload;
|
||||||
const layer = selectIPALayerOrThrow(state, layerId);
|
const layer = selectIPALayerOrThrow(state, layerId);
|
||||||
layer.ipAdapter.method = method;
|
layer.ipAdapter.method = method;
|
||||||
@ -383,12 +395,15 @@ export const controlLayersSlice = createSlice({
|
|||||||
},
|
},
|
||||||
ipaLayerCLIPVisionModelChanged: (
|
ipaLayerCLIPVisionModelChanged: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ layerId: string; clipVisionModel: CLIPVisionModel }>
|
action: PayloadAction<{ layerId: string; clipVisionModel: CLIPVisionModelV2 }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, clipVisionModel } = action.payload;
|
const { layerId, clipVisionModel } = action.payload;
|
||||||
const layer = selectIPALayerOrThrow(state, layerId);
|
const layer = selectIPALayerOrThrow(state, layerId);
|
||||||
layer.ipAdapter.clipVisionModel = clipVisionModel;
|
layer.ipAdapter.clipVisionModel = clipVisionModel;
|
||||||
},
|
},
|
||||||
|
ipaLayersDeleted: (state) => {
|
||||||
|
state.layers = state.layers.filter((l) => !isIPAdapterLayer(l));
|
||||||
|
},
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region CA or IPA Layers
|
//#region CA or IPA Layers
|
||||||
@ -435,6 +450,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
negativePrompt: null,
|
negativePrompt: null,
|
||||||
ipAdapters: [],
|
ipAdapters: [],
|
||||||
isSelected: true,
|
isSelected: true,
|
||||||
|
uploadedMaskImage: null,
|
||||||
};
|
};
|
||||||
state.layers.push(layer);
|
state.layers.push(layer);
|
||||||
state.selectedLayerId = layer.id;
|
state.selectedLayerId = layer.id;
|
||||||
@ -484,6 +500,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
strokeWidth: state.brushSize,
|
strokeWidth: state.brushSize,
|
||||||
});
|
});
|
||||||
layer.bboxNeedsUpdate = true;
|
layer.bboxNeedsUpdate = true;
|
||||||
|
layer.uploadedMaskImage = null;
|
||||||
if (!layer.needsPixelBbox && tool === 'eraser') {
|
if (!layer.needsPixelBbox && tool === 'eraser') {
|
||||||
layer.needsPixelBbox = true;
|
layer.needsPixelBbox = true;
|
||||||
}
|
}
|
||||||
@ -503,6 +520,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
// TODO: Handle this in the event listener
|
// TODO: Handle this in the event listener
|
||||||
lastLine.points.push(point[0] - layer.x, point[1] - layer.y);
|
lastLine.points.push(point[0] - layer.x, point[1] - layer.y);
|
||||||
layer.bboxNeedsUpdate = true;
|
layer.bboxNeedsUpdate = true;
|
||||||
|
layer.uploadedMaskImage = null;
|
||||||
},
|
},
|
||||||
rgLayerRectAdded: {
|
rgLayerRectAdded: {
|
||||||
reducer: (state, action: PayloadAction<{ layerId: string; rect: IRect; rectUuid: string }>) => {
|
reducer: (state, action: PayloadAction<{ layerId: string; rect: IRect; rectUuid: string }>) => {
|
||||||
@ -522,9 +540,15 @@ export const controlLayersSlice = createSlice({
|
|||||||
height: rect.height,
|
height: rect.height,
|
||||||
});
|
});
|
||||||
layer.bboxNeedsUpdate = true;
|
layer.bboxNeedsUpdate = true;
|
||||||
|
layer.uploadedMaskImage = null;
|
||||||
},
|
},
|
||||||
prepare: (payload: { layerId: string; rect: IRect }) => ({ payload: { ...payload, rectUuid: uuidv4() } }),
|
prepare: (payload: { layerId: string; rect: IRect }) => ({ payload: { ...payload, rectUuid: uuidv4() } }),
|
||||||
},
|
},
|
||||||
|
rgLayerMaskImageUploaded: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO }>) => {
|
||||||
|
const { layerId, imageDTO } = action.payload;
|
||||||
|
const layer = selectRGLayerOrThrow(state, layerId);
|
||||||
|
layer.uploadedMaskImage = imageDTOToImageWithDims(imageDTO);
|
||||||
|
},
|
||||||
rgLayerAutoNegativeChanged: (
|
rgLayerAutoNegativeChanged: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ layerId: string; autoNegative: ParameterAutoNegative }>
|
action: PayloadAction<{ layerId: string; autoNegative: ParameterAutoNegative }>
|
||||||
@ -533,7 +557,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
const layer = selectRGLayerOrThrow(state, layerId);
|
const layer = selectRGLayerOrThrow(state, layerId);
|
||||||
layer.autoNegative = autoNegative;
|
layer.autoNegative = autoNegative;
|
||||||
},
|
},
|
||||||
rgLayerIPAdapterAdded: (state, action: PayloadAction<{ layerId: string; ipAdapter: IPAdapterConfig }>) => {
|
rgLayerIPAdapterAdded: (state, action: PayloadAction<{ layerId: string; ipAdapter: IPAdapterConfigV2 }>) => {
|
||||||
const { layerId, ipAdapter } = action.payload;
|
const { layerId, ipAdapter } = action.payload;
|
||||||
const layer = selectRGLayerOrThrow(state, layerId);
|
const layer = selectRGLayerOrThrow(state, layerId);
|
||||||
layer.ipAdapters.push(ipAdapter);
|
layer.ipAdapters.push(ipAdapter);
|
||||||
@ -569,7 +593,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
},
|
},
|
||||||
rgLayerIPAdapterMethodChanged: (
|
rgLayerIPAdapterMethodChanged: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ layerId: string; ipAdapterId: string; method: IPMethod }>
|
action: PayloadAction<{ layerId: string; ipAdapterId: string; method: IPMethodV2 }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, ipAdapterId, method } = action.payload;
|
const { layerId, ipAdapterId, method } = action.payload;
|
||||||
const ipAdapter = selectRGLayerIPAdapterOrThrow(state, layerId, ipAdapterId);
|
const ipAdapter = selectRGLayerIPAdapterOrThrow(state, layerId, ipAdapterId);
|
||||||
@ -593,7 +617,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
},
|
},
|
||||||
rgLayerIPAdapterCLIPVisionModelChanged: (
|
rgLayerIPAdapterCLIPVisionModelChanged: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ layerId: string; ipAdapterId: string; clipVisionModel: CLIPVisionModel }>
|
action: PayloadAction<{ layerId: string; ipAdapterId: string; clipVisionModel: CLIPVisionModelV2 }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, ipAdapterId, clipVisionModel } = action.payload;
|
const { layerId, ipAdapterId, clipVisionModel } = action.payload;
|
||||||
const ipAdapter = selectRGLayerIPAdapterOrThrow(state, layerId, ipAdapterId);
|
const ipAdapter = selectRGLayerIPAdapterOrThrow(state, layerId, ipAdapterId);
|
||||||
@ -601,6 +625,49 @@ export const controlLayersSlice = createSlice({
|
|||||||
},
|
},
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
//#region Initial Image Layer
|
||||||
|
iiLayerAdded: {
|
||||||
|
reducer: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||||
|
const { layerId, imageDTO } = action.payload;
|
||||||
|
// Highlander! There can be only one!
|
||||||
|
state.layers = state.layers.filter((l) => (isInitialImageLayer(l) ? false : true));
|
||||||
|
const layer: InitialImageLayer = {
|
||||||
|
id: layerId,
|
||||||
|
type: 'initial_image_layer',
|
||||||
|
opacity: 1,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
bbox: null,
|
||||||
|
bboxNeedsUpdate: false,
|
||||||
|
isEnabled: true,
|
||||||
|
image: imageDTO ? imageDTOToImageWithDims(imageDTO) : null,
|
||||||
|
isSelected: true,
|
||||||
|
};
|
||||||
|
state.layers.push(layer);
|
||||||
|
state.selectedLayerId = layer.id;
|
||||||
|
for (const layer of state.layers.filter(isRenderableLayer)) {
|
||||||
|
if (layer.id !== layerId) {
|
||||||
|
layer.isSelected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prepare: (imageDTO: ImageDTO | null) => ({ payload: { layerId: 'initial_image_layer', imageDTO } }),
|
||||||
|
},
|
||||||
|
iiLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||||
|
const { layerId, imageDTO } = action.payload;
|
||||||
|
const layer = selectIILayerOrThrow(state, layerId);
|
||||||
|
layer.bbox = null;
|
||||||
|
layer.bboxNeedsUpdate = true;
|
||||||
|
layer.isEnabled = true;
|
||||||
|
layer.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
|
},
|
||||||
|
iiLayerOpacityChanged: (state, action: PayloadAction<{ layerId: string; opacity: number }>) => {
|
||||||
|
const { layerId, opacity } = action.payload;
|
||||||
|
const layer = selectIILayerOrThrow(state, layerId);
|
||||||
|
layer.opacity = opacity;
|
||||||
|
},
|
||||||
|
//#endregion
|
||||||
|
|
||||||
//#region Globals
|
//#region Globals
|
||||||
positivePromptChanged: (state, action: PayloadAction<string>) => {
|
positivePromptChanged: (state, action: PayloadAction<string>) => {
|
||||||
state.positivePrompt = action.payload;
|
state.positivePrompt = action.payload;
|
||||||
@ -617,20 +684,20 @@ export const controlLayersSlice = createSlice({
|
|||||||
shouldConcatPromptsChanged: (state, action: PayloadAction<boolean>) => {
|
shouldConcatPromptsChanged: (state, action: PayloadAction<boolean>) => {
|
||||||
state.shouldConcatPrompts = action.payload;
|
state.shouldConcatPrompts = action.payload;
|
||||||
},
|
},
|
||||||
widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean }>) => {
|
widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean; clamp?: boolean }>) => {
|
||||||
const { width, updateAspectRatio } = action.payload;
|
const { width, updateAspectRatio, clamp } = action.payload;
|
||||||
state.size.width = width;
|
state.size.width = clamp ? Math.max(roundDownToMultiple(width, 8), 64) : width;
|
||||||
if (updateAspectRatio) {
|
if (updateAspectRatio) {
|
||||||
state.size.aspectRatio.value = width / state.size.height;
|
state.size.aspectRatio.value = state.size.width / state.size.height;
|
||||||
state.size.aspectRatio.id = 'Free';
|
state.size.aspectRatio.id = 'Free';
|
||||||
state.size.aspectRatio.isLocked = false;
|
state.size.aspectRatio.isLocked = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
heightChanged: (state, action: PayloadAction<{ height: number; updateAspectRatio?: boolean }>) => {
|
heightChanged: (state, action: PayloadAction<{ height: number; updateAspectRatio?: boolean; clamp?: boolean }>) => {
|
||||||
const { height, updateAspectRatio } = action.payload;
|
const { height, updateAspectRatio, clamp } = action.payload;
|
||||||
state.size.height = height;
|
state.size.height = clamp ? Math.max(roundDownToMultiple(height, 8), 64) : height;
|
||||||
if (updateAspectRatio) {
|
if (updateAspectRatio) {
|
||||||
state.size.aspectRatio.value = state.size.width / height;
|
state.size.aspectRatio.value = state.size.width / state.size.height;
|
||||||
state.size.aspectRatio.id = 'Free';
|
state.size.aspectRatio.id = 'Free';
|
||||||
state.size.aspectRatio.isLocked = false;
|
state.size.aspectRatio.isLocked = false;
|
||||||
}
|
}
|
||||||
@ -728,7 +795,6 @@ export const {
|
|||||||
layerMovedToFront,
|
layerMovedToFront,
|
||||||
layerMovedBackward,
|
layerMovedBackward,
|
||||||
layerMovedToBack,
|
layerMovedToBack,
|
||||||
selectedLayerReset,
|
|
||||||
selectedLayerDeleted,
|
selectedLayerDeleted,
|
||||||
allLayersDeleted,
|
allLayersDeleted,
|
||||||
// CA Layers
|
// CA Layers
|
||||||
@ -741,12 +807,15 @@ export const {
|
|||||||
caLayerIsFilterEnabledChanged,
|
caLayerIsFilterEnabledChanged,
|
||||||
caLayerOpacityChanged,
|
caLayerOpacityChanged,
|
||||||
caLayerIsProcessingImageChanged,
|
caLayerIsProcessingImageChanged,
|
||||||
|
caLayerControlNetsDeleted,
|
||||||
|
caLayerT2IAdaptersDeleted,
|
||||||
// IPA Layers
|
// IPA Layers
|
||||||
ipaLayerAdded,
|
ipaLayerAdded,
|
||||||
ipaLayerImageChanged,
|
ipaLayerImageChanged,
|
||||||
ipaLayerMethodChanged,
|
ipaLayerMethodChanged,
|
||||||
ipaLayerModelChanged,
|
ipaLayerModelChanged,
|
||||||
ipaLayerCLIPVisionModelChanged,
|
ipaLayerCLIPVisionModelChanged,
|
||||||
|
ipaLayersDeleted,
|
||||||
// CA or IPA Layers
|
// CA or IPA Layers
|
||||||
caOrIPALayerWeightChanged,
|
caOrIPALayerWeightChanged,
|
||||||
caOrIPALayerBeginEndStepPctChanged,
|
caOrIPALayerBeginEndStepPctChanged,
|
||||||
@ -758,6 +827,7 @@ export const {
|
|||||||
rgLayerLineAdded,
|
rgLayerLineAdded,
|
||||||
rgLayerPointsAdded,
|
rgLayerPointsAdded,
|
||||||
rgLayerRectAdded,
|
rgLayerRectAdded,
|
||||||
|
rgLayerMaskImageUploaded,
|
||||||
rgLayerAutoNegativeChanged,
|
rgLayerAutoNegativeChanged,
|
||||||
rgLayerIPAdapterAdded,
|
rgLayerIPAdapterAdded,
|
||||||
rgLayerIPAdapterDeleted,
|
rgLayerIPAdapterDeleted,
|
||||||
@ -767,6 +837,10 @@ export const {
|
|||||||
rgLayerIPAdapterMethodChanged,
|
rgLayerIPAdapterMethodChanged,
|
||||||
rgLayerIPAdapterModelChanged,
|
rgLayerIPAdapterModelChanged,
|
||||||
rgLayerIPAdapterCLIPVisionModelChanged,
|
rgLayerIPAdapterCLIPVisionModelChanged,
|
||||||
|
// II Layer
|
||||||
|
iiLayerAdded,
|
||||||
|
iiLayerImageChanged,
|
||||||
|
iiLayerOpacityChanged,
|
||||||
// Globals
|
// Globals
|
||||||
positivePromptChanged,
|
positivePromptChanged,
|
||||||
negativePromptChanged,
|
negativePromptChanged,
|
||||||
@ -789,11 +863,10 @@ const migrateControlLayersState = (state: any): any => {
|
|||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const $isMouseDown = atom(false);
|
export const $isDrawing = atom(false);
|
||||||
export const $isMouseOver = atom(false);
|
|
||||||
export const $lastMouseDownPos = atom<Vector2d | null>(null);
|
export const $lastMouseDownPos = atom<Vector2d | null>(null);
|
||||||
export const $tool = atom<Tool>('brush');
|
export const $tool = atom<Tool>('brush');
|
||||||
export const $cursorPosition = atom<Vector2d | null>(null);
|
export const $lastCursorPos = atom<Vector2d | null>(null);
|
||||||
|
|
||||||
// IDs for singleton Konva layers and objects
|
// IDs for singleton Konva layers and objects
|
||||||
export const TOOL_PREVIEW_LAYER_ID = 'tool_preview_layer';
|
export const TOOL_PREVIEW_LAYER_ID = 'tool_preview_layer';
|
||||||
@ -813,7 +886,10 @@ export const RG_LAYER_NAME = 'regional_guidance_layer';
|
|||||||
export const RG_LAYER_LINE_NAME = 'regional_guidance_layer.line';
|
export const RG_LAYER_LINE_NAME = 'regional_guidance_layer.line';
|
||||||
export const RG_LAYER_OBJECT_GROUP_NAME = 'regional_guidance_layer.object_group';
|
export const RG_LAYER_OBJECT_GROUP_NAME = 'regional_guidance_layer.object_group';
|
||||||
export const RG_LAYER_RECT_NAME = 'regional_guidance_layer.rect';
|
export const RG_LAYER_RECT_NAME = 'regional_guidance_layer.rect';
|
||||||
|
export const INITIAL_IMAGE_LAYER_NAME = 'initial_image_layer';
|
||||||
|
export const INITIAL_IMAGE_LAYER_IMAGE_NAME = 'initial_image_layer.image';
|
||||||
export const LAYER_BBOX_NAME = 'layer.bbox';
|
export const LAYER_BBOX_NAME = 'layer.bbox';
|
||||||
|
export const COMPOSITING_RECT_NAME = 'compositing-rect';
|
||||||
|
|
||||||
// Getters for non-singleton layer and object IDs
|
// Getters for non-singleton layer and object IDs
|
||||||
const getRGLayerId = (layerId: string) => `${RG_LAYER_NAME}_${layerId}`;
|
const getRGLayerId = (layerId: string) => `${RG_LAYER_NAME}_${layerId}`;
|
||||||
@ -823,6 +899,7 @@ export const getRGLayerObjectGroupId = (layerId: string, groupId: string) => `${
|
|||||||
export const getLayerBboxId = (layerId: string) => `${layerId}.bbox`;
|
export const getLayerBboxId = (layerId: string) => `${layerId}.bbox`;
|
||||||
const getCALayerId = (layerId: string) => `control_adapter_layer_${layerId}`;
|
const getCALayerId = (layerId: string) => `control_adapter_layer_${layerId}`;
|
||||||
export const getCALayerImageId = (layerId: string, imageName: string) => `${layerId}.image_${imageName}`;
|
export const getCALayerImageId = (layerId: string, imageName: string) => `${layerId}.image_${imageName}`;
|
||||||
|
export const getIILayerImageId = (layerId: string, imageName: string) => `${layerId}.image_${imageName}`;
|
||||||
const getIPALayerId = (layerId: string) => `ip_adapter_layer_${layerId}`;
|
const getIPALayerId = (layerId: string) => `ip_adapter_layer_${layerId}`;
|
||||||
|
|
||||||
export const controlLayersPersistConfig: PersistConfig<ControlLayersState> = {
|
export const controlLayersPersistConfig: PersistConfig<ControlLayersState> = {
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
import type { ControlNetConfig, IPAdapterConfig, T2IAdapterConfig } from 'features/controlLayers/util/controlAdapters';
|
import type {
|
||||||
|
ControlNetConfigV2,
|
||||||
|
ImageWithDims,
|
||||||
|
IPAdapterConfigV2,
|
||||||
|
T2IAdapterConfigV2,
|
||||||
|
} from 'features/controlLayers/util/controlAdapters';
|
||||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||||
import type {
|
import type {
|
||||||
ParameterAutoNegative,
|
ParameterAutoNegative,
|
||||||
@ -50,12 +55,12 @@ export type ControlAdapterLayer = RenderableLayerBase & {
|
|||||||
type: 'control_adapter_layer'; // technically, also t2i adapter layer
|
type: 'control_adapter_layer'; // technically, also t2i adapter layer
|
||||||
opacity: number;
|
opacity: number;
|
||||||
isFilterEnabled: boolean;
|
isFilterEnabled: boolean;
|
||||||
controlAdapter: ControlNetConfig | T2IAdapterConfig;
|
controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IPAdapterLayer = LayerBase & {
|
export type IPAdapterLayer = LayerBase & {
|
||||||
type: 'ip_adapter_layer';
|
type: 'ip_adapter_layer';
|
||||||
ipAdapter: IPAdapterConfig;
|
ipAdapter: IPAdapterConfigV2;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RegionalGuidanceLayer = RenderableLayerBase & {
|
export type RegionalGuidanceLayer = RenderableLayerBase & {
|
||||||
@ -63,13 +68,20 @@ export type RegionalGuidanceLayer = RenderableLayerBase & {
|
|||||||
maskObjects: (VectorMaskLine | VectorMaskRect)[];
|
maskObjects: (VectorMaskLine | VectorMaskRect)[];
|
||||||
positivePrompt: ParameterPositivePrompt | null;
|
positivePrompt: ParameterPositivePrompt | null;
|
||||||
negativePrompt: ParameterNegativePrompt | null; // Up to one text prompt per mask
|
negativePrompt: ParameterNegativePrompt | null; // Up to one text prompt per mask
|
||||||
ipAdapters: IPAdapterConfig[]; // Any number of image prompts
|
ipAdapters: IPAdapterConfigV2[]; // Any number of image prompts
|
||||||
previewColor: RgbColor;
|
previewColor: RgbColor;
|
||||||
autoNegative: ParameterAutoNegative;
|
autoNegative: ParameterAutoNegative;
|
||||||
needsPixelBbox: boolean; // Needs the slower pixel-based bbox calculation - set to true when an there is an eraser object
|
needsPixelBbox: boolean; // Needs the slower pixel-based bbox calculation - set to true when an there is an eraser object
|
||||||
|
uploadedMaskImage: ImageWithDims | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Layer = RegionalGuidanceLayer | ControlAdapterLayer | IPAdapterLayer;
|
export type InitialImageLayer = RenderableLayerBase & {
|
||||||
|
type: 'initial_image_layer';
|
||||||
|
opacity: number;
|
||||||
|
image: ImageWithDims | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Layer = RegionalGuidanceLayer | ControlAdapterLayer | IPAdapterLayer | InitialImageLayer;
|
||||||
|
|
||||||
export type ControlLayersState = {
|
export type ControlLayersState = {
|
||||||
_version: 1;
|
_version: 1;
|
||||||
|
@ -123,7 +123,7 @@ export const getLayerBboxPixels = (layer: KonvaLayerType, preview: boolean = fal
|
|||||||
return correctedLayerBbox;
|
return correctedLayerBbox;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getLayerBboxFast = (layer: KonvaLayerType): IRect | null => {
|
export const getLayerBboxFast = (layer: KonvaLayerType): IRect => {
|
||||||
const bbox = layer.getClientRect(GET_CLIENT_RECT_CONFIG);
|
const bbox = layer.getClientRect(GET_CLIENT_RECT_CONFIG);
|
||||||
return {
|
return {
|
||||||
x: Math.floor(bbox.x),
|
x: Math.floor(bbox.x),
|
||||||
|
@ -4,20 +4,20 @@ import { assert } from 'tsafe';
|
|||||||
import { describe, test } from 'vitest';
|
import { describe, test } from 'vitest';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
CLIPVisionModel,
|
CLIPVisionModelV2,
|
||||||
ControlMode,
|
ControlModeV2,
|
||||||
DepthAnythingModelSize,
|
DepthAnythingModelSize,
|
||||||
IPMethod,
|
IPMethodV2,
|
||||||
ProcessorConfig,
|
ProcessorConfig,
|
||||||
ProcessorType,
|
ProcessorTypeV2,
|
||||||
} from './controlAdapters';
|
} from './controlAdapters';
|
||||||
|
|
||||||
describe('Control Adapter Types', () => {
|
describe('Control Adapter Types', () => {
|
||||||
test('ProcessorType', () => assert<Equals<ProcessorConfig['type'], ProcessorType>>());
|
test('ProcessorType', () => assert<Equals<ProcessorConfig['type'], ProcessorTypeV2>>());
|
||||||
test('IP Adapter Method', () => assert<Equals<NonNullable<S['IPAdapterInvocation']['method']>, IPMethod>>());
|
test('IP Adapter Method', () => assert<Equals<NonNullable<S['IPAdapterInvocation']['method']>, IPMethodV2>>());
|
||||||
test('CLIP Vision Model', () =>
|
test('CLIP Vision Model', () =>
|
||||||
assert<Equals<NonNullable<S['IPAdapterInvocation']['clip_vision_model']>, CLIPVisionModel>>());
|
assert<Equals<NonNullable<S['IPAdapterInvocation']['clip_vision_model']>, CLIPVisionModelV2>>());
|
||||||
test('Control Mode', () => assert<Equals<NonNullable<S['ControlNetInvocation']['control_mode']>, ControlMode>>());
|
test('Control Mode', () => assert<Equals<NonNullable<S['ControlNetInvocation']['control_mode']>, ControlModeV2>>());
|
||||||
test('DepthAnything Model Size', () =>
|
test('DepthAnything Model Size', () =>
|
||||||
assert<Equals<NonNullable<S['DepthAnythingImageProcessorInvocation']['model_size']>, DepthAnythingModelSize>>());
|
assert<Equals<NonNullable<S['DepthAnythingImageProcessorInvocation']['model_size']>, DepthAnythingModelSize>>());
|
||||||
});
|
});
|
||||||
|
@ -94,45 +94,45 @@ type ControlAdapterBase = {
|
|||||||
beginEndStepPct: [number, number];
|
beginEndStepPct: [number, number];
|
||||||
};
|
};
|
||||||
|
|
||||||
const zControlMode = z.enum(['balanced', 'more_prompt', 'more_control', 'unbalanced']);
|
const zControlModeV2 = z.enum(['balanced', 'more_prompt', 'more_control', 'unbalanced']);
|
||||||
export type ControlMode = z.infer<typeof zControlMode>;
|
export type ControlModeV2 = z.infer<typeof zControlModeV2>;
|
||||||
export const isControlMode = (v: unknown): v is ControlMode => zControlMode.safeParse(v).success;
|
export const isControlModeV2 = (v: unknown): v is ControlModeV2 => zControlModeV2.safeParse(v).success;
|
||||||
|
|
||||||
export type ControlNetConfig = ControlAdapterBase & {
|
export type ControlNetConfigV2 = ControlAdapterBase & {
|
||||||
type: 'controlnet';
|
type: 'controlnet';
|
||||||
model: ParameterControlNetModel | null;
|
model: ParameterControlNetModel | null;
|
||||||
controlMode: ControlMode;
|
controlMode: ControlModeV2;
|
||||||
};
|
};
|
||||||
export const isControlNetConfig = (ca: ControlNetConfig | T2IAdapterConfig): ca is ControlNetConfig =>
|
export const isControlNetConfigV2 = (ca: ControlNetConfigV2 | T2IAdapterConfigV2): ca is ControlNetConfigV2 =>
|
||||||
ca.type === 'controlnet';
|
ca.type === 'controlnet';
|
||||||
|
|
||||||
export type T2IAdapterConfig = ControlAdapterBase & {
|
export type T2IAdapterConfigV2 = ControlAdapterBase & {
|
||||||
type: 't2i_adapter';
|
type: 't2i_adapter';
|
||||||
model: ParameterT2IAdapterModel | null;
|
model: ParameterT2IAdapterModel | null;
|
||||||
};
|
};
|
||||||
export const isT2IAdapterConfig = (ca: ControlNetConfig | T2IAdapterConfig): ca is T2IAdapterConfig =>
|
export const isT2IAdapterConfigV2 = (ca: ControlNetConfigV2 | T2IAdapterConfigV2): ca is T2IAdapterConfigV2 =>
|
||||||
ca.type === 't2i_adapter';
|
ca.type === 't2i_adapter';
|
||||||
|
|
||||||
const zCLIPVisionModel = z.enum(['ViT-H', 'ViT-G']);
|
const zCLIPVisionModelV2 = z.enum(['ViT-H', 'ViT-G']);
|
||||||
export type CLIPVisionModel = z.infer<typeof zCLIPVisionModel>;
|
export type CLIPVisionModelV2 = z.infer<typeof zCLIPVisionModelV2>;
|
||||||
export const isCLIPVisionModel = (v: unknown): v is CLIPVisionModel => zCLIPVisionModel.safeParse(v).success;
|
export const isCLIPVisionModelV2 = (v: unknown): v is CLIPVisionModelV2 => zCLIPVisionModelV2.safeParse(v).success;
|
||||||
|
|
||||||
const zIPMethod = z.enum(['full', 'style', 'composition']);
|
const zIPMethodV2 = z.enum(['full', 'style', 'composition']);
|
||||||
export type IPMethod = z.infer<typeof zIPMethod>;
|
export type IPMethodV2 = z.infer<typeof zIPMethodV2>;
|
||||||
export const isIPMethod = (v: unknown): v is IPMethod => zIPMethod.safeParse(v).success;
|
export const isIPMethodV2 = (v: unknown): v is IPMethodV2 => zIPMethodV2.safeParse(v).success;
|
||||||
|
|
||||||
export type IPAdapterConfig = {
|
export type IPAdapterConfigV2 = {
|
||||||
id: string;
|
id: string;
|
||||||
type: 'ip_adapter';
|
type: 'ip_adapter';
|
||||||
weight: number;
|
weight: number;
|
||||||
method: IPMethod;
|
method: IPMethodV2;
|
||||||
image: ImageWithDims | null;
|
image: ImageWithDims | null;
|
||||||
model: ParameterIPAdapterModel | null;
|
model: ParameterIPAdapterModel | null;
|
||||||
clipVisionModel: CLIPVisionModel;
|
clipVisionModel: CLIPVisionModelV2;
|
||||||
beginEndStepPct: [number, number];
|
beginEndStepPct: [number, number];
|
||||||
};
|
};
|
||||||
|
|
||||||
const zProcessorType = z.enum([
|
const zProcessorTypeV2 = z.enum([
|
||||||
'canny_image_processor',
|
'canny_image_processor',
|
||||||
'color_map_image_processor',
|
'color_map_image_processor',
|
||||||
'content_shuffle_image_processor',
|
'content_shuffle_image_processor',
|
||||||
@ -148,10 +148,10 @@ const zProcessorType = z.enum([
|
|||||||
'pidi_image_processor',
|
'pidi_image_processor',
|
||||||
'zoe_depth_image_processor',
|
'zoe_depth_image_processor',
|
||||||
]);
|
]);
|
||||||
export type ProcessorType = z.infer<typeof zProcessorType>;
|
export type ProcessorTypeV2 = z.infer<typeof zProcessorTypeV2>;
|
||||||
export const isProcessorType = (v: unknown): v is ProcessorType => zProcessorType.safeParse(v).success;
|
export const isProcessorTypeV2 = (v: unknown): v is ProcessorTypeV2 => zProcessorTypeV2.safeParse(v).success;
|
||||||
|
|
||||||
type ProcessorData<T extends ProcessorType> = {
|
type ProcessorData<T extends ProcessorTypeV2> = {
|
||||||
type: T;
|
type: T;
|
||||||
labelTKey: string;
|
labelTKey: string;
|
||||||
descriptionTKey: string;
|
descriptionTKey: string;
|
||||||
@ -165,7 +165,7 @@ type ProcessorData<T extends ProcessorType> = {
|
|||||||
const minDim = (image: ImageWithDims): number => Math.min(image.width, image.height);
|
const minDim = (image: ImageWithDims): number => Math.min(image.width, image.height);
|
||||||
|
|
||||||
type CAProcessorsData = {
|
type CAProcessorsData = {
|
||||||
[key in ProcessorType]: ProcessorData<key>;
|
[key in ProcessorTypeV2]: ProcessorData<key>;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* A dict of ControlNet processors, including:
|
* A dict of ControlNet processors, including:
|
||||||
@ -176,7 +176,7 @@ type CAProcessorsData = {
|
|||||||
*
|
*
|
||||||
* TODO: Generate from the OpenAPI schema
|
* TODO: Generate from the OpenAPI schema
|
||||||
*/
|
*/
|
||||||
export const CONTROLNET_PROCESSORS: CAProcessorsData = {
|
export const CA_PROCESSOR_DATA: CAProcessorsData = {
|
||||||
canny_image_processor: {
|
canny_image_processor: {
|
||||||
type: 'canny_image_processor',
|
type: 'canny_image_processor',
|
||||||
labelTKey: 'controlnet.canny',
|
labelTKey: 'controlnet.canny',
|
||||||
@ -405,7 +405,7 @@ export const CONTROLNET_PROCESSORS: CAProcessorsData = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialControlNet: Omit<ControlNetConfig, 'id'> = {
|
export const initialControlNetV2: Omit<ControlNetConfigV2, 'id'> = {
|
||||||
type: 'controlnet',
|
type: 'controlnet',
|
||||||
model: null,
|
model: null,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
@ -414,10 +414,10 @@ const initialControlNet: Omit<ControlNetConfig, 'id'> = {
|
|||||||
image: null,
|
image: null,
|
||||||
processedImage: null,
|
processedImage: null,
|
||||||
isProcessingImage: false,
|
isProcessingImage: false,
|
||||||
processorConfig: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults(),
|
processorConfig: CA_PROCESSOR_DATA.canny_image_processor.buildDefaults(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialT2IAdapter: Omit<T2IAdapterConfig, 'id'> = {
|
export const initialT2IAdapterV2: Omit<T2IAdapterConfigV2, 'id'> = {
|
||||||
type: 't2i_adapter',
|
type: 't2i_adapter',
|
||||||
model: null,
|
model: null,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
@ -425,10 +425,10 @@ const initialT2IAdapter: Omit<T2IAdapterConfig, 'id'> = {
|
|||||||
image: null,
|
image: null,
|
||||||
processedImage: null,
|
processedImage: null,
|
||||||
isProcessingImage: false,
|
isProcessingImage: false,
|
||||||
processorConfig: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults(),
|
processorConfig: CA_PROCESSOR_DATA.canny_image_processor.buildDefaults(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialIPAdapter: Omit<IPAdapterConfig, 'id'> = {
|
export const initialIPAdapterV2: Omit<IPAdapterConfigV2, 'id'> = {
|
||||||
type: 'ip_adapter',
|
type: 'ip_adapter',
|
||||||
image: null,
|
image: null,
|
||||||
model: null,
|
model: null,
|
||||||
@ -438,26 +438,26 @@ const initialIPAdapter: Omit<IPAdapterConfig, 'id'> = {
|
|||||||
weight: 1,
|
weight: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildControlNet = (id: string, overrides?: Partial<ControlNetConfig>): ControlNetConfig => {
|
export const buildControlNet = (id: string, overrides?: Partial<ControlNetConfigV2>): ControlNetConfigV2 => {
|
||||||
return merge(deepClone(initialControlNet), { id, ...overrides });
|
return merge(deepClone(initialControlNetV2), { id, ...overrides });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildT2IAdapter = (id: string, overrides?: Partial<T2IAdapterConfig>): T2IAdapterConfig => {
|
export const buildT2IAdapter = (id: string, overrides?: Partial<T2IAdapterConfigV2>): T2IAdapterConfigV2 => {
|
||||||
return merge(deepClone(initialT2IAdapter), { id, ...overrides });
|
return merge(deepClone(initialT2IAdapterV2), { id, ...overrides });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildIPAdapter = (id: string, overrides?: Partial<IPAdapterConfig>): IPAdapterConfig => {
|
export const buildIPAdapter = (id: string, overrides?: Partial<IPAdapterConfigV2>): IPAdapterConfigV2 => {
|
||||||
return merge(deepClone(initialIPAdapter), { id, ...overrides });
|
return merge(deepClone(initialIPAdapterV2), { id, ...overrides });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildControlAdapterProcessor = (
|
export const buildControlAdapterProcessorV2 = (
|
||||||
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig
|
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig
|
||||||
): ProcessorConfig | null => {
|
): ProcessorConfig | null => {
|
||||||
const defaultPreprocessor = modelConfig.default_settings?.preprocessor;
|
const defaultPreprocessor = modelConfig.default_settings?.preprocessor;
|
||||||
if (!isProcessorType(defaultPreprocessor)) {
|
if (!isProcessorTypeV2(defaultPreprocessor)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const processorConfig = CONTROLNET_PROCESSORS[defaultPreprocessor].buildDefaults(modelConfig.base);
|
const processorConfig = CA_PROCESSOR_DATA[defaultPreprocessor].buildDefaults(modelConfig.base);
|
||||||
return processorConfig;
|
return processorConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -467,15 +467,15 @@ export const imageDTOToImageWithDims = ({ image_name, width, height }: ImageDTO)
|
|||||||
height,
|
height,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const t2iAdapterToControlNet = (t2iAdapter: T2IAdapterConfig): ControlNetConfig => {
|
export const t2iAdapterToControlNet = (t2iAdapter: T2IAdapterConfigV2): ControlNetConfigV2 => {
|
||||||
return {
|
return {
|
||||||
...deepClone(t2iAdapter),
|
...deepClone(t2iAdapter),
|
||||||
type: 'controlnet',
|
type: 'controlnet',
|
||||||
controlMode: initialControlNet.controlMode,
|
controlMode: initialControlNetV2.controlMode,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const controlNetToT2IAdapter = (controlNet: ControlNetConfig): T2IAdapterConfig => {
|
export const controlNetToT2IAdapter = (controlNet: ControlNetConfigV2): T2IAdapterConfigV2 => {
|
||||||
return {
|
return {
|
||||||
...omit(deepClone(controlNet), 'controlMode'),
|
...omit(deepClone(controlNet), 'controlMode'),
|
||||||
type: 't2i_adapter',
|
type: 't2i_adapter',
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
import { getStore } from 'app/store/nanostores/store';
|
import { getStore } from 'app/store/nanostores/store';
|
||||||
import { rgbaColorToString, rgbColorToString } from 'features/canvas/util/colorToString';
|
import { rgbaColorToString, rgbColorToString } from 'features/canvas/util/colorToString';
|
||||||
import { getScaledFlooredCursorPosition } from 'features/controlLayers/hooks/mouseEventHooks';
|
import { getScaledFlooredCursorPosition, snapPosToStage } from 'features/controlLayers/hooks/mouseEventHooks';
|
||||||
import {
|
import {
|
||||||
$tool,
|
$tool,
|
||||||
BACKGROUND_LAYER_ID,
|
BACKGROUND_LAYER_ID,
|
||||||
BACKGROUND_RECT_ID,
|
BACKGROUND_RECT_ID,
|
||||||
CA_LAYER_IMAGE_NAME,
|
CA_LAYER_IMAGE_NAME,
|
||||||
CA_LAYER_NAME,
|
CA_LAYER_NAME,
|
||||||
|
COMPOSITING_RECT_NAME,
|
||||||
getCALayerImageId,
|
getCALayerImageId,
|
||||||
|
getIILayerImageId,
|
||||||
getLayerBboxId,
|
getLayerBboxId,
|
||||||
getRGLayerObjectGroupId,
|
getRGLayerObjectGroupId,
|
||||||
|
INITIAL_IMAGE_LAYER_IMAGE_NAME,
|
||||||
|
INITIAL_IMAGE_LAYER_NAME,
|
||||||
isControlAdapterLayer,
|
isControlAdapterLayer,
|
||||||
|
isInitialImageLayer,
|
||||||
isRegionalGuidanceLayer,
|
isRegionalGuidanceLayer,
|
||||||
isRenderableLayer,
|
isRenderableLayer,
|
||||||
LAYER_BBOX_NAME,
|
LAYER_BBOX_NAME,
|
||||||
@ -28,6 +33,7 @@ import {
|
|||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type {
|
import type {
|
||||||
ControlAdapterLayer,
|
ControlAdapterLayer,
|
||||||
|
InitialImageLayer,
|
||||||
Layer,
|
Layer,
|
||||||
RegionalGuidanceLayer,
|
RegionalGuidanceLayer,
|
||||||
Tool,
|
Tool,
|
||||||
@ -35,6 +41,7 @@ import type {
|
|||||||
VectorMaskRect,
|
VectorMaskRect,
|
||||||
} from 'features/controlLayers/store/types';
|
} from 'features/controlLayers/store/types';
|
||||||
import { getLayerBboxFast, getLayerBboxPixels } from 'features/controlLayers/util/bbox';
|
import { getLayerBboxFast, getLayerBboxPixels } from 'features/controlLayers/util/bbox';
|
||||||
|
import { t } from 'i18next';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
@ -52,7 +59,8 @@ const STAGE_BG_DATAURL =
|
|||||||
|
|
||||||
const mapId = (object: { id: string }) => object.id;
|
const mapId = (object: { id: string }) => object.id;
|
||||||
|
|
||||||
const selectRenderableLayers = (n: Konva.Node) => n.name() === RG_LAYER_NAME || n.name() === CA_LAYER_NAME;
|
const selectRenderableLayers = (n: Konva.Node) =>
|
||||||
|
n.name() === RG_LAYER_NAME || n.name() === CA_LAYER_NAME || n.name() === INITIAL_IMAGE_LAYER_NAME;
|
||||||
|
|
||||||
const selectVectorMaskObjects = (node: Konva.Node) => {
|
const selectVectorMaskObjects = (node: Konva.Node) => {
|
||||||
return node.name() === RG_LAYER_LINE_NAME || node.name() === RG_LAYER_RECT_NAME;
|
return node.name() === RG_LAYER_LINE_NAME || node.name() === RG_LAYER_RECT_NAME;
|
||||||
@ -137,10 +145,9 @@ const renderToolPreview = (
|
|||||||
globalMaskLayerOpacity: number,
|
globalMaskLayerOpacity: number,
|
||||||
cursorPos: Vector2d | null,
|
cursorPos: Vector2d | null,
|
||||||
lastMouseDownPos: Vector2d | null,
|
lastMouseDownPos: Vector2d | null,
|
||||||
isMouseOver: boolean,
|
|
||||||
brushSize: number
|
brushSize: number
|
||||||
) => {
|
) => {
|
||||||
const layerCount = stage.find(`.${RG_LAYER_NAME}`).length;
|
const layerCount = stage.find(selectRenderableLayers).length;
|
||||||
// Update the stage's pointer style
|
// Update the stage's pointer style
|
||||||
if (layerCount === 0) {
|
if (layerCount === 0) {
|
||||||
// We have no layers, so we should not render any tool
|
// We have no layers, so we should not render any tool
|
||||||
@ -161,7 +168,7 @@ const renderToolPreview = (
|
|||||||
|
|
||||||
const toolPreviewLayer = stage.findOne<Konva.Layer>(`#${TOOL_PREVIEW_LAYER_ID}`) ?? createToolPreviewLayer(stage);
|
const toolPreviewLayer = stage.findOne<Konva.Layer>(`#${TOOL_PREVIEW_LAYER_ID}`) ?? createToolPreviewLayer(stage);
|
||||||
|
|
||||||
if (!isMouseOver || layerCount === 0) {
|
if (!cursorPos || layerCount === 0) {
|
||||||
// We can bail early if the mouse isn't over the stage or there are no layers
|
// We can bail early if the mouse isn't over the stage or there are no layers
|
||||||
toolPreviewLayer.visible(false);
|
toolPreviewLayer.visible(false);
|
||||||
return;
|
return;
|
||||||
@ -205,12 +212,13 @@ const renderToolPreview = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cursorPos && lastMouseDownPos && tool === 'rect') {
|
if (cursorPos && lastMouseDownPos && tool === 'rect') {
|
||||||
|
const snappedPos = snapPosToStage(cursorPos, stage);
|
||||||
const rectPreview = toolPreviewLayer.findOne<Konva.Rect>(`#${TOOL_PREVIEW_RECT_ID}`);
|
const rectPreview = toolPreviewLayer.findOne<Konva.Rect>(`#${TOOL_PREVIEW_RECT_ID}`);
|
||||||
rectPreview?.setAttrs({
|
rectPreview?.setAttrs({
|
||||||
x: Math.min(cursorPos.x, lastMouseDownPos.x),
|
x: Math.min(snappedPos.x, lastMouseDownPos.x),
|
||||||
y: Math.min(cursorPos.y, lastMouseDownPos.y),
|
y: Math.min(snappedPos.y, lastMouseDownPos.y),
|
||||||
width: Math.abs(cursorPos.x - lastMouseDownPos.x),
|
width: Math.abs(snappedPos.x - lastMouseDownPos.x),
|
||||||
height: Math.abs(cursorPos.y - lastMouseDownPos.y),
|
height: Math.abs(snappedPos.y - lastMouseDownPos.y),
|
||||||
});
|
});
|
||||||
rectPreview?.visible(true);
|
rectPreview?.visible(true);
|
||||||
} else {
|
} else {
|
||||||
@ -317,6 +325,12 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro
|
|||||||
return vectorMaskRect;
|
return vectorMaskRect;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createCompositingRect = (konvaLayer: Konva.Layer): Konva.Rect => {
|
||||||
|
const compositingRect = new Konva.Rect({ name: COMPOSITING_RECT_NAME, listening: false });
|
||||||
|
konvaLayer.add(compositingRect);
|
||||||
|
return compositingRect;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a vector mask layer.
|
* Renders a vector mask layer.
|
||||||
* @param stage The konva stage to render on.
|
* @param stage The konva stage to render on.
|
||||||
@ -394,19 +408,157 @@ const renderRegionalGuidanceLayer = (
|
|||||||
groupNeedsCache = true;
|
groupNeedsCache = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (konvaObjectGroup.children.length === 0) {
|
if (konvaObjectGroup.getChildren().length === 0) {
|
||||||
// No objects - clear the cache to reset the previous pixel data
|
// No objects - clear the cache to reset the previous pixel data
|
||||||
konvaObjectGroup.clearCache();
|
konvaObjectGroup.clearCache();
|
||||||
} else if (groupNeedsCache) {
|
return;
|
||||||
konvaObjectGroup.cache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updating group opacity does not require re-caching
|
const compositingRect =
|
||||||
if (konvaObjectGroup.opacity() !== globalMaskLayerOpacity) {
|
konvaLayer.findOne<Konva.Rect>(`.${COMPOSITING_RECT_NAME}`) ?? createCompositingRect(konvaLayer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the group is selected, we use a rect of the selected preview color, composited over the shapes. This allows
|
||||||
|
* shapes to render as a "raster" layer with all pixels drawn at the same color and opacity.
|
||||||
|
*
|
||||||
|
* Without this special handling, each shape is drawn individually with the given opacity, atop the other shapes. The
|
||||||
|
* effect is like if you have a Photoshop Group consisting of many shapes, each of which has the given opacity.
|
||||||
|
* Overlapping shapes will have their colors blended together, and the final color is the result of all the shapes.
|
||||||
|
*
|
||||||
|
* Instead, with the special handling, the effect is as if you drew all the shapes at 100% opacity, flattened them to
|
||||||
|
* a single raster image, and _then_ applied the 50% opacity.
|
||||||
|
*/
|
||||||
|
if (reduxLayer.isSelected && tool !== 'move') {
|
||||||
|
// We must clear the cache first so Konva will re-draw the group with the new compositing rect
|
||||||
|
if (konvaObjectGroup.isCached()) {
|
||||||
|
konvaObjectGroup.clearCache();
|
||||||
|
}
|
||||||
|
// The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work
|
||||||
|
konvaObjectGroup.opacity(1);
|
||||||
|
|
||||||
|
compositingRect.setAttrs({
|
||||||
|
// The rect should be the size of the layer - use the fast method bc it's OK if the rect is larger
|
||||||
|
...getLayerBboxFast(konvaLayer),
|
||||||
|
fill: rgbColor,
|
||||||
|
opacity: globalMaskLayerOpacity,
|
||||||
|
// Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes)
|
||||||
|
globalCompositeOperation: 'source-in',
|
||||||
|
visible: true,
|
||||||
|
// This rect must always be on top of all other shapes
|
||||||
|
zIndex: konvaObjectGroup.getChildren().length,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// The compositing rect should only be shown when the layer is selected.
|
||||||
|
compositingRect.visible(false);
|
||||||
|
// Cache only if needed - or if we are on this code path and _don't_ have a cache
|
||||||
|
if (groupNeedsCache || !konvaObjectGroup.isCached()) {
|
||||||
|
konvaObjectGroup.cache();
|
||||||
|
}
|
||||||
|
// Updating group opacity does not require re-caching
|
||||||
konvaObjectGroup.opacity(globalMaskLayerOpacity);
|
konvaObjectGroup.opacity(globalMaskLayerOpacity);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createInitialImageLayer = (stage: Konva.Stage, reduxLayer: InitialImageLayer): Konva.Layer => {
|
||||||
|
const konvaLayer = new Konva.Layer({
|
||||||
|
id: reduxLayer.id,
|
||||||
|
name: INITIAL_IMAGE_LAYER_NAME,
|
||||||
|
imageSmoothingEnabled: true,
|
||||||
|
});
|
||||||
|
stage.add(konvaLayer);
|
||||||
|
return konvaLayer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createInitialImageLayerImage = (konvaLayer: Konva.Layer, image: HTMLImageElement): Konva.Image => {
|
||||||
|
const konvaImage = new Konva.Image({
|
||||||
|
name: INITIAL_IMAGE_LAYER_IMAGE_NAME,
|
||||||
|
image,
|
||||||
|
});
|
||||||
|
konvaLayer.add(konvaImage);
|
||||||
|
return konvaImage;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateInitialImageLayerImageAttrs = (
|
||||||
|
stage: Konva.Stage,
|
||||||
|
konvaImage: Konva.Image,
|
||||||
|
reduxLayer: InitialImageLayer
|
||||||
|
) => {
|
||||||
|
const newWidth = stage.width() / stage.scaleX();
|
||||||
|
const newHeight = stage.height() / stage.scaleY();
|
||||||
|
if (
|
||||||
|
konvaImage.width() !== newWidth ||
|
||||||
|
konvaImage.height() !== newHeight ||
|
||||||
|
konvaImage.visible() !== reduxLayer.isEnabled
|
||||||
|
) {
|
||||||
|
konvaImage.setAttrs({
|
||||||
|
opacity: reduxLayer.opacity,
|
||||||
|
scaleX: 1,
|
||||||
|
scaleY: 1,
|
||||||
|
width: stage.width() / stage.scaleX(),
|
||||||
|
height: stage.height() / stage.scaleY(),
|
||||||
|
visible: reduxLayer.isEnabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (konvaImage.opacity() !== reduxLayer.opacity) {
|
||||||
|
konvaImage.opacity(reduxLayer.opacity);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateInitialImageLayerImageSource = async (
|
||||||
|
stage: Konva.Stage,
|
||||||
|
konvaLayer: Konva.Layer,
|
||||||
|
reduxLayer: InitialImageLayer
|
||||||
|
) => {
|
||||||
|
if (reduxLayer.image) {
|
||||||
|
const { imageName } = reduxLayer.image;
|
||||||
|
const req = getStore().dispatch(imagesApi.endpoints.getImageDTO.initiate(imageName));
|
||||||
|
const imageDTO = await req.unwrap();
|
||||||
|
req.unsubscribe();
|
||||||
|
const imageEl = new Image();
|
||||||
|
const imageId = getIILayerImageId(reduxLayer.id, imageName);
|
||||||
|
imageEl.onload = () => {
|
||||||
|
// Find the existing image or create a new one - must find using the name, bc the id may have just changed
|
||||||
|
const konvaImage =
|
||||||
|
konvaLayer.findOne<Konva.Image>(`.${INITIAL_IMAGE_LAYER_IMAGE_NAME}`) ??
|
||||||
|
createInitialImageLayerImage(konvaLayer, imageEl);
|
||||||
|
|
||||||
|
// Update the image's attributes
|
||||||
|
konvaImage.setAttrs({
|
||||||
|
id: imageId,
|
||||||
|
image: imageEl,
|
||||||
|
});
|
||||||
|
updateInitialImageLayerImageAttrs(stage, konvaImage, reduxLayer);
|
||||||
|
imageEl.id = imageId;
|
||||||
|
};
|
||||||
|
imageEl.src = imageDTO.image_url;
|
||||||
|
} else {
|
||||||
|
konvaLayer.findOne(`.${INITIAL_IMAGE_LAYER_IMAGE_NAME}`)?.destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderInitialImageLayer = (stage: Konva.Stage, reduxLayer: InitialImageLayer) => {
|
||||||
|
const konvaLayer = stage.findOne<Konva.Layer>(`#${reduxLayer.id}`) ?? createInitialImageLayer(stage, reduxLayer);
|
||||||
|
const konvaImage = konvaLayer.findOne<Konva.Image>(`.${INITIAL_IMAGE_LAYER_IMAGE_NAME}`);
|
||||||
|
const canvasImageSource = konvaImage?.image();
|
||||||
|
let imageSourceNeedsUpdate = false;
|
||||||
|
if (canvasImageSource instanceof HTMLImageElement) {
|
||||||
|
const image = reduxLayer.image;
|
||||||
|
if (image && canvasImageSource.id !== getCALayerImageId(reduxLayer.id, image.imageName)) {
|
||||||
|
imageSourceNeedsUpdate = true;
|
||||||
|
} else if (!image) {
|
||||||
|
imageSourceNeedsUpdate = true;
|
||||||
|
}
|
||||||
|
} else if (!canvasImageSource) {
|
||||||
|
imageSourceNeedsUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageSourceNeedsUpdate) {
|
||||||
|
updateInitialImageLayerImageSource(stage, konvaLayer, reduxLayer);
|
||||||
|
} else if (konvaImage) {
|
||||||
|
updateInitialImageLayerImageAttrs(stage, konvaImage, reduxLayer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const createControlNetLayer = (stage: Konva.Stage, reduxLayer: ControlAdapterLayer): Konva.Layer => {
|
const createControlNetLayer = (stage: Konva.Stage, reduxLayer: ControlAdapterLayer): Konva.Layer => {
|
||||||
const konvaLayer = new Konva.Layer({
|
const konvaLayer = new Konva.Layer({
|
||||||
id: reduxLayer.id,
|
id: reduxLayer.id,
|
||||||
@ -547,6 +699,9 @@ const renderLayers = (
|
|||||||
if (isControlAdapterLayer(reduxLayer)) {
|
if (isControlAdapterLayer(reduxLayer)) {
|
||||||
renderControlNetLayer(stage, reduxLayer);
|
renderControlNetLayer(stage, reduxLayer);
|
||||||
}
|
}
|
||||||
|
if (isInitialImageLayer(reduxLayer)) {
|
||||||
|
renderInitialImageLayer(stage, reduxLayer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -711,7 +866,7 @@ const createNoLayersMessageLayer = (stage: Konva.Stage): Konva.Layer => {
|
|||||||
y: 0,
|
y: 0,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
verticalAlign: 'middle',
|
verticalAlign: 'middle',
|
||||||
text: 'No Layers Added',
|
text: t('controlLayers.noLayersAdded'),
|
||||||
fontFamily: '"Inter Variable", sans-serif',
|
fontFamily: '"Inter Variable", sans-serif',
|
||||||
fontStyle: '600',
|
fontStyle: '600',
|
||||||
fill: 'white',
|
fill: 'white',
|
||||||
|
@ -3,6 +3,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
||||||
import { selectControlAdaptersSlice } from 'features/controlAdapters/store/controlAdaptersSlice';
|
import { selectControlAdaptersSlice } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
|
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
||||||
import { getImageUsage, selectImageUsage } from 'features/deleteImageModal/store/selectors';
|
import { getImageUsage, selectImageUsage } from 'features/deleteImageModal/store/selectors';
|
||||||
import {
|
import {
|
||||||
@ -12,7 +13,6 @@ import {
|
|||||||
} from 'features/deleteImageModal/store/slice';
|
} from 'features/deleteImageModal/store/slice';
|
||||||
import type { ImageUsage } from 'features/deleteImageModal/store/types';
|
import type { ImageUsage } from 'features/deleteImageModal/store/types';
|
||||||
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
||||||
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
|
||||||
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
|
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
|
||||||
import { some } from 'lodash-es';
|
import { some } from 'lodash-es';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
@ -24,24 +24,24 @@ import ImageUsageMessage from './ImageUsageMessage';
|
|||||||
const selectImageUsages = createMemoizedSelector(
|
const selectImageUsages = createMemoizedSelector(
|
||||||
[
|
[
|
||||||
selectDeleteImageModalSlice,
|
selectDeleteImageModalSlice,
|
||||||
selectGenerationSlice,
|
|
||||||
selectCanvasSlice,
|
selectCanvasSlice,
|
||||||
selectNodesSlice,
|
selectNodesSlice,
|
||||||
selectControlAdaptersSlice,
|
selectControlAdaptersSlice,
|
||||||
|
selectControlLayersSlice,
|
||||||
selectImageUsage,
|
selectImageUsage,
|
||||||
],
|
],
|
||||||
(deleteImageModal, generation, canvas, nodes, controlAdapters, imagesUsage) => {
|
(deleteImageModal, canvas, nodes, controlAdapters, controlLayers, imagesUsage) => {
|
||||||
const { imagesToDelete } = deleteImageModal;
|
const { imagesToDelete } = deleteImageModal;
|
||||||
|
|
||||||
const allImageUsage = (imagesToDelete ?? []).map(({ image_name }) =>
|
const allImageUsage = (imagesToDelete ?? []).map(({ image_name }) =>
|
||||||
getImageUsage(generation, canvas, nodes, controlAdapters, image_name)
|
getImageUsage(canvas, nodes, controlAdapters, controlLayers.present, image_name)
|
||||||
);
|
);
|
||||||
|
|
||||||
const imageUsageSummary: ImageUsage = {
|
const imageUsageSummary: ImageUsage = {
|
||||||
isInitialImage: some(allImageUsage, (i) => i.isInitialImage),
|
|
||||||
isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage),
|
isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage),
|
||||||
isNodesImage: some(allImageUsage, (i) => i.isNodesImage),
|
isNodesImage: some(allImageUsage, (i) => i.isNodesImage),
|
||||||
isControlImage: some(allImageUsage, (i) => i.isControlImage),
|
isControlImage: some(allImageUsage, (i) => i.isControlImage),
|
||||||
|
isControlLayerImage: some(allImageUsage, (i) => i.isControlLayerImage),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -29,10 +29,10 @@ const ImageUsageMessage = (props: Props) => {
|
|||||||
<>
|
<>
|
||||||
<Text>{topMessage}</Text>
|
<Text>{topMessage}</Text>
|
||||||
<UnorderedList paddingInlineStart={6}>
|
<UnorderedList paddingInlineStart={6}>
|
||||||
{imageUsage.isInitialImage && <ListItem>{t('common.img2img')}</ListItem>}
|
{imageUsage.isCanvasImage && <ListItem>{t('ui.tabs.canvasTab')}</ListItem>}
|
||||||
{imageUsage.isCanvasImage && <ListItem>{t('common.unifiedCanvas')}</ListItem>}
|
|
||||||
{imageUsage.isControlImage && <ListItem>{t('common.controlNet')}</ListItem>}
|
{imageUsage.isControlImage && <ListItem>{t('common.controlNet')}</ListItem>}
|
||||||
{imageUsage.isNodesImage && <ListItem>{t('common.nodeEditor')}</ListItem>}
|
{imageUsage.isNodesImage && <ListItem>{t('ui.tabs.workflowsTab')}</ListItem>}
|
||||||
|
{imageUsage.isControlLayerImage && <ListItem>{t('ui.tabs.generationTab')}</ListItem>}
|
||||||
</UnorderedList>
|
</UnorderedList>
|
||||||
<Text>{bottomMessage}</Text>
|
<Text>{bottomMessage}</Text>
|
||||||
</>
|
</>
|
||||||
|
@ -7,26 +7,30 @@ import {
|
|||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
import type { ControlAdaptersState } from 'features/controlAdapters/store/types';
|
import type { ControlAdaptersState } from 'features/controlAdapters/store/types';
|
||||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
||||||
|
import {
|
||||||
|
isControlAdapterLayer,
|
||||||
|
isInitialImageLayer,
|
||||||
|
isIPAdapterLayer,
|
||||||
|
isRegionalGuidanceLayer,
|
||||||
|
selectControlLayersSlice,
|
||||||
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import type { ControlLayersState } from 'features/controlLayers/store/types';
|
||||||
import { selectDeleteImageModalSlice } from 'features/deleteImageModal/store/slice';
|
import { selectDeleteImageModalSlice } from 'features/deleteImageModal/store/slice';
|
||||||
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
||||||
import type { NodesState } from 'features/nodes/store/types';
|
import type { NodesState } from 'features/nodes/store/types';
|
||||||
import { isImageFieldInputInstance } from 'features/nodes/types/field';
|
import { isImageFieldInputInstance } from 'features/nodes/types/field';
|
||||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||||
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
|
||||||
import type { GenerationState } from 'features/parameters/store/types';
|
|
||||||
import { some } from 'lodash-es';
|
import { some } from 'lodash-es';
|
||||||
|
|
||||||
import type { ImageUsage } from './types';
|
import type { ImageUsage } from './types';
|
||||||
|
|
||||||
export const getImageUsage = (
|
export const getImageUsage = (
|
||||||
generation: GenerationState,
|
|
||||||
canvas: CanvasState,
|
canvas: CanvasState,
|
||||||
nodes: NodesState,
|
nodes: NodesState,
|
||||||
controlAdapters: ControlAdaptersState,
|
controlAdapters: ControlAdaptersState,
|
||||||
|
controlLayers: ControlLayersState,
|
||||||
image_name: string
|
image_name: string
|
||||||
) => {
|
) => {
|
||||||
const isInitialImage = generation.initialImage?.imageName === image_name;
|
|
||||||
|
|
||||||
const isCanvasImage = canvas.layerState.objects.some((obj) => obj.kind === 'image' && obj.imageName === image_name);
|
const isCanvasImage = canvas.layerState.objects.some((obj) => obj.kind === 'image' && obj.imageName === image_name);
|
||||||
|
|
||||||
const isNodesImage = nodes.nodes.filter(isInvocationNode).some((node) => {
|
const isNodesImage = nodes.nodes.filter(isInvocationNode).some((node) => {
|
||||||
@ -40,11 +44,29 @@ export const getImageUsage = (
|
|||||||
(ca) => ca.controlImage === image_name || (isControlNetOrT2IAdapter(ca) && ca.processedControlImage === image_name)
|
(ca) => ca.controlImage === image_name || (isControlNetOrT2IAdapter(ca) && ca.processedControlImage === image_name)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isControlLayerImage = controlLayers.layers.some((l) => {
|
||||||
|
if (isRegionalGuidanceLayer(l)) {
|
||||||
|
return l.ipAdapters.some((ipa) => ipa.image?.imageName === image_name);
|
||||||
|
}
|
||||||
|
if (isControlAdapterLayer(l)) {
|
||||||
|
return (
|
||||||
|
l.controlAdapter.image?.imageName === image_name || l.controlAdapter.processedImage?.imageName === image_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isIPAdapterLayer(l)) {
|
||||||
|
return l.ipAdapter.image?.imageName === image_name;
|
||||||
|
}
|
||||||
|
if (isInitialImageLayer(l)) {
|
||||||
|
return l.image?.imageName === image_name;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
const imageUsage: ImageUsage = {
|
const imageUsage: ImageUsage = {
|
||||||
isInitialImage,
|
|
||||||
isCanvasImage,
|
isCanvasImage,
|
||||||
isNodesImage,
|
isNodesImage,
|
||||||
isControlImage,
|
isControlImage,
|
||||||
|
isControlLayerImage,
|
||||||
};
|
};
|
||||||
|
|
||||||
return imageUsage;
|
return imageUsage;
|
||||||
@ -52,11 +74,11 @@ export const getImageUsage = (
|
|||||||
|
|
||||||
export const selectImageUsage = createMemoizedSelector(
|
export const selectImageUsage = createMemoizedSelector(
|
||||||
selectDeleteImageModalSlice,
|
selectDeleteImageModalSlice,
|
||||||
selectGenerationSlice,
|
|
||||||
selectCanvasSlice,
|
selectCanvasSlice,
|
||||||
selectNodesSlice,
|
selectNodesSlice,
|
||||||
selectControlAdaptersSlice,
|
selectControlAdaptersSlice,
|
||||||
(deleteImageModal, generation, canvas, nodes, controlAdapters) => {
|
selectControlLayersSlice,
|
||||||
|
(deleteImageModal, canvas, nodes, controlAdapters, controlLayers) => {
|
||||||
const { imagesToDelete } = deleteImageModal;
|
const { imagesToDelete } = deleteImageModal;
|
||||||
|
|
||||||
if (!imagesToDelete.length) {
|
if (!imagesToDelete.length) {
|
||||||
@ -64,7 +86,7 @@ export const selectImageUsage = createMemoizedSelector(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const imagesUsage = imagesToDelete.map((i) =>
|
const imagesUsage = imagesToDelete.map((i) =>
|
||||||
getImageUsage(generation, canvas, nodes, controlAdapters, i.image_name)
|
getImageUsage(canvas, nodes, controlAdapters, controlLayers.present, i.image_name)
|
||||||
);
|
);
|
||||||
|
|
||||||
return imagesUsage;
|
return imagesUsage;
|
||||||
|
@ -6,8 +6,8 @@ export type DeleteImageState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ImageUsage = {
|
export type ImageUsage = {
|
||||||
isInitialImage: boolean;
|
|
||||||
isCanvasImage: boolean;
|
isCanvasImage: boolean;
|
||||||
isNodesImage: boolean;
|
isNodesImage: boolean;
|
||||||
isControlImage: boolean;
|
isControlImage: boolean;
|
||||||
|
isControlLayerImage: boolean;
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
const selectZoom = createSelector([selectNodesSlice, activeTabNameSelector], (nodes, activeTabName) =>
|
const selectZoom = createSelector([selectNodesSlice, activeTabNameSelector], (nodes, activeTabName) =>
|
||||||
activeTabName === 'nodes' ? nodes.viewport.zoom : 1
|
activeTabName === 'workflows' ? nodes.viewport.zoom : 1
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,10 +22,6 @@ type CurrentImageDropData = BaseDropData & {
|
|||||||
actionType: 'SET_CURRENT_IMAGE';
|
actionType: 'SET_CURRENT_IMAGE';
|
||||||
};
|
};
|
||||||
|
|
||||||
type InitialImageDropData = BaseDropData & {
|
|
||||||
actionType: 'SET_INITIAL_IMAGE';
|
|
||||||
};
|
|
||||||
|
|
||||||
type ControlAdapterDropData = BaseDropData & {
|
type ControlAdapterDropData = BaseDropData & {
|
||||||
actionType: 'SET_CONTROL_ADAPTER_IMAGE';
|
actionType: 'SET_CONTROL_ADAPTER_IMAGE';
|
||||||
context: {
|
context: {
|
||||||
@ -55,6 +51,13 @@ export type RGLayerIPAdapterImageDropData = BaseDropData & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type IILayerImageDropData = BaseDropData & {
|
||||||
|
actionType: 'SET_II_LAYER_IMAGE';
|
||||||
|
context: {
|
||||||
|
layerId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type CanvasInitialImageDropData = BaseDropData & {
|
export type CanvasInitialImageDropData = BaseDropData & {
|
||||||
actionType: 'SET_CANVAS_INITIAL_IMAGE';
|
actionType: 'SET_CANVAS_INITIAL_IMAGE';
|
||||||
};
|
};
|
||||||
@ -78,7 +81,6 @@ export type RemoveFromBoardDropData = BaseDropData & {
|
|||||||
|
|
||||||
export type TypesafeDroppableData =
|
export type TypesafeDroppableData =
|
||||||
| CurrentImageDropData
|
| CurrentImageDropData
|
||||||
| InitialImageDropData
|
|
||||||
| ControlAdapterDropData
|
| ControlAdapterDropData
|
||||||
| CanvasInitialImageDropData
|
| CanvasInitialImageDropData
|
||||||
| NodesImageDropData
|
| NodesImageDropData
|
||||||
@ -86,7 +88,8 @@ export type TypesafeDroppableData =
|
|||||||
| RemoveFromBoardDropData
|
| RemoveFromBoardDropData
|
||||||
| CALayerImageDropData
|
| CALayerImageDropData
|
||||||
| IPALayerImageDropData
|
| IPALayerImageDropData
|
||||||
| RGLayerIPAdapterImageDropData;
|
| RGLayerIPAdapterImageDropData
|
||||||
|
| IILayerImageDropData;
|
||||||
|
|
||||||
type BaseDragData = {
|
type BaseDragData = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -15,8 +15,6 @@ export const isValidDrop = (overData: TypesafeDroppableData | undefined, active:
|
|||||||
switch (actionType) {
|
switch (actionType) {
|
||||||
case 'SET_CURRENT_IMAGE':
|
case 'SET_CURRENT_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
return payloadType === 'IMAGE_DTO';
|
||||||
case 'SET_INITIAL_IMAGE':
|
|
||||||
return payloadType === 'IMAGE_DTO';
|
|
||||||
case 'SET_CONTROL_ADAPTER_IMAGE':
|
case 'SET_CONTROL_ADAPTER_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
return payloadType === 'IMAGE_DTO';
|
||||||
case 'SET_CA_LAYER_IMAGE':
|
case 'SET_CA_LAYER_IMAGE':
|
||||||
@ -25,6 +23,8 @@ export const isValidDrop = (overData: TypesafeDroppableData | undefined, active:
|
|||||||
return payloadType === 'IMAGE_DTO';
|
return payloadType === 'IMAGE_DTO';
|
||||||
case 'SET_RG_LAYER_IP_ADAPTER_IMAGE':
|
case 'SET_RG_LAYER_IP_ADAPTER_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
return payloadType === 'IMAGE_DTO';
|
||||||
|
case 'SET_II_LAYER_IMAGE':
|
||||||
|
return payloadType === 'IMAGE_DTO';
|
||||||
case 'SET_CANVAS_INITIAL_IMAGE':
|
case 'SET_CANVAS_INITIAL_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
return payloadType === 'IMAGE_DTO';
|
||||||
case 'SET_NODES_IMAGE':
|
case 'SET_NODES_IMAGE':
|
||||||
|
@ -15,11 +15,11 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
||||||
import { selectControlAdaptersSlice } from 'features/controlAdapters/store/controlAdaptersSlice';
|
import { selectControlAdaptersSlice } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
|
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import ImageUsageMessage from 'features/deleteImageModal/components/ImageUsageMessage';
|
import ImageUsageMessage from 'features/deleteImageModal/components/ImageUsageMessage';
|
||||||
import { getImageUsage } from 'features/deleteImageModal/store/selectors';
|
import { getImageUsage } from 'features/deleteImageModal/store/selectors';
|
||||||
import type { ImageUsage } from 'features/deleteImageModal/store/types';
|
import type { ImageUsage } from 'features/deleteImageModal/store/types';
|
||||||
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
||||||
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
|
||||||
import { some } from 'lodash-es';
|
import { some } from 'lodash-es';
|
||||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -43,17 +43,17 @@ const DeleteBoardModal = (props: Props) => {
|
|||||||
const selectImageUsageSummary = useMemo(
|
const selectImageUsageSummary = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createMemoizedSelector(
|
createMemoizedSelector(
|
||||||
[selectGenerationSlice, selectCanvasSlice, selectNodesSlice, selectControlAdaptersSlice],
|
[selectCanvasSlice, selectNodesSlice, selectControlAdaptersSlice, selectControlLayersSlice],
|
||||||
(generation, canvas, nodes, controlAdapters) => {
|
(canvas, nodes, controlAdapters, controlLayers) => {
|
||||||
const allImageUsage = (boardImageNames ?? []).map((imageName) =>
|
const allImageUsage = (boardImageNames ?? []).map((imageName) =>
|
||||||
getImageUsage(generation, canvas, nodes, controlAdapters, imageName)
|
getImageUsage(canvas, nodes, controlAdapters, controlLayers.present, imageName)
|
||||||
);
|
);
|
||||||
|
|
||||||
const imageUsageSummary: ImageUsage = {
|
const imageUsageSummary: ImageUsage = {
|
||||||
isInitialImage: some(allImageUsage, (i) => i.isInitialImage),
|
|
||||||
isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage),
|
isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage),
|
||||||
isNodesImage: some(allImageUsage, (i) => i.isNodesImage),
|
isNodesImage: some(allImageUsage, (i) => i.isNodesImage),
|
||||||
isControlImage: some(allImageUsage, (i) => i.isControlImage),
|
isControlImage: some(allImageUsage, (i) => i.isControlImage),
|
||||||
|
isControlLayerImage: some(allImageUsage, (i) => i.isControlLayerImage),
|
||||||
};
|
};
|
||||||
|
|
||||||
return imageUsageSummary;
|
return imageUsageSummary;
|
||||||
|
@ -1,254 +0,0 @@
|
|||||||
import { ButtonGroup, Flex, IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library';
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { skipToken } from '@reduxjs/toolkit/query';
|
|
||||||
import { useAppToaster } from 'app/components/Toaster';
|
|
||||||
import { upscaleRequested } from 'app/store/middleware/listenerMiddleware/listeners/upscaleRequested';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { DeleteImageButton } from 'features/deleteImageModal/components/DeleteImageButton';
|
|
||||||
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
|
||||||
import SingleSelectionMenuItems from 'features/gallery/components/ImageContextMenu/SingleSelectionMenuItems';
|
|
||||||
import { useImageActions } from 'features/gallery/hooks/useImageActions';
|
|
||||||
import { sentImageToImg2Img } from 'features/gallery/store/actions';
|
|
||||||
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
|
||||||
import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
|
||||||
import { parseAndRecallImageDimensions } from 'features/metadata/util/handlers';
|
|
||||||
import ParamUpscalePopover from 'features/parameters/components/Upscale/ParamUpscaleSettings';
|
|
||||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
|
||||||
import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress';
|
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
|
||||||
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
|
||||||
import { setShouldShowImageDetails, setShouldShowProgressInViewer } from 'features/ui/store/uiSlice';
|
|
||||||
import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow';
|
|
||||||
import { memo, useCallback } from 'react';
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import {
|
|
||||||
PiArrowsCounterClockwiseBold,
|
|
||||||
PiAsteriskBold,
|
|
||||||
PiDotsThreeOutlineFill,
|
|
||||||
PiFlowArrowBold,
|
|
||||||
PiHourglassHighBold,
|
|
||||||
PiInfoBold,
|
|
||||||
PiPlantBold,
|
|
||||||
PiQuotesBold,
|
|
||||||
PiRulerBold,
|
|
||||||
} from 'react-icons/pi';
|
|
||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
|
||||||
|
|
||||||
const selectShouldDisableToolbarButtons = createSelector(
|
|
||||||
selectSystemSlice,
|
|
||||||
selectGallerySlice,
|
|
||||||
selectLastSelectedImage,
|
|
||||||
(system, gallery, lastSelectedImage) => {
|
|
||||||
const hasProgressImage = Boolean(system.denoiseProgress?.progress_image);
|
|
||||||
return hasProgressImage || !lastSelectedImage;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const CurrentImageButtons = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
|
||||||
const shouldShowImageDetails = useAppSelector((s) => s.ui.shouldShowImageDetails);
|
|
||||||
const shouldShowProgressInViewer = useAppSelector((s) => s.ui.shouldShowProgressInViewer);
|
|
||||||
const lastSelectedImage = useAppSelector(selectLastSelectedImage);
|
|
||||||
const selection = useAppSelector((s) => s.gallery.selection);
|
|
||||||
const shouldDisableToolbarButtons = useAppSelector(selectShouldDisableToolbarButtons);
|
|
||||||
|
|
||||||
const isUpscalingEnabled = useFeatureStatus('upscaling');
|
|
||||||
const isQueueMutationInProgress = useIsQueueMutationInProgress();
|
|
||||||
const toaster = useAppToaster();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const { currentData: imageDTO } = useGetImageDTOQuery(lastSelectedImage?.image_name ?? skipToken);
|
|
||||||
|
|
||||||
const { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata } =
|
|
||||||
useImageActions(lastSelectedImage?.image_name);
|
|
||||||
|
|
||||||
const { getAndLoadEmbeddedWorkflow, getAndLoadEmbeddedWorkflowResult } = useGetAndLoadEmbeddedWorkflow({});
|
|
||||||
|
|
||||||
const handleLoadWorkflow = useCallback(() => {
|
|
||||||
if (!lastSelectedImage || !lastSelectedImage.has_workflow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
getAndLoadEmbeddedWorkflow(lastSelectedImage.image_name);
|
|
||||||
}, [getAndLoadEmbeddedWorkflow, lastSelectedImage]);
|
|
||||||
|
|
||||||
useHotkeys('w', handleLoadWorkflow, [lastSelectedImage]);
|
|
||||||
useHotkeys('a', recallAll, [recallAll]);
|
|
||||||
useHotkeys('s', recallSeed, [recallSeed]);
|
|
||||||
useHotkeys('p', recallPrompts, [recallPrompts]);
|
|
||||||
useHotkeys('r', remix, [remix]);
|
|
||||||
|
|
||||||
const handleUseSize = useCallback(() => {
|
|
||||||
parseAndRecallImageDimensions(lastSelectedImage);
|
|
||||||
}, [lastSelectedImage]);
|
|
||||||
|
|
||||||
useHotkeys('d', handleUseSize, [handleUseSize]);
|
|
||||||
|
|
||||||
const handleSendToImageToImage = useCallback(() => {
|
|
||||||
dispatch(sentImageToImg2Img());
|
|
||||||
dispatch(initialImageSelected(imageDTO));
|
|
||||||
}, [dispatch, imageDTO]);
|
|
||||||
|
|
||||||
useHotkeys('shift+i', handleSendToImageToImage, [imageDTO]);
|
|
||||||
|
|
||||||
const handleClickUpscale = useCallback(() => {
|
|
||||||
if (!imageDTO) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(upscaleRequested({ imageDTO }));
|
|
||||||
}, [dispatch, imageDTO]);
|
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
|
||||||
if (!imageDTO) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(imagesToDeleteSelected(selection));
|
|
||||||
}, [dispatch, imageDTO, selection]);
|
|
||||||
|
|
||||||
useHotkeys(
|
|
||||||
'Shift+U',
|
|
||||||
() => {
|
|
||||||
handleClickUpscale();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: () => Boolean(isUpscalingEnabled && !shouldDisableToolbarButtons && isConnected),
|
|
||||||
},
|
|
||||||
[isUpscalingEnabled, imageDTO, shouldDisableToolbarButtons, isConnected]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClickShowImageDetails = useCallback(
|
|
||||||
() => dispatch(setShouldShowImageDetails(!shouldShowImageDetails)),
|
|
||||||
[dispatch, shouldShowImageDetails]
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotkeys(
|
|
||||||
'i',
|
|
||||||
() => {
|
|
||||||
if (imageDTO) {
|
|
||||||
handleClickShowImageDetails();
|
|
||||||
} else {
|
|
||||||
toaster({
|
|
||||||
title: t('toast.metadataLoadFailed'),
|
|
||||||
status: 'error',
|
|
||||||
duration: 2500,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[imageDTO, shouldShowImageDetails, toaster]
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotkeys(
|
|
||||||
'delete',
|
|
||||||
() => {
|
|
||||||
handleDelete();
|
|
||||||
},
|
|
||||||
[dispatch, imageDTO]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClickProgressImagesToggle = useCallback(() => {
|
|
||||||
dispatch(setShouldShowProgressInViewer(!shouldShowProgressInViewer));
|
|
||||||
}, [dispatch, shouldShowProgressInViewer]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Flex flexWrap="wrap" justifyContent="center" alignItems="center" gap={2}>
|
|
||||||
<ButtonGroup isDisabled={shouldDisableToolbarButtons}>
|
|
||||||
<Menu isLazy>
|
|
||||||
<MenuButton
|
|
||||||
as={IconButton}
|
|
||||||
aria-label={t('parameters.imageActions')}
|
|
||||||
tooltip={t('parameters.imageActions')}
|
|
||||||
isDisabled={!imageDTO}
|
|
||||||
icon={<PiDotsThreeOutlineFill />}
|
|
||||||
/>
|
|
||||||
<MenuList>{imageDTO && <SingleSelectionMenuItems imageDTO={imageDTO} />}</MenuList>
|
|
||||||
</Menu>
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
<ButtonGroup isDisabled={shouldDisableToolbarButtons}>
|
|
||||||
<IconButton
|
|
||||||
icon={<PiFlowArrowBold />}
|
|
||||||
tooltip={`${t('nodes.loadWorkflow')} (W)`}
|
|
||||||
aria-label={`${t('nodes.loadWorkflow')} (W)`}
|
|
||||||
isDisabled={!imageDTO?.has_workflow}
|
|
||||||
onClick={handleLoadWorkflow}
|
|
||||||
isLoading={getAndLoadEmbeddedWorkflowResult.isLoading}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
isLoading={isLoadingMetadata}
|
|
||||||
icon={<PiArrowsCounterClockwiseBold />}
|
|
||||||
tooltip={`${t('parameters.remixImage')} (R)`}
|
|
||||||
aria-label={`${t('parameters.remixImage')} (R)`}
|
|
||||||
isDisabled={!hasMetadata}
|
|
||||||
onClick={remix}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
isLoading={isLoadingMetadata}
|
|
||||||
icon={<PiQuotesBold />}
|
|
||||||
tooltip={`${t('parameters.usePrompt')} (P)`}
|
|
||||||
aria-label={`${t('parameters.usePrompt')} (P)`}
|
|
||||||
isDisabled={!hasPrompts}
|
|
||||||
onClick={recallPrompts}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
isLoading={isLoadingMetadata}
|
|
||||||
icon={<PiPlantBold />}
|
|
||||||
tooltip={`${t('parameters.useSeed')} (S)`}
|
|
||||||
aria-label={`${t('parameters.useSeed')} (S)`}
|
|
||||||
isDisabled={!hasSeed}
|
|
||||||
onClick={recallSeed}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
isLoading={isLoadingMetadata}
|
|
||||||
icon={<PiRulerBold />}
|
|
||||||
tooltip={`${t('parameters.useSize')} (D)`}
|
|
||||||
aria-label={`${t('parameters.useSize')} (D)`}
|
|
||||||
onClick={handleUseSize}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
isLoading={isLoadingMetadata}
|
|
||||||
icon={<PiAsteriskBold />}
|
|
||||||
tooltip={`${t('parameters.useAll')} (A)`}
|
|
||||||
aria-label={`${t('parameters.useAll')} (A)`}
|
|
||||||
isDisabled={!hasMetadata}
|
|
||||||
onClick={recallAll}
|
|
||||||
/>
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
{isUpscalingEnabled && (
|
|
||||||
<ButtonGroup isDisabled={isQueueMutationInProgress}>
|
|
||||||
{isUpscalingEnabled && <ParamUpscalePopover imageDTO={imageDTO} />}
|
|
||||||
</ButtonGroup>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ButtonGroup>
|
|
||||||
<IconButton
|
|
||||||
icon={<PiInfoBold />}
|
|
||||||
tooltip={`${t('parameters.info')} (I)`}
|
|
||||||
aria-label={`${t('parameters.info')} (I)`}
|
|
||||||
isChecked={shouldShowImageDetails}
|
|
||||||
onClick={handleClickShowImageDetails}
|
|
||||||
/>
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
<ButtonGroup>
|
|
||||||
<IconButton
|
|
||||||
aria-label={t('settings.displayInProgress')}
|
|
||||||
tooltip={t('settings.displayInProgress')}
|
|
||||||
icon={<PiHourglassHighBold />}
|
|
||||||
isChecked={shouldShowProgressInViewer}
|
|
||||||
onClick={handleClickProgressImagesToggle}
|
|
||||||
/>
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
<ButtonGroup>
|
|
||||||
<DeleteImageButton onClick={handleDelete} />
|
|
||||||
</ButtonGroup>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(CurrentImageButtons);
|
|
@ -1,24 +0,0 @@
|
|||||||
import { Flex } from '@invoke-ai/ui-library';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
import CurrentImageButtons from './CurrentImageButtons';
|
|
||||||
import CurrentImagePreview from './CurrentImagePreview';
|
|
||||||
|
|
||||||
const CurrentImageDisplay = () => {
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
position="relative"
|
|
||||||
flexDirection="column"
|
|
||||||
height="100%"
|
|
||||||
width="100%"
|
|
||||||
rowGap={4}
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
>
|
|
||||||
<CurrentImageButtons />
|
|
||||||
<CurrentImagePreview />
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(CurrentImageDisplay);
|
|
@ -7,10 +7,10 @@ import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
|
|||||||
import { useDownloadImage } from 'common/hooks/useDownloadImage';
|
import { useDownloadImage } from 'common/hooks/useDownloadImage';
|
||||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||||
import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice';
|
import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice';
|
||||||
|
import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
||||||
import { useImageActions } from 'features/gallery/hooks/useImageActions';
|
import { useImageActions } from 'features/gallery/hooks/useImageActions';
|
||||||
import { sentImageToCanvas, sentImageToImg2Img } from 'features/gallery/store/actions';
|
import { sentImageToCanvas, sentImageToImg2Img } from 'features/gallery/store/actions';
|
||||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
|
||||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||||
@ -45,7 +45,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const toaster = useAppToaster();
|
const toaster = useAppToaster();
|
||||||
const isCanvasEnabled = useFeatureStatus('unifiedCanvas');
|
const isCanvasEnabled = useFeatureStatus('canvas');
|
||||||
const customStarUi = useStore($customStarUI);
|
const customStarUi = useStore($customStarUI);
|
||||||
const { downloadImage } = useDownloadImage();
|
const { downloadImage } = useDownloadImage();
|
||||||
|
|
||||||
@ -72,13 +72,13 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
|
|
||||||
const handleSendToImageToImage = useCallback(() => {
|
const handleSendToImageToImage = useCallback(() => {
|
||||||
dispatch(sentImageToImg2Img());
|
dispatch(sentImageToImg2Img());
|
||||||
dispatch(initialImageSelected(imageDTO));
|
dispatch(iiLayerAdded(imageDTO));
|
||||||
}, [dispatch, imageDTO]);
|
}, [dispatch, imageDTO]);
|
||||||
|
|
||||||
const handleSendToCanvas = useCallback(() => {
|
const handleSendToCanvas = useCallback(() => {
|
||||||
dispatch(sentImageToCanvas());
|
dispatch(sentImageToCanvas());
|
||||||
flushSync(() => {
|
flushSync(() => {
|
||||||
dispatch(setActiveTab('unifiedCanvas'));
|
dispatch(setActiveTab('canvas'));
|
||||||
});
|
});
|
||||||
dispatch(setInitialCanvasImage(imageDTO, optimalDimension));
|
dispatch(setInitialCanvasImage(imageDTO, optimalDimension));
|
||||||
|
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { MetadataControlNets } from 'features/metadata/components/MetadataControlNets';
|
import { MetadataControlNets } from 'features/metadata/components/MetadataControlNets';
|
||||||
|
import { MetadataControlNetsV2 } from 'features/metadata/components/MetadataControlNetsV2';
|
||||||
import { MetadataIPAdapters } from 'features/metadata/components/MetadataIPAdapters';
|
import { MetadataIPAdapters } from 'features/metadata/components/MetadataIPAdapters';
|
||||||
|
import { MetadataIPAdaptersV2 } from 'features/metadata/components/MetadataIPAdaptersV2';
|
||||||
import { MetadataItem } from 'features/metadata/components/MetadataItem';
|
import { MetadataItem } from 'features/metadata/components/MetadataItem';
|
||||||
import { MetadataLoRAs } from 'features/metadata/components/MetadataLoRAs';
|
import { MetadataLoRAs } from 'features/metadata/components/MetadataLoRAs';
|
||||||
import { MetadataT2IAdapters } from 'features/metadata/components/MetadataT2IAdapters';
|
import { MetadataT2IAdapters } from 'features/metadata/components/MetadataT2IAdapters';
|
||||||
|
import { MetadataT2IAdaptersV2 } from 'features/metadata/components/MetadataT2IAdaptersV2';
|
||||||
import { handlers } from 'features/metadata/util/handlers';
|
import { handlers } from 'features/metadata/util/handlers';
|
||||||
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -11,6 +16,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ImageMetadataActions = (props: Props) => {
|
const ImageMetadataActions = (props: Props) => {
|
||||||
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
const { metadata } = props;
|
const { metadata } = props;
|
||||||
|
|
||||||
if (!metadata || Object.keys(metadata).length === 0) {
|
if (!metadata || Object.keys(metadata).length === 0) {
|
||||||
@ -46,9 +52,12 @@ const ImageMetadataActions = (props: Props) => {
|
|||||||
<MetadataItem metadata={metadata} handlers={handlers.refinerStart} />
|
<MetadataItem metadata={metadata} handlers={handlers.refinerStart} />
|
||||||
<MetadataItem metadata={metadata} handlers={handlers.refinerSteps} />
|
<MetadataItem metadata={metadata} handlers={handlers.refinerSteps} />
|
||||||
<MetadataLoRAs metadata={metadata} />
|
<MetadataLoRAs metadata={metadata} />
|
||||||
<MetadataControlNets metadata={metadata} />
|
{activeTabName !== 'generation' && <MetadataControlNets metadata={metadata} />}
|
||||||
<MetadataT2IAdapters metadata={metadata} />
|
{activeTabName !== 'generation' && <MetadataT2IAdapters metadata={metadata} />}
|
||||||
<MetadataIPAdapters metadata={metadata} />
|
{activeTabName !== 'generation' && <MetadataIPAdapters metadata={metadata} />}
|
||||||
|
{activeTabName === 'generation' && <MetadataControlNetsV2 metadata={metadata} />}
|
||||||
|
{activeTabName === 'generation' && <MetadataT2IAdaptersV2 metadata={metadata} />}
|
||||||
|
{activeTabName === 'generation' && <MetadataIPAdaptersV2 metadata={metadata} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,202 @@
|
|||||||
|
import { ButtonGroup, IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { skipToken } from '@reduxjs/toolkit/query';
|
||||||
|
import { upscaleRequested } from 'app/store/middleware/listenerMiddleware/listeners/upscaleRequested';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import { DeleteImageButton } from 'features/deleteImageModal/components/DeleteImageButton';
|
||||||
|
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
||||||
|
import SingleSelectionMenuItems from 'features/gallery/components/ImageContextMenu/SingleSelectionMenuItems';
|
||||||
|
import { useImageActions } from 'features/gallery/hooks/useImageActions';
|
||||||
|
import { sentImageToImg2Img } from 'features/gallery/store/actions';
|
||||||
|
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
||||||
|
import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { parseAndRecallImageDimensions } from 'features/metadata/util/handlers';
|
||||||
|
import ParamUpscalePopover from 'features/parameters/components/Upscale/ParamUpscaleSettings';
|
||||||
|
import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress';
|
||||||
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
|
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
||||||
|
import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
PiArrowsCounterClockwiseBold,
|
||||||
|
PiAsteriskBold,
|
||||||
|
PiDotsThreeOutlineFill,
|
||||||
|
PiFlowArrowBold,
|
||||||
|
PiPlantBold,
|
||||||
|
PiQuotesBold,
|
||||||
|
PiRulerBold,
|
||||||
|
} from 'react-icons/pi';
|
||||||
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
|
const selectShouldDisableToolbarButtons = createSelector(
|
||||||
|
selectSystemSlice,
|
||||||
|
selectGallerySlice,
|
||||||
|
selectLastSelectedImage,
|
||||||
|
(system, gallery, lastSelectedImage) => {
|
||||||
|
const hasProgressImage = Boolean(system.denoiseProgress?.progress_image);
|
||||||
|
return hasProgressImage || !lastSelectedImage;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const CurrentImageButtons = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||||
|
const lastSelectedImage = useAppSelector(selectLastSelectedImage);
|
||||||
|
const selection = useAppSelector((s) => s.gallery.selection);
|
||||||
|
const shouldDisableToolbarButtons = useAppSelector(selectShouldDisableToolbarButtons);
|
||||||
|
|
||||||
|
const isUpscalingEnabled = useFeatureStatus('upscaling');
|
||||||
|
const isQueueMutationInProgress = useIsQueueMutationInProgress();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { currentData: imageDTO } = useGetImageDTOQuery(lastSelectedImage?.image_name ?? skipToken);
|
||||||
|
|
||||||
|
const { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata } =
|
||||||
|
useImageActions(lastSelectedImage?.image_name);
|
||||||
|
|
||||||
|
const { getAndLoadEmbeddedWorkflow, getAndLoadEmbeddedWorkflowResult } = useGetAndLoadEmbeddedWorkflow({});
|
||||||
|
|
||||||
|
const handleLoadWorkflow = useCallback(() => {
|
||||||
|
if (!lastSelectedImage || !lastSelectedImage.has_workflow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getAndLoadEmbeddedWorkflow(lastSelectedImage.image_name);
|
||||||
|
}, [getAndLoadEmbeddedWorkflow, lastSelectedImage]);
|
||||||
|
|
||||||
|
useHotkeys('w', handleLoadWorkflow, [lastSelectedImage]);
|
||||||
|
useHotkeys('a', recallAll, [recallAll]);
|
||||||
|
useHotkeys('s', recallSeed, [recallSeed]);
|
||||||
|
useHotkeys('p', recallPrompts, [recallPrompts]);
|
||||||
|
useHotkeys('r', remix, [remix]);
|
||||||
|
|
||||||
|
const handleUseSize = useCallback(() => {
|
||||||
|
parseAndRecallImageDimensions(lastSelectedImage);
|
||||||
|
}, [lastSelectedImage]);
|
||||||
|
|
||||||
|
useHotkeys('d', handleUseSize, [handleUseSize]);
|
||||||
|
|
||||||
|
const handleSendToImageToImage = useCallback(() => {
|
||||||
|
if (!imageDTO) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(sentImageToImg2Img());
|
||||||
|
dispatch(iiLayerAdded(imageDTO));
|
||||||
|
}, [dispatch, imageDTO]);
|
||||||
|
|
||||||
|
useHotkeys('shift+i', handleSendToImageToImage, [imageDTO]);
|
||||||
|
|
||||||
|
const handleClickUpscale = useCallback(() => {
|
||||||
|
if (!imageDTO) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(upscaleRequested({ imageDTO }));
|
||||||
|
}, [dispatch, imageDTO]);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
if (!imageDTO) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(imagesToDeleteSelected(selection));
|
||||||
|
}, [dispatch, imageDTO, selection]);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'Shift+U',
|
||||||
|
() => {
|
||||||
|
handleClickUpscale();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: () => Boolean(isUpscalingEnabled && !shouldDisableToolbarButtons && isConnected),
|
||||||
|
},
|
||||||
|
[isUpscalingEnabled, imageDTO, shouldDisableToolbarButtons, isConnected]
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'delete',
|
||||||
|
() => {
|
||||||
|
handleDelete();
|
||||||
|
},
|
||||||
|
[dispatch, imageDTO]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ButtonGroup isDisabled={shouldDisableToolbarButtons}>
|
||||||
|
<Menu isLazy>
|
||||||
|
<MenuButton
|
||||||
|
as={IconButton}
|
||||||
|
aria-label={t('parameters.imageActions')}
|
||||||
|
tooltip={t('parameters.imageActions')}
|
||||||
|
isDisabled={!imageDTO}
|
||||||
|
icon={<PiDotsThreeOutlineFill />}
|
||||||
|
/>
|
||||||
|
<MenuList>{imageDTO && <SingleSelectionMenuItems imageDTO={imageDTO} />}</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<ButtonGroup isDisabled={shouldDisableToolbarButtons}>
|
||||||
|
<IconButton
|
||||||
|
icon={<PiFlowArrowBold />}
|
||||||
|
tooltip={`${t('nodes.loadWorkflow')} (W)`}
|
||||||
|
aria-label={`${t('nodes.loadWorkflow')} (W)`}
|
||||||
|
isDisabled={!imageDTO?.has_workflow}
|
||||||
|
onClick={handleLoadWorkflow}
|
||||||
|
isLoading={getAndLoadEmbeddedWorkflowResult.isLoading}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
isLoading={isLoadingMetadata}
|
||||||
|
icon={<PiArrowsCounterClockwiseBold />}
|
||||||
|
tooltip={`${t('parameters.remixImage')} (R)`}
|
||||||
|
aria-label={`${t('parameters.remixImage')} (R)`}
|
||||||
|
isDisabled={!hasMetadata}
|
||||||
|
onClick={remix}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
isLoading={isLoadingMetadata}
|
||||||
|
icon={<PiQuotesBold />}
|
||||||
|
tooltip={`${t('parameters.usePrompt')} (P)`}
|
||||||
|
aria-label={`${t('parameters.usePrompt')} (P)`}
|
||||||
|
isDisabled={!hasPrompts}
|
||||||
|
onClick={recallPrompts}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
isLoading={isLoadingMetadata}
|
||||||
|
icon={<PiPlantBold />}
|
||||||
|
tooltip={`${t('parameters.useSeed')} (S)`}
|
||||||
|
aria-label={`${t('parameters.useSeed')} (S)`}
|
||||||
|
isDisabled={!hasSeed}
|
||||||
|
onClick={recallSeed}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
isLoading={isLoadingMetadata}
|
||||||
|
icon={<PiRulerBold />}
|
||||||
|
tooltip={`${t('parameters.useSize')} (D)`}
|
||||||
|
aria-label={`${t('parameters.useSize')} (D)`}
|
||||||
|
onClick={handleUseSize}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
isLoading={isLoadingMetadata}
|
||||||
|
icon={<PiAsteriskBold />}
|
||||||
|
tooltip={`${t('parameters.useAll')} (A)`}
|
||||||
|
aria-label={`${t('parameters.useAll')} (A)`}
|
||||||
|
isDisabled={!hasMetadata}
|
||||||
|
onClick={recallAll}
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
{isUpscalingEnabled && (
|
||||||
|
<ButtonGroup isDisabled={isQueueMutationInProgress}>
|
||||||
|
{isUpscalingEnabled && <ParamUpscalePopover imageDTO={imageDTO} />}
|
||||||
|
</ButtonGroup>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ButtonGroup>
|
||||||
|
<DeleteImageButton onClick={handleDelete} />
|
||||||
|
</ButtonGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(CurrentImageButtons);
|
@ -5,24 +5,25 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||||
import ProgressImage from 'features/gallery/components/CurrentImage/ProgressImage';
|
|
||||||
import ImageMetadataViewer from 'features/gallery/components/ImageMetadataViewer/ImageMetadataViewer';
|
import ImageMetadataViewer from 'features/gallery/components/ImageMetadataViewer/ImageMetadataViewer';
|
||||||
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
||||||
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
||||||
import type { AnimationProps } from 'framer-motion';
|
import type { AnimationProps } from 'framer-motion';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import type { CSSProperties } from 'react';
|
|
||||||
import { memo, useCallback, useMemo, useRef, useState } from 'react';
|
import { memo, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiImageBold } from 'react-icons/pi';
|
import { PiImageBold } from 'react-icons/pi';
|
||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
|
import ProgressImage from './ProgressImage';
|
||||||
|
|
||||||
const selectLastSelectedImageName = createSelector(
|
const selectLastSelectedImageName = createSelector(
|
||||||
selectLastSelectedImage,
|
selectLastSelectedImage,
|
||||||
(lastSelectedImage) => lastSelectedImage?.image_name
|
(lastSelectedImage) => lastSelectedImage?.image_name
|
||||||
);
|
);
|
||||||
|
|
||||||
const CurrentImagePreview = () => {
|
const CurrentImagePreview = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const shouldShowImageDetails = useAppSelector((s) => s.ui.shouldShowImageDetails);
|
const shouldShowImageDetails = useAppSelector((s) => s.ui.shouldShowImageDetails);
|
||||||
const imageName = useAppSelector(selectLastSelectedImageName);
|
const imageName = useAppSelector(selectLastSelectedImageName);
|
||||||
const hasDenoiseProgress = useAppSelector((s) => Boolean(s.system.denoiseProgress));
|
const hasDenoiseProgress = useAppSelector((s) => Boolean(s.system.denoiseProgress));
|
||||||
@ -50,17 +51,12 @@ const CurrentImagePreview = () => {
|
|||||||
|
|
||||||
// Show and hide the next/prev buttons on mouse move
|
// Show and hide the next/prev buttons on mouse move
|
||||||
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] = useState<boolean>(false);
|
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] = useState<boolean>(false);
|
||||||
|
|
||||||
const timeoutId = useRef(0);
|
const timeoutId = useRef(0);
|
||||||
|
const onMouseOver = useCallback(() => {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const handleMouseOver = useCallback(() => {
|
|
||||||
setShouldShowNextPrevButtons(true);
|
setShouldShowNextPrevButtons(true);
|
||||||
window.clearTimeout(timeoutId.current);
|
window.clearTimeout(timeoutId.current);
|
||||||
}, []);
|
}, []);
|
||||||
|
const onMouseOut = useCallback(() => {
|
||||||
const handleMouseOut = useCallback(() => {
|
|
||||||
timeoutId.current = window.setTimeout(() => {
|
timeoutId.current = window.setTimeout(() => {
|
||||||
setShouldShowNextPrevButtons(false);
|
setShouldShowNextPrevButtons(false);
|
||||||
}, 500);
|
}, 500);
|
||||||
@ -68,8 +64,8 @@ const CurrentImagePreview = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
onMouseOver={handleMouseOver}
|
onMouseOver={onMouseOver}
|
||||||
onMouseOut={handleMouseOut}
|
onMouseOut={onMouseOut}
|
||||||
width="full"
|
width="full"
|
||||||
height="full"
|
height="full"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
@ -91,16 +87,40 @@ const CurrentImagePreview = () => {
|
|||||||
dataTestId="image-preview"
|
dataTestId="image-preview"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{shouldShowImageDetails && imageDTO && (
|
|
||||||
<Box position="absolute" top="0" width="full" height="full" borderRadius="base">
|
|
||||||
<ImageMetadataViewer image={imageDTO} />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{!shouldShowImageDetails && imageDTO && shouldShowNextPrevButtons && (
|
{shouldShowImageDetails && imageDTO && (
|
||||||
<motion.div key="nextPrevButtons" initial={initial} animate={animate} exit={exit} style={motionStyles}>
|
<Box
|
||||||
|
as={motion.div}
|
||||||
|
key="metadataViewer"
|
||||||
|
initial={initial}
|
||||||
|
animate={animateMetadata}
|
||||||
|
exit={exit}
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
width="full"
|
||||||
|
height="full"
|
||||||
|
borderRadius="base"
|
||||||
|
>
|
||||||
|
<ImageMetadataViewer image={imageDTO} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
<AnimatePresence>
|
||||||
|
{shouldShowNextPrevButtons && imageDTO && (
|
||||||
|
<Box
|
||||||
|
as={motion.div}
|
||||||
|
key="nextPrevButtons"
|
||||||
|
initial={initial}
|
||||||
|
animate={animateArrows}
|
||||||
|
exit={exit}
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
width="full"
|
||||||
|
height="full"
|
||||||
|
pointerEvents="none"
|
||||||
|
>
|
||||||
<NextPrevImageButtons />
|
<NextPrevImageButtons />
|
||||||
</motion.div>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -112,18 +132,15 @@ export default memo(CurrentImagePreview);
|
|||||||
const initial: AnimationProps['initial'] = {
|
const initial: AnimationProps['initial'] = {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
};
|
};
|
||||||
const animate: AnimationProps['animate'] = {
|
const animateArrows: AnimationProps['animate'] = {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
transition: { duration: 0.1 },
|
transition: { duration: 0.07 },
|
||||||
|
};
|
||||||
|
const animateMetadata: AnimationProps['animate'] = {
|
||||||
|
opacity: 0.8,
|
||||||
|
transition: { duration: 0.07 },
|
||||||
};
|
};
|
||||||
const exit: AnimationProps['exit'] = {
|
const exit: AnimationProps['exit'] = {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
transition: { duration: 0.1 },
|
transition: { duration: 0.07 },
|
||||||
};
|
|
||||||
const motionStyles: CSSProperties = {
|
|
||||||
position: 'absolute',
|
|
||||||
top: '0',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
};
|
};
|
@ -0,0 +1,39 @@
|
|||||||
|
import { Button } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
||||||
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiArrowsDownUpBold } from 'react-icons/pi';
|
||||||
|
|
||||||
|
import { useImageViewer } from './useImageViewer';
|
||||||
|
|
||||||
|
const TAB_NAME_TO_TKEY_SHORT: Record<InvokeTabName, string> = {
|
||||||
|
generation: 'controlLayers.controlLayers',
|
||||||
|
canvas: 'ui.tabs.canvas',
|
||||||
|
workflows: 'ui.tabs.workflows',
|
||||||
|
models: 'ui.tabs.models',
|
||||||
|
queue: 'ui.tabs.queue',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditorButton = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { onClose } = useImageViewer();
|
||||||
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
|
const tooltip = useMemo(
|
||||||
|
() => t('gallery.switchTo', { tab: t(TAB_NAME_TO_TKEY_SHORT[activeTabName]) }),
|
||||||
|
[t, activeTabName]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
aria-label={tooltip}
|
||||||
|
tooltip={tooltip}
|
||||||
|
onClick={onClose}
|
||||||
|
variant="outline"
|
||||||
|
leftIcon={<PiArrowsDownUpBold />}
|
||||||
|
>
|
||||||
|
{t(TAB_NAME_TO_TKEY_SHORT[activeTabName])}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,92 @@
|
|||||||
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { ToggleMetadataViewerButton } from 'features/gallery/components/ImageViewer/ToggleMetadataViewerButton';
|
||||||
|
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
|
||||||
|
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||||
|
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
||||||
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
|
import type { AnimationProps } from 'framer-motion';
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import { memo, useMemo } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
|
import CurrentImageButtons from './CurrentImageButtons';
|
||||||
|
import CurrentImagePreview from './CurrentImagePreview';
|
||||||
|
import { EditorButton } from './EditorButton';
|
||||||
|
|
||||||
|
const initial: AnimationProps['initial'] = {
|
||||||
|
opacity: 0,
|
||||||
|
};
|
||||||
|
const animate: AnimationProps['animate'] = {
|
||||||
|
opacity: 1,
|
||||||
|
transition: { duration: 0.07 },
|
||||||
|
};
|
||||||
|
const exit: AnimationProps['exit'] = {
|
||||||
|
opacity: 0,
|
||||||
|
transition: { duration: 0.07 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const VIEWER_ENABLED_TABS: InvokeTabName[] = ['canvas', 'generation', 'workflows'];
|
||||||
|
|
||||||
|
export const ImageViewer = memo(() => {
|
||||||
|
const { isOpen, onToggle, onClose } = useImageViewer();
|
||||||
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
|
const isViewerEnabled = useMemo(() => VIEWER_ENABLED_TABS.includes(activeTabName), [activeTabName]);
|
||||||
|
const shouldShowViewer = useMemo(() => {
|
||||||
|
if (!isViewerEnabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isOpen;
|
||||||
|
}, [isOpen, isViewerEnabled]);
|
||||||
|
|
||||||
|
useHotkeys('z', onToggle, { enabled: isViewerEnabled }, [isViewerEnabled, onToggle]);
|
||||||
|
useHotkeys('esc', onClose, { enabled: isViewerEnabled }, [isViewerEnabled, onClose]);
|
||||||
|
|
||||||
|
// The AnimatePresence mode must be wait - else framer can get confused if you spam the toggle button
|
||||||
|
return (
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{shouldShowViewer && (
|
||||||
|
<Flex
|
||||||
|
key="imageViewer"
|
||||||
|
as={motion.div}
|
||||||
|
initial={initial}
|
||||||
|
animate={animate}
|
||||||
|
exit={exit}
|
||||||
|
layerStyle="first"
|
||||||
|
borderRadius="base"
|
||||||
|
position="absolute"
|
||||||
|
flexDirection="column"
|
||||||
|
top={0}
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
left={0}
|
||||||
|
p={2}
|
||||||
|
rowGap={4}
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
zIndex={10} // reactflow puts its minimap at 5, so we need to be above that
|
||||||
|
>
|
||||||
|
<Flex w="full" gap={2}>
|
||||||
|
<Flex flex={1} justifyContent="center">
|
||||||
|
<Flex gap={2} marginInlineEnd="auto">
|
||||||
|
<ToggleProgressButton />
|
||||||
|
<ToggleMetadataViewerButton />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex flex={1} gap={2} justifyContent="center">
|
||||||
|
<CurrentImageButtons />
|
||||||
|
</Flex>
|
||||||
|
<Flex flex={1} justifyContent="center">
|
||||||
|
<Flex gap={2} marginInlineStart="auto">
|
||||||
|
<EditorButton />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<CurrentImagePreview />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ImageViewer.displayName = 'ImageViewer';
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user