Compare commits

..

46 Commits

Author SHA1 Message Date
57dafd294d {release} v3.6.2
## What type of PR is this? (check all applicable)

Invoke v3.6.2 release


## Have you discussed this change with the InvokeAI team?
- [X] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [X] Yes
- [ ] No


## Description
Invoke v3.6.2

## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

[InvokeAI-installer-v3.6.2.zip](https://github.com/invoke-ai/InvokeAI/files/14046191/InvokeAI-installer-v3.6.2.zip)
2024-01-24 22:05:10 -05:00
e611baa4b4 {release} v3.6.2 2024-01-24 21:40:03 -05:00
fc448d5b6d feat(ui): handle proxy configs rewriting paths
We can't assume that the base URL is `host:port/` - it could be `host:port/some/path/`. Make the path handling dynamic to account for this.
2024-01-25 13:29:56 +11:00
e59954f956 fix workflow updating (#5567)
* retain id through workflow state so that we correctly update or save new

* lint

---------

Co-authored-by: Mary Hipp <maryhipp@Marys-MacBook-Air.local>
2024-01-24 16:10:19 -05:00
86c857b9c2 {release} v3.6.1 (#5564)
## What type of PR is this? (check all applicable)

Invoke 3.6.1 release

## QA Instructions, Screenshots, Recordings

[InvokeAI-installer-v3.6.1.zip](https://github.com/invoke-ai/InvokeAI/files/14041411/InvokeAI-installer-v3.6.1.zip)

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Merge Plan

This PR can be merged when approved

## [optional] Are there any post deployment tasks we need to perform?
PyPI Release & GitHub Release
2024-01-24 12:31:10 -05:00
0a13d7d2c7 {release} v3.6.1 2024-01-24 11:27:36 -05:00
68da5c6d22 feat: Add Depth Anything PreProcessor (#5548)
## What type of PR is this? (check all applicable)

- [x] Feature

## Have you discussed this change with the InvokeAI team?
- [x] Yes

## Have you updated all relevant documentation?
- [x] No


## Description

- This adds the newly released Depth Anything to InvokeAI. A new node
`Depth Anything Processor` has been added to generate depth maps using
this new technique. https://depth-anything.github.io

- All related checkpoints will be downloaded automatically on first
boot. The `DinoV2` models will be loaded to your torch cache dir and the
checkpoints pertaining to Depth Anything will be downloaded to
`any/annotators/depth_anything`.

- Alternatively you can find the checkpoints here and download them to
that folder:
https://huggingface.co/spaces/LiheYoung/Depth-Anything/tree/main/checkpoints

- This depth map can be used with any depth ControlNet model out there
but the folks at DepthAnything have also released a custom fine tuned
ControlNet model. From my limited testing, I still prefer the original
depth model because this one seems to be producing weird artifacts. Not
sure if that is a specific problem to Invoke or just the model itself.
I'll test more later. Place these in your controlnet folder like your
other ControlNets. You can get that here:
https://huggingface.co/spaces/LiheYoung/Depth-Anything/tree/main/checkpoints_controlnet

- Also available in the LinearUI

- DepthAnything has three models `large`, `base` and `small` -- I've
defaulted the processor to small but a user can change to the large
model if they wish to do so. Small is way faster but obviously somewhat
of a lesser quality.

- DepthAnything is now the default processor for depth controlnet
models.

## Screenshots


![opera_o3jHnWxVRi](https://github.com/invoke-ai/InvokeAI/assets/54517381/573c66f3-1492-45b0-b6df-25756f5e1d1a)

## Merge Plan

DO NOT MERGE YET. Test it first and I'm sure the model caching can be
done better. Coz I don't think I've done that at all. I would appreciate
if @brandonrising or @lstein or anyone can take a look at that part of
it.
2024-01-24 19:14:34 +05:30
f82744b95e fix: linting issues 2024-01-24 18:45:54 +05:30
5a67bc68a1 Merge branch 'main' into depth-anything 2024-01-23 22:31:19 -06:00
61cf4d4c70 feat: "Remix Image" option on images (#5553)
* feat:  "Remix Image" option on images

Adds a new "remix image" option where applicable, recalls all metadata except the seed

* refactor: 🚨 lint code

* feat:  "Remix Image" option on images

Adds a new "remix image" option where applicable, recalls all metadata except the seed

* refactor: 🚨 lint code

* feat:  add new remix hotkey to hotkeys modal

---------

Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
2024-01-24 00:45:30 +00:00
9d20a2d5a3 Update huggingface deps to their lastest versions 2024-01-24 11:14:21 +11:00
8b0ac451e3 translationBot(ui): update translation (Italian)
Currently translated at 97.3% (1378 of 1415 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2024-01-24 11:05:17 +11:00
470dbe75a2 translationBot(ui): update translation (German)
Currently translated at 60.0% (850 of 1415 strings)

Co-authored-by: Alexander Eichhorn <pfannkuchensack@einfach-doof.de>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/
Translation: InvokeAI/Web UI
2024-01-24 11:05:17 +11:00
b7d19b8130 add project as category to back-end 2024-01-24 10:59:04 +11:00
3dc13221d8 add project as a workflow category in the front-end 2024-01-24 10:59:04 +11:00
35184dbd86 fix: incorrect local file path 2024-01-24 03:37:16 +05:30
0868fc2558 Merge branch 'main' into depth-anything 2024-01-24 03:36:25 +05:30
92fb09c4df fix: Move the models to any folder to avoid boot warnings 2024-01-24 03:35:37 +05:30
b4cf5496b6 fix(ui): handle model names with spaces
Remove `trim()` from model identifier schema, which prevented parsed model identifiers from matching.

The root issue here is that model names are identifiers. This will be resolved in the model manager refactor.

Closes #5556
2024-01-23 15:48:10 -06:00
a0e68705dd feat(ui): improved dynamic prompts behaviour
- Bump `@invoke-ai/ui` for updated styles
- Update regex to parse prompts with newlines
- Update styling of overlay button when prompt has an error
- Fix bug where loading and error state sometimes weren't cleared
2024-01-23 15:26:12 -06:00
7cb49e65bd feat: Add Resolution to DepthAnything 2024-01-23 14:13:50 -06:00
39fedb090b feat: Make depth anything the default processor for depth controlnet 2024-01-23 14:13:50 -06:00
f36a691219 feat: Make the depth anything small model the default 2024-01-23 14:13:50 -06:00
6a2eb1d2e4 fix: Change the path of the annotator folder to annotators
Just making this change in case there are other models added to the folder in the future
2024-01-23 14:13:50 -06:00
13123daa3f feat: Add DepthAnything to Linear UI 2024-01-23 14:13:50 -06:00
c859eb865e fix: lint & other minor issues 2024-01-23 14:13:50 -06:00
8f5e2cbcc7 feat: Add Depth Anything PreProcessor 2024-01-23 14:13:50 -06:00
2aed6e2dba fix(ui): duplicate "base model" in merge UI
closes #5505
2024-01-23 14:13:18 -06:00
52b51a6088 fix(ui): recall/use size sets aspect ratio correctly
Added a new action that resets the aspect ratio when dispatched.

Closes #5456
2024-01-23 14:13:18 -06:00
52b24e01e2 feat(ui): remove chakra as direct dependency
Moved a number of things to `@invoke-ai/ui` to support this.

Unfortunately, the bundle size has increased a bit. I will work on that later.
2024-01-23 14:13:18 -06:00
1178fd8bd3 fix(ui): fix styling of some form elements 2024-01-23 14:13:18 -06:00
a0187cc9df fix(ui): remove unused import in storybook 2024-01-23 14:13:18 -06:00
2f656cc357 fix(ui): fix field context menu jank
Closes #5551
2024-01-23 14:13:18 -06:00
71f9ac9985 feat(ui): scollable areas support x-axis scrolling
Closes #5490
2024-01-23 14:13:18 -06:00
8bbdfc45fa fix(ui): increase size of control adapters advanced toggle button 2024-01-23 14:13:18 -06:00
3cbb1a7671 chore(ui): bump @invoke-ai/ui
This includes a minor enhancement, increasing the contrast on tabs.
2024-01-23 14:13:18 -06:00
b74e0de74a tidy(ui): remove unused state from uiSlice 2024-01-23 14:13:18 -06:00
e7e7793896 feat(ui): remember open/closed state of accordions/expanders 2024-01-23 14:13:18 -06:00
504bdac14a tidy(ui): remove unused state from uiSlice 2024-01-23 14:13:18 -06:00
b76d2cd716 fix(ui): handle base model compat when recalling parameters
We had a one-behind issue with recalling metadata items that had a model.

For example, when recalling LoRAs, we check against the current main model to decide whether or not the requested LoRA is compatible and may be recalled.

When recalling all params, we are often also recalling the main model, but the compat logic didn't compare against this new main model.

The logic is updated to check against the new main model, if one is being set.

Closes #5512
2024-01-23 14:13:18 -06:00
022b32c724 fix(ui): reset clip skip to 0 if new model is sdxl
Clip skip wasn't actually used in SDXL graphs so enabling it didn't do anything, just a UI quirk.

Closes #5508
2024-01-23 14:13:18 -06:00
653b820da1 tidy(ui): clearer variable names in modelSelected listener 2024-01-23 14:13:18 -06:00
4ba0bf4dcf docs(ui): update README (#5473)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [ ] Bug Fix
- [ ] Optimization
- [x] Documentation Update
- [ ] Community Node Submission

## Description

Update UI README


## Merge Plan

This PR can be merged when approved

<!--
A merge plan describes how this PR should be handled after it is
approved.

Example merge plans:
- "This PR can be merged when approved"
- "This must be squash-merged when approved"
- "DO NOT MERGE - I will rebase and tidy commits before merging"
- "#dev-chat on discord needs to be advised of this change when it is
merged"

A merge plan is particularly important for large PRs or PRs that touch
the
database in any way.
-->
2024-01-23 18:47:24 +05:30
5e4daf4bc6 docs: remove separate frontend docs and link to repo
The frontend docs should just be in the frontend. This is a standard practice for monorepos with developer information for specific packages within the monorepo.
2024-01-23 18:04:41 +11:00
7e0713c869 docs(ui): fix typo 2024-01-23 18:04:41 +11:00
099d516ac0 docs(ui): update README 2024-01-23 18:04:41 +11:00
75 changed files with 2913 additions and 703 deletions

40
.github/pr_labels.yml vendored
View File

@ -1,40 +0,0 @@
Root:
- changed-files:
- any-glob-to-any-file: '*'
PythonDeps:
- changed-files:
- any-glob-to-any-file: 'pyproject.toml'
Python:
- changed-files:
- any-glob-to-any-file:
- 'invokeai/**'
- '!invokeai/frontend/web/**'
- 'tests/**'
Invocations:
- changed-files:
- any-glob-to-any-file: 'invokeai/app/invocations/**'
Backend:
- changed-files:
- any-glob-to-any-file: 'invokeai/backend/**'
Api:
- changed-files:
- any-glob-to-any-file: 'invokeai/app/api/**'
Services:
- changed-files:
- any-glob-to-any-file: 'invokeai/app/services/**'
FrontendDeps:
- changed-files:
- any-glob-to-any-file:
- '**/*/package.json'
- '**/*/pnpm-lock.yaml'
Frontend:
- changed-files:
- any-glob-to-any-file: 'invokeai/frontend/web/**'

View File

@ -1,16 +0,0 @@
name: "Pull Request Labeler"
on:
- pull_request_target
jobs:
labeler:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/labeler@v5
with:
configuration-path: .github/pr_labels.yml

View File

@ -1,76 +0,0 @@
# Contributing to the Frontend
# InvokeAI Web UI
- [InvokeAI Web UI](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#invokeai-web-ui)
- [Stack](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#stack)
- [Contributing](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#contributing)
- [Dev Environment](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#dev-environment)
- [Production builds](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#production-builds)
The UI is a fairly straightforward Typescript React app, with the Unified Canvas being more complex.
Code is located in `invokeai/frontend/web/` for review.
## Stack
State management is Redux via [Redux Toolkit](https://github.com/reduxjs/redux-toolkit). We lean heavily on RTK:
- `createAsyncThunk` for HTTP requests
- `createEntityAdapter` for fetching images and models
- `createListenerMiddleware` for workflows
The API client and associated types are generated from the OpenAPI schema. See API_CLIENT.md.
Communication with server is a mix of HTTP and [socket.io](https://github.com/socketio/socket.io-client) (with a simple socket.io redux middleware to help).
[Chakra-UI](https://github.com/chakra-ui/chakra-ui) & [Mantine](https://github.com/mantinedev/mantine) for components and styling.
[Konva](https://github.com/konvajs/react-konva) for the canvas, but we are pushing the limits of what is feasible with it (and HTML canvas in general). We plan to rebuild it with [PixiJS](https://github.com/pixijs/pixijs) to take advantage of WebGL's improved raster handling.
[Vite](https://vitejs.dev/) for bundling.
Localisation is via [i18next](https://github.com/i18next/react-i18next), but translation happens on our [Weblate](https://hosted.weblate.org/engage/invokeai/) project. Only the English source strings should be changed on this repo.
## Contributing
Thanks for your interest in contributing to the InvokeAI Web UI!
We encourage you to ping @psychedelicious and @blessedcoolant on [Discord](https://discord.gg/ZmtBAhwWhy) if you want to contribute, just to touch base and ensure your work doesn't conflict with anything else going on. The project is very active.
### Dev Environment
**Setup**
1. Install [node](https://nodejs.org/en/download/). You can confirm node is installed with:
```bash
node --version
```
2. Install [pnpm](https://pnpm.io/) and confirm it is installed by running this:
```bash
npm install --global pnpm
pnpm --version
```
From `invokeai/frontend/web/` run `pnpm install` to get everything set up.
Start everything in dev mode:
1. Ensure your virtual environment is running
2. Start the dev server: `pnpm dev`
3. Start the InvokeAI Nodes backend: `python scripts/invokeai-web.py # run from the repo root`
4. Point your browser to the dev server address e.g. [http://localhost:5173/](http://localhost:5173/)
### VSCode Remote Dev
We've noticed an intermittent issue with the VSCode Remote Dev port forwarding. If you use this feature of VSCode, you may intermittently click the Invoke button and then get nothing until the request times out. Suggest disabling the IDE's port forwarding feature and doing it manually via SSH:
`ssh -L 9090:localhost:9090 -L 5173:localhost:5173 user@host`
### Production builds
For a number of technical and logistical reasons, we need to commit UI build artefacts to the repo.
If you submit a PR, there is a good chance we will ask you to include a separate commit with a build of the app.
To build for production, run `pnpm build`.

View File

@ -12,7 +12,7 @@ To get started, take a look at our [new contributors checklist](newContributorCh
Once you're setup, for more information, you can review the documentation specific to your area of interest:
* #### [InvokeAI Architecure](../ARCHITECTURE.md)
* #### [Frontend Documentation](./contributingToFrontend.md)
* #### [Frontend Documentation](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web)
* #### [Node Documentation](../INVOCATIONS.md)
* #### [Local Development](../LOCAL_DEVELOPMENT.md)

View File

@ -24,7 +24,6 @@ download_queue_router = APIRouter(prefix="/v1/download_queue", tags=["download_q
)
async def list_downloads() -> List[DownloadJob]:
"""Get a list of active and inactive jobs."""
print("test")
queue = ApiDependencies.invoker.services.download_queue
return queue.list_jobs()

View File

@ -30,6 +30,7 @@ from invokeai.app.invocations.primitives import ImageField, ImageOutput
from invokeai.app.invocations.util import validate_begin_end_step, validate_weights
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
from invokeai.app.shared.fields import FieldDescriptions
from invokeai.backend.image_util.depth_anything import DepthAnythingDetector
from ...backend.model_management import BaseModelType
from .baseinvocation import (
@ -602,3 +603,33 @@ class ColorMapImageProcessorInvocation(ImageProcessorInvocation):
color_map = cv2.resize(color_map, (width, height), interpolation=cv2.INTER_NEAREST)
color_map = Image.fromarray(color_map)
return color_map
DEPTH_ANYTHING_MODEL_SIZES = Literal["large", "base", "small"]
@invocation(
"depth_anything_image_processor",
title="Depth Anything Processor",
tags=["controlnet", "depth", "depth anything"],
category="controlnet",
version="1.0.0",
)
class DepthAnythingImageProcessorInvocation(ImageProcessorInvocation):
"""Generates a depth map based on the Depth Anything algorithm"""
model_size: DEPTH_ANYTHING_MODEL_SIZES = InputField(
default="small", description="The size of the depth model to use"
)
resolution: int = InputField(default=512, ge=64, multiple_of=64, description=FieldDescriptions.image_res)
offload: bool = InputField(default=False)
def run_processor(self, image):
depth_anything_detector = DepthAnythingDetector()
depth_anything_detector.load_model(model_size=self.model_size)
if image.mode == "RGBA":
image = image.convert("RGB")
processed_image = depth_anything_detector(image=image, resolution=self.resolution, offload=self.offload)
return processed_image

View File

@ -75,8 +75,6 @@ from .model import ModelInfo, UNetField, VaeField
if choose_torch_device() == torch.device("mps"):
from torch import mps
print("test")
DEFAULT_PRECISION = choose_precision(choose_torch_device())
SAMPLER_NAME_VALUES = Literal[tuple(SCHEDULER_MAP.keys())]

View File

@ -23,7 +23,6 @@ class DefaultInvocationProcessor(InvocationProcessorABC):
self.__threadLimit = BoundedSemaphore(1)
self.__invoker = invoker
self.__stop_event = Event()
print("test")
self.__invoker_thread = Thread(
name="invoker_processor",
target=self.__process,

View File

@ -31,6 +31,7 @@ class WorkflowRecordOrderBy(str, Enum, metaclass=MetaEnum):
class WorkflowCategory(str, Enum, metaclass=MetaEnum):
User = "user"
Default = "default"
Project = "project"
class WorkflowMeta(BaseModel):

View File

@ -0,0 +1,109 @@
import pathlib
from typing import Literal, Union
import cv2
import numpy as np
import torch
import torch.nn.functional as F
from einops import repeat
from PIL import Image
from torchvision.transforms import Compose
from invokeai.app.services.config.config_default import InvokeAIAppConfig
from invokeai.backend.image_util.depth_anything.model.dpt import DPT_DINOv2
from invokeai.backend.image_util.depth_anything.utilities.util import NormalizeImage, PrepareForNet, Resize
from invokeai.backend.util.devices import choose_torch_device
from invokeai.backend.util.util import download_with_progress_bar
config = InvokeAIAppConfig.get_config()
DEPTH_ANYTHING_MODELS = {
"large": {
"url": "https://huggingface.co/spaces/LiheYoung/Depth-Anything/resolve/main/checkpoints/depth_anything_vitl14.pth?download=true",
"local": "any/annotators/depth_anything/depth_anything_vitl14.pth",
},
"base": {
"url": "https://huggingface.co/spaces/LiheYoung/Depth-Anything/resolve/main/checkpoints/depth_anything_vitb14.pth?download=true",
"local": "any/annotators/depth_anything/depth_anything_vitb14.pth",
},
"small": {
"url": "https://huggingface.co/spaces/LiheYoung/Depth-Anything/resolve/main/checkpoints/depth_anything_vits14.pth?download=true",
"local": "any/annotators/depth_anything/depth_anything_vits14.pth",
},
}
transform = Compose(
[
Resize(
width=518,
height=518,
resize_target=False,
keep_aspect_ratio=True,
ensure_multiple_of=14,
resize_method="lower_bound",
image_interpolation_method=cv2.INTER_CUBIC,
),
NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
PrepareForNet(),
]
)
class DepthAnythingDetector:
def __init__(self) -> None:
self.model = None
self.model_size: Union[Literal["large", "base", "small"], None] = None
def load_model(self, model_size=Literal["large", "base", "small"]):
DEPTH_ANYTHING_MODEL_PATH = pathlib.Path(config.models_path / DEPTH_ANYTHING_MODELS[model_size]["local"])
if not DEPTH_ANYTHING_MODEL_PATH.exists():
download_with_progress_bar(DEPTH_ANYTHING_MODELS[model_size]["url"], DEPTH_ANYTHING_MODEL_PATH)
if not self.model or model_size != self.model_size:
del self.model
self.model_size = model_size
match self.model_size:
case "small":
self.model = DPT_DINOv2(encoder="vits", features=64, out_channels=[48, 96, 192, 384])
case "base":
self.model = DPT_DINOv2(encoder="vitb", features=128, out_channels=[96, 192, 384, 768])
case "large":
self.model = DPT_DINOv2(encoder="vitl", features=256, out_channels=[256, 512, 1024, 1024])
case _:
raise TypeError("Not a supported model")
self.model.load_state_dict(torch.load(DEPTH_ANYTHING_MODEL_PATH.as_posix(), map_location="cpu"))
self.model.eval()
self.model.to(choose_torch_device())
return self.model
def to(self, device):
self.model.to(device)
return self
def __call__(self, image, resolution=512, offload=False):
image = np.array(image, dtype=np.uint8)
image = image[:, :, ::-1] / 255.0
image_height, image_width = image.shape[:2]
image = transform({"image": image})["image"]
image = torch.from_numpy(image).unsqueeze(0).to(choose_torch_device())
with torch.no_grad():
depth = self.model(image)
depth = F.interpolate(depth[None], (image_height, image_width), mode="bilinear", align_corners=False)[0, 0]
depth = (depth - depth.min()) / (depth.max() - depth.min()) * 255.0
depth_map = repeat(depth, "h w -> h w 3").cpu().numpy().astype(np.uint8)
depth_map = Image.fromarray(depth_map)
new_height = int(image_height * (resolution / image_width))
depth_map = depth_map.resize((resolution, new_height))
if offload:
del self.model
return depth_map

View File

@ -0,0 +1,145 @@
import torch.nn as nn
def _make_scratch(in_shape, out_shape, groups=1, expand=False):
scratch = nn.Module()
out_shape1 = out_shape
out_shape2 = out_shape
out_shape3 = out_shape
if len(in_shape) >= 4:
out_shape4 = out_shape
if expand:
out_shape1 = out_shape
out_shape2 = out_shape * 2
out_shape3 = out_shape * 4
if len(in_shape) >= 4:
out_shape4 = out_shape * 8
scratch.layer1_rn = nn.Conv2d(
in_shape[0], out_shape1, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
)
scratch.layer2_rn = nn.Conv2d(
in_shape[1], out_shape2, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
)
scratch.layer3_rn = nn.Conv2d(
in_shape[2], out_shape3, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
)
if len(in_shape) >= 4:
scratch.layer4_rn = nn.Conv2d(
in_shape[3], out_shape4, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
)
return scratch
class ResidualConvUnit(nn.Module):
"""Residual convolution module."""
def __init__(self, features, activation, bn):
"""Init.
Args:
features (int): number of features
"""
super().__init__()
self.bn = bn
self.groups = 1
self.conv1 = nn.Conv2d(features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups)
self.conv2 = nn.Conv2d(features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups)
if self.bn:
self.bn1 = nn.BatchNorm2d(features)
self.bn2 = nn.BatchNorm2d(features)
self.activation = activation
self.skip_add = nn.quantized.FloatFunctional()
def forward(self, x):
"""Forward pass.
Args:
x (tensor): input
Returns:
tensor: output
"""
out = self.activation(x)
out = self.conv1(out)
if self.bn:
out = self.bn1(out)
out = self.activation(out)
out = self.conv2(out)
if self.bn:
out = self.bn2(out)
if self.groups > 1:
out = self.conv_merge(out)
return self.skip_add.add(out, x)
class FeatureFusionBlock(nn.Module):
"""Feature fusion block."""
def __init__(self, features, activation, deconv=False, bn=False, expand=False, align_corners=True, size=None):
"""Init.
Args:
features (int): number of features
"""
super(FeatureFusionBlock, self).__init__()
self.deconv = deconv
self.align_corners = align_corners
self.groups = 1
self.expand = expand
out_features = features
if self.expand:
out_features = features // 2
self.out_conv = nn.Conv2d(features, out_features, kernel_size=1, stride=1, padding=0, bias=True, groups=1)
self.resConfUnit1 = ResidualConvUnit(features, activation, bn)
self.resConfUnit2 = ResidualConvUnit(features, activation, bn)
self.skip_add = nn.quantized.FloatFunctional()
self.size = size
def forward(self, *xs, size=None):
"""Forward pass.
Returns:
tensor: output
"""
output = xs[0]
if len(xs) == 2:
res = self.resConfUnit1(xs[1])
output = self.skip_add.add(output, res)
output = self.resConfUnit2(output)
if (size is None) and (self.size is None):
modifier = {"scale_factor": 2}
elif size is None:
modifier = {"size": self.size}
else:
modifier = {"size": size}
output = nn.functional.interpolate(output, **modifier, mode="bilinear", align_corners=self.align_corners)
output = self.out_conv(output)
return output

View File

@ -0,0 +1,183 @@
from pathlib import Path
import torch
import torch.nn as nn
import torch.nn.functional as F
from .blocks import FeatureFusionBlock, _make_scratch
torchhub_path = Path(__file__).parent.parent / "torchhub"
def _make_fusion_block(features, use_bn, size=None):
return FeatureFusionBlock(
features,
nn.ReLU(False),
deconv=False,
bn=use_bn,
expand=False,
align_corners=True,
size=size,
)
class DPTHead(nn.Module):
def __init__(self, nclass, in_channels, features, out_channels, use_bn=False, use_clstoken=False):
super(DPTHead, self).__init__()
self.nclass = nclass
self.use_clstoken = use_clstoken
self.projects = nn.ModuleList(
[
nn.Conv2d(
in_channels=in_channels,
out_channels=out_channel,
kernel_size=1,
stride=1,
padding=0,
)
for out_channel in out_channels
]
)
self.resize_layers = nn.ModuleList(
[
nn.ConvTranspose2d(
in_channels=out_channels[0], out_channels=out_channels[0], kernel_size=4, stride=4, padding=0
),
nn.ConvTranspose2d(
in_channels=out_channels[1], out_channels=out_channels[1], kernel_size=2, stride=2, padding=0
),
nn.Identity(),
nn.Conv2d(
in_channels=out_channels[3], out_channels=out_channels[3], kernel_size=3, stride=2, padding=1
),
]
)
if use_clstoken:
self.readout_projects = nn.ModuleList()
for _ in range(len(self.projects)):
self.readout_projects.append(nn.Sequential(nn.Linear(2 * in_channels, in_channels), nn.GELU()))
self.scratch = _make_scratch(
out_channels,
features,
groups=1,
expand=False,
)
self.scratch.stem_transpose = None
self.scratch.refinenet1 = _make_fusion_block(features, use_bn)
self.scratch.refinenet2 = _make_fusion_block(features, use_bn)
self.scratch.refinenet3 = _make_fusion_block(features, use_bn)
self.scratch.refinenet4 = _make_fusion_block(features, use_bn)
head_features_1 = features
head_features_2 = 32
if nclass > 1:
self.scratch.output_conv = nn.Sequential(
nn.Conv2d(head_features_1, head_features_1, kernel_size=3, stride=1, padding=1),
nn.ReLU(True),
nn.Conv2d(head_features_1, nclass, kernel_size=1, stride=1, padding=0),
)
else:
self.scratch.output_conv1 = nn.Conv2d(
head_features_1, head_features_1 // 2, kernel_size=3, stride=1, padding=1
)
self.scratch.output_conv2 = nn.Sequential(
nn.Conv2d(head_features_1 // 2, head_features_2, kernel_size=3, stride=1, padding=1),
nn.ReLU(True),
nn.Conv2d(head_features_2, 1, kernel_size=1, stride=1, padding=0),
nn.ReLU(True),
nn.Identity(),
)
def forward(self, out_features, patch_h, patch_w):
out = []
for i, x in enumerate(out_features):
if self.use_clstoken:
x, cls_token = x[0], x[1]
readout = cls_token.unsqueeze(1).expand_as(x)
x = self.readout_projects[i](torch.cat((x, readout), -1))
else:
x = x[0]
x = x.permute(0, 2, 1).reshape((x.shape[0], x.shape[-1], patch_h, patch_w))
x = self.projects[i](x)
x = self.resize_layers[i](x)
out.append(x)
layer_1, layer_2, layer_3, layer_4 = out
layer_1_rn = self.scratch.layer1_rn(layer_1)
layer_2_rn = self.scratch.layer2_rn(layer_2)
layer_3_rn = self.scratch.layer3_rn(layer_3)
layer_4_rn = self.scratch.layer4_rn(layer_4)
path_4 = self.scratch.refinenet4(layer_4_rn, size=layer_3_rn.shape[2:])
path_3 = self.scratch.refinenet3(path_4, layer_3_rn, size=layer_2_rn.shape[2:])
path_2 = self.scratch.refinenet2(path_3, layer_2_rn, size=layer_1_rn.shape[2:])
path_1 = self.scratch.refinenet1(path_2, layer_1_rn)
out = self.scratch.output_conv1(path_1)
out = F.interpolate(out, (int(patch_h * 14), int(patch_w * 14)), mode="bilinear", align_corners=True)
out = self.scratch.output_conv2(out)
return out
class DPT_DINOv2(nn.Module):
def __init__(
self,
features,
out_channels,
encoder="vitl",
use_bn=False,
use_clstoken=False,
):
super(DPT_DINOv2, self).__init__()
assert encoder in ["vits", "vitb", "vitl"]
# # in case the Internet connection is not stable, please load the DINOv2 locally
# if use_local:
# self.pretrained = torch.hub.load(
# torchhub_path / "facebookresearch_dinov2_main",
# "dinov2_{:}14".format(encoder),
# source="local",
# pretrained=False,
# )
# else:
# self.pretrained = torch.hub.load(
# "facebookresearch/dinov2",
# "dinov2_{:}14".format(encoder),
# )
self.pretrained = torch.hub.load(
"facebookresearch/dinov2",
"dinov2_{:}14".format(encoder),
)
dim = self.pretrained.blocks[0].attn.qkv.in_features
self.depth_head = DPTHead(1, dim, features, out_channels=out_channels, use_bn=use_bn, use_clstoken=use_clstoken)
def forward(self, x):
h, w = x.shape[-2:]
features = self.pretrained.get_intermediate_layers(x, 4, return_class_token=True)
patch_h, patch_w = h // 14, w // 14
depth = self.depth_head(features, patch_h, patch_w)
depth = F.interpolate(depth, size=(h, w), mode="bilinear", align_corners=True)
depth = F.relu(depth)
return depth.squeeze(1)

View File

@ -0,0 +1,227 @@
import math
import cv2
import numpy as np
import torch
import torch.nn.functional as F
def apply_min_size(sample, size, image_interpolation_method=cv2.INTER_AREA):
"""Rezise the sample to ensure the given size. Keeps aspect ratio.
Args:
sample (dict): sample
size (tuple): image size
Returns:
tuple: new size
"""
shape = list(sample["disparity"].shape)
if shape[0] >= size[0] and shape[1] >= size[1]:
return sample
scale = [0, 0]
scale[0] = size[0] / shape[0]
scale[1] = size[1] / shape[1]
scale = max(scale)
shape[0] = math.ceil(scale * shape[0])
shape[1] = math.ceil(scale * shape[1])
# resize
sample["image"] = cv2.resize(sample["image"], tuple(shape[::-1]), interpolation=image_interpolation_method)
sample["disparity"] = cv2.resize(sample["disparity"], tuple(shape[::-1]), interpolation=cv2.INTER_NEAREST)
sample["mask"] = cv2.resize(
sample["mask"].astype(np.float32),
tuple(shape[::-1]),
interpolation=cv2.INTER_NEAREST,
)
sample["mask"] = sample["mask"].astype(bool)
return tuple(shape)
class Resize(object):
"""Resize sample to given size (width, height)."""
def __init__(
self,
width,
height,
resize_target=True,
keep_aspect_ratio=False,
ensure_multiple_of=1,
resize_method="lower_bound",
image_interpolation_method=cv2.INTER_AREA,
):
"""Init.
Args:
width (int): desired output width
height (int): desired output height
resize_target (bool, optional):
True: Resize the full sample (image, mask, target).
False: Resize image only.
Defaults to True.
keep_aspect_ratio (bool, optional):
True: Keep the aspect ratio of the input sample.
Output sample might not have the given width and height, and
resize behaviour depends on the parameter 'resize_method'.
Defaults to False.
ensure_multiple_of (int, optional):
Output width and height is constrained to be multiple of this parameter.
Defaults to 1.
resize_method (str, optional):
"lower_bound": Output will be at least as large as the given size.
"upper_bound": Output will be at max as large as the given size. (Output size might be smaller
than given size.)
"minimal": Scale as least as possible. (Output size might be smaller than given size.)
Defaults to "lower_bound".
"""
self.__width = width
self.__height = height
self.__resize_target = resize_target
self.__keep_aspect_ratio = keep_aspect_ratio
self.__multiple_of = ensure_multiple_of
self.__resize_method = resize_method
self.__image_interpolation_method = image_interpolation_method
def constrain_to_multiple_of(self, x, min_val=0, max_val=None):
y = (np.round(x / self.__multiple_of) * self.__multiple_of).astype(int)
if max_val is not None and y > max_val:
y = (np.floor(x / self.__multiple_of) * self.__multiple_of).astype(int)
if y < min_val:
y = (np.ceil(x / self.__multiple_of) * self.__multiple_of).astype(int)
return y
def get_size(self, width, height):
# determine new height and width
scale_height = self.__height / height
scale_width = self.__width / width
if self.__keep_aspect_ratio:
if self.__resize_method == "lower_bound":
# scale such that output size is lower bound
if scale_width > scale_height:
# fit width
scale_height = scale_width
else:
# fit height
scale_width = scale_height
elif self.__resize_method == "upper_bound":
# scale such that output size is upper bound
if scale_width < scale_height:
# fit width
scale_height = scale_width
else:
# fit height
scale_width = scale_height
elif self.__resize_method == "minimal":
# scale as least as possbile
if abs(1 - scale_width) < abs(1 - scale_height):
# fit width
scale_height = scale_width
else:
# fit height
scale_width = scale_height
else:
raise ValueError(f"resize_method {self.__resize_method} not implemented")
if self.__resize_method == "lower_bound":
new_height = self.constrain_to_multiple_of(scale_height * height, min_val=self.__height)
new_width = self.constrain_to_multiple_of(scale_width * width, min_val=self.__width)
elif self.__resize_method == "upper_bound":
new_height = self.constrain_to_multiple_of(scale_height * height, max_val=self.__height)
new_width = self.constrain_to_multiple_of(scale_width * width, max_val=self.__width)
elif self.__resize_method == "minimal":
new_height = self.constrain_to_multiple_of(scale_height * height)
new_width = self.constrain_to_multiple_of(scale_width * width)
else:
raise ValueError(f"resize_method {self.__resize_method} not implemented")
return (new_width, new_height)
def __call__(self, sample):
width, height = self.get_size(sample["image"].shape[1], sample["image"].shape[0])
# resize sample
sample["image"] = cv2.resize(
sample["image"],
(width, height),
interpolation=self.__image_interpolation_method,
)
if self.__resize_target:
if "disparity" in sample:
sample["disparity"] = cv2.resize(
sample["disparity"],
(width, height),
interpolation=cv2.INTER_NEAREST,
)
if "depth" in sample:
sample["depth"] = cv2.resize(sample["depth"], (width, height), interpolation=cv2.INTER_NEAREST)
if "semseg_mask" in sample:
# sample["semseg_mask"] = cv2.resize(
# sample["semseg_mask"], (width, height), interpolation=cv2.INTER_NEAREST
# )
sample["semseg_mask"] = F.interpolate(
torch.from_numpy(sample["semseg_mask"]).float()[None, None, ...], (height, width), mode="nearest"
).numpy()[0, 0]
if "mask" in sample:
sample["mask"] = cv2.resize(
sample["mask"].astype(np.float32),
(width, height),
interpolation=cv2.INTER_NEAREST,
)
# sample["mask"] = sample["mask"].astype(bool)
# print(sample['image'].shape, sample['depth'].shape)
return sample
class NormalizeImage(object):
"""Normlize image by given mean and std."""
def __init__(self, mean, std):
self.__mean = mean
self.__std = std
def __call__(self, sample):
sample["image"] = (sample["image"] - self.__mean) / self.__std
return sample
class PrepareForNet(object):
"""Prepare sample for usage as network input."""
def __init__(self):
pass
def __call__(self, sample):
image = np.transpose(sample["image"], (2, 0, 1))
sample["image"] = np.ascontiguousarray(image).astype(np.float32)
if "mask" in sample:
sample["mask"] = sample["mask"].astype(np.float32)
sample["mask"] = np.ascontiguousarray(sample["mask"])
if "depth" in sample:
depth = sample["depth"].astype(np.float32)
sample["depth"] = np.ascontiguousarray(depth)
if "semseg_mask" in sample:
sample["semseg_mask"] = sample["semseg_mask"].astype(np.float32)
sample["semseg_mask"] = np.ascontiguousarray(sample["semseg_mask"])
return sample

View File

@ -113,7 +113,7 @@ class ModelPatcher:
for layer_key, layer in lora.layers.items():
if not layer_key.startswith(prefix):
continue
print("test")
# TODO(ryand): A non-negligible amount of time is currently spent resolving LoRA keys. This
# should be improved in the following ways:
# 1. The key mapping could be more-efficiently pre-computed. This would save time every time a

View File

@ -6,7 +6,6 @@ import { Provider } from 'react-redux';
import ThemeLocaleProvider from '../src/app/components/ThemeLocaleProvider';
import { $baseUrl } from '../src/app/store/nanostores/baseUrl';
import { createStore } from '../src/app/store/store';
import { Container } from '@chakra-ui/react';
// TODO: Disabled for IDE performance issues with our translation JSON
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore

View File

@ -1,79 +1,51 @@
# InvokeAI Web UI
# Invoke UI
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->
<!-- code_chunk_output -->
- [InvokeAI Web UI](#invokeai-web-ui)
- [Invoke UI](#invoke-ui)
- [Core Libraries](#core-libraries)
- [Redux Toolkit](#redux-toolkit)
- [Socket\.IO](#socketio)
- [Chakra UI](#chakra-ui)
- [KonvaJS](#konvajs)
- [Vite](#vite)
- [i18next & Weblate](#i18next--weblate)
- [openapi-typescript](#openapi-typescript)
- [reactflow](#reactflow)
- [zod](#zod)
- [Client Types Generation](#client-types-generation)
- [Package Scripts](#package-scripts)
- [Client Types Generation](#client-types-generation)
- [Contributing](#contributing)
- [Localization](#localization)
- [Dev Environment](#dev-environment)
- [VSCode Remote Dev](#vscode-remote-dev)
- [Production builds](#production-builds)
- [VSCode Remote Dev](#vscode-remote-dev)
<!-- /code_chunk_output -->
The UI is a fairly straightforward Typescript React app.
## Core Libraries
InvokeAI's UI is made possible by a number of excellent open-source libraries. The most heavily-used are listed below, but there are many others.
Invoke's UI is made possible by a number of excellent open-source libraries. The most heavily-used are listed below, but there are many others.
### Redux Toolkit
- [Redux Toolkit]
- [redux-remember]
- [Socket.IO]
- [Chakra UI]
- [KonvaJS]
- [Vite]
- [openapi-typescript]
- [reactflow]
- [zod]
[Redux Toolkit] is used for state management and fetching/caching:
## Package Scripts
- `RTK-Query` for data fetching and caching
- `createAsyncThunk` for a couple other HTTP requests
- `createEntityAdapter` to normalize things like images and models
- `createListenerMiddleware` for async workflows
See `package.json` for all scripts.
We use [redux-remember] for persistence.
Run with `pnpm <script name>`.
### Socket\.IO
- `dev`: run the frontend in dev mode, enabling hot reloading
- `build`: run all checks (madge, eslint, prettier, tsc) and then build the frontend
- `typegen`: generate types from the OpenAPI schema (see [Client Types Generation])
- `lint:madge`: check frontend for circular dependencies
- `lint:eslint`: check frontend for code quality
- `lint:prettier`: check frontend for code formatting
- `lint:tsc`: check frontend for type issues
- `lint`: run all checks concurrently
- `fix`: run `eslint` and `prettier`, fixing fixable issues
[Socket.IO] is used for server-to-client events, like generation process and queue state changes.
### Chakra UI
[Chakra UI] is our primary UI library, but we also use a few components from [Mantine v6].
### KonvaJS
[KonvaJS] powers the canvas. In the future, we'd like to explore [PixiJS] or WebGPU.
### Vite
[Vite] is our bundler.
### i18next & Weblate
We use [i18next] for localization, but translation to languages other than English happens on our [Weblate] project. **Only the English source strings should be changed on this repo.**
### openapi-typescript
[openapi-typescript] is used to generate types from the server's OpenAPI schema. See TYPES_CODEGEN.md.
### reactflow
[reactflow] powers the Workflow Editor.
### zod
[zod] schemas are used to model data structures and provide runtime validation.
## Client Types Generation
### Client Types Generation
We use [openapi-typescript] to generate types from the app's OpenAPI schema.
@ -88,28 +60,18 @@ python scripts/invokeai-web.py
pnpm typegen
```
## Package Scripts
See `package.json` for all scripts.
Run with `pnpm <script name>`.
- `dev`: run the frontend in dev mode, enabling hot reloading
- `build`: run all checks (madge, eslint, prettier, tsc) and then build the frontend
- `typegen`: generate types from the OpenAPI schema (see [Client Types Generation](#client-types-generation))
- `lint:madge`: check frontend for circular dependencies
- `lint:eslint`: check frontend for code quality
- `lint:prettier`: check frontend for code formatting
- `lint:tsc`: check frontend for type issues
- `lint`: run all checks concurrently
- `fix`: run `eslint` and `prettier`, fixing fixable issues
## Contributing
Thanks for your interest in contributing to the InvokeAI Web UI!
Thanks for your interest in contributing to the Invoke Web UI!
We encourage you to ping @psychedelicious and @blessedcoolant on [discord] if you want to contribute, just to touch base and ensure your work doesn't conflict with anything else going on. The project is very active.
### Localization
We use [i18next] for localization, but translation to languages other than English happens on our [Weblate] project.
**Only the English source strings should be changed on this repo.**
### Dev Environment
Install [node] and [pnpm].
@ -118,23 +80,19 @@ From `invokeai/frontend/web/` run `pnpm i` to get everything set up.
Start everything in dev mode:
1. Start the dev server: `pnpm dev`
2. Start the InvokeAI Nodes backend: `python scripts/invokeai-web.py # run from the repo root`
1. From `invokeai/frontend/web/`: `pnpm dev`
2. From repo root: `python scripts/invokeai-web.py`
3. Point your browser to the dev server address e.g. <http://localhost:5173/>
#### VSCode Remote Dev
### VSCode Remote Dev
We've noticed an intermittent issue with the VSCode Remote Dev port forwarding. If you use this feature of VSCode, you may intermittently click the Invoke button and then get nothing until the request times out. Suggest disabling the IDE's port forwarding feature and doing it manually via SSH:
We've noticed an intermittent issue with the VSCode Remote Dev port forwarding. If you use this feature of VSCode, you may intermittently click the Invoke button and then get nothing until the request times out.
`ssh -L 9090:localhost:9090 -L 5173:localhost:5173 user@host`
We suggest disabling the IDE's port forwarding feature and doing it manually via SSH:
### Production builds
For a number of technical and logistical reasons, we need to commit UI build artefacts to the repo.
If you submit a PR, there is a good chance we will ask you to include a separate commit with a build of the app.
To build for production, run `pnpm build`.
```sh
ssh -L 9090:localhost:9090 -L 5173:localhost:5173 user@host
```
[node]: https://nodejs.org/en/download/
[pnpm]: https://github.com/pnpm/pnpm
@ -143,12 +101,11 @@ To build for production, run `pnpm build`.
[redux-remember]: https://github.com/zewish/redux-remember
[Socket.IO]: https://github.com/socketio/socket.io
[Chakra UI]: https://github.com/chakra-ui/chakra-ui
[Mantine v6]: https://v6.mantine.dev/
[KonvaJS]: https://github.com/konvajs/react-konva
[PixiJS]: https://github.com/pixijs/pixijs
[Vite]: https://github.com/vitejs/vite
[i18next]: https://github.com/i18next/react-i18next
[Weblate]: https://hosted.weblate.org/engage/invokeai/
[openapi-typescript]: https://github.com/drwpow/openapi-typescript
[reactflow]: https://github.com/xyflow/xyflow
[zod]: https://github.com/colinhacks/zod
[Client Types Generation]: #client-types-generation

View File

@ -23,7 +23,7 @@
- [Primitive Types](#primitive-types)
- [Complex Types](#complex-types)
- [Collection Types](#collection-types)
- [Polymorphic Types](#polymorphic-types)
- [Collection or Scalar Types](#collection-or-scalar-types)
- [Optional Fields](#optional-fields)
- [Building Field Input Templates](#building-field-input-templates)
- [Building Field Output Templates](#building-field-output-templates)

View File

@ -20,7 +20,6 @@
],
"scripts": {
"dev": "concurrently \"vite dev\" \"pnpm run theme:watch\"",
"hi": "echo test",
"dev:host": "concurrently \"vite dev --host\" \"pnpm run theme:watch\"",
"build": "pnpm run lint && vite build",
"typegen": "node scripts/typegen.js",
@ -53,21 +52,12 @@
}
},
"dependencies": {
"@chakra-ui/anatomy": "^2.2.2",
"@chakra-ui/icons": "^2.1.1",
"@chakra-ui/layout": "^2.3.1",
"@chakra-ui/portal": "^2.1.0",
"@chakra-ui/react": "^2.8.2",
"@chakra-ui/react-use-size": "^2.1.0",
"@chakra-ui/styled-system": "^2.9.2",
"@chakra-ui/theme-tools": "^2.1.2",
"@dagrejs/graphlib": "^2.1.13",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/utilities": "^3.2.2",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@fontsource-variable/inter": "^5.0.16",
"@invoke-ai/ui": "0.0.10",
"@invoke-ai/ui": "0.0.13",
"@mantine/form": "6.0.21",
"@nanostores/react": "^0.7.1",
"@reduxjs/toolkit": "2.0.1",

View File

@ -10,30 +10,12 @@ patchedDependencies:
path: patches/reselect@5.0.1.patch
dependencies:
'@chakra-ui/anatomy':
specifier: ^2.2.2
version: 2.2.2
'@chakra-ui/icons':
specifier: ^2.1.1
version: 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
'@chakra-ui/layout':
specifier: ^2.3.1
version: 2.3.1(@chakra-ui/system@2.6.2)(react@18.2.0)
'@chakra-ui/portal':
specifier: ^2.1.0
version: 2.1.0(react-dom@18.2.0)(react@18.2.0)
'@chakra-ui/react':
specifier: ^2.8.2
version: 2.8.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(@types/react@18.2.48)(framer-motion@10.18.0)(react-dom@18.2.0)(react@18.2.0)
'@chakra-ui/react-use-size':
specifier: ^2.1.0
version: 2.1.0(react@18.2.0)
'@chakra-ui/styled-system':
specifier: ^2.9.2
version: 2.9.2
'@chakra-ui/theme-tools':
specifier: ^2.1.2
version: 2.1.2(@chakra-ui/styled-system@2.9.2)
'@dagrejs/graphlib':
specifier: ^2.1.13
version: 2.1.13
@ -43,18 +25,12 @@ dependencies:
'@dnd-kit/utilities':
specifier: ^3.2.2
version: 3.2.2(react@18.2.0)
'@emotion/react':
specifier: ^11.11.3
version: 11.11.3(@types/react@18.2.48)(react@18.2.0)
'@emotion/styled':
specifier: ^11.11.0
version: 11.11.0(@emotion/react@11.11.3)(@types/react@18.2.48)(react@18.2.0)
'@fontsource-variable/inter':
specifier: ^5.0.16
version: 5.0.16
'@invoke-ai/ui':
specifier: 0.0.10
version: 0.0.10(@chakra-ui/anatomy@2.2.2)(@chakra-ui/icons@2.1.1)(@chakra-ui/layout@2.3.1)(@chakra-ui/portal@2.1.0)(@chakra-ui/react@2.8.2)(@chakra-ui/styled-system@2.9.2)(@chakra-ui/theme-tools@2.1.2)(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(@fontsource-variable/inter@5.0.16)(@nanostores/react@0.7.1)(chakra-react-select@4.7.6)(framer-motion@10.18.0)(lodash-es@4.17.21)(nanostores@0.9.5)(overlayscrollbars-react@0.5.3)(overlayscrollbars@2.4.6)(react-dom@18.2.0)(react-i18next@14.0.0)(react-select@5.8.0)(react@18.2.0)
specifier: 0.0.13
version: 0.0.13(@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.16)(@internationalized/date@3.5.1)(@types/react@18.2.48)(i18next@23.7.16)(react-dom@18.2.0)(react@18.2.0)
'@mantine/form':
specifier: 6.0.21
version: 6.0.21(react@18.2.0)
@ -356,6 +332,92 @@ packages:
'@jridgewell/trace-mapping': 0.3.21
dev: true
/@ark-ui/anatomy@1.3.0(@internationalized/date@3.5.1):
resolution: {integrity: sha512-1yG2MrzUlix6KthjQMCNiHnkXrWwEdFAX6D+HqGJaNu0XvaGul2J+wDNtjsdX+gxiWu1nXXEEOAWlFVYMUf65w==}
dependencies:
'@zag-js/accordion': 0.32.1
'@zag-js/anatomy': 0.32.1
'@zag-js/avatar': 0.32.1
'@zag-js/carousel': 0.32.1
'@zag-js/checkbox': 0.32.1
'@zag-js/color-picker': 0.32.1
'@zag-js/color-utils': 0.32.1
'@zag-js/combobox': 0.32.1
'@zag-js/date-picker': 0.32.1
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.1)
'@zag-js/dialog': 0.32.1
'@zag-js/editable': 0.32.1
'@zag-js/file-upload': 0.32.1
'@zag-js/hover-card': 0.32.1
'@zag-js/menu': 0.32.1
'@zag-js/number-input': 0.32.1
'@zag-js/pagination': 0.32.1
'@zag-js/pin-input': 0.32.1
'@zag-js/popover': 0.32.1
'@zag-js/presence': 0.32.1
'@zag-js/progress': 0.32.1
'@zag-js/radio-group': 0.32.1
'@zag-js/rating-group': 0.32.1
'@zag-js/select': 0.32.1
'@zag-js/slider': 0.32.1
'@zag-js/splitter': 0.32.1
'@zag-js/switch': 0.32.1
'@zag-js/tabs': 0.32.1
'@zag-js/tags-input': 0.32.1
'@zag-js/toast': 0.32.1
'@zag-js/toggle-group': 0.32.1
'@zag-js/tooltip': 0.32.1
transitivePeerDependencies:
- '@internationalized/date'
dev: false
/@ark-ui/react@1.3.0(@internationalized/date@3.5.1)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-JHjNoIX50+mUCTaEGMjfGQWGGi31pKsV646jZJlR/1xohpYJigzg8BvO97cTsVk8fwtur+cm11gz3Nf7f5QUnA==}
peerDependencies:
react: '>=18.0.0'
react-dom: '>=18.0.0'
dependencies:
'@ark-ui/anatomy': 1.3.0(@internationalized/date@3.5.1)
'@zag-js/accordion': 0.32.1
'@zag-js/avatar': 0.32.1
'@zag-js/carousel': 0.32.1
'@zag-js/checkbox': 0.32.1
'@zag-js/color-picker': 0.32.1
'@zag-js/color-utils': 0.32.1
'@zag-js/combobox': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/date-picker': 0.32.1
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.1)
'@zag-js/dialog': 0.32.1
'@zag-js/editable': 0.32.1
'@zag-js/file-upload': 0.32.1
'@zag-js/hover-card': 0.32.1
'@zag-js/menu': 0.32.1
'@zag-js/number-input': 0.32.1
'@zag-js/pagination': 0.32.1
'@zag-js/pin-input': 0.32.1
'@zag-js/popover': 0.32.1
'@zag-js/presence': 0.32.1
'@zag-js/progress': 0.32.1
'@zag-js/radio-group': 0.32.1
'@zag-js/rating-group': 0.32.1
'@zag-js/react': 0.32.1(react-dom@18.2.0)(react@18.2.0)
'@zag-js/select': 0.32.1
'@zag-js/slider': 0.32.1
'@zag-js/splitter': 0.32.1
'@zag-js/switch': 0.32.1
'@zag-js/tabs': 0.32.1
'@zag-js/tags-input': 0.32.1
'@zag-js/toast': 0.32.1
'@zag-js/toggle-group': 0.32.1
'@zag-js/tooltip': 0.32.1
'@zag-js/types': 0.32.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies:
- '@internationalized/date'
dev: false
/@arthurgeron/eslint-plugin-react-usememo@2.2.3:
resolution: {integrity: sha512-YJG+8hULmhHAxztaANswpa9hWNqEOSvbZcbd6R/JQzyNlEZ49Xh97kqZGuJGZ74rrmULckEO1m3Jh5ctqrGA2A==}
dependencies:
@ -2940,7 +3002,7 @@ packages:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.23.6
'@babel/runtime': 7.23.8
'@emotion/babel-plugin': 11.11.0
'@emotion/is-prop-valid': 1.2.1
'@emotion/react': 11.11.3(@types/react@18.2.48)(react@18.2.0)
@ -3629,7 +3691,6 @@ packages:
resolution: {integrity: sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q==}
dependencies:
'@floating-ui/utils': 0.2.1
dev: true
/@floating-ui/dom@1.5.3:
resolution: {integrity: sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==}
@ -3643,7 +3704,6 @@ packages:
dependencies:
'@floating-ui/core': 1.5.3
'@floating-ui/utils': 0.2.1
dev: true
/@floating-ui/react-dom@2.0.6(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-IB8aCRFxr8nFkdYZgH+Otd9EVQPJoynxeFRGTB8voPoZMRWo8XjYuCRgpI1btvuKY69XMiLnW+ym7zoBHM90Rw==}
@ -3662,7 +3722,6 @@ packages:
/@floating-ui/utils@0.2.1:
resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==}
dev: true
/@fontsource-variable/inter@5.0.16:
resolution: {integrity: sha512-k+BUNqksTL+AN+o+OV7ILeiE9B5M5X+/jA7LWvCwjbV9ovXTqZyKRhA/x7uYv/ml8WQ0XNLBM7cRFIx4jW0/hg==}
@ -3688,31 +3747,26 @@ packages:
resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
dev: true
/@invoke-ai/ui@0.0.10(@chakra-ui/anatomy@2.2.2)(@chakra-ui/icons@2.1.1)(@chakra-ui/layout@2.3.1)(@chakra-ui/portal@2.1.0)(@chakra-ui/react@2.8.2)(@chakra-ui/styled-system@2.9.2)(@chakra-ui/theme-tools@2.1.2)(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(@fontsource-variable/inter@5.0.16)(@nanostores/react@0.7.1)(chakra-react-select@4.7.6)(framer-motion@10.18.0)(lodash-es@4.17.21)(nanostores@0.9.5)(overlayscrollbars-react@0.5.3)(overlayscrollbars@2.4.6)(react-dom@18.2.0)(react-i18next@14.0.0)(react-select@5.8.0)(react@18.2.0):
resolution: {integrity: sha512-e3cX3g1xap57mkMfjsNznN6V9YS8qUTpSiyjSSr80HEsD3NjXxCoL+Ik6y/Na/KwXgcK00jM6H+xF6ahJFobvw==}
/@internationalized/date@3.5.1:
resolution: {integrity: sha512-LUQIfwU9e+Fmutc/DpRTGXSdgYZLBegi4wygCWDSVmUdLTaMHsQyASDiJtREwanwKuQLq0hY76fCJ9J/9I2xOQ==}
dependencies:
'@swc/helpers': 0.5.3
dev: false
/@internationalized/number@3.5.0:
resolution: {integrity: sha512-ZY1BW8HT9WKYvaubbuqXbbDdHhOUMfE2zHHFJeTppid0S+pc8HtdIxFxaYMsGjCb4UsF+MEJ4n2TfU7iHnUK8w==}
dependencies:
'@swc/helpers': 0.5.3
dev: false
/@invoke-ai/ui@0.0.13(@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.16)(@internationalized/date@3.5.1)(@types/react@18.2.48)(i18next@23.7.16)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-X4Txij2dMnzPUXTPhorBHezByJQ/ceyHxCM+zZ0gpFsSyXUieOFWjaSu+dAVpghS9y0dxFQGayHvNyX6VsX/PA==}
peerDependencies:
'@chakra-ui/anatomy': ^2.2.2
'@chakra-ui/icons': ^2.1.1
'@chakra-ui/layout': ^2.3.1
'@chakra-ui/portal': ^2.1.0
'@chakra-ui/react': ^2.8.2
'@chakra-ui/styled-system': ^2.9.2
'@chakra-ui/theme-tools': ^2.1.2
'@emotion/react': ^11.11.3
'@emotion/styled': ^11.11.0
'@fontsource-variable/inter': ^5.0.16
'@nanostores/react': ^0.7.1
chakra-react-select: ^4.7.6
framer-motion: ^10.18.0
lodash-es: ^4.17.21
nanostores: ^0.9.5
overlayscrollbars: ^2.4.6
overlayscrollbars-react: ^0.5.3
react: ^18.2.0
react-dom: ^18.2.0
react-i18next: ^14.0.0
react-select: ^5.8.0
dependencies:
'@ark-ui/react': 1.3.0(@internationalized/date@3.5.1)(react-dom@18.2.0)(react@18.2.0)
'@chakra-ui/anatomy': 2.2.2
'@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)
@ -3734,6 +3788,17 @@ packages:
react-dom: 18.2.0(react@18.2.0)
react-i18next: 14.0.0(i18next@23.7.16)(react-dom@18.2.0)(react@18.2.0)
react-select: 5.8.0(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0)
transitivePeerDependencies:
- '@chakra-ui/form-control'
- '@chakra-ui/icon'
- '@chakra-ui/media-query'
- '@chakra-ui/menu'
- '@chakra-ui/spinner'
- '@chakra-ui/system'
- '@internationalized/date'
- '@types/react'
- i18next
- react-native
dev: false
/@isaacs/cliui@8.0.2:
@ -5719,6 +5784,12 @@ packages:
resolution: {integrity: sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==}
dev: true
/@swc/helpers@0.5.3:
resolution: {integrity: sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==}
dependencies:
tslib: 2.6.2
dev: false
/@swc/types@0.1.5:
resolution: {integrity: sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==}
dev: true
@ -6679,20 +6750,567 @@ packages:
tslib: 1.14.1
dev: true
/@zag-js/accordion@0.32.1:
resolution: {integrity: sha512-16beDVpEhXFQsQRMZLmHFruhGphSprJ5XrRu6+OM2U7aTulo1w3ENUd9uI+mIs4oTVO66lYI4Lp+dFcT2UUIYA==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/anatomy@0.32.1:
resolution: {integrity: sha512-bR+tfFfkbxwhBzGGjEQG+RUnbeCjMx7tWJxykGnGdVLwAh0wKTQBEfHEOCOQh5qU8RhKUieqemAdvc7oP3Tp4w==}
dev: false
/@zag-js/aria-hidden@0.32.1:
resolution: {integrity: sha512-kznwxvUUHDax8Kd7YNVVCzQcwGARTRaZNOcIkw7MTLE8g/pU+C4pYkwR9iqA7/8imGfjYrZfSsQqZRTb4bkS0g==}
dependencies:
'@zag-js/dom-query': 0.32.1
dev: false
/@zag-js/auto-resize@0.32.1:
resolution: {integrity: sha512-MO6N5gPs2xDKbFgrakn6LDWv1GgN8uhfwpsqchLJX+EaZVvLIz8cXFD+jDv3RjK+5GRWV4mIF+26SXuHRSt9Ug==}
dependencies:
'@zag-js/dom-query': 0.32.1
dev: false
/@zag-js/avatar@0.32.1:
resolution: {integrity: sha512-5P+95pkMX2Na4yljN1etdgYyA+3HPORjWKn0Y3JamkYIAqJwRFO+taEdSm/xcRkuT6aGA3luheUowjt8wZssyA==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/mutation-observer': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/carousel@0.32.1:
resolution: {integrity: sha512-S7dUrPtiLr42Fa+S3O18kqKVqSu2yuk67bqGDtppIZSaFOugYHK4feBkZqjKw+eF12NVRRVO2j+A40d3MvxbSA==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/checkbox@0.32.1:
resolution: {integrity: sha512-5reRreGyDZ5IlBNd5m1QrYXCehVIl/pmfKMEcAfad5DcgCaHGv5j76eahxbKln/8TEdwz4eWzBrqNtwSkKL5+w==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/form-utils': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
'@zag-js/visually-hidden': 0.32.1
dev: false
/@zag-js/collection@0.32.1:
resolution: {integrity: sha512-dAzcVQ/n+xAYoxWB/65/CQinv66RNVuq5ig0fEYszBqP+HjFnOpeGkIrEvP+bFI38hFEViiGtfr6oGAsVByOVQ==}
dev: false
/@zag-js/color-picker@0.32.1:
resolution: {integrity: sha512-ov3FC+c2WBYmEGRXWFVb2jih2Ecejj5JqBjDL9iMLBs2KNY9jnpvtH7WnZbijNY+RMDBj+C/DNI7K2NVaamSIA==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/color-utils': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dismissable': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/form-utils': 0.32.1
'@zag-js/popper': 0.32.1
'@zag-js/tabbable': 0.32.1
'@zag-js/text-selection': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
'@zag-js/visually-hidden': 0.32.1
dev: false
/@zag-js/color-utils@0.32.1:
resolution: {integrity: sha512-AzupfOD7oD0mE+H9roTzwnLqtw1wYiJGOQKLPAwdwPQdznJUQD6sMOpxR/6RBuITVTm8Bl12Mr4+7s29LVJruw==}
dependencies:
'@zag-js/numeric-range': 0.32.1
dev: false
/@zag-js/combobox@0.32.1:
resolution: {integrity: sha512-skz2C5UxLD5JoYNP4hcPaQJu2cW7vycKqjDNI9ZtygSkZHOHx+JxpYiACBnr1vqzXatIOuDQm/HUuWW9yOT4eA==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/aria-hidden': 0.32.1
'@zag-js/collection': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dismissable': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/mutation-observer': 0.32.1
'@zag-js/popper': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/core@0.32.1:
resolution: {integrity: sha512-F9F7920/CisoLWALQACIhqbMvemgbv86qBULJ+UEe+a/9XgGwPh9UGn/H/q5EWkNpgEapz2b3pl3ONgKmXsK1A==}
dependencies:
'@zag-js/store': 0.32.1
klona: 2.0.6
dev: false
/@zag-js/date-picker@0.32.1:
resolution: {integrity: sha512-n/hYmF+/R4+NuyfPRzCgeuLT6LJihKSuKzK29STPWy3sC/tBBHiqhNv1/4UKbatHUJXdBW2XF+N8Rw08RffcFQ==}
dependencies:
'@internationalized/date': 3.5.1
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.1)
'@zag-js/dismissable': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/form-utils': 0.32.1
'@zag-js/live-region': 0.32.1
'@zag-js/popper': 0.32.1
'@zag-js/text-selection': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/date-utils@0.32.1(@internationalized/date@3.5.1):
resolution: {integrity: sha512-dbBDRSVr5pRUw3rXndyGuSshZiWqQI5JQO4D2KIFGkXzorj6WzoOpcO910Z7AdM/9cCAMpCjUrka8d8o9BpJBg==}
peerDependencies:
'@internationalized/date': '>=3.0.0'
dependencies:
'@internationalized/date': 3.5.1
dev: false
/@zag-js/dialog@0.32.1:
resolution: {integrity: sha512-czp+qXcdAOM70SrvDo4gBpYZx6gS6HXyrpiptW3+EHa2eiCfc/Z2w+Nu+ZadOTEQGgNWlKlCLW7Ery0i9mMDsw==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/aria-hidden': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dismissable': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/remove-scroll': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
focus-trap: 7.5.4
dev: false
/@zag-js/dismissable@0.32.1:
resolution: {integrity: sha512-UIkG+9Eb5wrus2F2Dy4zqk0pwCV53sdnMYBxk9dpvDzBJHzW+InhVeg3UeKmPL8ELcYlhH/Bap99XCRJvxsXow==}
dependencies:
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/interact-outside': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/dom-event@0.32.1:
resolution: {integrity: sha512-wN6f5Kkf7C/YFN3wbEG3gUockSebyy1fPNL2BuL4C8PIP8vOD14hnHTzZWg5yYfO+veybIAL38r8I46C+bOVBQ==}
dependencies:
'@zag-js/text-selection': 0.32.1
'@zag-js/types': 0.32.1
dev: false
/@zag-js/dom-query@0.16.0:
resolution: {integrity: sha512-Oqhd6+biWyKnhKwFFuZrrf6lxBz2tX2pRQe6grUnYwO6HJ8BcbqZomy2lpOdr+3itlaUqx+Ywj5E5ZZDr/LBfQ==}
dev: false
/@zag-js/dom-query@0.32.1:
resolution: {integrity: sha512-u6hrQHQ0/dcUi6xJn8d2Mu1ClN4KZpPqOKrJFSaxadWjSy+x0qp48WY2CBQ6gZ3j8IwR/XjzU9bu9wY5jJfHgA==}
dev: false
/@zag-js/editable@0.32.1:
resolution: {integrity: sha512-QEGnfp2P9nWVp9vGNWtszspvQcF3KtBRToZrv5/DT30Mpo/uPDKtqijLs0SnB/W60ELzcIRhK4J9taGoK8O8uw==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/form-utils': 0.32.1
'@zag-js/interact-outside': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/element-rect@0.32.1:
resolution: {integrity: sha512-tAmxgxU2LsByK8PIs/Cj6cBJ8xZCVXE9RoStxthhuPL7xKYUfZvFGuhHVOHTHd6sDKEqbj6K1ds/TGPuglIh4w==}
dev: false
/@zag-js/element-size@0.10.5:
resolution: {integrity: sha512-uQre5IidULANvVkNOBQ1tfgwTQcGl4hliPSe69Fct1VfYb2Fd0jdAcGzqQgPhfrXFpR62MxLPB7erxJ/ngtL8w==}
dev: false
/@zag-js/element-size@0.32.1:
resolution: {integrity: sha512-ACklufmJQpah2UqwZUlYFaKi6uWfZBeTghtbfYHcDfzRbg2Hni612v8L1JeS4vAgjeDpcdHQpXXR4AZSpGZgNw==}
dev: false
/@zag-js/file-upload@0.32.1:
resolution: {integrity: sha512-cD0NRIDof9Vv2DemmnYe9ZPZxOZ6b8XZl8eq4G0e8+WLYtnRXyEURl8Dw0QJpfdDPQaHnnD4CNxPTQcLgP+9Sg==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/file-utils': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
'@zag-js/visually-hidden': 0.32.1
dev: false
/@zag-js/file-utils@0.32.1:
resolution: {integrity: sha512-0PxTrljW51Lf9OCuYNlZuaLgF0v1NoVRzXa/osZ9HGceQjfo77R5G9u+/TP3u53W2PTxajEZ4eNzTibgpzNXFg==}
dev: false
/@zag-js/focus-visible@0.16.0:
resolution: {integrity: sha512-a7U/HSopvQbrDU4GLerpqiMcHKEkQkNPeDZJWz38cw/6Upunh41GjHetq5TB84hxyCaDzJ6q2nEdNoBQfC0FKA==}
dependencies:
'@zag-js/dom-query': 0.16.0
dev: false
/@zag-js/form-utils@0.32.1:
resolution: {integrity: sha512-OemLBlHCHHm7t8wNcf78FRudRA7FegSgsNEzAjrRTyx+lJztDyHRLaoyI1gCEIg+0Kzl2nMxjOl4MStGsDj8iw==}
dependencies:
'@zag-js/mutation-observer': 0.32.1
dev: false
/@zag-js/hover-card@0.32.1:
resolution: {integrity: sha512-k66YK0z0P4LuK78+jnRoUPxJiM9GA0sbEEz3oPlvcFVXMMwnRTPNIw1OjksfAPI+Nvgg7H6D3A+7HCdRI/oBjw==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dismissable': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/popper': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/interact-outside@0.32.1:
resolution: {integrity: sha512-8zHuswfTAgfMCaQnp3N4WStvnL32VyxURafb21+mE4neAF/DaKfJHWnJpeUMG1Qh/eXsrMRBxVoX+nBMhHj9bg==}
dependencies:
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/tabbable': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/live-region@0.32.1:
resolution: {integrity: sha512-6/9QMLVZbTRh/G6MoJc/auN8r5vjdY9vUgNT680C2LOa2vnRR5/y0DkIpVgttNh1rSenQ/eLEYxp8hQF1rIYNw==}
dependencies:
'@zag-js/visually-hidden': 0.32.1
dev: false
/@zag-js/menu@0.32.1:
resolution: {integrity: sha512-IPsTljVF0N9xTwub1cpGl3GAG5ttAq3h38PdZERREzT3qRgw4v3K/I1TG2vIiDXgJz8UZzUKox6ZYdU7UIAkRA==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dismissable': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/mutation-observer': 0.32.1
'@zag-js/popper': 0.32.1
'@zag-js/rect-utils': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/mutation-observer@0.32.1:
resolution: {integrity: sha512-/hlObxGnhAaYYVnwRJC227md0M3kSE6mO24vkqVGwq2GglS+u4zbVcBBUuWgHdMML+ZjIQrZuVycCBMfVlHq0g==}
dev: false
/@zag-js/number-input@0.32.1:
resolution: {integrity: sha512-atyIOvoMITb4hZtQym7yD6I7grvPW83UeMFO8hCQg3HWwd2zR4+63mouWuyMoWb4QrzVFRVQBaU8OG5xGlknEw==}
dependencies:
'@internationalized/number': 3.5.0
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/form-utils': 0.32.1
'@zag-js/mutation-observer': 0.32.1
'@zag-js/number-utils': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/number-utils@0.32.1:
resolution: {integrity: sha512-x/nttU31TtFVTqFBM8e3ZH/0MCc+u15WAfk0rT6ESkoZcdb80rTzZVMokCKCUdpi/JdB1vjEeCLSnj+ig8oAIQ==}
dev: false
/@zag-js/numeric-range@0.32.1:
resolution: {integrity: sha512-1Qe2URTenlrdsWuArlnQ+v5bBH2mHZD3XsK6jYV+C2lgatVzdcoN4GCSNTiF7w+So6J+NTeLMkVHMGCW1Kzx1g==}
dev: false
/@zag-js/pagination@0.32.1:
resolution: {integrity: sha512-lhogzKxJnx5D2Xoni/xm5rkOuy15KWSxqBHVwe8+j5aSNqMy7+aRtEN2F2VQCDVL/v1fdciQvOCA9udm37kZ4w==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/pin-input@0.32.1:
resolution: {integrity: sha512-d18cCXKUr7INL0Xm5KyIoiTRSNsPXfIlIEMl2HrAvM3r70wtEag0PmiDNA5NS2tB4LnnX0XowchGB4HsdFS/ng==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/form-utils': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
'@zag-js/visually-hidden': 0.32.1
dev: false
/@zag-js/popover@0.32.1:
resolution: {integrity: sha512-B01if49v3crCjkvtSvIX4CBdT/475nj3DttOObc36s0YOxCEt3UihMITBD5JvIKwEqjZ6oU5t0sLcUYOqQ4f2A==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/aria-hidden': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dismissable': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/popper': 0.32.1
'@zag-js/remove-scroll': 0.32.1
'@zag-js/tabbable': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
focus-trap: 7.5.4
dev: false
/@zag-js/popper@0.32.1:
resolution: {integrity: sha512-aQgogW1N4VreNACSQhXQoZeXtQQtB//FXUvt1CBnW2DtmZ6YkNnaAfn186Q2lkw2/T0chITRy3eYeviwMmMrqg==}
dependencies:
'@floating-ui/dom': 1.5.4
'@zag-js/dom-query': 0.32.1
'@zag-js/element-rect': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/presence@0.32.1:
resolution: {integrity: sha512-8189QMUf/L1dztAZdurx18ZwPyWlq58Mrd+GdATSaf8JstgrI1ovzVs606inQghWptKHMsH7dUIaV9UkhbSx3Q==}
dependencies:
'@zag-js/core': 0.32.1
'@zag-js/types': 0.32.1
dev: false
/@zag-js/progress@0.32.1:
resolution: {integrity: sha512-ClkQvNYnuIpKfAPUceZXY5E2m/3NnIm21cvHe4gAoJ88YdqEHd5rIRoHP63g8ET8Ct/2KkBRkgR+LrQnGQOomA==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/radio-group@0.32.1:
resolution: {integrity: sha512-NvdSjwRF38qIh0oM68jERf71uiwV2JFTrGeQEs3EIqONzULwL6jR2p4P1wm3JJNBAkSYBKZMER5cVUUcqM3kjQ==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/element-rect': 0.32.1
'@zag-js/form-utils': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
'@zag-js/visually-hidden': 0.32.1
dev: false
/@zag-js/rating-group@0.32.1:
resolution: {integrity: sha512-RBaFRCw7P00bgTrEjUHT3h/OGRO8XmXKkQYqqhm1tsVbeTsT47iwHoc6XnMEiGBonaJDwN/J0oFasw7GNg5sow==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/form-utils': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/react@0.32.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-b1SB7hXXv1K6CmXkcy5Y7mb0YRWkyvulyhK8VW5O5hIAPuGxOTx70psmVeZbmVzhjdORCiro9jKx8Ec0LfolFg==}
peerDependencies:
react: '>=18.0.0'
react-dom: '>=18.0.0'
dependencies:
'@zag-js/core': 0.32.1
'@zag-js/store': 0.32.1
'@zag-js/types': 0.32.1
proxy-compare: 2.5.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@zag-js/rect-utils@0.32.1:
resolution: {integrity: sha512-cI07kgldjUZP+SLhXeG9VSl47nrENlC96Fs7jWcTfHj62rhdY8WsBJ0tiTztvwar9m1chwxXZwJowHN+nPIgDQ==}
dev: false
/@zag-js/remove-scroll@0.32.1:
resolution: {integrity: sha512-LyXt2rNMSKb9MKeJRyKTgpk4R7jdA+9kEQTSG5qyA94jo1og7FVgA1W/E+pNkdxDEk1VplL768VU6y7E/L3DHg==}
dependencies:
'@zag-js/dom-query': 0.32.1
dev: false
/@zag-js/select@0.32.1:
resolution: {integrity: sha512-jSzmTKCN1Fk/ZDDWM8TVGOtwgpYUDgyceegjYT+hW1mmEetu4tQcEvAr0557NOzh8akqLvcVFbg/kMj0IriKAA==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/collection': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dismissable': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/form-utils': 0.32.1
'@zag-js/mutation-observer': 0.32.1
'@zag-js/popper': 0.32.1
'@zag-js/tabbable': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
'@zag-js/visually-hidden': 0.32.1
dev: false
/@zag-js/slider@0.32.1:
resolution: {integrity: sha512-iZSB3Y8/Maakxem0Ha3rBRa8AyAplhN5K50Bgz+wsv0VEzNNUmK4QgaTWReWd6SfeTRpnC5ftKCcfM2aQrLm6g==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/element-size': 0.32.1
'@zag-js/form-utils': 0.32.1
'@zag-js/numeric-range': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/splitter@0.32.1:
resolution: {integrity: sha512-NdHLUXtQAlnz6QpdPwcqZCqYul7LaVqsp0hgtXR2PN4HbH+VAaDfY76pUk6LBerUcykChGZvtM9U0A5FCo1x4A==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/number-utils': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/store@0.32.1:
resolution: {integrity: sha512-hKwzpqAPljw06oOI+eO+Is2udpmY9GsGfmdoqvZVYoK4f5sawpZY9EC/84tbK9QKWUDTbFS+0Ujc254GUThmDA==}
dependencies:
proxy-compare: 2.5.1
dev: false
/@zag-js/switch@0.32.1:
resolution: {integrity: sha512-+5w/AtINA+jpORX1cuUrnyIFXrfjhqV7667EKK/zbPi0Pf1E10+TEihpfFjY6bgms9CSNWZVEb6w2f2C0PNBDA==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/form-utils': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
'@zag-js/visually-hidden': 0.32.1
dev: false
/@zag-js/tabbable@0.32.1:
resolution: {integrity: sha512-fMXtVgBiX7z3Qmdv+McrfihiSkqsDbNX2nn3e63L7jdy9ZpgnR3jG9BwUZvv7hvzkuOAFhhdKgBYYT+fkBavGg==}
dependencies:
'@zag-js/dom-query': 0.32.1
dev: false
/@zag-js/tabs@0.32.1:
resolution: {integrity: sha512-5l8/k2Pw9Kbfsvvx6HWcVqK7Ns7ca+nyPGLSZtZLMp/Zn2q3xSG32C1U3oDaYtQVIQSiEHdnMjw0C2v+CxGDMA==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/element-rect': 0.32.1
'@zag-js/tabbable': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/tags-input@0.32.1:
resolution: {integrity: sha512-oliLhiMpRNbWFixHF+Oe7hySQBp7NKtL/s8rN5dLT1G1GFRMzuuht/QnmL1h8EoGGpTwaaokMo4zl4uVzHbwyw==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/auto-resize': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/form-utils': 0.32.1
'@zag-js/interact-outside': 0.32.1
'@zag-js/live-region': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/text-selection@0.32.1:
resolution: {integrity: sha512-aK1uswWYF76PFoxGL+3HW/kth9uldFWSW4lOh89NfEcc6Ym7qS5B+P0HKJVM9DuQbihvQX9dyc9PvM7/LJTSRA==}
dependencies:
'@zag-js/dom-query': 0.32.1
dev: false
/@zag-js/toast@0.32.1:
resolution: {integrity: sha512-HrfVzFX7ANS9qOewCr8qOCbgko635bZxYKMv+ojjo4U/TtwkGb43+lVU7/qwZj0z18/OtXBH5YQjFwQZXg5x8g==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/toggle-group@0.32.1:
resolution: {integrity: sha512-MM1XI4J45rRCZiDHcMtZWud0+bWMu6IcMnrbd9oig330YAF3RzcjTlxX93YRY35F04OUMBq5el9qe3qc2vyMuw==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/tooltip@0.32.1:
resolution: {integrity: sha512-+rsmDYTELFBHoYKg5iKShGfRD3H9FJDaZRq915Uc9YnyePMXCnWRgnVp+lk3zI+FDgysQm67SDLRJsR24Iioqg==}
dependencies:
'@zag-js/anatomy': 0.32.1
'@zag-js/core': 0.32.1
'@zag-js/dom-event': 0.32.1
'@zag-js/dom-query': 0.32.1
'@zag-js/popper': 0.32.1
'@zag-js/types': 0.32.1
'@zag-js/utils': 0.32.1
dev: false
/@zag-js/types@0.32.1:
resolution: {integrity: sha512-BLfqb+im4vtXXJqhd2ZUg/4LquEd1qPt9XN56XVjudGDTftN8n3EDpuail7VKxdL59W4jR7wW8lvl4sSgrQKWw==}
dependencies:
csstype: 3.1.3
dev: false
/@zag-js/utils@0.32.1:
resolution: {integrity: sha512-jrcmWYcA3L6TO4fZbPFvpSGEy2Z/mbWt6bPQbmcVgq/BltSS0YxxfPl+eD+S/rZI9aneszwsr04Z5TpladFiVA==}
dev: false
/@zag-js/visually-hidden@0.32.1:
resolution: {integrity: sha512-Vzieo4vNulzY/0zqmVfeYW/LcFJp5xtEoyUgR1FBctH8uBPBRhTIEXxKtoMablW6/vccOVo7zcu0UrR5Vx+eYQ==}
dev: false
/accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
@ -9074,6 +9692,12 @@ packages:
tslib: 2.6.2
dev: false
/focus-trap@7.5.4:
resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==}
dependencies:
tabbable: 6.2.0
dev: false
/for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
dependencies:
@ -11307,6 +11931,10 @@ packages:
ipaddr.js: 1.9.1
dev: true
/proxy-compare@2.5.1:
resolution: {integrity: sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==}
dev: false
/proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: true
@ -11435,7 +12063,7 @@ packages:
peerDependencies:
react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
dependencies:
'@babel/runtime': 7.23.7
'@babel/runtime': 7.23.8
react: 18.2.0
dev: false
@ -11530,7 +12158,7 @@ packages:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.23.7
'@babel/runtime': 7.23.8
'@types/react': 18.2.48
focus-lock: 1.0.0
prop-types: 15.8.1
@ -12777,6 +13405,10 @@ packages:
resolution: {integrity: sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==}
dev: true
/tabbable@6.2.0:
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
dev: false
/tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}

View File

@ -110,7 +110,28 @@
"somethingWentWrong": "Etwas ist schief gelaufen",
"copyError": "$t(gallery.copy) Fehler",
"input": "Eingabe",
"notInstalled": "Nicht $t(common.installed)"
"notInstalled": "Nicht $t(common.installed)",
"advancedOptions": "Erweiterte Einstellungen",
"alpha": "Alpha",
"red": "Rot",
"green": "Grün",
"blue": "Blau",
"delete": "Löschen",
"or": "oder",
"direction": "Richtung",
"free": "Frei",
"save": "Speichern",
"preferencesLabel": "Präferenzen",
"created": "Erstellt",
"prevPage": "Vorherige Seite",
"nextPage": "Nächste Seite",
"unknownError": "Unbekannter Fehler",
"unsaved": "Nicht gespeichert",
"aboutDesc": "Verwenden Sie Invoke für die Arbeit? Dann siehe hier:",
"localSystem": "Lokales System",
"orderBy": "Ordnen nach",
"saveAs": "Speicher als",
"updated": "Aktualisiert"
},
"gallery": {
"generations": "Erzeugungen",
@ -701,7 +722,8 @@
"invokeProgressBar": "Invoke Fortschrittsanzeige",
"mode": "Modus",
"resetUI": "$t(accessibility.reset) von UI",
"createIssue": "Ticket erstellen"
"createIssue": "Ticket erstellen",
"about": "Über"
},
"boards": {
"autoAddBoard": "Automatisches Hinzufügen zum Ordner",
@ -809,7 +831,8 @@
"canny": "Canny",
"hedDescription": "Ganzheitlich verschachtelte Kantenerkennung",
"scribble": "Scribble",
"maxFaces": "Maximal Anzahl Gesichter"
"maxFaces": "Maximal Anzahl Gesichter",
"resizeSimple": "Größe ändern (einfach)"
},
"queue": {
"status": "Status",
@ -999,5 +1022,27 @@
"selectLoRA": "Wählen ein LoRA aus",
"esrganModel": "ESRGAN Modell",
"addLora": "LoRA hinzufügen"
},
"accordions": {
"generation": {
"title": "Erstellung",
"modelTab": "Modell",
"conceptsTab": "Konzepte"
},
"image": {
"title": "Bild"
},
"advanced": {
"title": "Erweitert"
},
"control": {
"title": "Kontrolle",
"controlAdaptersTab": "Kontroll Adapter",
"ipTab": "Bild Beschreibung"
},
"compositing": {
"coherenceTab": "Kohärenzpass",
"infillTab": "Füllung"
}
}
}

View File

@ -224,6 +224,7 @@
"amult": "a_mult",
"autoConfigure": "Auto configure processor",
"balanced": "Balanced",
"base": "Base",
"beginEndStepPercent": "Begin / End Step Percentage",
"bgth": "bg_th",
"canny": "Canny",
@ -237,6 +238,8 @@
"controlMode": "Control Mode",
"crop": "Crop",
"delete": "Delete",
"depthAnything": "Depth Anything",
"depthAnythingDescription": "Depth map generation using the Depth Anything technique",
"depthMidas": "Depth (Midas)",
"depthMidasDescription": "Depth map generation using Midas",
"depthZoe": "Depth (Zoe)",
@ -256,6 +259,7 @@
"colorMapTileSize": "Tile Size",
"importImageFromCanvas": "Import Image From Canvas",
"importMaskFromCanvas": "Import Mask From Canvas",
"large": "Large",
"lineart": "Lineart",
"lineartAnime": "Lineart Anime",
"lineartAnimeDescription": "Anime-style lineart processing",
@ -268,6 +272,7 @@
"minConfidence": "Min Confidence",
"mlsd": "M-LSD",
"mlsdDescription": "Minimalist Line Segment Detector",
"modelSize": "Model Size",
"none": "None",
"noneDescription": "No processing applied",
"normalBae": "Normal BAE",
@ -288,6 +293,7 @@
"selectModel": "Select a model",
"setControlImageDimensions": "Set Control Image Dimensions To W/H",
"showAdvanced": "Show Advanced",
"small": "Small",
"toggleControlNet": "Toggle this ControlNet",
"w": "W",
"weight": "Weight",
@ -600,6 +606,10 @@
"desc": "Send current image to Image to Image",
"title": "Send To Image To Image"
},
"remixImage": {
"desc": "Use all parameters except seed from the current image",
"title": "Remix image"
},
"setParameters": {
"desc": "Use all parameters of the current image",
"title": "Set Parameters"
@ -1216,6 +1226,7 @@
"useCpuNoise": "Use CPU Noise",
"cpuNoise": "CPU Noise",
"gpuNoise": "GPU Noise",
"remixImage": "Remix Image",
"useInitImg": "Use Initial Image",
"usePrompt": "Use Prompt",
"useSeed": "Use Seed",
@ -1697,6 +1708,7 @@
"workflowLibrary": "Library",
"userWorkflows": "My Workflows",
"defaultWorkflows": "Default Workflows",
"projectWorkflows": "Project Workflows",
"openWorkflow": "Open Workflow",
"uploadWorkflow": "Load from File",
"deleteWorkflow": "Delete Workflow",
@ -1709,6 +1721,7 @@
"workflowSaved": "Workflow Saved",
"noRecentWorkflows": "No Recent Workflows",
"noUserWorkflows": "No User Workflows",
"noWorkflows": "No Workflows",
"noSystemWorkflows": "No System Workflows",
"problemLoading": "Problem Loading Workflows",
"loading": "Loading Workflows",

View File

@ -118,7 +118,14 @@
"advancedOptions": "Opzioni avanzate",
"free": "Libero",
"or": "o",
"preferencesLabel": "Preferenze"
"preferencesLabel": "Preferenze",
"red": "Rosso",
"aboutHeading": "Possiedi il tuo potere creativo",
"aboutDesc": "Utilizzi Invoke per lavoro? Guarda qui:",
"localSystem": "Sistema locale",
"green": "Verde",
"blue": "Blu",
"alpha": "Alfa"
},
"gallery": {
"generations": "Generazioni",
@ -521,7 +528,8 @@
"customConfigFileLocation": "Posizione del file di configurazione personalizzato",
"vaePrecision": "Precisione VAE",
"noModelSelected": "Nessun modello selezionato",
"conversionNotSupported": "Conversione non supportata"
"conversionNotSupported": "Conversione non supportata",
"configFile": "File di configurazione"
},
"parameters": {
"images": "Immagini",
@ -660,7 +668,9 @@
"lockAspectRatio": "Blocca proporzioni",
"swapDimensions": "Scambia dimensioni",
"aspect": "Aspetto",
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (potrebbe essere troppo grande)"
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (potrebbe essere troppo grande)",
"boxBlur": "Box",
"gaussianBlur": "Gaussian"
},
"settings": {
"models": "Modelli",
@ -794,7 +804,9 @@
"invalidUpload": "Caricamento non valido",
"problemDeletingWorkflow": "Problema durante l'eliminazione del flusso di lavoro",
"workflowDeleted": "Flusso di lavoro eliminato",
"problemRetrievingWorkflow": "Problema nel recupero del flusso di lavoro"
"problemRetrievingWorkflow": "Problema nel recupero del flusso di lavoro",
"resetInitialImage": "Reimposta l'immagine iniziale",
"uploadInitialImage": "Carica l'immagine iniziale"
},
"tooltip": {
"feature": {
@ -899,7 +911,8 @@
"loadMore": "Carica altro",
"mode": "Modalità",
"resetUI": "$t(accessibility.reset) l'Interfaccia Utente",
"createIssue": "Segnala un problema"
"createIssue": "Segnala un problema",
"about": "Informazioni"
},
"ui": {
"hideProgressImages": "Nascondi avanzamento immagini",

View File

@ -45,7 +45,7 @@ export const useSocketIO = () => {
const socketOptions = useMemo(() => {
const options: Partial<ManagerOptions & SocketOptions> = {
timeout: 60000,
path: '/ws/socket.io',
path: `${window.location.pathname}ws/socket.io`,
autoConnect: false, // achtung! removing this breaks the dynamic middleware
forceNew: true,
};

View File

@ -36,9 +36,9 @@ export const addModelSelectedListener = () => {
const newModel = result.data;
const { base_model } = newModel;
const newBaseModel = newModel.base_model;
const didBaseModelChange =
state.generation.model?.base_model !== base_model;
state.generation.model?.base_model !== newBaseModel;
if (didBaseModelChange) {
// we may need to reset some incompatible submodels
@ -46,7 +46,7 @@ export const addModelSelectedListener = () => {
// handle incompatible loras
forEach(state.lora.loras, (lora, id) => {
if (lora.base_model !== base_model) {
if (lora.base_model !== newBaseModel) {
dispatch(loraRemoved(id));
modelsCleared += 1;
}
@ -54,14 +54,14 @@ export const addModelSelectedListener = () => {
// handle incompatible vae
const { vae } = state.generation;
if (vae && vae.base_model !== base_model) {
if (vae && vae.base_model !== newBaseModel) {
dispatch(vaeSelected(null));
modelsCleared += 1;
}
// handle incompatible controlnets
selectControlAdapterAll(state.controlAdapters).forEach((ca) => {
if (ca.model?.base_model !== base_model) {
if (ca.model?.base_model !== newBaseModel) {
dispatch(
controlAdapterIsEnabledChanged({ id: ca.id, isEnabled: false })
);

View File

@ -46,14 +46,14 @@ export const addDynamicPromptsListener = () => {
if (cachedPrompts) {
dispatch(promptsChanged(cachedPrompts.prompts));
dispatch(parsingErrorChanged(cachedPrompts.error));
return;
}
if (!getShouldProcessPrompt(state.generation.positivePrompt)) {
if (state.dynamicPrompts.isLoading) {
dispatch(isLoadingChanged(false));
}
dispatch(promptsChanged([state.generation.positivePrompt]));
dispatch(parsingErrorChanged(undefined));
dispatch(isErrorChanged(false));
return;
}
@ -78,7 +78,6 @@ export const addDynamicPromptsListener = () => {
dispatch(promptsChanged(res.prompts));
dispatch(parsingErrorChanged(res.error));
dispatch(isErrorChanged(false));
dispatch(isLoadingChanged(false));
} catch {
dispatch(isErrorChanged(true));
dispatch(isLoadingChanged(false));

View File

@ -1,24 +1,35 @@
import type { ChakraProps } from '@invoke-ai/ui';
import { Box, Flex } from '@invoke-ai/ui';
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
import { getOverlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import type { CSSProperties, PropsWithChildren } from 'react';
import { memo } from 'react';
import { memo, useMemo } from 'react';
type Props = PropsWithChildren & {
maxHeight?: ChakraProps['maxHeight'];
overflowX?: 'hidden' | 'scroll';
overflowY?: 'hidden' | 'scroll';
};
const styles: CSSProperties = { height: '100%', width: '100%' };
const ScrollableContent = ({ children, maxHeight }: Props) => {
const ScrollableContent = ({
children,
maxHeight,
overflowX = 'hidden',
overflowY = 'scroll',
}: Props) => {
const overlayscrollbarsOptions = useMemo(
() => getOverlayScrollbarsParams(overflowX, overflowY).options,
[overflowX, overflowY]
);
return (
<Flex w="full" h="full" maxHeight={maxHeight} position="relative">
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
<OverlayScrollbarsComponent
defer
style={styles}
options={overlayScrollbarsParams.options}
options={overlayscrollbarsOptions}
>
{children}
</OverlayScrollbarsComponent>

View File

@ -1,3 +1,4 @@
import { cloneDeep, merge } from 'lodash-es';
import { ClickScrollPlugin, OverlayScrollbars } from 'overlayscrollbars';
import type { UseOverlayScrollbarsParams } from 'overlayscrollbars-react';
@ -16,3 +17,12 @@ export const overlayScrollbarsParams: UseOverlayScrollbarsParams = {
overflow: { x: 'hidden' },
},
};
export const getOverlayScrollbarsParams = (
overflowX: 'hidden' | 'scroll' = 'hidden',
overflowY: 'hidden' | 'scroll' = 'scroll'
) => {
const params = cloneDeep(overlayScrollbarsParams);
merge(params, { options: { overflow: { y: overflowY, x: overflowX } } });
return params;
};

View File

@ -1,9 +1,12 @@
import type { FormLabelProps } from '@invoke-ai/ui';
import {
Box,
Button,
ButtonGroup,
Checkbox,
Flex,
FormControl,
FormControlGroup,
FormLabel,
IconButton,
Popover,
@ -33,6 +36,10 @@ import {
PiTrashSimpleFill,
} from 'react-icons/pi';
const formLabelProps: FormLabelProps = {
flexGrow: 1,
};
const IAICanvasMaskOptions = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
@ -124,40 +131,44 @@ const IAICanvasMaskOptions = () => {
<PopoverContent>
<PopoverBody>
<Flex direction="column" gap={2}>
<FormControl>
<FormLabel>{`${t('unifiedCanvas.enableMask')} (H)`}</FormLabel>
<Checkbox
isChecked={isMaskEnabled}
onChange={handleToggleEnableMask}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.preserveMaskedArea')}</FormLabel>
<Checkbox
isChecked={shouldPreserveMaskedArea}
onChange={handleChangePreserveMaskedArea}
/>
</FormControl>
<FormControlGroup formLabelProps={formLabelProps}>
<FormControl>
<FormLabel>{`${t('unifiedCanvas.enableMask')} (H)`}</FormLabel>
<Checkbox
isChecked={isMaskEnabled}
onChange={handleToggleEnableMask}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.preserveMaskedArea')}</FormLabel>
<Checkbox
isChecked={shouldPreserveMaskedArea}
onChange={handleChangePreserveMaskedArea}
/>
</FormControl>
</FormControlGroup>
<Box pt={2} pb={2}>
<IAIColorPicker
color={maskColor}
onChange={handleChangeMaskColor}
/>
</Box>
<Button
size="sm"
leftIcon={<PiFloppyDiskBackFill />}
onClick={handleSaveMask}
>
{t('unifiedCanvas.saveMask')}
</Button>
<Button
size="sm"
leftIcon={<PiTrashSimpleFill />}
onClick={handleClearMask}
>
{t('unifiedCanvas.clearMask')}
</Button>
<ButtonGroup isAttached={false}>
<Button
size="sm"
leftIcon={<PiFloppyDiskBackFill />}
onClick={handleSaveMask}
>
{t('unifiedCanvas.saveMask')}
</Button>
<Button
size="sm"
leftIcon={<PiTrashSimpleFill />}
onClick={handleClearMask}
>
{t('unifiedCanvas.clearMask')}
</Button>
</ButtonGroup>
</Flex>
</PopoverBody>
</PopoverContent>

View File

@ -1,7 +1,9 @@
import type { FormLabelProps } from '@invoke-ai/ui';
import {
Checkbox,
Flex,
FormControl,
FormControlGroup,
FormLabel,
IconButton,
Popover,
@ -28,6 +30,10 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { PiGearSixBold } from 'react-icons/pi';
const formLabelProps: FormLabelProps = {
flexGrow: 1,
};
const IAICanvasSettingsButtonPopover = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
@ -122,69 +128,73 @@ const IAICanvasSettingsButtonPopover = () => {
<PopoverContent>
<PopoverBody>
<Flex direction="column" gap={2}>
<FormControl>
<FormLabel>{t('unifiedCanvas.showIntermediates')}</FormLabel>
<Checkbox
isChecked={shouldShowIntermediates}
onChange={handleChangeShouldShowIntermediates}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.showGrid')}</FormLabel>
<Checkbox
isChecked={shouldShowGrid}
onChange={handleChangeShouldShowGrid}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.snapToGrid')}</FormLabel>
<Checkbox
isChecked={shouldSnapToGrid}
onChange={handleChangeShouldSnapToGrid}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.darkenOutsideSelection')}</FormLabel>
<Checkbox
isChecked={shouldDarkenOutsideBoundingBox}
onChange={handleChangeShouldDarkenOutsideBoundingBox}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.autoSaveToGallery')}</FormLabel>
<Checkbox
isChecked={shouldAutoSave}
onChange={handleChangeShouldAutoSave}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.saveBoxRegionOnly')}</FormLabel>
<Checkbox
isChecked={shouldCropToBoundingBoxOnSave}
onChange={handleChangeShouldCropToBoundingBoxOnSave}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.limitStrokesToBox')}</FormLabel>
<Checkbox
isChecked={shouldRestrictStrokesToBox}
onChange={handleChangeShouldRestrictStrokesToBox}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.showCanvasDebugInfo')}</FormLabel>
<Checkbox
isChecked={shouldShowCanvasDebugInfo}
onChange={handleChangeShouldShowCanvasDebugInfo}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.antialiasing')}</FormLabel>
<Checkbox
isChecked={shouldAntialias}
onChange={handleChangeShouldAntialias}
/>
</FormControl>
<FormControlGroup formLabelProps={formLabelProps}>
<FormControl>
<FormLabel>{t('unifiedCanvas.showIntermediates')}</FormLabel>
<Checkbox
isChecked={shouldShowIntermediates}
onChange={handleChangeShouldShowIntermediates}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.showGrid')}</FormLabel>
<Checkbox
isChecked={shouldShowGrid}
onChange={handleChangeShouldShowGrid}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.snapToGrid')}</FormLabel>
<Checkbox
isChecked={shouldSnapToGrid}
onChange={handleChangeShouldSnapToGrid}
/>
</FormControl>
<FormControl>
<FormLabel>
{t('unifiedCanvas.darkenOutsideSelection')}
</FormLabel>
<Checkbox
isChecked={shouldDarkenOutsideBoundingBox}
onChange={handleChangeShouldDarkenOutsideBoundingBox}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.autoSaveToGallery')}</FormLabel>
<Checkbox
isChecked={shouldAutoSave}
onChange={handleChangeShouldAutoSave}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.saveBoxRegionOnly')}</FormLabel>
<Checkbox
isChecked={shouldCropToBoundingBoxOnSave}
onChange={handleChangeShouldCropToBoundingBoxOnSave}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.limitStrokesToBox')}</FormLabel>
<Checkbox
isChecked={shouldRestrictStrokesToBox}
onChange={handleChangeShouldRestrictStrokesToBox}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.showCanvasDebugInfo')}</FormLabel>
<Checkbox
isChecked={shouldShowCanvasDebugInfo}
onChange={handleChangeShouldShowCanvasDebugInfo}
/>
</FormControl>
<FormControl>
<FormLabel>{t('unifiedCanvas.antialiasing')}</FormLabel>
<Checkbox
isChecked={shouldAntialias}
onChange={handleChangeShouldAntialias}
/>
</FormControl>
</FormControlGroup>
<ClearCanvasHistoryButtonModal />
</Flex>
</PopoverBody>

View File

@ -276,9 +276,9 @@ const IAICanvasToolChooserOptions = () => {
</Flex>
<Box w="full" pt={2} pb={2}>
<IAIColorPicker
withNumberInput={true}
color={brushColor}
onChange={handleChangeBrushColor}
withNumberInput
/>
</Box>
</Flex>

View File

@ -1,9 +1,9 @@
import { ChevronUpIcon } from '@chakra-ui/icons';
import {
Box,
Flex,
FormControl,
FormLabel,
Icon,
IconButton,
Switch,
} from '@invoke-ai/ui';
@ -19,7 +19,7 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiCopyBold, PiTrashSimpleBold } from 'react-icons/pi';
import { PiCaretUpBold, PiCopyBold, PiTrashSimpleBold } from 'react-icons/pi';
import { useToggle } from 'react-use';
import ControlAdapterImagePreview from './ControlAdapterImagePreview';
@ -130,7 +130,9 @@ const ControlAdapterConfig = (props: { id: string; number: number }) => {
onClick={toggleIsExpanded}
variant="ghost"
icon={
<ChevronUpIcon
<Icon
boxSize={4}
as={PiCaretUpBold}
transform={isExpanded ? 'rotate(0deg)' : 'rotate(180deg)'}
transitionProperty="common"
transitionDuration="normal"

View File

@ -5,6 +5,7 @@ import { memo } from 'react';
import CannyProcessor from './processors/CannyProcessor';
import ColorMapProcessor from './processors/ColorMapProcessor';
import ContentShuffleProcessor from './processors/ContentShuffleProcessor';
import DepthAnyThingProcessor from './processors/DepthAnyThingProcessor';
import HedProcessor from './processors/HedProcessor';
import LineartAnimeProcessor from './processors/LineartAnimeProcessor';
import LineartProcessor from './processors/LineartProcessor';
@ -48,6 +49,16 @@ const ControlAdapterProcessorComponent = ({ id }: Props) => {
);
}
if (processorNode.type === 'depth_anything_image_processor') {
return (
<DepthAnyThingProcessor
controlNetId={id}
processorNode={processorNode}
isEnabled={isEnabled}
/>
);
}
if (processorNode.type === 'hed_image_processor') {
return (
<HedProcessor

View File

@ -0,0 +1,110 @@
import type { ComboboxOnChange } from '@invoke-ai/ui';
import {
Combobox,
CompositeNumberInput,
CompositeSlider,
FormControl,
FormLabel,
} from '@invoke-ai/ui';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
import type {
DepthAnythingModelSize,
RequiredDepthAnythingImageProcessorInvocation,
} from 'features/controlAdapters/store/types';
import { isDepthAnythingModelSize } from 'features/controlAdapters/store/types';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
const DEFAULTS = CONTROLNET_PROCESSORS.midas_depth_image_processor
.default as RequiredDepthAnythingImageProcessorInvocation;
type Props = {
controlNetId: string;
processorNode: RequiredDepthAnythingImageProcessorInvocation;
isEnabled: boolean;
};
const DepthAnythingProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { model_size, resolution } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const handleModelSizeChange = useCallback<ComboboxOnChange>(
(v) => {
if (!isDepthAnythingModelSize(v?.value)) {
return;
}
processorChanged(controlNetId, {
model_size: v.value,
});
},
[controlNetId, processorChanged]
);
const options: { label: string; value: DepthAnythingModelSize }[] = useMemo(
() => [
{ label: t('controlnet.small'), value: 'small' },
{ label: t('controlnet.base'), value: 'base' },
{ label: t('controlnet.large'), value: 'large' },
],
[t]
);
const value = useMemo(
() => options.filter((o) => o.value === model_size)[0],
[options, model_size]
);
const handleResolutionChange = useCallback(
(v: number) => {
processorChanged(controlNetId, { resolution: v });
},
[controlNetId, processorChanged]
);
const handleResolutionDefaultChange = useCallback(() => {
processorChanged(controlNetId, { resolution: 512 });
}, [controlNetId, processorChanged]);
return (
<ProcessorWrapper>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.modelSize')}</FormLabel>
<Combobox
value={value}
defaultInputValue={DEFAULTS.model_size}
options={options}
onChange={handleModelSizeChange}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.imageResolution')}</FormLabel>
<CompositeSlider
value={resolution}
onChange={handleResolutionChange}
defaultValue={DEFAULTS.resolution}
min={64}
max={4096}
step={64}
marks
onReset={handleResolutionDefaultChange}
/>
<CompositeNumberInput
value={resolution}
onChange={handleResolutionChange}
defaultValue={DEFAULTS.resolution}
min={64}
max={4096}
step={64}
/>
</FormControl>
</ProcessorWrapper>
);
};
export default memo(DepthAnythingProcessor);

View File

@ -16,7 +16,7 @@ export const useAddControlAdapter = (type: ControlAdapterType) => {
const firstCompatibleModel = models.filter((m) =>
baseModel ? m.base_model === baseModel : true
)[0];
console.log("test")
if (firstCompatibleModel) {
return firstCompatibleModel;
}

View File

@ -83,6 +83,22 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
f: 256,
},
},
depth_anything_image_processor: {
type: 'depth_anything_image_processor',
get label() {
return i18n.t('controlnet.depthAnything');
},
get description() {
return i18n.t('controlnet.depthAnythingDescription');
},
default: {
id: 'depth_anything_image_processor',
type: 'depth_anything_image_processor',
model_size: 'small',
resolution: 512,
offload: false,
},
},
hed_image_processor: {
type: 'hed_image_processor',
get label() {
@ -245,7 +261,7 @@ export const CONTROLNET_MODEL_DEFAULT_PROCESSORS: {
} = {
canny: 'canny_image_processor',
mlsd: 'mlsd_image_processor',
depth: 'midas_depth_image_processor',
depth: 'depth_anything_image_processor',
bae: 'normalbae_image_processor',
sketch: 'pidi_image_processor',
scribble: 'lineart_image_processor',

View File

@ -10,6 +10,7 @@ import type {
CannyImageProcessorInvocation,
ColorMapImageProcessorInvocation,
ContentShuffleImageProcessorInvocation,
DepthAnythingImageProcessorInvocation,
HedImageProcessorInvocation,
LineartAnimeImageProcessorInvocation,
LineartImageProcessorInvocation,
@ -31,6 +32,7 @@ export type ControlAdapterProcessorNode =
| CannyImageProcessorInvocation
| ColorMapImageProcessorInvocation
| ContentShuffleImageProcessorInvocation
| DepthAnythingImageProcessorInvocation
| HedImageProcessorInvocation
| LineartAnimeImageProcessorInvocation
| LineartImageProcessorInvocation
@ -73,6 +75,20 @@ export type RequiredContentShuffleImageProcessorInvocation = O.Required<
'type' | 'detect_resolution' | 'image_resolution' | 'w' | 'h' | 'f'
>;
/**
* The DepthAnything processor node, with parameters flagged as required
*/
export type RequiredDepthAnythingImageProcessorInvocation = O.Required<
DepthAnythingImageProcessorInvocation,
'type' | 'model_size' | 'resolution' | 'offload'
>;
export const zDepthAnythingModelSize = z.enum(['large', 'base', 'small']);
export type DepthAnythingModelSize = z.infer<typeof zDepthAnythingModelSize>;
export const isDepthAnythingModelSize = (
v: unknown
): v is DepthAnythingModelSize => zDepthAnythingModelSize.safeParse(v).success;
/**
* The HED processor node, with parameters flagged as required
*/
@ -161,6 +177,7 @@ export type RequiredControlAdapterProcessorNode =
| RequiredCannyImageProcessorInvocation
| RequiredColorMapImageProcessorInvocation
| RequiredContentShuffleImageProcessorInvocation
| RequiredDepthAnythingImageProcessorInvocation
| RequiredHedImageProcessorInvocation
| RequiredLineartAnimeImageProcessorInvocation
| RequiredLineartImageProcessorInvocation
@ -219,6 +236,22 @@ export const isContentShuffleImageProcessorInvocation = (
return false;
};
/**
* Type guard for DepthAnythingImageProcessorInvocation
*/
export const isDepthAnythingImageProcessorInvocation = (
obj: unknown
): obj is DepthAnythingImageProcessorInvocation => {
if (
isObject(obj) &&
'type' in obj &&
obj.type === 'depth_anything_image_processor'
) {
return true;
}
return false;
};
/**
* Type guard for HedImageprocessorInvocation
*/

View File

@ -61,7 +61,12 @@ const ParamDynamicPromptsPreview = () => {
}
return (
<FormControl orientation="vertical" w="full" h="full">
<FormControl
orientation="vertical"
w="full"
h="full"
isInvalid={Boolean(parsingError || isError)}
>
<InformationalPopover feature="dynamicPrompts" inPortal={false}>
<FormLabel>{label}</FormLabel>
</InformationalPopover>

View File

@ -13,7 +13,11 @@ const loadingStyles: SystemStyleObject = {
export const ShowDynamicPromptsPreviewButton = memo(() => {
const { t } = useTranslation();
const isLoading = useAppSelector((s) => s.dynamicPrompts.isLoading);
const isError = useAppSelector((s) =>
Boolean(s.dynamicPrompts.isError || s.dynamicPrompts.parsingError)
);
const { isOpen, onOpen } = useDynamicPromptsModal();
return (
<Tooltip
label={
@ -30,6 +34,7 @@ export const ShowDynamicPromptsPreviewButton = memo(() => {
icon={<BsBracesAsterisk />}
onClick={onOpen}
sx={isLoading ? loadingStyles : undefined}
colorScheme={isError && !isLoading ? 'error' : 'base'}
/>
</Tooltip>
);

View File

@ -47,6 +47,7 @@ export const dynamicPromptsSlice = createSlice({
},
promptsChanged: (state, action: PayloadAction<string[]>) => {
state.prompts = action.payload;
state.isLoading = false;
},
parsingErrorChanged: (
state,

View File

@ -1,3 +1,3 @@
const hasOpenCloseCurlyBracesRegex = /.*\{.*\}.*/;
const hasOpenCloseCurlyBracesRegex = /.*\{[\s\S]*\}.*/;
export const getShouldProcessPrompt = (prompt: string): boolean =>
hasOpenCloseCurlyBracesRegex.test(prompt);

View File

@ -58,7 +58,7 @@ const BoardsSearch = () => {
<IconButton
onClick={clearBoardSearch}
size="sm"
variant="ghost"
variant="link"
aria-label={t('boards.clearSearch')}
icon={<PiXBold />}
/>

View File

@ -32,6 +32,7 @@ import { memo, useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import {
PiArrowsCounterClockwiseBold,
PiAsteriskBold,
PiDotsThreeOutlineFill,
PiFlowArrowBold,
@ -129,6 +130,16 @@ const CurrentImageButtons = () => {
useHotkeys('p', handleUsePrompt, [metadata]);
const handleRemixImage = useCallback(() => {
// Recalls all metadata parameters except seed
recallAllParameters({
...metadata,
seed: undefined,
});
}, [metadata, recallAllParameters]);
useHotkeys('r', handleRemixImage, [metadata]);
const handleUseSize = useCallback(() => {
recallWidthAndHeight(metadata?.width, metadata?.height);
}, [metadata?.width, metadata?.height, recallWidthAndHeight]);
@ -231,6 +242,14 @@ const CurrentImageButtons = () => {
onClick={handleLoadWorkflow}
isLoading={getAndLoadEmbeddedWorkflowResult.isLoading}
/>
<IconButton
isLoading={isLoadingMetadata}
icon={<PiArrowsCounterClockwiseBold />}
tooltip={`${t('parameters.remixImage')} (R)`}
aria-label={`${t('parameters.remixImage')} (R)`}
isDisabled={!metadata?.positive_prompt}
onClick={handleRemixImage}
/>
<IconButton
isLoading={isLoadingMetadata}
icon={<PiQuotesBold />}

View File

@ -1,7 +1,7 @@
import { ChevronUpIcon } from '@chakra-ui/icons';
import { Button, Flex, Spacer } from '@invoke-ai/ui';
import { Button, Flex, Icon, Spacer } from '@invoke-ai/ui';
import { useAppSelector } from 'app/store/storeHooks';
import { memo, useMemo } from 'react';
import { PiCaretUpBold } from 'react-icons/pi';
import { useBoardName } from 'services/api/hooks/useBoardName';
type Props = {
@ -36,7 +36,9 @@ const GalleryBoardName = (props: Props) => {
<Spacer />
{formattedBoardName}
<Spacer />
<ChevronUpIcon
<Icon
as={PiCaretUpBold}
boxSize={4}
transform={isOpen ? 'rotate(0deg)' : 'rotate(180deg)'}
transitionProperty="common"
transitionDuration="normal"

View File

@ -1,8 +1,10 @@
import type { FormLabelProps } from '@invoke-ai/ui';
import {
Checkbox,
CompositeSlider,
Flex,
FormControl,
FormControlGroup,
FormLabel,
IconButton,
Popover,
@ -24,6 +26,10 @@ import { RiSettings4Fill } from 'react-icons/ri';
import BoardAutoAddSelect from './Boards/BoardAutoAddSelect';
const formLabelProps: FormLabelProps = {
flexGrow: 1,
};
const GallerySettingsPopover = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
@ -78,20 +84,22 @@ const GallerySettingsPopover = () => {
defaultValue={90}
/>
</FormControl>
<FormControl>
<FormLabel>{t('gallery.autoSwitchNewImages')}</FormLabel>
<Switch
isChecked={shouldAutoSwitch}
onChange={handleChangeAutoSwitch}
/>
</FormControl>
<FormControl>
<FormLabel>{t('gallery.autoAssignBoardOnClick')}</FormLabel>
<Checkbox
isChecked={autoAssignBoardOnClick}
onChange={handleChangeAutoAssignBoardOnClick}
/>
</FormControl>
<FormControlGroup formLabelProps={formLabelProps}>
<FormControl>
<FormLabel>{t('gallery.autoSwitchNewImages')}</FormLabel>
<Switch
isChecked={shouldAutoSwitch}
onChange={handleChangeAutoSwitch}
/>
</FormControl>
<FormControl>
<FormLabel>{t('gallery.autoAssignBoardOnClick')}</FormLabel>
<Checkbox
isChecked={autoAssignBoardOnClick}
onChange={handleChangeAutoAssignBoardOnClick}
/>
</FormControl>
</FormControlGroup>
<BoardAutoAddSelect />
</Flex>
</PopoverBody>

View File

@ -24,6 +24,7 @@ import { memo, useCallback } from 'react';
import { flushSync } from 'react-dom';
import { useTranslation } from 'react-i18next';
import {
PiArrowsCounterClockwiseBold,
PiAsteriskBold,
PiCopyBold,
PiDownloadSimpleBold,
@ -127,6 +128,14 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
recallAllParameters(metadata);
}, [metadata, recallAllParameters]);
const handleRemixImage = useCallback(() => {
// Recalls all metadata parameters except seed
recallAllParameters({
...metadata,
seed: undefined,
});
}, [metadata, recallAllParameters]);
const handleChangeBoard = useCallback(() => {
dispatch(imagesToChangeSelected([imageDTO]));
dispatch(isModalOpenChanged(true));
@ -187,6 +196,19 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
>
{t('nodes.loadWorkflow')}
</MenuItem>
<MenuItem
icon={
isLoadingMetadata ? <SpinnerIcon /> : <PiArrowsCounterClockwiseBold />
}
onClickCapture={handleRemixImage}
isDisabled={
isLoadingMetadata ||
(metadata?.positive_prompt === undefined &&
metadata?.negative_prompt === undefined)
}
>
{t('parameters.remixImage')}
</MenuItem>
<MenuItem
icon={isLoadingMetadata ? <SpinnerIcon /> : <PiQuotesBold />}
onClickCapture={handleRecallPrompt}

View File

@ -1,5 +1,5 @@
import { Box, Flex, IconButton, Tooltip } from '@invoke-ai/ui';
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
import { getOverlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
import { isString } from 'lodash-es';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import type { CSSProperties } from 'react';
@ -15,6 +15,11 @@ type Props = {
withCopy?: boolean;
};
const overlayscrollbarsOptions = getOverlayScrollbarsParams(
'scroll',
'scroll'
).options;
const DataViewer = (props: Props) => {
const { label, data, fileName, withDownload = true, withCopy = true } = props;
const dataString = useMemo(
@ -60,7 +65,7 @@ const DataViewer = (props: Props) => {
<OverlayScrollbarsComponent
defer
style={overlayScrollbarsStyles}
options={overlayScrollbarsParams.options}
options={overlayscrollbarsOptions}
>
<pre>{dataString}</pre>
</OverlayScrollbarsComponent>

View File

@ -1,5 +1,4 @@
import { ExternalLinkIcon } from '@chakra-ui/icons';
import { Flex, IconButton, Link, Text, Tooltip } from '@invoke-ai/ui';
import { ExternalLink, Flex, IconButton, Text, Tooltip } from '@invoke-ai/ui';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
@ -67,9 +66,7 @@ const ImageMetadataItem = ({
{label}:
</Text>
{isLink ? (
<Link href={value.toString()} isExternal wordBreak="break-all">
{value.toString()} <ExternalLinkIcon mx="2px" />
</Link>
<ExternalLink href={value.toString()} label={value.toString()} />
) : (
<Text overflowY="scroll" wordBreak="break-all">
{value.toString()}

View File

@ -1,13 +1,11 @@
import { ExternalLinkIcon } from '@chakra-ui/icons';
import {
ExternalLink,
Flex,
Link,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
Text,
} from '@invoke-ai/ui';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
@ -46,13 +44,7 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
position="absolute"
overflow="hidden"
>
<Flex gap={2}>
<Text fontWeight="semibold">{t('common.file')}:</Text>
<Link href={image.image_url} isExternal maxW="calc(100% - 3rem)">
{image.image_name}
<ExternalLinkIcon mx="2px" />
</Link>
</Flex>
<ExternalLink href={image.image_url} label={image.image_name} />
<Tabs
variant="line"

View File

@ -122,13 +122,10 @@ const AdvancedAddDiffusers = (props: AdvancedAddDiffusersProps) => {
<FormErrorMessage>{errors.model_name?.message}</FormErrorMessage>
)}
</FormControl>
<FormControl>
<FormLabel>{t('modelManager.baseModel')}</FormLabel>
<BaseModelSelect<DiffusersModelConfig>
control={control}
name="base_model"
/>
</FormControl>
<BaseModelSelect<DiffusersModelConfig>
control={control}
name="base_model"
/>
<FormControl isInvalid={Boolean(errors.path)}>
<FormLabel>{t('modelManager.modelLocation')}</FormLabel>
<Input
@ -137,7 +134,7 @@ const AdvancedAddDiffusers = (props: AdvancedAddDiffusersProps) => {
value.trim().length > 0 || 'Must provide a path',
onBlur,
})}
/>{' '}
/>
{errors.path?.message && (
<FormErrorMessage>{errors.path?.message}</FormErrorMessage>
)}

View File

@ -109,28 +109,28 @@ const InputField = ({ nodeId, fieldName }: Props) => {
return (
<InputFieldWrapper shouldDim={shouldDim}>
<FieldContextMenu nodeId={nodeId} fieldName={fieldName} kind="input">
{(ref) => (
<FormControl
ref={ref}
isInvalid={isMissingInput}
isDisabled={isConnected}
orientation="vertical"
px={2}
>
<Flex flexDir="column" w="full" gap={1}>
<FormControl
isInvalid={isMissingInput}
isDisabled={isConnected}
orientation="vertical"
px={2}
>
<Flex flexDir="column" w="full" gap={1}>
<FieldContextMenu nodeId={nodeId} fieldName={fieldName} kind="input">
{(ref) => (
<EditableFieldTitle
ref={ref}
nodeId={nodeId}
fieldName={fieldName}
kind="input"
isMissingInput={isMissingInput}
withTooltip
/>
<InputFieldRenderer nodeId={nodeId} fieldName={fieldName} />
</Flex>
</FormControl>
)}
</FieldContextMenu>
)}
</FieldContextMenu>
<InputFieldRenderer nodeId={nodeId} fieldName={fieldName} />
</Flex>
</FormControl>
{fieldTemplate.input !== 'direct' && (
<FieldHandle

View File

@ -1,8 +1,8 @@
import { ChevronUpIcon } from '@chakra-ui/icons';
import { IconButton } from '@invoke-ai/ui';
import { Icon, IconButton } from '@invoke-ai/ui';
import { useAppDispatch } from 'app/store/storeHooks';
import { nodeIsOpenChanged } from 'features/nodes/store/nodesSlice';
import { memo, useCallback } from 'react';
import { PiCaretUpBold } from 'react-icons/pi';
import { useUpdateNodeInternals } from 'reactflow';
interface Props {
@ -29,7 +29,8 @@ const NodeCollapseButton = ({ nodeId, isOpen }: Props) => {
h={8}
variant="link"
icon={
<ChevronUpIcon
<Icon
as={PiCaretUpBold}
transform={isOpen ? 'rotate(0deg)' : 'rotate(180deg)'}
transitionProperty="common"
transitionDuration="normal"

View File

@ -65,7 +65,7 @@ export const zModelType = z.enum([
'controlnet',
'embedding',
]);
export const zModelName = z.string().trim().min(1);
export const zModelName = z.string().min(3);
export const zModelIdentifier = z.object({
model_name: zModelName,
base_model: zBaseModel,

View File

@ -15,7 +15,7 @@ export type XYPosition = z.infer<typeof zXYPosition>;
export const zDimension = z.number().gt(0).nullish();
export type Dimension = z.infer<typeof zDimension>;
export const zWorkflowCategory = z.enum(['user', 'default']);
export const zWorkflowCategory = z.enum(['user', 'default', 'project']);
export type WorkflowCategory = z.infer<typeof zWorkflowCategory>;
// #endregion

View File

@ -24,6 +24,7 @@ const workflowKeys = [
'notes',
'exposedFields',
'meta',
'id',
] satisfies (keyof WorkflowV2)[];
export type BuildWorkflowFunction = (arg: BuildWorkflowArg) => WorkflowV2;

View File

@ -34,7 +34,7 @@ import {
modelSelected,
} from 'features/parameters/store/actions';
import {
heightChanged,
heightRecalled,
selectGenerationSlice,
setCfgRescaleMultiplier,
setCfgScale,
@ -45,8 +45,9 @@ import {
setSeed,
setSteps,
vaeSelected,
widthChanged,
widthRecalled,
} from 'features/parameters/store/generationSlice';
import type { ParameterModel } from 'features/parameters/types/parameterSchemas';
import {
isParameterCFGRescaleMultiplier,
isParameterCFGScale,
@ -372,7 +373,7 @@ export const useRecallParameters = () => {
parameterNotSetToast();
return;
}
dispatch(widthChanged(width));
dispatch(widthRecalled(width));
parameterSetToast();
},
[dispatch, parameterSetToast, parameterNotSetToast]
@ -387,7 +388,7 @@ export const useRecallParameters = () => {
parameterNotSetToast();
return;
}
dispatch(heightChanged(height));
dispatch(heightRecalled(height));
parameterSetToast();
},
[dispatch, parameterSetToast, parameterNotSetToast]
@ -406,8 +407,8 @@ export const useRecallParameters = () => {
allParameterNotSetToast();
return;
}
dispatch(heightChanged(height));
dispatch(widthChanged(width));
dispatch(heightRecalled(height));
dispatch(widthRecalled(width));
allParameterSetToast();
},
[dispatch, allParameterSetToast, allParameterNotSetToast]
@ -480,7 +481,7 @@ export const useRecallParameters = () => {
const { data: loraModels } = useGetLoRAModelsQuery(undefined);
const prepareLoRAMetadataItem = useCallback(
(loraMetadataItem: LoRAMetadataItem) => {
(loraMetadataItem: LoRAMetadataItem, newModel?: ParameterModel) => {
if (!isParameterLoRAModel(loraMetadataItem.lora)) {
return { lora: null, error: 'Invalid LoRA model' };
}
@ -499,7 +500,7 @@ export const useRecallParameters = () => {
}
const isCompatibleBaseModel =
matchingLoRA?.base_model === model?.base_model;
matchingLoRA?.base_model === (newModel ?? model)?.base_model;
if (!isCompatibleBaseModel) {
return {
@ -510,7 +511,7 @@ export const useRecallParameters = () => {
return { lora: matchingLoRA, error: null };
},
[loraModels, model?.base_model]
[loraModels, model]
);
const recallLoRA = useCallback(
@ -538,7 +539,10 @@ export const useRecallParameters = () => {
const { data: controlNetModels } = useGetControlNetModelsQuery(undefined);
const prepareControlNetMetadataItem = useCallback(
(controlnetMetadataItem: ControlNetMetadataItem) => {
(
controlnetMetadataItem: ControlNetMetadataItem,
newModel?: ParameterModel
) => {
if (!isParameterControlNetModel(controlnetMetadataItem.control_model)) {
return { controlnet: null, error: 'Invalid ControlNet model' };
}
@ -565,7 +569,7 @@ export const useRecallParameters = () => {
}
const isCompatibleBaseModel =
matchingControlNetModel?.base_model === model?.base_model;
matchingControlNetModel?.base_model === (newModel ?? model)?.base_model;
if (!isCompatibleBaseModel) {
return {
@ -600,7 +604,7 @@ export const useRecallParameters = () => {
return { controlnet, error: null };
},
[controlNetModels, model?.base_model]
[controlNetModels, model]
);
const recallControlNet = useCallback(
@ -631,7 +635,10 @@ export const useRecallParameters = () => {
const { data: t2iAdapterModels } = useGetT2IAdapterModelsQuery(undefined);
const prepareT2IAdapterMetadataItem = useCallback(
(t2iAdapterMetadataItem: T2IAdapterMetadataItem) => {
(
t2iAdapterMetadataItem: T2IAdapterMetadataItem,
newModel?: ParameterModel
) => {
if (
!isParameterControlNetModel(t2iAdapterMetadataItem.t2i_adapter_model)
) {
@ -659,7 +666,7 @@ export const useRecallParameters = () => {
}
const isCompatibleBaseModel =
matchingT2IAdapterModel?.base_model === model?.base_model;
matchingT2IAdapterModel?.base_model === (newModel ?? model)?.base_model;
if (!isCompatibleBaseModel) {
return {
@ -690,7 +697,7 @@ export const useRecallParameters = () => {
return { t2iAdapter, error: null };
},
[model?.base_model, t2iAdapterModels]
[model, t2iAdapterModels]
);
const recallT2IAdapter = useCallback(
@ -721,7 +728,10 @@ export const useRecallParameters = () => {
const { data: ipAdapterModels } = useGetIPAdapterModelsQuery(undefined);
const prepareIPAdapterMetadataItem = useCallback(
(ipAdapterMetadataItem: IPAdapterMetadataItem) => {
(
ipAdapterMetadataItem: IPAdapterMetadataItem,
newModel?: ParameterModel
) => {
if (!isParameterIPAdapterModel(ipAdapterMetadataItem?.ip_adapter_model)) {
return { ipAdapter: null, error: 'Invalid IP Adapter model' };
}
@ -746,7 +756,7 @@ export const useRecallParameters = () => {
}
const isCompatibleBaseModel =
matchingIPAdapterModel?.base_model === model?.base_model;
matchingIPAdapterModel?.base_model === (newModel ?? model)?.base_model;
if (!isCompatibleBaseModel) {
return {
@ -768,7 +778,7 @@ export const useRecallParameters = () => {
return { ipAdapter, error: null };
},
[ipAdapterModels, model?.base_model]
[ipAdapterModels, model]
);
const recallIPAdapter = useCallback(
@ -840,6 +850,13 @@ export const useRecallParameters = () => {
t2iAdapters,
} = metadata;
let newModel: ParameterModel | undefined = undefined;
if (isParameterModel(model)) {
newModel = model;
dispatch(modelSelected(model));
}
if (isParameterCFGScale(cfg_scale)) {
dispatch(setCfgScale(cfg_scale));
}
@ -848,10 +865,6 @@ export const useRecallParameters = () => {
dispatch(setCfgRescaleMultiplier(cfg_rescale_multiplier));
}
if (isParameterModel(model)) {
dispatch(modelSelected(model));
}
if (isParameterPositivePrompt(positive_prompt)) {
dispatch(setPositivePrompt(positive_prompt));
}
@ -880,11 +893,11 @@ export const useRecallParameters = () => {
}
if (isParameterWidth(width)) {
dispatch(widthChanged(width));
dispatch(widthRecalled(width));
}
if (isParameterHeight(height)) {
dispatch(heightChanged(height));
dispatch(heightRecalled(height));
}
if (isParameterStrength(strength)) {
@ -953,7 +966,7 @@ export const useRecallParameters = () => {
dispatch(lorasCleared());
loras?.forEach((lora) => {
const result = prepareLoRAMetadataItem(lora);
const result = prepareLoRAMetadataItem(lora, newModel);
if (result.lora) {
dispatch(loraRecalled({ ...result.lora, weight: lora.weight }));
}
@ -961,21 +974,21 @@ export const useRecallParameters = () => {
dispatch(controlAdaptersReset());
controlnets?.forEach((controlnet) => {
const result = prepareControlNetMetadataItem(controlnet);
const result = prepareControlNetMetadataItem(controlnet, newModel);
if (result.controlnet) {
dispatch(controlAdapterRecalled(result.controlnet));
}
});
ipAdapters?.forEach((ipAdapter) => {
const result = prepareIPAdapterMetadataItem(ipAdapter);
const result = prepareIPAdapterMetadataItem(ipAdapter, newModel);
if (result.ipAdapter) {
dispatch(controlAdapterRecalled(result.ipAdapter));
}
});
t2iAdapters?.forEach((t2iAdapter) => {
const result = prepareT2IAdapterMetadataItem(t2iAdapter);
const result = prepareT2IAdapterMetadataItem(t2iAdapter, newModel);
if (result.t2iAdapter) {
dispatch(controlAdapterRecalled(result.t2iAdapter));
}

View File

@ -175,8 +175,15 @@ export const generationSlice = createSlice({
}
// Clamp ClipSkip Based On Selected Model
const { maxClip } = CLIP_SKIP_MAP[newModel.base_model];
state.clipSkip = clamp(state.clipSkip, 0, maxClip);
// TODO(psyche): remove this special handling when https://github.com/invoke-ai/InvokeAI/issues/4583 is resolved
// WIP PR here: https://github.com/invoke-ai/InvokeAI/pull/4624
if (newModel.base_model === 'sdxl') {
// We don't support clip skip for SDXL yet - it's not in the graphs
state.clipSkip = 0;
} else {
const { maxClip } = CLIP_SKIP_MAP[newModel.base_model];
state.clipSkip = clamp(state.clipSkip, 0, maxClip);
}
if (action.meta.previousModel?.base_model === newModel.base_model) {
// The base model hasn't changed, we don't need to optimize the size
@ -223,6 +230,18 @@ export const generationSlice = createSlice({
heightChanged: (state, action: PayloadAction<number>) => {
state.height = action.payload;
},
widthRecalled: (state, action: PayloadAction<number>) => {
state.width = action.payload;
state.aspectRatio.value = action.payload / state.height;
state.aspectRatio.id = 'Free';
state.aspectRatio.isLocked = false;
},
heightRecalled: (state, action: PayloadAction<number>) => {
state.height = action.payload;
state.aspectRatio.value = state.width / action.payload;
state.aspectRatio.id = 'Free';
state.aspectRatio.isLocked = false;
},
aspectRatioChanged: (state, action: PayloadAction<AspectRatioState>) => {
state.aspectRatio = action.payload;
},
@ -299,6 +318,8 @@ export const {
aspectRatioChanged,
widthChanged,
heightChanged,
widthRecalled,
heightRecalled,
} = generationSlice.actions;
export const { selectOptimalDimension } = generationSlice.selectors;

View File

@ -6,7 +6,6 @@ import {
Spinner,
Text,
} from '@invoke-ai/ui';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { useCancelBatch } from 'features/queue/hooks/useCancelBatch';
import { useCancelQueueItem } from 'features/queue/hooks/useCancelQueueItem';
@ -127,9 +126,7 @@ const QueueItemComponent = ({ queueItemDTO }: Props) => {
justifyContent="center"
>
{queueItem ? (
<ScrollableContent>
<DataViewer label="Queue Item" data={queueItem} />
</ScrollableContent>
<DataViewer label="Queue Item" data={queueItem} />
) : (
<Spinner opacity={0.5} />
)}

View File

@ -1,23 +1,26 @@
import { useDisclosure } from '@invoke-ai/ui';
import { useAppDispatch } from 'app/store/storeHooks';
import { expanderToggled } from 'features/settingsAccordions/store/actions';
import { useCallback } from 'react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { expanderStateChanged, selectUiSlice } from 'features/ui/store/uiSlice';
import { useCallback, useMemo } from 'react';
type UseExpanderToggleArg = {
defaultIsOpen: boolean;
id?: string;
id: string;
};
export const useExpanderToggle = (arg: UseExpanderToggleArg) => {
const dispatch = useAppDispatch();
const { isOpen, onToggle: _onToggle } = useDisclosure({
defaultIsOpen: arg.defaultIsOpen,
});
const selectIsOpen = useMemo(
() =>
createSelector(
selectUiSlice,
(ui) => ui.expanders[arg.id] ?? arg.defaultIsOpen
),
[arg]
);
const isOpen = useAppSelector(selectIsOpen);
const onToggle = useCallback(() => {
if (arg.id) {
dispatch(expanderToggled({ id: arg.id, isOpen }));
}
_onToggle();
}, [_onToggle, dispatch, arg.id, isOpen]);
dispatch(expanderStateChanged({ id: arg.id, isOpen: !isOpen }));
}, [dispatch, arg.id, isOpen]);
return { isOpen, onToggle };
};

View File

@ -1,25 +1,31 @@
import { useDisclosure } from '@invoke-ai/ui';
import { useAppDispatch } from 'app/store/storeHooks';
import { standaloneAccordionToggled } from 'features/settingsAccordions/store/actions';
import { useCallback } from 'react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
accordionStateChanged,
selectUiSlice,
} from 'features/ui/store/uiSlice';
import { useCallback, useMemo } from 'react';
type UseStandaloneAccordionToggleArg = {
defaultIsOpen: boolean;
id?: string;
id: string;
};
export const useStandaloneAccordionToggle = (
arg: UseStandaloneAccordionToggleArg
) => {
const dispatch = useAppDispatch();
const { isOpen, onToggle: _onToggle } = useDisclosure({
defaultIsOpen: arg.defaultIsOpen,
});
const selectIsOpen = useMemo(
() =>
createSelector(
selectUiSlice,
(ui) => ui.accordions[arg.id] ?? arg.defaultIsOpen
),
[arg]
);
const isOpen = useAppSelector(selectIsOpen);
const onToggle = useCallback(() => {
if (arg.id) {
dispatch(standaloneAccordionToggled({ id: arg.id, isOpen }));
}
_onToggle();
}, [_onToggle, arg.id, dispatch, isOpen]);
dispatch(accordionStateChanged({ id: arg.id, isOpen: !isOpen }));
}, [arg.id, dispatch, isOpen]);
return { isOpen, onToggle };
};

View File

@ -1,10 +0,0 @@
import { createAction } from '@reduxjs/toolkit';
export const expanderToggled = createAction<{ id: string; isOpen: boolean }>(
'parameters/expanderToggled'
);
export const standaloneAccordionToggled = createAction<{
id: string;
isOpen: boolean;
}>('parameters/standaloneAccordionToggled');

View File

@ -1,11 +1,10 @@
import { ExternalLinkIcon } from '@chakra-ui/icons';
import {
ExternalLink,
Flex,
Grid,
GridItem,
Heading,
Image,
Link,
Modal,
ModalBody,
ModalCloseButton,
@ -103,23 +102,21 @@ const AboutModal = ({ children }: AboutModalProps) => {
{appVersion && <Text>{`v${appVersion?.version}`}</Text>}
<Grid templateColumns="repeat(2, 1fr)" gap="3">
<GridItem>
<Link fontSize="sm" href={githubLink} isExternal>
{t('common.githubLabel')}
<ExternalLinkIcon mx="2px" />
</Link>
<ExternalLink
href={githubLink}
label={t('common.githubLabel')}
/>
</GridItem>
<GridItem>
<Link fontSize="sm" href={discordLink} isExternal>
{t('common.discordLabel')}
<ExternalLinkIcon mx="2px" />
</Link>
<ExternalLink
href={discordLink}
label={t('common.discordLabel')}
/>
</GridItem>
</Grid>
<Heading fontSize="large">{t('common.aboutHeading')}</Heading>
<Text fontSize="sm">{t('common.aboutDesc')}</Text>
<Link isExternal href={websiteLink} fontSize="sm">
{websiteLink} <ExternalLinkIcon mx="2px" />
</Link>
<ExternalLink href={websiteLink} label={websiteLink} />
</Flex>
</GridItem>
</Grid>

View File

@ -1,4 +1,3 @@
import { CloseIcon } from '@chakra-ui/icons';
import {
Divider,
Flex,
@ -30,6 +29,7 @@ import {
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { PiXBold } from 'react-icons/pi';
import HotkeyListItem from './HotkeyListItem';
@ -103,7 +103,8 @@ const HotkeysModal = ({ children }: HotkeysModalProps) => {
size="sm"
variant="ghost"
aria-label={t('hotkeys.clearSearch')}
icon={<CloseIcon boxSize={3} />}
boxSize={4}
icon={<PiXBold />}
/>
</InputRightElement>
)}

View File

@ -81,6 +81,11 @@ export const useHotkeyData = (): HotkeyGroup[] => {
() => ({
title: t('hotkeys.generalHotkeys'),
hotkeyListItems: [
{
title: t('hotkeys.remixImage.title'),
desc: t('hotkeys.remixImage.desc'),
hotkeys: [['R']],
},
{
title: t('hotkeys.setPrompt.title'),
desc: t('hotkeys.setPrompt.desc'),

View File

@ -1,8 +1,8 @@
import { SpinnerIcon } from '@chakra-ui/icons';
import type { SystemStyleObject } from '@invoke-ai/ui';
import {
ButtonGroup,
Flex,
Icon,
IconButton,
Portal,
spinAnimation,
@ -14,7 +14,7 @@ import { useQueueBack } from 'features/queue/hooks/useQueueBack';
import type { UsePanelReturn } from 'features/ui/hooks/usePanel';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiSlidersHorizontalBold } from 'react-icons/pi';
import { PiCircleNotchBold, PiSlidersHorizontalBold } from 'react-icons/pi';
import { RiSparklingFill } from 'react-icons/ri';
import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
@ -35,7 +35,7 @@ const FloatingSidePanelButtons = (props: Props) => {
const queueButtonIcon = useMemo(
() =>
!isDisabled && queueStatus?.processor.is_processing ? (
<SpinnerIcon animation={spinAnimation} />
<Icon boxSize={6} as={PiCircleNotchBold} animation={spinAnimation} />
) : (
<RiSparklingFill size="16px" />
),

View File

@ -10,10 +10,10 @@ export const initialUIState: UIState = {
_version: 1,
activeTab: 'txt2img',
shouldShowImageDetails: false,
shouldShowExistingModelsInSearch: false,
shouldHidePreview: false,
shouldShowProgressInViewer: true,
panels: {},
accordions: {},
expanders: {},
};
export const uiSlice = createSlice({
@ -26,15 +26,6 @@ export const uiSlice = createSlice({
setShouldShowImageDetails: (state, action: PayloadAction<boolean>) => {
state.shouldShowImageDetails = action.payload;
},
setShouldHidePreview: (state, action: PayloadAction<boolean>) => {
state.shouldHidePreview = action.payload;
},
setShouldShowExistingModelsInSearch: (
state,
action: PayloadAction<boolean>
) => {
state.shouldShowExistingModelsInSearch = action.payload;
},
setShouldShowProgressInViewer: (state, action: PayloadAction<boolean>) => {
state.shouldShowProgressInViewer = action.payload;
},
@ -44,6 +35,20 @@ export const uiSlice = createSlice({
) => {
state.panels[action.payload.name] = action.payload.value;
},
accordionStateChanged: (
state,
action: PayloadAction<{ id: string; isOpen: boolean }>
) => {
const { id, isOpen } = action.payload;
state.accordions[id] = isOpen;
},
expanderStateChanged: (
state,
action: PayloadAction<{ id: string; isOpen: boolean }>
) => {
const { id, isOpen } = action.payload;
state.expanders[id] = isOpen;
},
},
extraReducers(builder) {
builder.addCase(initialImageChanged, (state) => {
@ -55,10 +60,10 @@ export const uiSlice = createSlice({
export const {
setActiveTab,
setShouldShowImageDetails,
setShouldShowExistingModelsInSearch,
setShouldHidePreview,
setShouldShowProgressInViewer,
panelsChanged,
accordionStateChanged,
expanderStateChanged,
} = uiSlice.actions;
export default uiSlice.reducer;

View File

@ -1,11 +1,32 @@
import type { InvokeTabName } from './tabMap';
export interface UIState {
/**
* Slice schema version.
*/
_version: 1;
/**
* The currently active tab.
*/
activeTab: InvokeTabName;
/**
* Whether or not to show image details, e.g. metadata, workflow, etc.
*/
shouldShowImageDetails: boolean;
shouldShowExistingModelsInSearch: boolean;
shouldHidePreview: boolean;
/**
* Whether or not to show progress in the viewer.
*/
shouldShowProgressInViewer: boolean;
/**
* The react-resizable-panels state. The shape is managed by react-resizable-panels.
*/
panels: Record<string, string>;
/**
* The state of accordions. The key is the id of the accordion, and the value is a boolean representing the open state.
*/
accordions: Record<string, boolean>;
/**
* The state of expanders. The key is the id of the expander, and the value is a boolean representing the open state.
*/
expanders: Record<string, boolean>;
}

View File

@ -1,4 +1,3 @@
import { CloseIcon } from '@chakra-ui/icons';
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui';
import {
Button,
@ -14,6 +13,8 @@ import {
InputRightElement,
Spacer,
} from '@invoke-ai/ui';
import { useStore } from '@nanostores/react';
import { $projectId } from 'app/store/nanostores/projectId';
import {
IAINoContentFallback,
IAINoContentFallbackWithSpinner,
@ -25,6 +26,7 @@ import WorkflowLibraryPagination from 'features/workflowLibrary/components/Workf
import type { ChangeEvent, KeyboardEvent } from 'react';
import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PiXBold } from 'react-icons/pi';
import { useListWorkflowsQuery } from 'services/api/endpoints/workflows';
import type {
SQLiteDirection,
@ -62,6 +64,7 @@ const WorkflowLibraryList = () => {
const [order_by, setOrderBy] = useState<WorkflowRecordOrderBy>('opened_at');
const [direction, setDirection] = useState<SQLiteDirection>('ASC');
const [debouncedQuery] = useDebounce(query, 500);
const projectId = useStore($projectId);
const queryArg = useMemo<Parameters<typeof useListWorkflowsQuery>[0]>(() => {
if (category === 'user') {
@ -142,13 +145,8 @@ const WorkflowLibraryList = () => {
[]
);
const handleSetUserCategory = useCallback(() => {
setCategory('user');
setPage(0);
}, []);
const handleSetDefaultCategory = useCallback(() => {
setCategory('default');
const handleSetCategory = useCallback((category: WorkflowCategory) => {
setCategory(category);
setPage(0);
}, []);
@ -158,21 +156,31 @@ const WorkflowLibraryList = () => {
<ButtonGroup>
<Button
variant={category === 'user' ? undefined : 'ghost'}
onClick={handleSetUserCategory}
onClick={handleSetCategory.bind(null, 'user')}
isChecked={category === 'user'}
>
{t('workflows.userWorkflows')}
</Button>
<Button
variant={category === 'default' ? undefined : 'ghost'}
onClick={handleSetDefaultCategory}
isChecked={category === 'default'}
>
{t('workflows.defaultWorkflows')}
</Button>
{projectId ? (
<Button
variant={category === 'project' ? undefined : 'ghost'}
onClick={handleSetCategory.bind(null, 'project')}
isChecked={category === 'project'}
>
{t('workflows.projectWorkflows')}
</Button>
) : (
<Button
variant={category === 'default' ? undefined : 'ghost'}
onClick={handleSetCategory.bind(null, 'default')}
isChecked={category === 'default'}
>
{t('workflows.defaultWorkflows')}
</Button>
)}
</ButtonGroup>
<Spacer />
{category === 'user' && (
{category !== 'default' && (
<>
<FormControl isDisabled={isFetching} w={64} minW={56}>
<FormLabel>{t('common.orderBy')}</FormLabel>
@ -202,14 +210,13 @@ const WorkflowLibraryList = () => {
minW={64}
/>
{query.trim().length && (
<InputRightElement>
<InputRightElement h="full" pe={2}>
<IconButton
onClick={resetFilterText}
size="xs"
variant="ghost"
size="sm"
variant="link"
aria-label={t('workflows.clearWorkflowSearchFilter')}
opacity={0.5}
icon={<CloseIcon boxSize={2} />}
icon={<PiXBold />}
/>
</InputRightElement>
)}
@ -229,7 +236,7 @@ const WorkflowLibraryList = () => {
</Flex>
</ScrollableContent>
) : (
<IAINoContentFallback label={t('workflows.noUserWorkflows')} />
<IAINoContentFallback label={t('workflows.noWorkflows')} />
)}
<Divider />
{data && (

View File

@ -52,7 +52,7 @@ const WorkflowLibraryListItem = ({ workflowDTO }: Props) => {
{workflowDTO.name || t('workflows.unnamedWorkflow')}
</Heading>
<Spacer />
{workflowDTO.category === 'user' && (
{workflowDTO.category !== 'default' && (
<Text
fontSize="sm"
variant="subtext"
@ -81,7 +81,7 @@ const WorkflowLibraryListItem = ({ workflowDTO }: Props) => {
</Text>
)}
<Spacer />
{workflowDTO.category === 'user' && (
{workflowDTO.category !== 'default' && (
<Text
fontSize="sm"
variant="subtext"
@ -104,7 +104,7 @@ const WorkflowLibraryListItem = ({ workflowDTO }: Props) => {
>
{t('common.load')}
</Button>
{workflowDTO.category === 'user' && (
{workflowDTO.category !== 'default' && (
<Button
flexShrink={0}
colorScheme="error"

View File

@ -32,7 +32,7 @@ if (import.meta.env.MODE === 'package') {
fallbackLng: 'en',
debug: false,
backend: {
loadPath: '/locales/{{lng}}.json',
loadPath: `${window.location.href.replace(/\/$/, '')}/locales/{{lng}}.json`,
},
interpolation: {
escapeValue: false,

View File

@ -57,7 +57,9 @@ const dynamicBaseQuery: BaseQueryFn<
const projectId = $projectId.get();
const rawBaseQuery = fetchBaseQuery({
baseUrl: `${baseUrl ?? ''}/api/v1`,
baseUrl: baseUrl
? `${baseUrl}/api/v1`
: `${window.location.href.replace(/\/$/, '')}/api/v1`,
prepareHeaders: (headers) => {
if (authToken) {
headers.set('Authorization', `Bearer ${authToken}`);

File diff suppressed because one or more lines are too long

View File

@ -26,8 +26,9 @@ export const receivedOpenAPISchema = createAsyncThunk(
'nodes/receivedOpenAPISchema',
async (_, { rejectWithValue }) => {
try {
const url = [window.location.origin, 'openapi.json'].join('/');
const response = await fetch(url);
const response = await fetch(
`${window.location.href.replace(/\/$/, '')}/openapi.json`
);
const openAPISchema = await response.json();
const schemaJSON = JSON.parse(

View File

@ -164,6 +164,8 @@ export type ColorMapImageProcessorInvocation =
s['ColorMapImageProcessorInvocation'];
export type ContentShuffleImageProcessorInvocation =
s['ContentShuffleImageProcessorInvocation'];
export type DepthAnythingImageProcessorInvocation =
s['DepthAnythingImageProcessorInvocation'];
export type HedImageProcessorInvocation = s['HedImageProcessorInvocation'];
export type LineartAnimeImageProcessorInvocation =
s['LineartAnimeImageProcessorInvocation'];

View File

@ -1 +1 @@
__version__ = "3.6.0"
__version__ = "3.6.2"

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "InvokeAI"
description = "An test implementation of Stable Diffusion which provides various new features and options to aid the image generation process"
description = "An implementation of Stable Diffusion which provides various new features and options to aid the image generation process"
requires-python = ">=3.10, <3.12"
readme = { content-type = "text/markdown", file = "README.md" }
keywords = ["stable-diffusion", "AI"]
@ -33,12 +33,12 @@ classifiers = [
]
dependencies = [
# Core generation dependencies, pinned for reproducible builds.
"accelerate==0.25.0",
"accelerate==0.26.1",
"basicsr==1.4.2",
"clip_anytorch==2.5.2", # replacing "clip @ https://github.com/openai/CLIP/archive/eaa22acb90a5876642d0507623e859909230a52d.zip",
"compel==2.0.2",
"controlnet-aux==0.0.7",
"diffusers[torch]==0.25.0",
"diffusers[torch]==0.25.1",
"invisible-watermark==0.2.0", # needed to install SDXL base and refiner using their repo_ids
"mediapipe==0.10.7", # needed for "mediapipeface" controlnet model
"numpy==1.26.3", # >1.24.0 is needed to use the 'strict' argument to np.testing.assert_array_equal()
@ -52,7 +52,7 @@ dependencies = [
"torchmetrics==0.11.4",
"torchsde==0.2.6",
"torchvision==0.16.2",
"transformers==4.36.2",
"transformers==4.37.0",
# Core application dependencies, pinned for reproducible builds.
"fastapi-events==0.10.0",