mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-01-22 21:08:08 -05:00
Compare commits
8 Commits
psyche/fea
...
psyche/exp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
affdca4316 | ||
|
|
10ca09a5b2 | ||
|
|
b88d23e916 | ||
|
|
04460a87ac | ||
|
|
da27dcff33 | ||
|
|
e3af5d59ed | ||
|
|
bf0fa4be76 | ||
|
|
fe32973e1c |
@@ -105,7 +105,7 @@ Invoke features an organized gallery system for easily storing, accessing, and r
|
||||
### Other features
|
||||
|
||||
- Support for both ckpt and diffusers models
|
||||
- SD1.5, SD2.0, SDXL, and FLUX support
|
||||
- SD1.5, SD2.0, and SDXL support
|
||||
- Upscaling Tools
|
||||
- Embedding Manager & Support
|
||||
- Model Manager & Support
|
||||
|
||||
@@ -40,7 +40,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
elif [ "$GPU_DRIVER" = "rocm" ]; then \
|
||||
extra_index_url_arg="--extra-index-url https://download.pytorch.org/whl/rocm5.6"; \
|
||||
else \
|
||||
extra_index_url_arg="--extra-index-url https://download.pytorch.org/whl/cu124"; \
|
||||
extra_index_url_arg="--extra-index-url https://download.pytorch.org/whl/cu121"; \
|
||||
fi &&\
|
||||
|
||||
# xformers + triton fails to install on arm64
|
||||
|
||||
@@ -144,7 +144,7 @@ As you might have noticed, we added two new arguments to the `InputField`
|
||||
definition for `width` and `height`, called `gt` and `le`. They stand for
|
||||
_greater than or equal to_ and _less than or equal to_.
|
||||
|
||||
These impose constraints on those fields, and will raise an exception if the
|
||||
These impose contraints on those fields, and will raise an exception if the
|
||||
values do not meet the constraints. Field constraints are provided by
|
||||
**pydantic**, so anything you see in the **pydantic docs** will work.
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ Consult the
|
||||
get it set up.
|
||||
|
||||
Suggest using VSCode's included settings sync so that your remote dev host has
|
||||
all the same app settings and extensions automatically.
|
||||
all the same app settings and extensions automagically.
|
||||
|
||||
##### One remote dev gotcha
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## **What do I need to know to help?**
|
||||
|
||||
If you are looking to help with a code contribution, InvokeAI uses several different technologies under the hood: Python (Pydantic, FastAPI, diffusers) and Typescript (React, Redux Toolkit, ChakraUI, Mantine, Konva). Familiarity with StableDiffusion and image generation concepts is helpful, but not essential.
|
||||
If you are looking to help to with a code contribution, InvokeAI uses several different technologies under the hood: Python (Pydantic, FastAPI, diffusers) and Typescript (React, Redux Toolkit, ChakraUI, Mantine, Konva). Familiarity with StableDiffusion and image generation concepts is helpful, but not essential.
|
||||
|
||||
|
||||
## **Get Started**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Tutorials
|
||||
|
||||
Tutorials help new & existing users expand their ability to use InvokeAI to the full extent of our features and services.
|
||||
Tutorials help new & existing users expand their abilty to use InvokeAI to the full extent of our features and services.
|
||||
|
||||
Currently, we have a set of tutorials available on our [YouTube channel](https://www.youtube.com/@invokeai), but as InvokeAI continues to evolve with new updates, we want to ensure that we are giving our users the resources they need to succeed.
|
||||
|
||||
@@ -8,4 +8,4 @@ Tutorials can be in the form of videos or article walkthroughs on a subject of y
|
||||
|
||||
## Contributing
|
||||
|
||||
Please reach out to @imic or @hipsterusername on [Discord](https://discord.gg/ZmtBAhwWhy) to help create tutorials for InvokeAI.
|
||||
Please reach out to @imic or @hipsterusername on [Discord](https://discord.gg/ZmtBAhwWhy) to help create tutorials for InvokeAI.
|
||||
@@ -21,7 +21,6 @@ To use a community workflow, download the `.json` node graph file and load it in
|
||||
+ [Clothing Mask](#clothing-mask)
|
||||
+ [Contrast Limited Adaptive Histogram Equalization](#contrast-limited-adaptive-histogram-equalization)
|
||||
+ [Depth Map from Wavefront OBJ](#depth-map-from-wavefront-obj)
|
||||
+ [Enhance Detail](#enhance-detail)
|
||||
+ [Film Grain](#film-grain)
|
||||
+ [Generative Grammar-Based Prompt Nodes](#generative-grammar-based-prompt-nodes)
|
||||
+ [GPT2RandomPromptMaker](#gpt2randompromptmaker)
|
||||
@@ -82,7 +81,7 @@ Note: These are inherited from the core nodes so any update to the core nodes sh
|
||||
|
||||
**Example Usage:**
|
||||
</br>
|
||||
<img src="https://raw.githubusercontent.com/skunkworxdark/autostereogram_nodes/refs/heads/main/images/spider.png" width="200" /> -> <img src="https://raw.githubusercontent.com/skunkworxdark/autostereogram_nodes/refs/heads/main/images/spider-depth.png" width="200" /> -> <img src="https://raw.githubusercontent.com/skunkworxdark/autostereogram_nodes/refs/heads/main/images/spider-dots.png" width="200" /> <img src="https://raw.githubusercontent.com/skunkworxdark/autostereogram_nodes/refs/heads/main/images/spider-pattern.png" width="200" />
|
||||
<img src="https://github.com/skunkworxdark/autostereogram_nodes/blob/main/images/spider.png" width="200" /> -> <img src="https://github.com/skunkworxdark/autostereogram_nodes/blob/main/images/spider-depth.png" width="200" /> -> <img src="https://github.com/skunkworxdark/autostereogram_nodes/raw/main/images/spider-dots.png" width="200" /> <img src="https://github.com/skunkworxdark/autostereogram_nodes/raw/main/images/spider-pattern.png" width="200" />
|
||||
|
||||
--------------------------------
|
||||
### Average Images
|
||||
@@ -143,17 +142,6 @@ To be imported, an .obj must use triangulated meshes, so make sure to enable tha
|
||||
**Example Usage:**
|
||||
</br><img src="https://raw.githubusercontent.com/dwringer/depth-from-obj-node/main/depth_from_obj_usage.jpg" width="500" />
|
||||
|
||||
--------------------------------
|
||||
### Enhance Detail
|
||||
|
||||
**Description:** A single node that can enhance the detail in an image. Increase or decrease details in an image using a guided filter (as opposed to the typical Gaussian blur used by most sharpening filters.) Based on the `Enhance Detail` ComfyUI node from https://github.com/spacepxl/ComfyUI-Image-Filters
|
||||
|
||||
**Node Link:** https://github.com/skunkworxdark/enhance-detail-node
|
||||
|
||||
**Example Usage:**
|
||||
</br>
|
||||
<img src="https://raw.githubusercontent.com/skunkworxdark/enhance-detail-node/refs/heads/main/images/Comparison.png" />
|
||||
|
||||
--------------------------------
|
||||
### Film Grain
|
||||
|
||||
@@ -320,7 +308,7 @@ View:
|
||||
**Node Link:** https://github.com/helix4u/load_video_frame
|
||||
|
||||
**Output Example:**
|
||||
<img src="https://raw.githubusercontent.com/helix4u/load_video_frame/refs/heads/main/_git_assets/dance1736978273.gif" width="500" />
|
||||
<img src="https://raw.githubusercontent.com/helix4u/load_video_frame/main/_git_assets/testmp4_embed_converted.gif" width="500" />
|
||||
|
||||
--------------------------------
|
||||
### Make 3D
|
||||
@@ -361,7 +349,7 @@ See full docs here: https://github.com/skunkworxdark/Prompt-tools-nodes/edit/mai
|
||||
|
||||
**Output Examples**
|
||||
|
||||
<img src="https://github.com/skunkworxdark/match_histogram/assets/21961335/ed12f329-a0ef-444a-9bae-129ed60d6097" />
|
||||
<img src="https://github.com/skunkworxdark/match_histogram/assets/21961335/ed12f329-a0ef-444a-9bae-129ed60d6097" width="300" />
|
||||
|
||||
--------------------------------
|
||||
### Metadata Linked Nodes
|
||||
@@ -419,7 +407,7 @@ View:
|
||||
--------------------------------
|
||||
### One Button Prompt
|
||||
|
||||
<img src="https://raw.githubusercontent.com/AIrjen/OneButtonPrompt_X_InvokeAI/refs/heads/main/images/background.png" width="800" />
|
||||
<img src="https://github.com/AIrjen/OneButtonPrompt_X_InvokeAI/blob/main/images/background.png" width="800" />
|
||||
|
||||
**Description:** an extensive suite of auto prompt generation and prompt helper nodes based on extensive logic. Get creative with the best prompt generator in the world.
|
||||
|
||||
@@ -429,7 +417,7 @@ The main node generates interesting prompts based on a set of parameters. There
|
||||
|
||||
**Nodes:**
|
||||
|
||||
<img src="https://raw.githubusercontent.com/AIrjen/OneButtonPrompt_X_InvokeAI/refs/heads/main/images/OBP_nodes_invokeai.png" width="800" />
|
||||
<img src="https://github.com/AIrjen/OneButtonPrompt_X_InvokeAI/blob/main/images/OBP_nodes_invokeai.png" width="800" />
|
||||
|
||||
--------------------------------
|
||||
### Oobabooga
|
||||
@@ -482,7 +470,7 @@ See full docs here: https://github.com/skunkworxdark/Prompt-tools-nodes/edit/mai
|
||||
|
||||
**Workflow Examples**
|
||||
|
||||
<img src="https://raw.githubusercontent.com/skunkworxdark/prompt-tools/refs/heads/main/images/CSVToIndexStringNode.png"/>
|
||||
<img src="https://github.com/skunkworxdark/prompt-tools/blob/main/images/CSVToIndexStringNode.png" width="300" />
|
||||
|
||||
--------------------------------
|
||||
### Remote Image
|
||||
@@ -620,7 +608,7 @@ See full docs here: https://github.com/skunkworxdark/XYGrid_nodes/edit/main/READ
|
||||
|
||||
**Output Examples**
|
||||
|
||||
<img src="https://raw.githubusercontent.com/skunkworxdark/XYGrid_nodes/refs/heads/main/images/collage.png" />
|
||||
<img src="https://github.com/skunkworxdark/XYGrid_nodes/blob/main/images/collage.png" width="300" />
|
||||
|
||||
|
||||
--------------------------------
|
||||
|
||||
6
flake.lock
generated
6
flake.lock
generated
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1727955264,
|
||||
"narHash": "sha256-lrd+7mmb5NauRoMa8+J1jFKYVa+rc8aq2qc9+CxPDKc=",
|
||||
"lastModified": 1690630721,
|
||||
"narHash": "sha256-Y04onHyBQT4Erfr2fc82dbJTfXGYrf4V0ysLUYnPOP8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "71cd616696bd199ef18de62524f3df3ffe8b9333",
|
||||
"rev": "d2b52322f35597c62abf56de91b0236746b2a03d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
cudaPackages.cudnn
|
||||
cudaPackages.cuda_nvrtc
|
||||
cudatoolkit
|
||||
pkg-config
|
||||
pkgconfig
|
||||
libconfig
|
||||
cmake
|
||||
blas
|
||||
@@ -66,7 +66,7 @@
|
||||
black
|
||||
|
||||
# Frontend.
|
||||
pnpm_8
|
||||
yarn
|
||||
nodejs
|
||||
];
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
|
||||
|
||||
@@ -282,6 +282,12 @@ class InvokeAiInstance:
|
||||
shutil.copy(src, dest)
|
||||
os.chmod(dest, 0o0755)
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
def remove(self):
|
||||
pass
|
||||
|
||||
|
||||
### Utility functions ###
|
||||
|
||||
@@ -396,7 +402,7 @@ def get_torch_source() -> Tuple[str | None, str | None]:
|
||||
:rtype: list
|
||||
"""
|
||||
|
||||
from messages import GpuType, select_gpu
|
||||
from messages import select_gpu
|
||||
|
||||
# device can be one of: "cuda", "rocm", "cpu", "cuda_and_dml, autodetect"
|
||||
device = select_gpu()
|
||||
@@ -406,21 +412,15 @@ def get_torch_source() -> Tuple[str | None, str | None]:
|
||||
url = None
|
||||
optional_modules: str | None = None
|
||||
if OS == "Linux":
|
||||
if device == GpuType.ROCM:
|
||||
if device.value == "rocm":
|
||||
url = "https://download.pytorch.org/whl/rocm5.6"
|
||||
elif device == GpuType.CPU:
|
||||
elif device.value == "cpu":
|
||||
url = "https://download.pytorch.org/whl/cpu"
|
||||
elif device == GpuType.CUDA:
|
||||
url = "https://download.pytorch.org/whl/cu124"
|
||||
optional_modules = "[onnx-cuda]"
|
||||
elif device == GpuType.CUDA_WITH_XFORMERS:
|
||||
url = "https://download.pytorch.org/whl/cu124"
|
||||
elif device.value == "cuda":
|
||||
# CUDA uses the default PyPi index
|
||||
optional_modules = "[xformers,onnx-cuda]"
|
||||
elif OS == "Windows":
|
||||
if device == GpuType.CUDA:
|
||||
url = "https://download.pytorch.org/whl/cu124"
|
||||
optional_modules = "[onnx-cuda]"
|
||||
elif device == GpuType.CUDA_WITH_XFORMERS:
|
||||
if device.value == "cuda":
|
||||
url = "https://download.pytorch.org/whl/cu124"
|
||||
optional_modules = "[xformers,onnx-cuda]"
|
||||
elif device.value == "cpu":
|
||||
|
||||
@@ -206,7 +206,6 @@ def dest_path(dest: Optional[str | Path] = None) -> Path | None:
|
||||
|
||||
|
||||
class GpuType(Enum):
|
||||
CUDA_WITH_XFORMERS = "xformers"
|
||||
CUDA = "cuda"
|
||||
ROCM = "rocm"
|
||||
CPU = "cpu"
|
||||
@@ -222,15 +221,11 @@ def select_gpu() -> GpuType:
|
||||
return GpuType.CPU
|
||||
|
||||
nvidia = (
|
||||
"an [gold1 b]NVIDIA[/] RTX 3060 or newer GPU using CUDA",
|
||||
"an [gold1 b]NVIDIA[/] GPU (using CUDA™)",
|
||||
GpuType.CUDA,
|
||||
)
|
||||
vintage_nvidia = (
|
||||
"an [gold1 b]NVIDIA[/] RTX 20xx or older GPU using CUDA+xFormers",
|
||||
GpuType.CUDA_WITH_XFORMERS,
|
||||
)
|
||||
amd = (
|
||||
"an [gold1 b]AMD[/] GPU using ROCm",
|
||||
"an [gold1 b]AMD[/] GPU (using ROCm™)",
|
||||
GpuType.ROCM,
|
||||
)
|
||||
cpu = (
|
||||
@@ -240,13 +235,14 @@ def select_gpu() -> GpuType:
|
||||
|
||||
options = []
|
||||
if OS == "Windows":
|
||||
options = [nvidia, vintage_nvidia, cpu]
|
||||
options = [nvidia, cpu]
|
||||
if OS == "Linux":
|
||||
options = [nvidia, vintage_nvidia, amd, cpu]
|
||||
options = [nvidia, amd, cpu]
|
||||
elif OS == "Darwin":
|
||||
options = [cpu]
|
||||
|
||||
if len(options) == 1:
|
||||
print(f'Your platform [gold1]{OS}-{ARCH}[/] only supports the "{options[0][1]}" driver. Proceeding with that.')
|
||||
return options[0][1]
|
||||
|
||||
options = {str(i): opt for i, opt in enumerate(options, 1)}
|
||||
|
||||
@@ -5,7 +5,7 @@ from fastapi.routing import APIRouter
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from invokeai.app.api.dependencies import ApiDependencies
|
||||
from invokeai.app.services.board_records.board_records_common import BoardChanges
|
||||
from invokeai.app.services.board_records.board_records_common import BoardChanges, UncategorizedImageCounts
|
||||
from invokeai.app.services.boards.boards_common import BoardDTO
|
||||
from invokeai.app.services.shared.pagination import OffsetPaginatedResults
|
||||
|
||||
@@ -146,3 +146,25 @@ async def list_all_board_image_names(
|
||||
board_id,
|
||||
)
|
||||
return image_names
|
||||
|
||||
|
||||
@boards_router.get(
|
||||
"/uncategorized/counts",
|
||||
operation_id="get_uncategorized_image_counts",
|
||||
response_model=UncategorizedImageCounts,
|
||||
)
|
||||
async def get_uncategorized_image_counts() -> UncategorizedImageCounts:
|
||||
"""Gets count of images and assets for uncategorized images (images with no board assocation)"""
|
||||
|
||||
return ApiDependencies.invoker.services.board_records.get_uncategorized_image_counts()
|
||||
|
||||
|
||||
@boards_router.get(
|
||||
"/uncategorized/names",
|
||||
operation_id="get_uncategorized_image_names",
|
||||
response_model=list[str],
|
||||
)
|
||||
async def get_uncategorized_image_names() -> list[str]:
|
||||
"""Gets count of images and assets for uncategorized images (images with no board assocation)"""
|
||||
|
||||
return ApiDependencies.invoker.services.board_records.get_uncategorized_image_names()
|
||||
|
||||
61
invokeai/app/api/routers/dupe images.ipynb
Normal file
61
invokeai/app/api/routers/dupe images.ipynb
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pathlib import Path\n",
|
||||
"import sqlite3\n",
|
||||
"from uuid import uuid4\n",
|
||||
"\n",
|
||||
"# duplicate _all_ images in gallery\n",
|
||||
"\n",
|
||||
"def duplicate_images(database_path: Path, num_copies: int):\n",
|
||||
" conn = sqlite3.connect(database_path)\n",
|
||||
" cursor = conn.cursor()\n",
|
||||
"\n",
|
||||
" cursor.execute(\"SELECT * FROM images\")\n",
|
||||
" rows = cursor.fetchall()\n",
|
||||
"\n",
|
||||
" for _ in range(num_copies):\n",
|
||||
" for row in rows:\n",
|
||||
" new_row = list(row)\n",
|
||||
" new_row[0] = str(uuid4()) # image_name is the first column\n",
|
||||
" placeholders = \", \".join(\"?\" for _ in new_row)\n",
|
||||
" cursor.execute(f\"INSERT INTO images VALUES ({placeholders})\", new_row)\n",
|
||||
"\n",
|
||||
" conn.commit()\n",
|
||||
" conn.close()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" database_path = Path(\"/home/bat/invokeai-4.0.0/databases/invokeai.db\")\n",
|
||||
" num_copies = 50\n",
|
||||
" duplicate_images(database_path, num_copies)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.12"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import io
|
||||
import traceback
|
||||
from typing import Optional
|
||||
from typing import Literal, Optional
|
||||
|
||||
from fastapi import BackgroundTasks, Body, HTTPException, Path, Query, Request, Response, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
@@ -12,10 +12,11 @@ from invokeai.app.api.dependencies import ApiDependencies
|
||||
from invokeai.app.invocations.fields import MetadataField
|
||||
from invokeai.app.services.image_records.image_records_common import (
|
||||
ImageCategory,
|
||||
ImageRecord,
|
||||
ImageRecordChanges,
|
||||
ResourceOrigin,
|
||||
)
|
||||
from invokeai.app.services.images.images_common import ImageDTO, ImageUrlsDTO
|
||||
from invokeai.app.services.images.images_common import ImageDTO, ImageUrlsDTO, image_record_to_dto
|
||||
from invokeai.app.services.shared.pagination import OffsetPaginatedResults
|
||||
from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection
|
||||
|
||||
@@ -462,3 +463,76 @@ async def get_bulk_download_item(
|
||||
return response
|
||||
except Exception:
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
|
||||
@images_router.get(
|
||||
"/image_names",
|
||||
operation_id="list_image_names",
|
||||
response_model=list[str],
|
||||
)
|
||||
async def list_image_names(
|
||||
board_id: str | None = Query(default=None),
|
||||
category: Literal["images", "assets"] = Query(default="images"),
|
||||
starred_first: bool = Query(default=True),
|
||||
order_dir: SQLiteDirection = Query(default=SQLiteDirection.Descending),
|
||||
search_term: Optional[str] = Query(default=None),
|
||||
) -> list[str]:
|
||||
"""Gets a list of image names"""
|
||||
|
||||
return ApiDependencies.invoker.services.image_records.get_image_names(
|
||||
board_id,
|
||||
category,
|
||||
starred_first,
|
||||
order_dir,
|
||||
search_term,
|
||||
)
|
||||
|
||||
|
||||
@images_router.get(
|
||||
"/images",
|
||||
operation_id="list_images",
|
||||
response_model=list[ImageRecord],
|
||||
)
|
||||
async def images(
|
||||
board_id: str | None = Query(default=None),
|
||||
category: Literal["images", "assets"] = Query(default="images"),
|
||||
starred_first: bool = Query(default=True),
|
||||
order_dir: SQLiteDirection = Query(default=SQLiteDirection.Descending),
|
||||
search_term: str | None = Query(default=None),
|
||||
from_image_name: str | None = Query(default=None),
|
||||
count: int = Query(default=10),
|
||||
) -> list[ImageRecord]:
|
||||
"""Gets a list of image names"""
|
||||
|
||||
return ApiDependencies.invoker.services.image_records.get_images(
|
||||
board_id,
|
||||
category,
|
||||
starred_first,
|
||||
order_dir,
|
||||
search_term,
|
||||
from_image_name,
|
||||
count,
|
||||
)
|
||||
|
||||
|
||||
@images_router.post(
|
||||
"/images/by_name",
|
||||
operation_id="get_images_by_name",
|
||||
response_model=list[ImageDTO],
|
||||
)
|
||||
async def get_images_by_name(image_names: list[str] = Body(embed=True)) -> list[ImageDTO]:
|
||||
"""Gets a list of image names"""
|
||||
|
||||
image_records = ApiDependencies.invoker.services.image_records.get_images_by_name(image_names)
|
||||
|
||||
image_dtos = [
|
||||
image_record_to_dto(
|
||||
image_record=r,
|
||||
image_url=ApiDependencies.invoker.services.urls.get_image_url(r.image_name),
|
||||
thumbnail_url=ApiDependencies.invoker.services.urls.get_image_url(r.image_name, True),
|
||||
board_id=ApiDependencies.invoker.services.board_image_records.get_board_for_image(r.image_name),
|
||||
)
|
||||
for r in image_records
|
||||
]
|
||||
|
||||
return image_dtos
|
||||
|
||||
@@ -83,15 +83,15 @@ async def create_workflow(
|
||||
)
|
||||
async def list_workflows(
|
||||
page: int = Query(default=0, description="The page to get"),
|
||||
per_page: Optional[int] = Query(default=None, description="The number of workflows per page"),
|
||||
per_page: int = Query(default=10, description="The number of workflows per page"),
|
||||
order_by: WorkflowRecordOrderBy = Query(
|
||||
default=WorkflowRecordOrderBy.Name, description="The attribute to order by"
|
||||
),
|
||||
direction: SQLiteDirection = Query(default=SQLiteDirection.Ascending, description="The direction to order by"),
|
||||
category: Optional[WorkflowCategory] = Query(default=None, description="The category of workflow to get"),
|
||||
category: WorkflowCategory = Query(default=WorkflowCategory.User, description="The category of workflow to get"),
|
||||
query: Optional[str] = Query(default=None, description="The text to query by (matches name and description)"),
|
||||
) -> PaginatedResults[WorkflowRecordListItemDTO]:
|
||||
"""Gets a page of workflows"""
|
||||
return ApiDependencies.invoker.services.workflow_records.get_many(
|
||||
order_by=order_by, direction=direction, page=page, per_page=per_page, query=query, category=category
|
||||
page=page, per_page=per_page, order_by=order_by, direction=direction, query=query, category=category
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from invokeai.app.services.board_records.board_records_common import BoardChanges, BoardRecord
|
||||
from invokeai.app.services.board_records.board_records_common import BoardChanges, BoardRecord, UncategorizedImageCounts
|
||||
from invokeai.app.services.shared.pagination import OffsetPaginatedResults
|
||||
|
||||
|
||||
@@ -48,3 +48,13 @@ class BoardRecordStorageBase(ABC):
|
||||
def get_all(self, include_archived: bool = False) -> list[BoardRecord]:
|
||||
"""Gets all board records."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_uncategorized_image_counts(self) -> UncategorizedImageCounts:
|
||||
"""Gets count of images and assets for uncategorized images (images with no board assocation)."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_uncategorized_image_names(self) -> list[str]:
|
||||
"""Gets names of uncategorized images."""
|
||||
pass
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional, Union
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -26,21 +26,25 @@ class BoardRecord(BaseModelExcludeNull):
|
||||
"""Whether or not the board is archived."""
|
||||
is_private: Optional[bool] = Field(default=None, description="Whether the board is private.")
|
||||
"""Whether the board is private."""
|
||||
image_count: int = Field(description="The number of images in the board.")
|
||||
asset_count: int = Field(description="The number of assets in the board.")
|
||||
|
||||
|
||||
def deserialize_board_record(board_dict: dict) -> BoardRecord:
|
||||
def deserialize_board_record(board_dict: dict[str, Any]) -> BoardRecord:
|
||||
"""Deserializes a board record."""
|
||||
|
||||
# Retrieve all the values, setting "reasonable" defaults if they are not present.
|
||||
|
||||
board_id = board_dict.get("board_id", "unknown")
|
||||
board_name = board_dict.get("board_name", "unknown")
|
||||
cover_image_name = board_dict.get("cover_image_name", "unknown")
|
||||
cover_image_name = board_dict.get("cover_image_name", None)
|
||||
created_at = board_dict.get("created_at", get_iso_timestamp())
|
||||
updated_at = board_dict.get("updated_at", get_iso_timestamp())
|
||||
deleted_at = board_dict.get("deleted_at", get_iso_timestamp())
|
||||
archived = board_dict.get("archived", False)
|
||||
is_private = board_dict.get("is_private", False)
|
||||
image_count = board_dict.get("image_count", 0)
|
||||
asset_count = board_dict.get("asset_count", 0)
|
||||
|
||||
return BoardRecord(
|
||||
board_id=board_id,
|
||||
@@ -51,6 +55,8 @@ def deserialize_board_record(board_dict: dict) -> BoardRecord:
|
||||
deleted_at=deleted_at,
|
||||
archived=archived,
|
||||
is_private=is_private,
|
||||
image_count=image_count,
|
||||
asset_count=asset_count,
|
||||
)
|
||||
|
||||
|
||||
@@ -63,19 +69,24 @@ class BoardChanges(BaseModel, extra="forbid"):
|
||||
class BoardRecordNotFoundException(Exception):
|
||||
"""Raised when an board record is not found."""
|
||||
|
||||
def __init__(self, message="Board record not found"):
|
||||
def __init__(self, message: str = "Board record not found"):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class BoardRecordSaveException(Exception):
|
||||
"""Raised when an board record cannot be saved."""
|
||||
|
||||
def __init__(self, message="Board record not saved"):
|
||||
def __init__(self, message: str = "Board record not saved"):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class BoardRecordDeleteException(Exception):
|
||||
"""Raised when an board record cannot be deleted."""
|
||||
|
||||
def __init__(self, message="Board record not deleted"):
|
||||
def __init__(self, message: str = "Board record not deleted"):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class UncategorizedImageCounts(BaseModel):
|
||||
image_count: int = Field(description="The number of uncategorized images.")
|
||||
asset_count: int = Field(description="The number of uncategorized assets.")
|
||||
|
||||
@@ -9,12 +9,121 @@ from invokeai.app.services.board_records.board_records_common import (
|
||||
BoardRecordDeleteException,
|
||||
BoardRecordNotFoundException,
|
||||
BoardRecordSaveException,
|
||||
UncategorizedImageCounts,
|
||||
deserialize_board_record,
|
||||
)
|
||||
from invokeai.app.services.shared.pagination import OffsetPaginatedResults
|
||||
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
|
||||
from invokeai.app.util.misc import uuid_string
|
||||
|
||||
_BASE_BOARD_RECORD_QUERY = """
|
||||
-- This query retrieves board records, joining with the board_images and images tables to get image counts and cover image names.
|
||||
-- It is not a complete query, as it is missing a GROUP BY or WHERE clause (and is unterminated).
|
||||
SELECT b.board_id,
|
||||
b.board_name,
|
||||
b.created_at,
|
||||
b.updated_at,
|
||||
b.archived,
|
||||
-- Count the number of images in the board, alias image_count
|
||||
COUNT(
|
||||
CASE
|
||||
WHEN i.image_category in ('general') -- "Images" are images in the 'general' category
|
||||
AND i.is_intermediate = 0 THEN 1 -- Intermediates are not counted
|
||||
END
|
||||
) AS image_count,
|
||||
-- Count the number of assets in the board, alias asset_count
|
||||
COUNT(
|
||||
CASE
|
||||
WHEN i.image_category in ('control', 'mask', 'user', 'other') -- "Assets" are images in any of the other categories ('control', 'mask', 'user', 'other')
|
||||
AND i.is_intermediate = 0 THEN 1 -- Intermediates are not counted
|
||||
END
|
||||
) AS asset_count,
|
||||
-- Get the name of the the most recent image in the board, alias cover_image_name
|
||||
(
|
||||
SELECT bi.image_name
|
||||
FROM board_images bi
|
||||
JOIN images i ON bi.image_name = i.image_name
|
||||
WHERE bi.board_id = b.board_id
|
||||
AND i.is_intermediate = 0 -- Intermediates cannot be cover images
|
||||
ORDER BY i.created_at DESC -- Sort by created_at to get the most recent image
|
||||
LIMIT 1
|
||||
) AS cover_image_name
|
||||
FROM boards b
|
||||
LEFT JOIN board_images bi ON b.board_id = bi.board_id
|
||||
LEFT JOIN images i ON bi.image_name = i.image_name
|
||||
"""
|
||||
|
||||
|
||||
def get_paginated_list_board_records_queries(include_archived: bool) -> str:
|
||||
"""Gets a query to retrieve a paginated list of board records. The query has placeholders for limit and offset.
|
||||
|
||||
Args:
|
||||
include_archived: Whether to include archived board records in the results.
|
||||
|
||||
Returns:
|
||||
A query to retrieve a paginated list of board records.
|
||||
"""
|
||||
|
||||
archived_condition = "WHERE b.archived = 0" if not include_archived else ""
|
||||
|
||||
# The GROUP BY must be added _after_ the WHERE clause!
|
||||
query = f"""
|
||||
{_BASE_BOARD_RECORD_QUERY}
|
||||
{archived_condition}
|
||||
GROUP BY b.board_id,
|
||||
b.board_name,
|
||||
b.created_at,
|
||||
b.updated_at
|
||||
ORDER BY b.created_at DESC
|
||||
LIMIT ? OFFSET ?;
|
||||
"""
|
||||
|
||||
return query
|
||||
|
||||
|
||||
def get_total_boards_count_query(include_archived: bool) -> str:
|
||||
"""Gets a query to retrieve the total count of board records.
|
||||
|
||||
Args:
|
||||
include_archived: Whether to include archived board records in the count.
|
||||
|
||||
Returns:
|
||||
A query to retrieve the total count of board records.
|
||||
"""
|
||||
|
||||
archived_condition = "WHERE b.archived = 0" if not include_archived else ""
|
||||
|
||||
return f"SELECT COUNT(*) FROM boards {archived_condition};"
|
||||
|
||||
|
||||
def get_list_all_board_records_query(include_archived: bool) -> str:
|
||||
"""Gets a query to retrieve all board records.
|
||||
|
||||
Args:
|
||||
include_archived: Whether to include archived board records in the results.
|
||||
|
||||
Returns:
|
||||
A query to retrieve all board records.
|
||||
"""
|
||||
|
||||
archived_condition = "WHERE b.archived = 0" if not include_archived else ""
|
||||
|
||||
return f"""
|
||||
{_BASE_BOARD_RECORD_QUERY}
|
||||
{archived_condition}
|
||||
GROUP BY b.board_id,
|
||||
b.board_name,
|
||||
b.created_at,
|
||||
b.updated_at
|
||||
ORDER BY b.created_at DESC;
|
||||
"""
|
||||
|
||||
|
||||
def get_board_record_query() -> str:
|
||||
"""Gets a query to retrieve a board record. The query has a placeholder for the board_id."""
|
||||
|
||||
return f"{_BASE_BOARD_RECORD_QUERY} WHERE b.board_id = ?;"
|
||||
|
||||
|
||||
class SqliteBoardRecordStorage(BoardRecordStorageBase):
|
||||
_conn: sqlite3.Connection
|
||||
@@ -76,11 +185,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase):
|
||||
try:
|
||||
self._lock.acquire()
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
SELECT *
|
||||
FROM boards
|
||||
WHERE board_id = ?;
|
||||
""",
|
||||
get_board_record_query(),
|
||||
(board_id,),
|
||||
)
|
||||
|
||||
@@ -92,7 +197,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase):
|
||||
self._lock.release()
|
||||
if result is None:
|
||||
raise BoardRecordNotFoundException
|
||||
return BoardRecord(**dict(result))
|
||||
return deserialize_board_record(dict(result))
|
||||
|
||||
def update(
|
||||
self,
|
||||
@@ -149,45 +254,15 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase):
|
||||
try:
|
||||
self._lock.acquire()
|
||||
|
||||
# Build base query
|
||||
base_query = """
|
||||
SELECT *
|
||||
FROM boards
|
||||
{archived_filter}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?;
|
||||
"""
|
||||
main_query = get_paginated_list_board_records_queries(include_archived=include_archived)
|
||||
|
||||
# Determine archived filter condition
|
||||
if include_archived:
|
||||
archived_filter = ""
|
||||
else:
|
||||
archived_filter = "WHERE archived = 0"
|
||||
|
||||
final_query = base_query.format(archived_filter=archived_filter)
|
||||
|
||||
# Execute query to fetch boards
|
||||
self._cursor.execute(final_query, (limit, offset))
|
||||
self._cursor.execute(main_query, (limit, offset))
|
||||
|
||||
result = cast(list[sqlite3.Row], self._cursor.fetchall())
|
||||
boards = [deserialize_board_record(dict(r)) for r in result]
|
||||
|
||||
# Determine count query
|
||||
if include_archived:
|
||||
count_query = """
|
||||
SELECT COUNT(*)
|
||||
FROM boards;
|
||||
"""
|
||||
else:
|
||||
count_query = """
|
||||
SELECT COUNT(*)
|
||||
FROM boards
|
||||
WHERE archived = 0;
|
||||
"""
|
||||
|
||||
# Execute count query
|
||||
self._cursor.execute(count_query)
|
||||
|
||||
total_query = get_total_boards_count_query(include_archived=include_archived)
|
||||
self._cursor.execute(total_query)
|
||||
count = cast(int, self._cursor.fetchone()[0])
|
||||
|
||||
return OffsetPaginatedResults[BoardRecord](items=boards, offset=offset, limit=limit, total=count)
|
||||
@@ -201,26 +276,10 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase):
|
||||
def get_all(self, include_archived: bool = False) -> list[BoardRecord]:
|
||||
try:
|
||||
self._lock.acquire()
|
||||
|
||||
base_query = """
|
||||
SELECT *
|
||||
FROM boards
|
||||
{archived_filter}
|
||||
ORDER BY created_at DESC
|
||||
"""
|
||||
|
||||
if include_archived:
|
||||
archived_filter = ""
|
||||
else:
|
||||
archived_filter = "WHERE archived = 0"
|
||||
|
||||
final_query = base_query.format(archived_filter=archived_filter)
|
||||
|
||||
self._cursor.execute(final_query)
|
||||
|
||||
query = get_list_all_board_records_query(include_archived=include_archived)
|
||||
self._cursor.execute(query)
|
||||
result = cast(list[sqlite3.Row], self._cursor.fetchall())
|
||||
boards = [deserialize_board_record(dict(r)) for r in result]
|
||||
|
||||
return boards
|
||||
|
||||
except sqlite3.Error as e:
|
||||
@@ -228,3 +287,47 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase):
|
||||
raise e
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def get_uncategorized_image_counts(self) -> UncategorizedImageCounts:
|
||||
try:
|
||||
self._lock.acquire()
|
||||
query = """
|
||||
-- Get the count of uncategorized images and assets.
|
||||
SELECT
|
||||
CASE
|
||||
WHEN i.image_category = 'general' THEN 'image_count' -- "Images" are images in the 'general' category
|
||||
ELSE 'asset_count' -- "Assets" are images in any of the other categories ('control', 'mask', 'user', 'other')
|
||||
END AS category_type,
|
||||
COUNT(*) AS unassigned_count
|
||||
FROM images i
|
||||
LEFT JOIN board_images bi ON i.image_name = bi.image_name
|
||||
WHERE bi.board_id IS NULL -- Uncategorized images have no board association
|
||||
AND i.is_intermediate = 0 -- Omit intermediates from the counts
|
||||
GROUP BY category_type; -- Group by category_type alias, as derived from the image_category column earlier
|
||||
"""
|
||||
self._cursor.execute(query)
|
||||
results = self._cursor.fetchall()
|
||||
image_count = dict(results)["image_count"]
|
||||
asset_count = dict(results)["asset_count"]
|
||||
return UncategorizedImageCounts(image_count=image_count, asset_count=asset_count)
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def get_uncategorized_image_names(self) -> list[str]:
|
||||
try:
|
||||
self._lock.acquire()
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
SELECT image_name
|
||||
FROM images
|
||||
WHERE image_name NOT IN (
|
||||
SELECT image_name
|
||||
FROM board_images
|
||||
);
|
||||
"""
|
||||
)
|
||||
result = cast(list[sqlite3.Row], self._cursor.fetchall())
|
||||
image_names = [r[0] for r in result]
|
||||
return image_names
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
@@ -1,23 +1,8 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from invokeai.app.services.board_records.board_records_common import BoardRecord
|
||||
|
||||
|
||||
# TODO(psyche): BoardDTO is now identical to BoardRecord. We should consider removing it.
|
||||
class BoardDTO(BoardRecord):
|
||||
"""Deserialized board record with cover image URL and image count."""
|
||||
"""Deserialized board record."""
|
||||
|
||||
cover_image_name: Optional[str] = Field(description="The name of the board's cover image.")
|
||||
"""The URL of the thumbnail of the most recent image in the board."""
|
||||
image_count: int = Field(description="The number of images in the board.")
|
||||
"""The number of images in the board."""
|
||||
|
||||
|
||||
def board_record_to_dto(board_record: BoardRecord, cover_image_name: Optional[str], image_count: int) -> BoardDTO:
|
||||
"""Converts a board record to a board DTO."""
|
||||
return BoardDTO(
|
||||
**board_record.model_dump(exclude={"cover_image_name"}),
|
||||
cover_image_name=cover_image_name,
|
||||
image_count=image_count,
|
||||
)
|
||||
pass
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from invokeai.app.services.board_records.board_records_common import BoardChanges
|
||||
from invokeai.app.services.boards.boards_base import BoardServiceABC
|
||||
from invokeai.app.services.boards.boards_common import BoardDTO, board_record_to_dto
|
||||
from invokeai.app.services.boards.boards_common import BoardDTO
|
||||
from invokeai.app.services.invoker import Invoker
|
||||
from invokeai.app.services.shared.pagination import OffsetPaginatedResults
|
||||
|
||||
@@ -16,17 +16,11 @@ class BoardService(BoardServiceABC):
|
||||
board_name: str,
|
||||
) -> BoardDTO:
|
||||
board_record = self.__invoker.services.board_records.save(board_name)
|
||||
return board_record_to_dto(board_record, None, 0)
|
||||
return BoardDTO.model_validate(board_record.model_dump())
|
||||
|
||||
def get_dto(self, board_id: str) -> BoardDTO:
|
||||
board_record = self.__invoker.services.board_records.get(board_id)
|
||||
cover_image = self.__invoker.services.image_records.get_most_recent_image_for_board(board_record.board_id)
|
||||
if cover_image:
|
||||
cover_image_name = cover_image.image_name
|
||||
else:
|
||||
cover_image_name = None
|
||||
image_count = self.__invoker.services.board_image_records.get_image_count_for_board(board_id)
|
||||
return board_record_to_dto(board_record, cover_image_name, image_count)
|
||||
return BoardDTO.model_validate(board_record.model_dump())
|
||||
|
||||
def update(
|
||||
self,
|
||||
@@ -34,14 +28,7 @@ class BoardService(BoardServiceABC):
|
||||
changes: BoardChanges,
|
||||
) -> BoardDTO:
|
||||
board_record = self.__invoker.services.board_records.update(board_id, changes)
|
||||
cover_image = self.__invoker.services.image_records.get_most_recent_image_for_board(board_record.board_id)
|
||||
if cover_image:
|
||||
cover_image_name = cover_image.image_name
|
||||
else:
|
||||
cover_image_name = None
|
||||
|
||||
image_count = self.__invoker.services.board_image_records.get_image_count_for_board(board_id)
|
||||
return board_record_to_dto(board_record, cover_image_name, image_count)
|
||||
return BoardDTO.model_validate(board_record.model_dump())
|
||||
|
||||
def delete(self, board_id: str) -> None:
|
||||
self.__invoker.services.board_records.delete(board_id)
|
||||
@@ -50,30 +37,10 @@ class BoardService(BoardServiceABC):
|
||||
self, offset: int = 0, limit: int = 10, include_archived: bool = False
|
||||
) -> OffsetPaginatedResults[BoardDTO]:
|
||||
board_records = self.__invoker.services.board_records.get_many(offset, limit, include_archived)
|
||||
board_dtos = []
|
||||
for r in board_records.items:
|
||||
cover_image = self.__invoker.services.image_records.get_most_recent_image_for_board(r.board_id)
|
||||
if cover_image:
|
||||
cover_image_name = cover_image.image_name
|
||||
else:
|
||||
cover_image_name = None
|
||||
|
||||
image_count = self.__invoker.services.board_image_records.get_image_count_for_board(r.board_id)
|
||||
board_dtos.append(board_record_to_dto(r, cover_image_name, image_count))
|
||||
|
||||
board_dtos = [BoardDTO.model_validate(r.model_dump()) for r in board_records.items]
|
||||
return OffsetPaginatedResults[BoardDTO](items=board_dtos, offset=offset, limit=limit, total=len(board_dtos))
|
||||
|
||||
def get_all(self, include_archived: bool = False) -> list[BoardDTO]:
|
||||
board_records = self.__invoker.services.board_records.get_all(include_archived)
|
||||
board_dtos = []
|
||||
for r in board_records:
|
||||
cover_image = self.__invoker.services.image_records.get_most_recent_image_for_board(r.board_id)
|
||||
if cover_image:
|
||||
cover_image_name = cover_image.image_name
|
||||
else:
|
||||
cover_image_name = None
|
||||
|
||||
image_count = self.__invoker.services.board_image_records.get_image_count_for_board(r.board_id)
|
||||
board_dtos.append(board_record_to_dto(r, cover_image_name, image_count))
|
||||
|
||||
board_dtos = [BoardDTO.model_validate(r.model_dump()) for r in board_records]
|
||||
return board_dtos
|
||||
|
||||
@@ -250,9 +250,9 @@ class InvokeAIAppConfig(BaseSettings):
|
||||
)
|
||||
|
||||
if as_example:
|
||||
file.write("# This is an example file with default and example settings.\n")
|
||||
file.write("# You should not copy this whole file into your config.\n")
|
||||
file.write("# Only add the settings you need to change to your config file.\n\n")
|
||||
file.write(
|
||||
"# This is an example file with default and example settings. Use the values here as a baseline.\n\n"
|
||||
)
|
||||
file.write("# Internal metadata - do not edit:\n")
|
||||
file.write(yaml.dump(meta_dict, sort_keys=False))
|
||||
file.write("\n")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from typing import Literal, Optional
|
||||
|
||||
from invokeai.app.invocations.fields import MetadataField
|
||||
from invokeai.app.services.image_records.image_records_common import (
|
||||
@@ -97,3 +97,32 @@ class ImageRecordStorageBase(ABC):
|
||||
def get_most_recent_image_for_board(self, board_id: str) -> Optional[ImageRecord]:
|
||||
"""Gets the most recent image for a board."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_image_names(
|
||||
self,
|
||||
board_id: str | None,
|
||||
category: Literal["images", "assets"],
|
||||
starred_first: bool = True,
|
||||
order_dir: SQLiteDirection = SQLiteDirection.Descending,
|
||||
search_term: Optional[str] = None,
|
||||
) -> list[str]:
|
||||
"""Gets image names."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_images_by_name(self, image_names: list[str]) -> list[ImageRecord]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_images(
|
||||
self,
|
||||
board_id: str | None = None,
|
||||
category: Literal["images", "assets"] = "images",
|
||||
starred_first: bool = True,
|
||||
order_dir: SQLiteDirection = SQLiteDirection.Descending,
|
||||
search_term: str | None = None,
|
||||
from_image_name: str | None = None, # omit for first page
|
||||
count: int = 10,
|
||||
) -> list[ImageRecord]:
|
||||
pass
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import sqlite3
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from typing import Optional, Union, cast
|
||||
from typing import Literal, Optional, Union, cast
|
||||
|
||||
from invokeai.app.invocations.fields import MetadataField, MetadataFieldValidator
|
||||
from invokeai.app.services.image_records.image_records_base import ImageRecordStorageBase
|
||||
@@ -140,6 +140,264 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
# def get_image_names(
|
||||
# self,
|
||||
# board_id: str | None = None,
|
||||
# category: Literal["images", "assets"] = "images",
|
||||
# starred_first: bool = True,
|
||||
# order_dir: SQLiteDirection = SQLiteDirection.Descending,
|
||||
# search_term: Optional[str] = None,
|
||||
# ) -> list[str]:
|
||||
# try:
|
||||
# self._lock.acquire()
|
||||
|
||||
# query = """
|
||||
# SELECT images.image_name
|
||||
# FROM images
|
||||
# LEFT JOIN board_images ON board_images.image_name = images.image_name
|
||||
# WHERE images.is_intermediate = FALSE
|
||||
# """
|
||||
# params: list[int | str | bool] = []
|
||||
|
||||
# if board_id:
|
||||
# query += """
|
||||
# AND board_images.board_id = ?
|
||||
# """
|
||||
# params.append(board_id)
|
||||
# else:
|
||||
# query += """
|
||||
# AND board_images.board_id IS NULL
|
||||
# """
|
||||
|
||||
# if category == "images":
|
||||
# query += """
|
||||
# AND images.image_category = 'general'
|
||||
# """
|
||||
# elif category == "assets":
|
||||
# query += """
|
||||
# AND images.image_category IN ('control', 'mask', 'user', 'other')
|
||||
# """
|
||||
# else:
|
||||
# raise ValueError(f"Invalid category: {category}")
|
||||
|
||||
# if search_term:
|
||||
# query += """
|
||||
# AND images.metadata LIKE ?
|
||||
# """
|
||||
# params.append(f"%{search_term.lower()}%")
|
||||
|
||||
# if starred_first:
|
||||
# query += f"""
|
||||
# ORDER BY images.starred DESC, images.created_at {order_dir.value} -- cannot use parameter substitution here
|
||||
# """
|
||||
# else:
|
||||
# query += f"""
|
||||
# ORDER BY images.created_at {order_dir.value} -- cannot use parameter substitution here
|
||||
# """
|
||||
|
||||
# query += ";"
|
||||
# params_tuple = tuple(params)
|
||||
|
||||
# self._cursor.execute(query, params_tuple)
|
||||
# result = cast(list[sqlite3.Row], self._cursor.fetchall())
|
||||
# image_names = [str(r[0]) for r in result]
|
||||
# except Exception:
|
||||
# raise
|
||||
# finally:
|
||||
# self._lock.release()
|
||||
|
||||
# return image_names
|
||||
|
||||
def get_image_names(
|
||||
self,
|
||||
board_id: str | None = None,
|
||||
category: Literal["images", "assets"] = "images",
|
||||
starred_first: bool = True,
|
||||
order_dir: SQLiteDirection = SQLiteDirection.Descending,
|
||||
search_term: str | None = None,
|
||||
) -> list[str]:
|
||||
try:
|
||||
self._lock.acquire()
|
||||
|
||||
base_query = """
|
||||
SELECT images.image_name
|
||||
FROM images
|
||||
LEFT JOIN board_images ON board_images.image_name = images.image_name
|
||||
WHERE images.is_intermediate = FALSE
|
||||
"""
|
||||
params: list[int | str | bool] = []
|
||||
|
||||
if board_id:
|
||||
base_query += """
|
||||
AND board_images.board_id = ?
|
||||
"""
|
||||
params.append(board_id)
|
||||
else:
|
||||
base_query += """
|
||||
AND board_images.board_id IS NULL
|
||||
"""
|
||||
|
||||
if category == "images":
|
||||
base_query += """
|
||||
AND images.image_category = 'general'
|
||||
"""
|
||||
elif category == "assets":
|
||||
base_query += """
|
||||
AND images.image_category IN ('control', 'mask', 'user', 'other')
|
||||
"""
|
||||
else:
|
||||
raise ValueError(f"Invalid category: {category}")
|
||||
|
||||
if search_term:
|
||||
base_query += """
|
||||
AND images.metadata LIKE ?
|
||||
"""
|
||||
params.append(f"%{search_term.lower()}%")
|
||||
|
||||
if starred_first:
|
||||
base_query += f"""
|
||||
ORDER BY images.starred DESC, images.created_at {order_dir.value}, images.image_name {order_dir.value}
|
||||
"""
|
||||
else:
|
||||
base_query += f"""
|
||||
ORDER BY images.created_at {order_dir.value}, images.image_name {order_dir.value}
|
||||
"""
|
||||
|
||||
final_query = f"{base_query};"
|
||||
|
||||
self._cursor.execute(final_query, tuple(params))
|
||||
result = cast(list[sqlite3.Row], self._cursor.fetchall())
|
||||
images = [str(r[0]) for r in result]
|
||||
|
||||
except Exception:
|
||||
raise
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
return images
|
||||
|
||||
def get_images_by_name(self, image_names: list[str]) -> list[ImageRecord]:
|
||||
try:
|
||||
self._lock.acquire()
|
||||
|
||||
query = f"""
|
||||
SELECT {IMAGE_DTO_COLS}
|
||||
FROM images
|
||||
WHERE images.image_name in ({",".join("?" for _ in image_names)});
|
||||
"""
|
||||
params = tuple(image_names)
|
||||
|
||||
self._cursor.execute(query, tuple(params))
|
||||
result = cast(list[sqlite3.Row], self._cursor.fetchall())
|
||||
images = [deserialize_image_record(dict(r)) for r in result]
|
||||
|
||||
except Exception:
|
||||
raise
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
return images
|
||||
|
||||
def get_images(
|
||||
self,
|
||||
board_id: str | None = None,
|
||||
category: Literal["images", "assets"] = "images",
|
||||
starred_first: bool = True,
|
||||
order_dir: SQLiteDirection = SQLiteDirection.Descending,
|
||||
search_term: str | None = None,
|
||||
from_image_name: str | None = None, # omit for first page
|
||||
count: int = 10,
|
||||
) -> list[ImageRecord]:
|
||||
try:
|
||||
self._lock.acquire()
|
||||
|
||||
base_query = f"""
|
||||
SELECT {IMAGE_DTO_COLS}
|
||||
FROM images
|
||||
LEFT JOIN board_images ON board_images.image_name = images.image_name
|
||||
WHERE images.is_intermediate = FALSE
|
||||
"""
|
||||
params: list[int | str | bool] = []
|
||||
|
||||
if board_id:
|
||||
base_query += """
|
||||
AND board_images.board_id = ?
|
||||
"""
|
||||
params.append(board_id)
|
||||
else:
|
||||
base_query += """
|
||||
AND board_images.board_id IS NULL
|
||||
"""
|
||||
|
||||
if category == "images":
|
||||
base_query += """
|
||||
AND images.image_category = 'general'
|
||||
"""
|
||||
elif category == "assets":
|
||||
base_query += """
|
||||
AND images.image_category IN ('control', 'mask', 'user', 'other')
|
||||
"""
|
||||
else:
|
||||
raise ValueError(f"Invalid category: {category}")
|
||||
|
||||
if search_term:
|
||||
base_query += """
|
||||
AND images.metadata LIKE ?
|
||||
"""
|
||||
params.append(f"%{search_term.lower()}%")
|
||||
|
||||
if from_image_name:
|
||||
# Use keyset pagination to get the next page of results
|
||||
|
||||
keyset_query = f"""
|
||||
WITH image_keyset AS (
|
||||
SELECT created_at,
|
||||
image_name
|
||||
FROM images
|
||||
WHERE image_name = ?
|
||||
)
|
||||
{base_query}
|
||||
AND (images.created_at, images.image_name) < (
|
||||
(
|
||||
SELECT created_at
|
||||
FROM image_keyset
|
||||
),
|
||||
(
|
||||
SELECT image_name
|
||||
FROM image_keyset
|
||||
)
|
||||
)
|
||||
"""
|
||||
base_query = keyset_query
|
||||
params.append(from_image_name)
|
||||
|
||||
if starred_first:
|
||||
order_by_clause = f"""
|
||||
ORDER BY images.starred DESC, images.created_at {order_dir.value}, images.image_name {order_dir.value}
|
||||
"""
|
||||
else:
|
||||
order_by_clause = f"""
|
||||
ORDER BY images.created_at {order_dir.value}, images.image_name {order_dir.value}
|
||||
"""
|
||||
|
||||
final_query = f"""
|
||||
{base_query}
|
||||
{order_by_clause}
|
||||
LIMIT ?;
|
||||
"""
|
||||
params.append(count)
|
||||
|
||||
self._cursor.execute(final_query, tuple(params))
|
||||
result = cast(list[sqlite3.Row], self._cursor.fetchall())
|
||||
images = [deserialize_image_record(dict(r)) for r in result]
|
||||
|
||||
except Exception:
|
||||
raise
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
return images
|
||||
|
||||
def get_many(
|
||||
self,
|
||||
offset: int = 0,
|
||||
|
||||
59
invokeai/app/services/image_records/pagination notes.md
Normal file
59
invokeai/app/services/image_records/pagination notes.md
Normal file
@@ -0,0 +1,59 @@
|
||||
these ideas are trying to figure out the macOS photos UX where you use scroll position instead of page number to go to a specific range of images.
|
||||
|
||||
### Brute Force
|
||||
|
||||
Two new methods/endpoints:
|
||||
|
||||
- `get_image_names`: gets a list of ordered image names for the query params (e.g. board, starred_first)
|
||||
- `get_images_by_name`: gets the dtos for a list of image names
|
||||
|
||||
Broad strokes of client handling:
|
||||
|
||||
- Fetch a list of all image names for a board.
|
||||
- Render a big scroll area, large enough to hold all images. The list of image names is passed to `react-virtuoso` (virtualized list lib).
|
||||
- As you scroll, we use the rangeChanged callback from `react-virtuoso`, which provides the indices of the currently-visible images in the list of all images. These indices map back to the list of image names from which we can derive the list of image names we need to fetch
|
||||
- Debounce the rnageChanged callback
|
||||
- Call the `get_images_by_name` endpoint with hte image names to fetch, use the result to update the `getImageDTO` query cache. De-duplicate the image_names against existing cache before fetching so we aren't requesting the smae data over and over
|
||||
- Each item/image in the virtualized list fetches its image DTO from the cache _without initiating a network request_. it just kinda waits until the image is in the cache and then displays it
|
||||
|
||||
this is roughed out in this branch
|
||||
|
||||
#### FATAL FLAW
|
||||
|
||||
Once you generate an image, you want to do an optimistic update and insert its name into the big ol' image_names list right? well, where do you insert it? depends on the query parms that can affect the sort order and which images are shown... we only have the image names at this point so we can't easily figure out where to insert
|
||||
|
||||
workarounds (?):
|
||||
|
||||
- along with the image names, we retrieve `starred_first` and `created_at`. then from the query params we can easily figure out where to insert the new image into the list to match the sort that he backend will be doing. eh
|
||||
- fetch `starred_first` images separately? so we don't have to worry about inserting the image into the right spot?
|
||||
|
||||
ahh but also metadata search... we won't know when to insert the image into the list if the user has a search term...
|
||||
|
||||
#### Sub-idea
|
||||
|
||||
Ok let's still use pagination but use virtuoso to tell us which page we are on.
|
||||
|
||||
virtuoso has an alternate mode where you just tell it how many items you have and it renders each item, passing only an index to it. Maybe we can derive the limit and offset from this information. here's an untested idea:
|
||||
|
||||
- pass virtuoso the board count
|
||||
- Instead of rendering individual images in the list, we render pages (ranges) of images. The list library’s rangeChanged indices now refer to pages or ranges. To the user, it still looks like a bunch of individual images, but internally we group it into pages/ranges of whatever size.
|
||||
- The page/range size is calculated via DOM, or we can rely on virtuoso to tell us how many items are to be rendered. only thing is it the number can different depending on scroll position, so we'd probably want to like take `endIndex - startIndex` as the limit, add 20% buffer to each end of the limit and round it to the nearest multiple of 5 or 10. that would give us a consistent limit
|
||||
- then we can derive offset from that value
|
||||
|
||||
still has the issue where we aren't sure if we should trigger a image list cache invalidation...
|
||||
|
||||
### More Efficient Pagination
|
||||
|
||||
sql OFFSET requires a scan thru the whole table upt othe offset. that means the higher the offset, the slower the query. unsure of the practical impact of this, probably negligible for us right now.
|
||||
|
||||
I did some quick experiments with cursor/keyset pagination, using an image name as the cursor. this doesn't have the perf issue w/ offset.
|
||||
|
||||
Also! This kind of pagination is unaffected by insertions and deletions, which is a problem for limit/offset pagination. When you insert or delete an image, it doesn't shift images at higher pages down. I think this pagination strategy suits our gallery better than limit/offset, given how volatile it is with adding and removing images regularly.
|
||||
|
||||
see the `test_keyset` notebook for implementation (also some scattered methods in services as I was fiddling withh it)
|
||||
|
||||
may be some way to use this pagination strat in combination with the above ideas to more elegantly handle inserting and deleting images...
|
||||
|
||||
### Alternative approach to the whole "how do we know when to insert new images in the list (or invalidate the list cache)" issue
|
||||
|
||||
What if we _always_ invalidate the cache when youa re at the top of the list ,but never invalidate it when you have scrolled down?
|
||||
210
invokeai/app/services/image_records/test_keyset.ipynb
Normal file
210
invokeai/app/services/image_records/test_keyset.ipynb
Normal file
@@ -0,0 +1,210 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"first query\n",
|
||||
"36e62fec-5c3a-4b28-867b-9029fb6d2319.png False\n",
|
||||
"c7f4f4b8-7ce6-4594-abf6-3f5e13fb7fe9.png False\n",
|
||||
"d8f57fda-5084-4d87-8668-06fb300282e4.png False\n",
|
||||
"a2fd7b8b-bbe5-4629-9d46-000f99b64931.png False\n",
|
||||
"c0880bc1-5f7a-452b-acea-53a261f4c0c4.png False\n",
|
||||
"0ad957df-c341-48e3-b384-f656985c2722.png False\n",
|
||||
"8c788d82-c81c-4ffe-bf6b-bdad601c5add.png False\n",
|
||||
"9b1179a0-09a0-4430-918d-60b618ff040c.png False\n",
|
||||
"c8ad6a32-75db-4d8b-a865-066365fa1563.png False\n",
|
||||
"e5eb1c19-8c69-4d29-a447-fbc2d649334a.png False\n",
|
||||
"\n",
|
||||
"next query, starting from the second image\n",
|
||||
"36e62fec-5c3a-4b28-867b-9029fb6d2319.png False\n",
|
||||
"c7f4f4b8-7ce6-4594-abf6-3f5e13fb7fe9.png False\n",
|
||||
"d8f57fda-5084-4d87-8668-06fb300282e4.png False\n",
|
||||
"a2fd7b8b-bbe5-4629-9d46-000f99b64931.png False\n",
|
||||
"c0880bc1-5f7a-452b-acea-53a261f4c0c4.png False\n",
|
||||
"0ad957df-c341-48e3-b384-f656985c2722.png False\n",
|
||||
"8c788d82-c81c-4ffe-bf6b-bdad601c5add.png False\n",
|
||||
"9b1179a0-09a0-4430-918d-60b618ff040c.png False\n",
|
||||
"c8ad6a32-75db-4d8b-a865-066365fa1563.png False\n",
|
||||
"e5eb1c19-8c69-4d29-a447-fbc2d649334a.png False\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import sqlite3\n",
|
||||
"from typing import Literal, cast\n",
|
||||
"from invokeai.app.services.image_records.image_records_common import (\n",
|
||||
" IMAGE_DTO_COLS,\n",
|
||||
" ImageRecord,\n",
|
||||
" deserialize_image_record,\n",
|
||||
")\n",
|
||||
"from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_images(\n",
|
||||
" from_image_name: str | None = None, # omit for first page\n",
|
||||
" count: int = 10,\n",
|
||||
" board_id: str | None = None,\n",
|
||||
" category: Literal[\"images\", \"assets\"] = \"images\",\n",
|
||||
" starred_first: bool = False,\n",
|
||||
" order_dir: SQLiteDirection = SQLiteDirection.Descending,\n",
|
||||
" search_term: str | None = None,\n",
|
||||
") -> list[ImageRecord]:\n",
|
||||
" conn = sqlite3.connect(\"/home/bat/invokeai-4.0.0/databases/invokeai.db\")\n",
|
||||
" conn.row_factory = sqlite3.Row\n",
|
||||
" cursor = conn.cursor()\n",
|
||||
"\n",
|
||||
" base_query = f\"\"\"\n",
|
||||
" SELECT {IMAGE_DTO_COLS}\n",
|
||||
" FROM images\n",
|
||||
" LEFT JOIN board_images ON board_images.image_name = images.image_name\n",
|
||||
" WHERE images.is_intermediate = FALSE\n",
|
||||
" \"\"\"\n",
|
||||
" params: list[int | str | bool] = []\n",
|
||||
"\n",
|
||||
" if board_id:\n",
|
||||
" base_query += \"\"\"\n",
|
||||
" AND board_images.board_id = ?\n",
|
||||
" \"\"\"\n",
|
||||
" params.append(board_id)\n",
|
||||
" else:\n",
|
||||
" base_query += \"\"\"\n",
|
||||
" AND board_images.board_id IS NULL\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" if category == \"images\":\n",
|
||||
" base_query += \"\"\"\n",
|
||||
" AND images.image_category = 'general'\n",
|
||||
" \"\"\"\n",
|
||||
" elif category == \"assets\":\n",
|
||||
" base_query += \"\"\"\n",
|
||||
" AND images.image_category IN ('control', 'mask', 'user', 'other')\n",
|
||||
" \"\"\"\n",
|
||||
" else:\n",
|
||||
" raise ValueError(f\"Invalid category: {category}\")\n",
|
||||
"\n",
|
||||
" if search_term:\n",
|
||||
" base_query += \"\"\"\n",
|
||||
" AND images.metadata LIKE ?\n",
|
||||
" \"\"\"\n",
|
||||
" params.append(f\"%{search_term.lower()}%\")\n",
|
||||
"\n",
|
||||
" if from_image_name:\n",
|
||||
" # Use keyset pagination to get the next page of results\n",
|
||||
"\n",
|
||||
" # This uses `<` so that the cursor image is NOT included in the results - only images after it\n",
|
||||
" if starred_first:\n",
|
||||
" keyset_query = f\"\"\"\n",
|
||||
" WITH image_keyset AS (\n",
|
||||
" SELECT created_at,\n",
|
||||
" image_name,\n",
|
||||
" starred\n",
|
||||
" FROM images\n",
|
||||
" WHERE image_name = ?\n",
|
||||
" )\n",
|
||||
" {base_query}\n",
|
||||
" AND (images.starred, images.created_at, images.image_name) < ((SELECT starred FROM image_keyset), (SELECT created_at FROM image_keyset), (SELECT image_name FROM image_keyset))\n",
|
||||
" \"\"\"\n",
|
||||
" else:\n",
|
||||
" keyset_query = f\"\"\"\n",
|
||||
" WITH image_keyset AS (\n",
|
||||
" SELECT created_at,\n",
|
||||
" image_name\n",
|
||||
" FROM images\n",
|
||||
" WHERE image_name = ?\n",
|
||||
" )\n",
|
||||
" {base_query}\n",
|
||||
" AND (images.created_at, images.image_name) < ((SELECT created_at FROM image_keyset), (SELECT image_name FROM image_keyset))\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # This uses `<=` so that the cursor image IS included in the results\n",
|
||||
" # if starred_first:\n",
|
||||
" # keyset_query = f\"\"\"\n",
|
||||
" # WITH image_keyset AS (\n",
|
||||
" # SELECT created_at,\n",
|
||||
" # image_name,\n",
|
||||
" # starred\n",
|
||||
" # FROM images\n",
|
||||
" # WHERE image_name = ?\n",
|
||||
" # )\n",
|
||||
" # {base_query}\n",
|
||||
" # AND (images.starred, images.created_at, images.image_name) <= ((SELECT starred FROM image_keyset), (SELECT created_at FROM image_keyset), (SELECT image_name FROM image_keyset))\n",
|
||||
" # \"\"\"\n",
|
||||
" # else:\n",
|
||||
" # keyset_query = f\"\"\"\n",
|
||||
" # WITH image_keyset AS (\n",
|
||||
" # SELECT created_at,\n",
|
||||
" # image_name\n",
|
||||
" # FROM images\n",
|
||||
" # WHERE image_name = ?\n",
|
||||
" # )\n",
|
||||
" # {base_query}\n",
|
||||
" # AND (images.created_at, images.image_name) <= ((SELECT created_at FROM image_keyset), (SELECT image_name FROM image_keyset))\n",
|
||||
" # \"\"\"\n",
|
||||
" base_query = keyset_query\n",
|
||||
" params.append(from_image_name)\n",
|
||||
"\n",
|
||||
" if starred_first:\n",
|
||||
" order_by_clause = f\"\"\"\n",
|
||||
" ORDER BY images.starred DESC, images.created_at {order_dir.value}, images.image_name {order_dir.value}\n",
|
||||
" \"\"\"\n",
|
||||
" else:\n",
|
||||
" order_by_clause = f\"\"\"\n",
|
||||
" ORDER BY images.created_at {order_dir.value}, images.image_name {order_dir.value}\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" final_query = f\"\"\"\n",
|
||||
" {base_query}\n",
|
||||
" {order_by_clause}\n",
|
||||
" LIMIT ?;\n",
|
||||
" \"\"\"\n",
|
||||
" params.append(count)\n",
|
||||
"\n",
|
||||
" cursor.execute(final_query, tuple(params))\n",
|
||||
" result = cast(list[sqlite3.Row], cursor.fetchall())\n",
|
||||
" images = [deserialize_image_record(dict(r)) for r in result]\n",
|
||||
"\n",
|
||||
" return images\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"kwargs = {\"starred_first\": False}\n",
|
||||
"\n",
|
||||
"images = get_images(**kwargs)\n",
|
||||
"print(\"first query\")\n",
|
||||
"for image in images:\n",
|
||||
" print(image.image_name, image.starred)\n",
|
||||
"\n",
|
||||
"print(\"\\nnext query, starting from the second image\")\n",
|
||||
"images_2 = get_images(from_image_name=images[0].image_name, **kwargs)\n",
|
||||
"for image in images_2:\n",
|
||||
" print(image.image_name, image.starred)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.12"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -39,11 +39,11 @@ class WorkflowRecordsStorageBase(ABC):
|
||||
@abstractmethod
|
||||
def get_many(
|
||||
self,
|
||||
page: int,
|
||||
per_page: int,
|
||||
order_by: WorkflowRecordOrderBy,
|
||||
direction: SQLiteDirection,
|
||||
page: int,
|
||||
per_page: Optional[int],
|
||||
category: Optional[WorkflowCategory],
|
||||
category: WorkflowCategory,
|
||||
query: Optional[str],
|
||||
) -> PaginatedResults[WorkflowRecordListItemDTO]:
|
||||
"""Gets many workflows."""
|
||||
|
||||
@@ -125,11 +125,11 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
|
||||
def get_many(
|
||||
self,
|
||||
page: int,
|
||||
per_page: int,
|
||||
order_by: WorkflowRecordOrderBy,
|
||||
direction: SQLiteDirection,
|
||||
page: int = 0,
|
||||
per_page: Optional[int] = None,
|
||||
category: Optional[WorkflowCategory] = None,
|
||||
category: WorkflowCategory,
|
||||
query: Optional[str] = None,
|
||||
) -> PaginatedResults[WorkflowRecordListItemDTO]:
|
||||
try:
|
||||
@@ -137,7 +137,8 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
# sanitize!
|
||||
assert order_by in WorkflowRecordOrderBy
|
||||
assert direction in SQLiteDirection
|
||||
count_query = "SELECT COUNT(*) FROM workflow_library"
|
||||
assert category in WorkflowCategory
|
||||
count_query = "SELECT COUNT(*) FROM workflow_library WHERE category = ?"
|
||||
main_query = """
|
||||
SELECT
|
||||
workflow_id,
|
||||
@@ -148,51 +149,32 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
updated_at,
|
||||
opened_at
|
||||
FROM workflow_library
|
||||
WHERE category = ?
|
||||
"""
|
||||
main_params: list[int | str] = []
|
||||
count_params: list[int | str] = []
|
||||
|
||||
if category:
|
||||
assert category in WorkflowCategory
|
||||
main_query += " WHERE category = ?"
|
||||
count_query += " WHERE category = ?"
|
||||
main_params.append(category.value)
|
||||
count_params.append(category.value)
|
||||
|
||||
main_params: list[int | str] = [category.value]
|
||||
count_params: list[int | str] = [category.value]
|
||||
stripped_query = query.strip() if query else None
|
||||
if stripped_query:
|
||||
wildcard_query = "%" + stripped_query + "%"
|
||||
if "WHERE" in main_query:
|
||||
main_query += " AND (name LIKE ? OR description LIKE ?)"
|
||||
count_query += " AND (name LIKE ? OR description LIKE ?)"
|
||||
else:
|
||||
main_query += " WHERE name LIKE ? OR description LIKE ?"
|
||||
count_query += " WHERE name LIKE ? OR description LIKE ?"
|
||||
main_query += " AND name LIKE ? OR description LIKE ? "
|
||||
count_query += " AND name LIKE ? OR description LIKE ?;"
|
||||
main_params.extend([wildcard_query, wildcard_query])
|
||||
count_params.extend([wildcard_query, wildcard_query])
|
||||
|
||||
main_query += f" ORDER BY {order_by.value} {direction.value}"
|
||||
|
||||
if per_page:
|
||||
main_query += " LIMIT ? OFFSET ?"
|
||||
main_params.extend([per_page, page * per_page])
|
||||
|
||||
main_query += f" ORDER BY {order_by.value} {direction.value} LIMIT ? OFFSET ?;"
|
||||
main_params.extend([per_page, page * per_page])
|
||||
self._cursor.execute(main_query, main_params)
|
||||
rows = self._cursor.fetchall()
|
||||
workflows = [WorkflowRecordListItemDTOValidator.validate_python(dict(row)) for row in rows]
|
||||
|
||||
self._cursor.execute(count_query, count_params)
|
||||
total = self._cursor.fetchone()[0]
|
||||
|
||||
if per_page:
|
||||
pages = total // per_page + (total % per_page > 0)
|
||||
else:
|
||||
pages = 1 # If no pagination, there is only one page
|
||||
pages = total // per_page + (total % per_page > 0)
|
||||
|
||||
return PaginatedResults(
|
||||
items=workflows,
|
||||
page=page,
|
||||
per_page=per_page if per_page else total,
|
||||
per_page=per_page,
|
||||
pages=pages,
|
||||
total=total,
|
||||
)
|
||||
|
||||
@@ -16,10 +16,7 @@ def attention(q: Tensor, k: Tensor, v: Tensor, pe: Tensor) -> Tensor:
|
||||
|
||||
def rope(pos: Tensor, dim: int, theta: int) -> Tensor:
|
||||
assert dim % 2 == 0
|
||||
scale = (
|
||||
torch.arange(0, dim, 2, dtype=torch.float32 if pos.device.type == "mps" else torch.float64, device=pos.device)
|
||||
/ dim
|
||||
)
|
||||
scale = torch.arange(0, dim, 2, dtype=torch.float64, device=pos.device) / dim
|
||||
omega = 1.0 / (theta**scale)
|
||||
out = torch.einsum("...n,d->...nd", pos, omega)
|
||||
out = torch.stack([torch.cos(out), -torch.sin(out), torch.sin(out), torch.cos(out)], dim=-1)
|
||||
|
||||
@@ -171,19 +171,8 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
|
||||
"""
|
||||
if xformers is available, use it, otherwise use sliced attention.
|
||||
"""
|
||||
|
||||
# On 30xx and 40xx series GPUs, `torch-sdp` is faster than `xformers`. This corresponds to a CUDA major
|
||||
# version of 8 or higher. So, for major version 7 or below, we prefer `xformers`.
|
||||
# See:
|
||||
# - https://developer.nvidia.com/cuda-gpus
|
||||
# - https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#compute-capabilities
|
||||
try:
|
||||
prefer_xformers = torch.cuda.is_available() and torch.cuda.get_device_properties("cuda").major <= 7 # type: ignore # Type of "get_device_properties" is partially unknown
|
||||
except Exception:
|
||||
prefer_xformers = False
|
||||
|
||||
config = get_config()
|
||||
if config.attention_type == "xformers" and is_xformers_available() and prefer_xformers:
|
||||
if config.attention_type == "xformers":
|
||||
self.enable_xformers_memory_efficient_attention()
|
||||
return
|
||||
elif config.attention_type == "sliced":
|
||||
@@ -206,7 +195,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
|
||||
|
||||
# the remainder if this code is called when attention_type=='auto'
|
||||
if self.unet.device.type == "cuda":
|
||||
if is_xformers_available() and prefer_xformers:
|
||||
if is_xformers_available():
|
||||
self.enable_xformers_memory_efficient_attention()
|
||||
return
|
||||
elif hasattr(torch.nn.functional, "scaled_dot_product_attention"):
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@fontsource-variable/inter": "^5.1.0",
|
||||
"@invoke-ai/ui-library": "^0.0.42",
|
||||
"@invoke-ai/ui-library": "^0.0.40",
|
||||
"@nanostores/react": "^0.7.3",
|
||||
"@reduxjs/toolkit": "2.2.3",
|
||||
"@roarr/browser-log-writer": "^1.3.0",
|
||||
@@ -83,7 +83,6 @@
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"perfect-freehand": "^1.2.2",
|
||||
"query-string": "^9.1.0",
|
||||
"raf-throttle": "^2.0.6",
|
||||
"react": "^18.3.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
111
invokeai/frontend/web/pnpm-lock.yaml
generated
111
invokeai/frontend/web/pnpm-lock.yaml
generated
@@ -24,8 +24,8 @@ dependencies:
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0
|
||||
'@invoke-ai/ui-library':
|
||||
specifier: ^0.0.42
|
||||
version: 0.0.42(@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.1.0)(@types/react@18.3.11)(i18next@23.15.1)(react-dom@18.3.1)(react@18.3.1)
|
||||
specifier: ^0.0.40
|
||||
version: 0.0.40(@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.1.0)(@types/react@18.3.11)(i18next@23.15.1)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@nanostores/react':
|
||||
specifier: ^0.7.3
|
||||
version: 0.7.3(nanostores@0.11.3)(react@18.3.1)
|
||||
@@ -98,9 +98,6 @@ dependencies:
|
||||
query-string:
|
||||
specifier: ^9.1.0
|
||||
version: 9.1.0
|
||||
raf-throttle:
|
||||
specifier: ^2.0.6
|
||||
version: 2.0.6
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
@@ -493,8 +490,8 @@ packages:
|
||||
resolution: {integrity: sha512-MV6D4VLRIHr4PkW4zMyqfrNS1mPlCTiCXwvYGtDFQYr+xHFfonhAuf9WjsSc0nyp2m0OdkSLnzmVKkZFLo25Tg==}
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/anatomy@2.3.4:
|
||||
resolution: {integrity: sha512-fFIYN7L276gw0Q7/ikMMlZxP7mvnjRaWJ7f3Jsf9VtDOi6eAYIBRrhQe6+SZ0PGmoOkRaBc7gSE5oeIbgFFyrw==}
|
||||
/@chakra-ui/anatomy@2.3.3:
|
||||
resolution: {integrity: sha512-Sy2VAG0WrzkQE40Y0fY406c6AlyqFxAc7j6fDz8Wwotz9veAvm+y5UgFUyhZ6FoYNAjDMPQ7JCcN7OGz74pNlA==}
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/breakpoint-utils@2.0.8:
|
||||
@@ -551,12 +548,12 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/hooks@2.4.2(react@18.3.1):
|
||||
resolution: {integrity: sha512-LRKiVE1oA7afT5tbbSKAy7Uas2xFHE6IkrQdbhWCHmkHBUtPvjQQDgwtnd4IRZPmoEfNGwoJ/MQpwOM/NRTTwA==}
|
||||
/@chakra-ui/hooks@2.3.3(react@18.3.1):
|
||||
resolution: {integrity: sha512-nvqQfR+u0qAJ2/mdGF1XTrnfW9WahSsOc62E/xtRm5hPClfkxPCIXDuw0C17lZ2RYfg/hxsYKLJCGnQWZcC/7w==}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/utils': 2.2.2(react@18.3.1)
|
||||
'@chakra-ui/utils': 2.1.3(react@18.3.1)
|
||||
'@zag-js/element-size': 0.31.1
|
||||
copy-to-clipboard: 3.3.3
|
||||
framesync: 6.1.2
|
||||
@@ -574,13 +571,13 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/icons@2.2.4(@chakra-ui/react@2.10.2)(react@18.3.1):
|
||||
resolution: {integrity: sha512-l5QdBgwrAg3Sc2BRqtNkJpfuLw/pWRDwwT58J6c4PqQT6wzXxyNa8Q0PForu1ltB5qEiFb1kxr/F/HO1EwNa6g==}
|
||||
/@chakra-ui/icons@2.2.3(@chakra-ui/react@2.9.4)(react@18.3.1):
|
||||
resolution: {integrity: sha512-BihIvFvAKq+9/U3sI47Vdo3Mmr9VxTvWcFBl3qZsbJSBpqK7GYaakNWADyPvgsCRGo2be72AZgcOAYaAqWDThQ==}
|
||||
peerDependencies:
|
||||
'@chakra-ui/react': '>=2.0.0'
|
||||
react: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/react': 2.10.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@chakra-ui/react': 2.9.4(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1)
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
@@ -803,8 +800,8 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/react@2.10.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-TfIHTqTlxTHYJZBtpiR5EZasPUrLYKJxdbHkdOJb5G1OQ+2c5kKl5XA7c2pMtsEptzb7KxAAIB62t3hxdfWp1w==}
|
||||
/@chakra-ui/react@2.9.4(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-e7fMItdoUjZsQuVsq4DSvrX/dpmYHEwJD2UM5dkHvR2Vzsrili0EWfXrT9R+4kCDHtc6vlECbHA13RHru7XUUg==}
|
||||
peerDependencies:
|
||||
'@emotion/react': '>=11'
|
||||
'@emotion/styled': '>=11'
|
||||
@@ -812,10 +809,10 @@ packages:
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/hooks': 2.4.2(react@18.3.1)
|
||||
'@chakra-ui/styled-system': 2.11.2(react@18.3.1)
|
||||
'@chakra-ui/theme': 3.4.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1)
|
||||
'@chakra-ui/utils': 2.2.2(react@18.3.1)
|
||||
'@chakra-ui/hooks': 2.3.3(react@18.3.1)
|
||||
'@chakra-ui/styled-system': 2.10.3(react@18.3.1)
|
||||
'@chakra-ui/theme': 3.4.3(@chakra-ui/styled-system@2.10.3)(react@18.3.1)
|
||||
'@chakra-ui/utils': 2.1.3(react@18.3.1)
|
||||
'@emotion/react': 11.13.3(@types/react@18.3.11)(react@18.3.1)
|
||||
'@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.11)(react@18.3.1)
|
||||
'@popperjs/core': 2.11.8
|
||||
@@ -826,6 +823,7 @@ packages:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-fast-compare: 3.2.2
|
||||
react-focus-lock: 2.13.2(@types/react@18.3.11)(react@18.3.1)
|
||||
react-lorem-component: 0.13.0(react@18.3.1)
|
||||
react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
@@ -846,10 +844,10 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/styled-system@2.11.2(react@18.3.1):
|
||||
resolution: {integrity: sha512-y++z2Uop+hjfZX9mbH88F1ikazPv32asD2er56zMJBemUAzweXnHTpiCQbluEDSUDhqmghVZAdb+5L4XLbsRxA==}
|
||||
/@chakra-ui/styled-system@2.10.3(react@18.3.1):
|
||||
resolution: {integrity: sha512-rU4sG712pnp3Qrc8XT5AKcMhZjByXp1IrErLJ8wmiez2v8hAl/Dv8roK2BTqd4GfkJOrtkyfq2e2ZcDWjbd9Dw==}
|
||||
dependencies:
|
||||
'@chakra-ui/utils': 2.2.2(react@18.3.1)
|
||||
'@chakra-ui/utils': 2.1.3(react@18.3.1)
|
||||
csstype: 3.1.3
|
||||
transitivePeerDependencies:
|
||||
- react
|
||||
@@ -893,14 +891,14 @@ packages:
|
||||
color2k: 2.0.3
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/theme-tools@2.2.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1):
|
||||
resolution: {integrity: sha512-3UhKPyzKbV3l/bg1iQN9PBvffYp+EBOoYMUaeTUdieQRPFzo2jbYR0lNCxqv8h5aGM/k54nCHU2M/GStyi9F2A==}
|
||||
/@chakra-ui/theme-tools@2.2.3(@chakra-ui/styled-system@2.10.3)(react@18.3.1):
|
||||
resolution: {integrity: sha512-9fbBh4YaF8k1puovMnvdZtoVxQd1IKlRvWQBmIzXoae3KSJi9p1znRLzEX+Qjvph15dFCa2Q4h1gynI+HOh8oQ==}
|
||||
peerDependencies:
|
||||
'@chakra-ui/styled-system': '>=2.0.0'
|
||||
dependencies:
|
||||
'@chakra-ui/anatomy': 2.3.4
|
||||
'@chakra-ui/styled-system': 2.11.2(react@18.3.1)
|
||||
'@chakra-ui/utils': 2.2.2(react@18.3.1)
|
||||
'@chakra-ui/anatomy': 2.3.3
|
||||
'@chakra-ui/styled-system': 2.10.3(react@18.3.1)
|
||||
'@chakra-ui/utils': 2.1.3(react@18.3.1)
|
||||
color2k: 2.0.3
|
||||
transitivePeerDependencies:
|
||||
- react
|
||||
@@ -926,15 +924,15 @@ packages:
|
||||
'@chakra-ui/theme-tools': 2.1.2(@chakra-ui/styled-system@2.9.2)
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/theme@3.4.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1):
|
||||
resolution: {integrity: sha512-ZwFBLfiMC3URwaO31ONXoKH9k0TX0OW3UjdPF3EQkQpYyrk/fm36GkkzajjtdpWEd7rzDLRsQjPmvwNaSoNDtg==}
|
||||
/@chakra-ui/theme@3.4.3(@chakra-ui/styled-system@2.10.3)(react@18.3.1):
|
||||
resolution: {integrity: sha512-WxGk5wEMr8x/YmR99TfVcnt+qsHt9qy5FJycPgcKoL8blQiZ+v/rLhdWhXvu8K03DyfAoLkQDh2guVl+wKFfHA==}
|
||||
peerDependencies:
|
||||
'@chakra-ui/styled-system': '>=2.8.0'
|
||||
dependencies:
|
||||
'@chakra-ui/anatomy': 2.3.4
|
||||
'@chakra-ui/styled-system': 2.11.2(react@18.3.1)
|
||||
'@chakra-ui/theme-tools': 2.2.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1)
|
||||
'@chakra-ui/utils': 2.2.2(react@18.3.1)
|
||||
'@chakra-ui/anatomy': 2.3.3
|
||||
'@chakra-ui/styled-system': 2.10.3(react@18.3.1)
|
||||
'@chakra-ui/theme-tools': 2.2.3(@chakra-ui/styled-system@2.10.3)(react@18.3.1)
|
||||
'@chakra-ui/utils': 2.1.3(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
- react
|
||||
dev: false
|
||||
@@ -959,8 +957,8 @@ packages:
|
||||
lodash.mergewith: 4.6.2
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/utils@2.2.2(react@18.3.1):
|
||||
resolution: {integrity: sha512-jUPLT0JzRMWxpdzH6c+t0YMJYrvc5CLericgITV3zDSXblkfx3DsYXqU11DJTSGZI9dUKzM1Wd0Wswn4eJwvFQ==}
|
||||
/@chakra-ui/utils@2.1.3(react@18.3.1):
|
||||
resolution: {integrity: sha512-qIuyEg1ThVrUAnkV5nOngMDxUVCKavC04LfuraOCS1PHU4zhU4urJC2FURriALIQSgy6LpegASjvRzi7CIDDQQ==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
dependencies:
|
||||
@@ -1696,18 +1694,18 @@ packages:
|
||||
prettier: 3.3.3
|
||||
dev: true
|
||||
|
||||
/@invoke-ai/ui-library@0.0.42(@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.1.0)(@types/react@18.3.11)(i18next@23.15.1)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-OuDXRipBO5mu+Nv4qN8cd8MiwiGBdq6h4PirVgPI9/ltbdcIzePgUJ0dJns26lflHSTRWW38I16wl4YTw3mNWA==}
|
||||
/@invoke-ai/ui-library@0.0.40(@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.1.0)(@types/react@18.3.11)(i18next@23.15.1)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-GoqihMV1uaHPRgJ/GAmtt5+0ES1S3YpWUAkXAdRFqRWBoMs7i6mWddAY+qB9r5dWUR+LTESrGLKADHJBYjtVEQ==}
|
||||
peerDependencies:
|
||||
'@fontsource-variable/inter': ^5.0.16
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
dependencies:
|
||||
'@chakra-ui/anatomy': 2.2.2
|
||||
'@chakra-ui/icons': 2.2.4(@chakra-ui/react@2.10.2)(react@18.3.1)
|
||||
'@chakra-ui/icons': 2.2.3(@chakra-ui/react@2.9.4)(react@18.3.1)
|
||||
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
|
||||
'@chakra-ui/react': 2.10.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@chakra-ui/react': 2.9.4(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@chakra-ui/styled-system': 2.9.2
|
||||
'@chakra-ui/theme-tools': 2.1.2(@chakra-ui/styled-system@2.9.2)
|
||||
'@emotion/react': 11.13.3(@types/react@18.3.11)(react@18.3.1)
|
||||
@@ -4690,6 +4688,13 @@ packages:
|
||||
yaml: 1.10.2
|
||||
dev: false
|
||||
|
||||
/create-react-class@15.7.0:
|
||||
resolution: {integrity: sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==}
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
object-assign: 4.1.1
|
||||
dev: false
|
||||
|
||||
/cross-fetch@4.0.0:
|
||||
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
|
||||
dependencies:
|
||||
@@ -6808,6 +6813,13 @@ packages:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
/lorem-ipsum@1.0.6:
|
||||
resolution: {integrity: sha512-Rx4XH8X4KSDCKAVvWGYlhAfNqdUP5ZdT4rRyf0jjrvWgtViZimDIlopWNfn/y3lGM5K4uuiAoY28TaD+7YKFrQ==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
minimist: 1.2.8
|
||||
dev: false
|
||||
|
||||
/loupe@2.3.7:
|
||||
resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
|
||||
dependencies:
|
||||
@@ -7001,7 +7013,6 @@ packages:
|
||||
|
||||
/minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
dev: true
|
||||
|
||||
/minipass@7.1.2:
|
||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||
@@ -7557,10 +7568,6 @@ packages:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
dev: true
|
||||
|
||||
/raf-throttle@2.0.6:
|
||||
resolution: {integrity: sha512-C7W6hy78A+vMmk5a/B6C5szjBHrUzWJkVyakjKCK59Uy2CcA7KhO1JUvvH32IXYFIcyJ3FMKP3ZzCc2/71I6Vg==}
|
||||
dev: false
|
||||
|
||||
/railroad-diagrams@1.0.0:
|
||||
resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==}
|
||||
dev: false
|
||||
@@ -7760,6 +7767,18 @@ packages:
|
||||
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
||||
dev: true
|
||||
|
||||
/react-lorem-component@0.13.0(react@18.3.1):
|
||||
resolution: {integrity: sha512-4mWjxmcG/DJJwdxdKwXWyP2N9zohbJg/yYaC+7JffQNrKj3LYDpA/A4u/Dju1v1ZF6Jew2gbFKGb5Z6CL+UNTw==}
|
||||
peerDependencies:
|
||||
react: 16.x
|
||||
dependencies:
|
||||
create-react-class: 15.7.0
|
||||
lorem-ipsum: 1.0.6
|
||||
object-assign: 4.1.1
|
||||
react: 18.3.1
|
||||
seedable-random: 0.0.1
|
||||
dev: false
|
||||
|
||||
/react-redux@9.1.2(@types/react@18.3.11)(react@18.3.1)(redux@5.0.1):
|
||||
resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==}
|
||||
peerDependencies:
|
||||
@@ -8274,6 +8293,10 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/seedable-random@0.0.1:
|
||||
resolution: {integrity: sha512-uZWbEfz3BQdBl4QlUPELPqhInGEO1Q6zjzqrTDkd3j7mHaWWJo7h4ydr2g24a2WtTLk3imTLc8mPbBdQqdsbGw==}
|
||||
dev: false
|
||||
|
||||
/semver-compare@1.0.0:
|
||||
resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
|
||||
dev: false
|
||||
|
||||
@@ -651,8 +651,7 @@
|
||||
"imageCopied": "Bild kopiert",
|
||||
"parametersNotSet": "Parameter nicht festgelegt",
|
||||
"addedToBoard": "Dem Board hinzugefügt",
|
||||
"loadedWithWarnings": "Workflow mit Warnungen geladen",
|
||||
"imageSaved": "Bild gespeichert"
|
||||
"loadedWithWarnings": "Workflow mit Warnungen geladen"
|
||||
},
|
||||
"accessibility": {
|
||||
"uploadImage": "Bild hochladen",
|
||||
@@ -665,9 +664,7 @@
|
||||
"resetUI": "$t(accessibility.reset) von UI",
|
||||
"createIssue": "Ticket erstellen",
|
||||
"about": "Über",
|
||||
"submitSupportTicket": "Support-Ticket senden",
|
||||
"toggleRightPanel": "Rechtes Bedienfeld umschalten (G)",
|
||||
"toggleLeftPanel": "Linkes Bedienfeld umschalten (T)"
|
||||
"submitSupportTicket": "Support-Ticket senden"
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "Board automatisch erstellen",
|
||||
@@ -1065,22 +1062,7 @@
|
||||
"missingFieldTemplate": "Fehlende Feldvorlage",
|
||||
"missingNode": "Fehlender Aufrufknoten",
|
||||
"missingInvocationTemplate": "Fehlende Aufrufvorlage",
|
||||
"edit": "Bearbeiten",
|
||||
"workflowAuthor": "Autor",
|
||||
"graph": "Graph",
|
||||
"workflowDescription": "Kurze Beschreibung",
|
||||
"versionUnknown": " Version unbekannt",
|
||||
"workflow": "Arbeitsablauf",
|
||||
"noGraph": "Kein Graph",
|
||||
"version": "Version",
|
||||
"zoomInNodes": "Hineinzoomen",
|
||||
"zoomOutNodes": "Herauszoomen",
|
||||
"workflowName": "Name",
|
||||
"unknownNode": "Unbekannter Knoten",
|
||||
"workflowContact": "Kontaktdaten",
|
||||
"workflowNotes": "Notizen",
|
||||
"workflowTags": "Tags",
|
||||
"workflowVersion": "Version"
|
||||
"edit": "Bearbeiten"
|
||||
},
|
||||
"hrf": {
|
||||
"enableHrf": "Korrektur für hohe Auflösungen",
|
||||
|
||||
@@ -90,7 +90,6 @@
|
||||
"batch": "Batch Manager",
|
||||
"beta": "Beta",
|
||||
"cancel": "Cancel",
|
||||
"close": "Close",
|
||||
"copy": "Copy",
|
||||
"copyError": "$t(gallery.copy) Error",
|
||||
"on": "On",
|
||||
@@ -282,7 +281,6 @@
|
||||
"gallery": "Gallery",
|
||||
"alwaysShowImageSizeBadge": "Always Show Image Size Badge",
|
||||
"assets": "Assets",
|
||||
"assetsTab": "Files you’ve uploaded for use in your projects.",
|
||||
"autoAssignBoardOnClick": "Auto-Assign Board on Click",
|
||||
"autoSwitchNewImages": "Auto-Switch to New Images",
|
||||
"copy": "Copy",
|
||||
@@ -303,7 +301,6 @@
|
||||
"gallerySettings": "Gallery Settings",
|
||||
"go": "Go",
|
||||
"image": "image",
|
||||
"imagesTab": "Images you’ve created and saved within Invoke.",
|
||||
"jump": "Jump",
|
||||
"loading": "Loading",
|
||||
"newestFirst": "Newest First",
|
||||
@@ -702,7 +699,7 @@
|
||||
"convert": "Convert",
|
||||
"convertingModelBegin": "Converting Model. Please wait.",
|
||||
"convertToDiffusers": "Convert To Diffusers",
|
||||
"convertToDiffusersHelpText1": "This model will be converted to the 🧨 Diffusers format.",
|
||||
"convertToDiffusersHelpText1": "This model will be converted to the \ud83e\udde8 Diffusers format.",
|
||||
"convertToDiffusersHelpText2": "This process will replace your Model Manager entry with the Diffusers version of the same model.",
|
||||
"convertToDiffusersHelpText3": "Your checkpoint file on disk WILL be deleted if it is in InvokeAI root folder. If it is in a custom location, then it WILL NOT be deleted.",
|
||||
"convertToDiffusersHelpText4": "This is a one time process only. It might take around 30s-60s depending on the specifications of your computer.",
|
||||
@@ -857,8 +854,6 @@
|
||||
"ipAdapter": "IP-Adapter",
|
||||
"loadingNodes": "Loading Nodes...",
|
||||
"loadWorkflow": "Load Workflow",
|
||||
"noWorkflows": "No Workflows",
|
||||
"noMatchingWorkflows": "No Matching Workflows",
|
||||
"noWorkflow": "No Workflow",
|
||||
"mismatchedVersion": "Invalid node: node {{node}} of type {{type}} has mismatched version (try updating?)",
|
||||
"missingTemplate": "Invalid node: node {{node}} of type {{type}} missing template (not installed?)",
|
||||
@@ -875,7 +870,6 @@
|
||||
"nodeType": "Node Type",
|
||||
"noFieldsLinearview": "No fields added to Linear View",
|
||||
"noFieldsViewMode": "This workflow has no selected fields to display. View the full workflow to configure values.",
|
||||
"workflowHelpText": "Need Help? Check out our guide to <LinkComponent>Getting Started with Workflows</LinkComponent>.",
|
||||
"noNodeSelected": "No node selected",
|
||||
"nodeOpacity": "Node Opacity",
|
||||
"nodeVersion": "Node Version",
|
||||
@@ -1126,7 +1120,6 @@
|
||||
"canceled": "Processing Canceled",
|
||||
"connected": "Connected to Server",
|
||||
"imageCopied": "Image Copied",
|
||||
"linkCopied": "Link Copied",
|
||||
"unableToLoadImage": "Unable to Load Image",
|
||||
"unableToLoadImageMetadata": "Unable to Load Image Metadata",
|
||||
"unableToLoadStylePreset": "Unable to Load Style Preset",
|
||||
@@ -1523,7 +1516,6 @@
|
||||
}
|
||||
},
|
||||
"workflows": {
|
||||
"chooseWorkflowFromLibrary": "Choose Workflow from Library",
|
||||
"defaultWorkflows": "Default Workflows",
|
||||
"userWorkflows": "User Workflows",
|
||||
"projectWorkflows": "Project Workflows",
|
||||
@@ -1536,9 +1528,7 @@
|
||||
"openWorkflow": "Open Workflow",
|
||||
"updated": "Updated",
|
||||
"uploadWorkflow": "Load from File",
|
||||
"uploadAndSaveWorkflow": "Upload to Library",
|
||||
"deleteWorkflow": "Delete Workflow",
|
||||
"deleteWorkflow2": "Are you sure you want to delete this workflow? This cannot be undone.",
|
||||
"unnamedWorkflow": "Unnamed Workflow",
|
||||
"downloadWorkflow": "Save to File",
|
||||
"saveWorkflow": "Save Workflow",
|
||||
@@ -1561,13 +1551,9 @@
|
||||
"loadFromGraph": "Load Workflow from Graph",
|
||||
"convertGraph": "Convert Graph",
|
||||
"loadWorkflow": "$t(common.load) Workflow",
|
||||
"autoLayout": "Auto Layout",
|
||||
"edit": "Edit",
|
||||
"download": "Download",
|
||||
"copyShareLink": "Copy Share Link",
|
||||
"copyShareLinkForWorkflow": "Copy Share Link for Workflow",
|
||||
"delete": "Delete"
|
||||
"autoLayout": "Auto Layout"
|
||||
},
|
||||
"app": {},
|
||||
"controlLayers": {
|
||||
"regional": "Regional",
|
||||
"global": "Global",
|
||||
@@ -1646,14 +1632,14 @@
|
||||
"viewProgressInViewer": "View progress and outputs in the <Btn>Image Viewer</Btn>.",
|
||||
"viewProgressOnCanvas": "View progress and stage outputs on the <Btn>Canvas</Btn>.",
|
||||
"rasterLayer_withCount_one": "$t(controlLayers.rasterLayer)",
|
||||
"rasterLayer_withCount_other": "Raster Layers",
|
||||
"controlLayer_withCount_one": "$t(controlLayers.controlLayer)",
|
||||
"controlLayer_withCount_other": "Control Layers",
|
||||
"inpaintMask_withCount_one": "$t(controlLayers.inpaintMask)",
|
||||
"inpaintMask_withCount_other": "Inpaint Masks",
|
||||
"regionalGuidance_withCount_one": "$t(controlLayers.regionalGuidance)",
|
||||
"regionalGuidance_withCount_other": "Regional Guidance",
|
||||
"globalReferenceImage_withCount_one": "$t(controlLayers.globalReferenceImage)",
|
||||
"rasterLayer_withCount_other": "Raster Layers",
|
||||
"controlLayer_withCount_other": "Control Layers",
|
||||
"inpaintMask_withCount_other": "Inpaint Masks",
|
||||
"regionalGuidance_withCount_other": "Regional Guidance",
|
||||
"globalReferenceImage_withCount_other": "Global Reference Images",
|
||||
"opacity": "Opacity",
|
||||
"regionalGuidance_withCount_hidden": "Regional Guidance ({{count}} hidden)",
|
||||
@@ -1666,6 +1652,7 @@
|
||||
"rasterLayers_withCount_visible": "Raster Layers ({{count}})",
|
||||
"globalReferenceImages_withCount_visible": "Global Reference Images ({{count}})",
|
||||
"inpaintMasks_withCount_visible": "Inpaint Masks ({{count}})",
|
||||
"layer": "Layer",
|
||||
"layer_one": "Layer",
|
||||
"layer_other": "Layers",
|
||||
"layer_withCount_one": "Layer ({{count}})",
|
||||
@@ -1816,10 +1803,6 @@
|
||||
"transform": {
|
||||
"transform": "Transform",
|
||||
"fitToBbox": "Fit to Bbox",
|
||||
"fitMode": "Fit Mode",
|
||||
"fitModeContain": "Contain",
|
||||
"fitModeCover": "Cover",
|
||||
"fitModeFill": "Fill",
|
||||
"reset": "Reset",
|
||||
"apply": "Apply",
|
||||
"cancel": "Cancel"
|
||||
@@ -1986,7 +1969,7 @@
|
||||
}
|
||||
},
|
||||
"newUserExperience": {
|
||||
"toGetStarted": "To get started, enter a prompt in the box and click <StrongComponent>Invoke</StrongComponent> to generate your first image. Select a prompt template to improve results. You can choose to save your images directly to the <StrongComponent>Gallery</StrongComponent> or edit them to the <StrongComponent>Canvas</StrongComponent>.",
|
||||
"toGetStarted": "To get started, enter a prompt in the box and click <StrongComponent>Invoke</StrongComponent> to generate your first image. You can choose to save your images directly to the <StrongComponent>Gallery</StrongComponent> or edit them to the <StrongComponent>Canvas</StrongComponent>.",
|
||||
"gettingStartedSeries": "Want more guidance? Check out our <LinkComponent>Getting Started Series</LinkComponent> for tips on unlocking the full potential of the Invoke Studio."
|
||||
},
|
||||
"whatsNew": {
|
||||
|
||||
@@ -667,8 +667,7 @@
|
||||
"paramVAEPrecision": {
|
||||
"heading": "Précision du VAE",
|
||||
"paragraphs": [
|
||||
"La précision utilisée lors de l'encodage et du décodage VAE.",
|
||||
"La pr'ecision Fp16/Half est plus efficace, au détriment de légères variations d'image."
|
||||
"La précision utilisée lors de l'encodage et du décodage VAE."
|
||||
]
|
||||
},
|
||||
"controlNetWeight": {
|
||||
@@ -839,15 +838,11 @@
|
||||
"paramCFGScale": {
|
||||
"heading": "Échelle CFG",
|
||||
"paragraphs": [
|
||||
"Contrôle de l'influence du prompt sur le processus de génération.",
|
||||
"Des valeurs élevées de l'échelle CFG peuvent entraîner une saturation excessive et des distortions. "
|
||||
"Contrôle de l'influence du prompt sur le processus de génération."
|
||||
]
|
||||
},
|
||||
"loraWeight": {
|
||||
"heading": "Poids",
|
||||
"paragraphs": [
|
||||
"Poids du LoRA. Un poids plus élevé aura un impact plus important sur l'image finale."
|
||||
]
|
||||
"heading": "Poids"
|
||||
},
|
||||
"imageFit": {
|
||||
"heading": "Ajuster l'image initiale à la taille de sortie",
|
||||
@@ -858,8 +853,7 @@
|
||||
"paramCFGRescaleMultiplier": {
|
||||
"heading": "Multiplicateur de mise à l'échelle CFG",
|
||||
"paragraphs": [
|
||||
"Multiplicateur de mise à l'échelle pour le guidage CFG, utilisé pour les modèles entraînés en utilisant le zero-terminal SNR (ztsnr).",
|
||||
"Une valeur de 0.7 est suggérée pour ces modèles."
|
||||
"Multiplicateur de mise à l'échelle pour le guidage CFG, utilisé pour les modèles entraînés en utilisant le zero-terminal SNR (ztsnr)."
|
||||
]
|
||||
},
|
||||
"controlNetProcessor": {
|
||||
@@ -867,66 +861,6 @@
|
||||
"paragraphs": [
|
||||
"Méthode de traitement de l'image d'entrée pour guider le processus de génération. Différents processeurs fourniront différents effets ou styles dans vos images générées."
|
||||
]
|
||||
},
|
||||
"paramUpscaleMethod": {
|
||||
"paragraphs": [
|
||||
"Méthode utilisée pour améliorer l'image pour la correction de haute résolution."
|
||||
],
|
||||
"heading": "Méthode d'agrandissement"
|
||||
},
|
||||
"refinerModel": {
|
||||
"heading": "Modèle de Raffinage",
|
||||
"paragraphs": [
|
||||
"Modèle utilisé pendant la partie raffinage du processus de génération.",
|
||||
"Similaire au Modèle de Génération."
|
||||
]
|
||||
},
|
||||
"paramWidth": {
|
||||
"paragraphs": [
|
||||
"Largeur de l'image générée. Doit être un multiple de 8."
|
||||
],
|
||||
"heading": "Largeur"
|
||||
},
|
||||
"paramHeight": {
|
||||
"heading": "Hauteur",
|
||||
"paragraphs": [
|
||||
"Hauteur de l'image générée. Doit être un multiple de 8."
|
||||
]
|
||||
},
|
||||
"paramHrf": {
|
||||
"heading": "Activer la correction haute résolution",
|
||||
"paragraphs": [
|
||||
"Générez des images de haute qualité à une résolution plus grande que celle qui est optimale pour le modèle. Cela est généralement utilisé pour prévenir la duplication dans l'image générée."
|
||||
]
|
||||
},
|
||||
"patchmatchDownScaleSize": {
|
||||
"paragraphs": [
|
||||
"Intensité du sous-échantillonage qui se produit avant le remplissage ?",
|
||||
"Un sous-échantillonage plus élevé améliorera les performances et réduira la qualité."
|
||||
],
|
||||
"heading": "Sous-échantillonage"
|
||||
},
|
||||
"paramAspect": {
|
||||
"paragraphs": [
|
||||
"Rapport hauteur/largeur de l'image générée. Changer le rapport mettra à jour la largeur et la hauteur en conséquence.",
|
||||
"\"Optimiser\" définira la largeur et la hauteur aux dimensions optimales pour le modèle choisi."
|
||||
],
|
||||
"heading": "Aspect"
|
||||
},
|
||||
"refinerScheduler": {
|
||||
"heading": "Planificateur"
|
||||
},
|
||||
"refinerPositiveAestheticScore": {
|
||||
"paragraphs": [
|
||||
"Ajoute un biais envers les générations pour qu'elles soient plus similaires aux images ayant un score esthétique élevé, en fonction des données d'entraînement."
|
||||
],
|
||||
"heading": "Score Esthétique Positif"
|
||||
},
|
||||
"refinerNegativeAestheticScore": {
|
||||
"heading": "Score Esthétique Négatif",
|
||||
"paragraphs": [
|
||||
"Ajoute un biais envers les générations pour qu'elles soient plus similaires aux images ayant un faible score esthétique, en fonction des données d'entraînement."
|
||||
]
|
||||
}
|
||||
},
|
||||
"dynamicPrompts": {
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"blue": "Blu",
|
||||
"alpha": "Alfa",
|
||||
"copy": "Copia",
|
||||
"on": "Acceso",
|
||||
"on": "Attivato",
|
||||
"checkpoint": "Checkpoint",
|
||||
"safetensors": "Safetensors",
|
||||
"ai": "ia",
|
||||
@@ -85,7 +85,7 @@
|
||||
"openInViewer": "Apri nel visualizzatore",
|
||||
"apply": "Applica",
|
||||
"loadingImage": "Caricamento immagine",
|
||||
"off": "Spento",
|
||||
"off": "Disattivato",
|
||||
"edit": "Modifica",
|
||||
"placeholderSelectAModel": "Seleziona un modello",
|
||||
"reset": "Reimposta",
|
||||
@@ -155,9 +155,7 @@
|
||||
"move": "Sposta",
|
||||
"gallery": "Galleria",
|
||||
"openViewer": "Apri visualizzatore",
|
||||
"closeViewer": "Chiudi visualizzatore",
|
||||
"imagesTab": "Immagini create e salvate in Invoke.",
|
||||
"assetsTab": "File che hai caricato per usarli nei tuoi progetti."
|
||||
"closeViewer": "Chiudi visualizzatore"
|
||||
},
|
||||
"hotkeys": {
|
||||
"searchHotkeys": "Cerca tasti di scelta rapida",
|
||||
@@ -323,22 +321,6 @@
|
||||
"selectViewTool": {
|
||||
"title": "Strumento Visualizza",
|
||||
"desc": "Seleziona lo strumento Visualizza."
|
||||
},
|
||||
"applyFilter": {
|
||||
"title": "Applica filtro",
|
||||
"desc": "Applica il filtro in sospeso al livello selezionato."
|
||||
},
|
||||
"cancelFilter": {
|
||||
"title": "Annulla filtro",
|
||||
"desc": "Annulla il filtro in sospeso."
|
||||
},
|
||||
"cancelTransform": {
|
||||
"desc": "Annulla la trasformazione in sospeso.",
|
||||
"title": "Annulla Trasforma"
|
||||
},
|
||||
"applyTransform": {
|
||||
"title": "Applica trasformazione",
|
||||
"desc": "Applica la trasformazione in sospeso al livello selezionato."
|
||||
}
|
||||
},
|
||||
"workflows": {
|
||||
@@ -592,8 +574,8 @@
|
||||
"scale": "Scala",
|
||||
"imageFit": "Adatta l'immagine iniziale alle dimensioni di output",
|
||||
"scaleBeforeProcessing": "Scala prima dell'elaborazione",
|
||||
"scaledWidth": "Larghezza scalata",
|
||||
"scaledHeight": "Altezza scalata",
|
||||
"scaledWidth": "Larghezza ridimensionata",
|
||||
"scaledHeight": "Altezza ridimensionata",
|
||||
"infillMethod": "Metodo di riempimento",
|
||||
"tileSize": "Dimensione piastrella",
|
||||
"downloadImage": "Scarica l'immagine",
|
||||
@@ -635,11 +617,7 @@
|
||||
"ipAdapterIncompatibleBaseModel": "Il modello base dell'adattatore IP non è compatibile",
|
||||
"ipAdapterNoImageSelected": "Nessuna immagine dell'adattatore IP selezionata",
|
||||
"rgNoPromptsOrIPAdapters": "Nessun prompt o adattatore IP",
|
||||
"rgNoRegion": "Nessuna regione selezionata",
|
||||
"t2iAdapterIncompatibleBboxWidth": "$t(parameters.invoke.layer.t2iAdapterRequiresDimensionsToBeMultipleOf) {{multiple}}, larghezza riquadro è {{width}}",
|
||||
"t2iAdapterIncompatibleBboxHeight": "$t(parameters.invoke.layer.t2iAdapterRequiresDimensionsToBeMultipleOf) {{multiple}}, altezza riquadro è {{height}}",
|
||||
"t2iAdapterIncompatibleScaledBboxWidth": "$t(parameters.invoke.layer.t2iAdapterRequiresDimensionsToBeMultipleOf) {{multiple}}, larghezza del riquadro scalato {{width}}",
|
||||
"t2iAdapterIncompatibleScaledBboxHeight": "$t(parameters.invoke.layer.t2iAdapterRequiresDimensionsToBeMultipleOf) {{multiple}}, altezza del riquadro scalato {{height}}"
|
||||
"rgNoRegion": "Nessuna regione selezionata"
|
||||
},
|
||||
"fluxModelIncompatibleBboxHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), altezza riquadro è {{height}}",
|
||||
"fluxModelIncompatibleBboxWidth": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), larghezza riquadro è {{width}}",
|
||||
@@ -647,11 +625,7 @@
|
||||
"fluxModelIncompatibleScaledBboxHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), altezza del riquadro scalato è {{height}}",
|
||||
"noT5EncoderModelSelected": "Nessun modello di encoder T5 selezionato per la generazione con FLUX",
|
||||
"noCLIPEmbedModelSelected": "Nessun modello CLIP Embed selezionato per la generazione con FLUX",
|
||||
"noFLUXVAEModelSelected": "Nessun modello VAE selezionato per la generazione con FLUX",
|
||||
"canvasIsTransforming": "La tela sta trasformando",
|
||||
"canvasIsRasterizing": "La tela sta rasterizzando",
|
||||
"canvasIsCompositing": "La tela è in fase di composizione",
|
||||
"canvasIsFiltering": "La tela sta filtrando"
|
||||
"noFLUXVAEModelSelected": "Nessun modello VAE selezionato per la generazione con FLUX"
|
||||
},
|
||||
"useCpuNoise": "Usa la CPU per generare rumore",
|
||||
"iterations": "Iterazioni",
|
||||
@@ -670,12 +644,7 @@
|
||||
"processImage": "Elabora Immagine",
|
||||
"sendToUpscale": "Invia a Amplia",
|
||||
"postProcessing": "Post-elaborazione (Shift + U)",
|
||||
"guidance": "Guida",
|
||||
"gaussianBlur": "Sfocatura Gaussiana",
|
||||
"boxBlur": "Sfocatura Box",
|
||||
"staged": "Maschera espansa",
|
||||
"optimizedImageToImage": "Immagine-a-immagine ottimizzata",
|
||||
"sendToCanvas": "Invia alla Tela"
|
||||
"guidance": "Guida"
|
||||
},
|
||||
"settings": {
|
||||
"models": "Modelli",
|
||||
@@ -709,8 +678,7 @@
|
||||
"enableInformationalPopovers": "Abilita testo informativo a comparsa",
|
||||
"reloadingIn": "Ricaricando in",
|
||||
"informationalPopoversDisabled": "Testo informativo a comparsa disabilitato",
|
||||
"informationalPopoversDisabledDesc": "I testi informativi a comparsa sono disabilitati. Attivali nelle impostazioni.",
|
||||
"confirmOnNewSession": "Conferma su nuova sessione"
|
||||
"informationalPopoversDisabledDesc": "I testi informativi a comparsa sono disabilitati. Attivali nelle impostazioni."
|
||||
},
|
||||
"toast": {
|
||||
"uploadFailed": "Caricamento fallito",
|
||||
@@ -753,20 +721,7 @@
|
||||
"somethingWentWrong": "Qualcosa è andato storto",
|
||||
"outOfMemoryErrorDesc": "Le impostazioni della generazione attuale superano la capacità del sistema. Modifica le impostazioni e riprova.",
|
||||
"importFailed": "Importazione non riuscita",
|
||||
"importSuccessful": "Importazione riuscita",
|
||||
"layerSavedToAssets": "Livello salvato nelle risorse",
|
||||
"problemSavingLayer": "Impossibile salvare il livello",
|
||||
"unableToLoadImage": "Impossibile caricare l'immagine",
|
||||
"problemCopyingLayer": "Impossibile copiare il livello",
|
||||
"sentToCanvas": "Inviato alla Tela",
|
||||
"sentToUpscale": "Inviato a Amplia",
|
||||
"unableToLoadStylePreset": "Impossibile caricare lo stile predefinito",
|
||||
"stylePresetLoaded": "Stile predefinito caricato",
|
||||
"unableToLoadImageMetadata": "Impossibile caricare i metadati dell'immagine",
|
||||
"imageSaved": "Immagine salvata",
|
||||
"imageSavingFailed": "Salvataggio dell'immagine non riuscito",
|
||||
"layerCopiedToClipboard": "Livello copiato negli appunti",
|
||||
"imageNotLoadedDesc": "Impossibile trovare l'immagine"
|
||||
"importSuccessful": "Importazione riuscita"
|
||||
},
|
||||
"accessibility": {
|
||||
"invokeProgressBar": "Barra di avanzamento generazione",
|
||||
@@ -779,9 +734,7 @@
|
||||
"resetUI": "$t(accessibility.reset) l'Interfaccia Utente",
|
||||
"createIssue": "Segnala un problema",
|
||||
"about": "Informazioni",
|
||||
"submitSupportTicket": "Invia ticket di supporto",
|
||||
"toggleLeftPanel": "Attiva/disattiva il pannello sinistro (T)",
|
||||
"toggleRightPanel": "Attiva/disattiva il pannello destro (G)"
|
||||
"submitSupportTicket": "Invia ticket di supporto"
|
||||
},
|
||||
"nodes": {
|
||||
"zoomOutNodes": "Rimpicciolire",
|
||||
@@ -901,7 +854,7 @@
|
||||
"clearWorkflowDesc": "Cancellare questo flusso di lavoro e avviarne uno nuovo?",
|
||||
"clearWorkflow": "Cancella il flusso di lavoro",
|
||||
"clearWorkflowDesc2": "Il tuo flusso di lavoro attuale presenta modifiche non salvate.",
|
||||
"viewMode": "Usa la vista lineare",
|
||||
"viewMode": "Utilizzare nella vista lineare",
|
||||
"reorderLinearView": "Riordina la vista lineare",
|
||||
"editMode": "Modifica nell'editor del flusso di lavoro",
|
||||
"resetToDefaultValue": "Ripristina il valore predefinito",
|
||||
@@ -919,10 +872,7 @@
|
||||
"imageAccessError": "Impossibile trovare l'immagine {{image_name}}, ripristino ai valori predefiniti",
|
||||
"boardAccessError": "Impossibile trovare la bacheca {{board_id}}, ripristino ai valori predefiniti",
|
||||
"modelAccessError": "Impossibile trovare il modello {{key}}, ripristino ai valori predefiniti",
|
||||
"saveToGallery": "Salva nella Galleria",
|
||||
"noMatchingWorkflows": "Nessun flusso di lavoro corrispondente",
|
||||
"noWorkflows": "Nessun flusso di lavoro",
|
||||
"workflowHelpText": "Hai bisogno di aiuto? Consulta la nostra guida <LinkComponent>Introduzione ai flussi di lavoro</LinkComponent>"
|
||||
"saveToGallery": "Salva nella Galleria"
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "Aggiungi automaticamente bacheca",
|
||||
@@ -966,8 +916,7 @@
|
||||
"noBoards": "Nessuna bacheca {{boardType}}",
|
||||
"hideBoards": "Nascondi bacheche",
|
||||
"viewBoards": "Visualizza bacheche",
|
||||
"deletedPrivateBoardsCannotbeRestored": "Le bacheche cancellate non possono essere ripristinate. Selezionando 'Cancella solo bacheca', le immagini verranno spostate nella bacheca \"Non categorizzato\" privata dell'autore dell'immagine.",
|
||||
"updateBoardError": "Errore durante l'aggiornamento della bacheca"
|
||||
"deletedPrivateBoardsCannotbeRestored": "Le bacheche cancellate non possono essere ripristinate. Selezionando 'Cancella solo bacheca', le immagini verranno spostate nella bacheca \"Non categorizzato\" privata dell'autore dell'immagine."
|
||||
},
|
||||
"queue": {
|
||||
"queueFront": "Aggiungi all'inizio della coda",
|
||||
@@ -1452,25 +1401,6 @@
|
||||
"paragraphs": [
|
||||
"La struttura determina quanto l'immagine finale rispecchierà il layout dell'originale. Una struttura bassa permette cambiamenti significativi, mentre una struttura alta conserva la composizione e il layout originali."
|
||||
]
|
||||
},
|
||||
"fluxDevLicense": {
|
||||
"heading": "Licenza non commerciale",
|
||||
"paragraphs": [
|
||||
"I modelli FLUX.1 [dev] sono concessi in licenza con la licenza non commerciale FLUX [dev]. Per utilizzare questo tipo di modello per scopi commerciali in Invoke, visita il nostro sito Web per saperne di più."
|
||||
]
|
||||
},
|
||||
"optimizedDenoising": {
|
||||
"heading": "Immagine-a-immagine ottimizzata",
|
||||
"paragraphs": [
|
||||
"Abilita 'Immagine-a-immagine ottimizzata' per una scala di riduzione del rumore più graduale per le trasformazioni da immagine a immagine e di inpainting con modelli Flux. Questa impostazione migliora la capacità di controllare la quantità di modifica applicata a un'immagine, ma può essere disattivata se preferisci usare la scala di riduzione rumore standard. Questa impostazione è ancora in fase di messa a punto ed è in stato beta."
|
||||
]
|
||||
},
|
||||
"paramGuidance": {
|
||||
"heading": "Guida",
|
||||
"paragraphs": [
|
||||
"Controlla quanto il prompt influenza il processo di generazione.",
|
||||
"Valori di guida elevati possono causare sovrasaturazione e una guida elevata o bassa può causare risultati di generazione distorti. La guida si applica solo ai modelli FLUX DEV."
|
||||
]
|
||||
}
|
||||
},
|
||||
"sdxl": {
|
||||
@@ -1564,13 +1494,7 @@
|
||||
"convertGraph": "Converti grafico",
|
||||
"loadWorkflow": "$t(common.load) Flusso di lavoro",
|
||||
"autoLayout": "Disposizione automatica",
|
||||
"loadFromGraph": "Carica il flusso di lavoro dal grafico",
|
||||
"userWorkflows": "Flussi di lavoro utente",
|
||||
"projectWorkflows": "Flussi di lavoro del progetto",
|
||||
"defaultWorkflows": "Flussi di lavoro predefiniti",
|
||||
"uploadAndSaveWorkflow": "Carica nella libreria",
|
||||
"chooseWorkflowFromLibrary": "Scegli il flusso di lavoro dalla libreria",
|
||||
"deleteWorkflow2": "Vuoi davvero eliminare questo flusso di lavoro? Questa operazione non può essere annullata."
|
||||
"loadFromGraph": "Carica il flusso di lavoro dal grafico"
|
||||
},
|
||||
"accordions": {
|
||||
"compositing": {
|
||||
@@ -1609,302 +1533,7 @@
|
||||
"addPositivePrompt": "Aggiungi $t(controlLayers.prompt)",
|
||||
"addNegativePrompt": "Aggiungi $t(controlLayers.negativePrompt)",
|
||||
"regionalGuidance": "Guida regionale",
|
||||
"opacity": "Opacità",
|
||||
"mergeVisible": "Fondi il visibile",
|
||||
"mergeVisibleOk": "Livelli visibili uniti",
|
||||
"deleteReferenceImage": "Elimina l'immagine di riferimento",
|
||||
"referenceImage": "Immagine di riferimento",
|
||||
"fitBboxToLayers": "Adatta il riquadro di delimitazione ai livelli",
|
||||
"mergeVisibleError": "Errore durante l'unione dei livelli visibili",
|
||||
"regionalReferenceImage": "Immagine di riferimento Regionale",
|
||||
"newLayerFromImage": "Nuovo livello da immagine",
|
||||
"newCanvasFromImage": "Nuova tela da immagine",
|
||||
"globalReferenceImage": "Immagine di riferimento Globale",
|
||||
"copyToClipboard": "Copia negli appunti",
|
||||
"sendingToCanvas": "Effettua le generazioni nella Tela",
|
||||
"clearHistory": "Cancella la cronologia",
|
||||
"inpaintMask": "Maschera Inpaint",
|
||||
"sendToGallery": "Invia alla Galleria",
|
||||
"controlLayer": "Livello di Controllo",
|
||||
"rasterLayer_withCount_one": "$t(controlLayers.rasterLayer)",
|
||||
"rasterLayer_withCount_many": "Livelli Raster",
|
||||
"rasterLayer_withCount_other": "Livelli Raster",
|
||||
"controlLayer_withCount_one": "$t(controlLayers.controlLayer)",
|
||||
"controlLayer_withCount_many": "Livelli di controllo",
|
||||
"controlLayer_withCount_other": "Livelli di controllo",
|
||||
"clipToBbox": "Ritaglia i tratti al riquadro",
|
||||
"duplicate": "Duplica",
|
||||
"width": "Larghezza",
|
||||
"addControlLayer": "Aggiungi $t(controlLayers.controlLayer)",
|
||||
"addInpaintMask": "Aggiungi $t(controlLayers.inpaintMask)",
|
||||
"addRegionalGuidance": "Aggiungi $t(controlLayers.regionalGuidance)",
|
||||
"sendToCanvasDesc": "Premendo Invoke il lavoro in corso viene visualizzato sulla tela.",
|
||||
"addRasterLayer": "Aggiungi $t(controlLayers.rasterLayer)",
|
||||
"clearCaches": "Svuota le cache",
|
||||
"regionIsEmpty": "La regione selezionata è vuota",
|
||||
"recalculateRects": "Ricalcola rettangoli",
|
||||
"removeBookmark": "Rimuovi segnalibro",
|
||||
"saveCanvasToGallery": "Salva la tela nella Galleria",
|
||||
"regional": "Regionale",
|
||||
"global": "Globale",
|
||||
"canvas": "Tela",
|
||||
"bookmark": "Segnalibro per cambio rapido",
|
||||
"newRegionalReferenceImageOk": "Immagine di riferimento regionale creata",
|
||||
"newRegionalReferenceImageError": "Problema nella creazione dell'immagine di riferimento regionale",
|
||||
"newControlLayerOk": "Livello di controllo creato",
|
||||
"bboxOverlay": "Mostra sovrapposizione riquadro",
|
||||
"resetCanvas": "Reimposta la tela",
|
||||
"outputOnlyMaskedRegions": "Solo regioni mascherate in uscita",
|
||||
"enableAutoNegative": "Abilita Auto Negativo",
|
||||
"disableAutoNegative": "Disabilita Auto Negativo",
|
||||
"showHUD": "Mostra HUD",
|
||||
"maskFill": "Riempimento maschera",
|
||||
"addReferenceImage": "Aggiungi $t(controlLayers.referenceImage)",
|
||||
"addGlobalReferenceImage": "Aggiungi $t(controlLayers.globalReferenceImage)",
|
||||
"sendingToGallery": "Inviare generazioni alla Galleria",
|
||||
"sendToGalleryDesc": "Premendo Invoke viene generata e salvata un'immagine unica nella tua galleria.",
|
||||
"sendToCanvas": "Invia alla Tela",
|
||||
"viewProgressInViewer": "Visualizza i progressi e i risultati nel <Btn>Visualizzatore immagini</Btn>.",
|
||||
"viewProgressOnCanvas": "Visualizza i progressi e i risultati nella <Btn>Tela</Btn>.",
|
||||
"saveBboxToGallery": "Salva il riquadro di delimitazione nella Galleria",
|
||||
"cropLayerToBbox": "Ritaglia il livello al riquadro di delimitazione",
|
||||
"savedToGalleryError": "Errore durante il salvataggio nella galleria",
|
||||
"rasterLayer": "Livello Raster",
|
||||
"regionalGuidance_withCount_one": "$t(controlLayers.regionalGuidance)",
|
||||
"regionalGuidance_withCount_many": "Guide regionali",
|
||||
"regionalGuidance_withCount_other": "Guide regionali",
|
||||
"inpaintMask_withCount_one": "$t(controlLayers.inpaintMask)",
|
||||
"inpaintMask_withCount_many": "Maschere Inpaint",
|
||||
"inpaintMask_withCount_other": "Maschere Inpaint",
|
||||
"savedToGalleryOk": "Salvato nella Galleria",
|
||||
"newGlobalReferenceImageOk": "Immagine di riferimento globale creata",
|
||||
"newGlobalReferenceImageError": "Problema nella creazione dell'immagine di riferimento globale",
|
||||
"newControlLayerError": "Problema nella creazione del livello di controllo",
|
||||
"newRasterLayerOk": "Livello raster creato",
|
||||
"newRasterLayerError": "Problema nella creazione del livello raster",
|
||||
"saveLayerToAssets": "Salva il livello nelle Risorse",
|
||||
"pullBboxIntoLayerError": "Problema nel caricare il riquadro nel livello",
|
||||
"pullBboxIntoReferenceImageOk": "Contenuto del riquadro inserito nell'immagine di riferimento",
|
||||
"pullBboxIntoLayerOk": "Riquadro caricato nel livello",
|
||||
"pullBboxIntoReferenceImageError": "Problema nell'inserimento del contenuto del riquadro nell'immagine di riferimento",
|
||||
"globalReferenceImage_withCount_one": "$t(controlLayers.globalReferenceImage)",
|
||||
"globalReferenceImage_withCount_many": "Immagini di riferimento Globali",
|
||||
"globalReferenceImage_withCount_other": "Immagini di riferimento Globali",
|
||||
"controlMode": {
|
||||
"balanced": "Bilanciato",
|
||||
"controlMode": "Modalità di controllo",
|
||||
"prompt": "Prompt",
|
||||
"control": "Controllo",
|
||||
"megaControl": "Mega Controllo"
|
||||
},
|
||||
"negativePrompt": "Prompt Negativo",
|
||||
"prompt": "Prompt Positivo",
|
||||
"beginEndStepPercentShort": "Inizio/Fine %",
|
||||
"stagingOnCanvas": "Genera immagini nella",
|
||||
"ipAdapterMethod": {
|
||||
"full": "Completo",
|
||||
"style": "Solo Stile",
|
||||
"composition": "Solo Composizione",
|
||||
"ipAdapterMethod": "Metodo Adattatore IP"
|
||||
},
|
||||
"showingType": "Mostrare {{type}}",
|
||||
"dynamicGrid": "Griglia dinamica",
|
||||
"tool": {
|
||||
"view": "Muovi",
|
||||
"colorPicker": "Selettore Colore",
|
||||
"rectangle": "Rettangolo",
|
||||
"bbox": "Riquadro di delimitazione",
|
||||
"move": "Sposta",
|
||||
"brush": "Pennello",
|
||||
"eraser": "Cancellino"
|
||||
},
|
||||
"filter": {
|
||||
"apply": "Applica",
|
||||
"reset": "Reimposta",
|
||||
"process": "Elabora",
|
||||
"cancel": "Annulla",
|
||||
"autoProcess": "Processo automatico",
|
||||
"filterType": "Tipo Filtro",
|
||||
"filter": "Filtro",
|
||||
"filters": "Filtri",
|
||||
"mlsd_detection": {
|
||||
"score_threshold": "Soglia di punteggio",
|
||||
"distance_threshold": "Soglia di distanza",
|
||||
"description": "Genera una mappa dei segmenti di linea dal livello selezionato utilizzando il modello di rilevamento dei segmenti di linea MLSD."
|
||||
},
|
||||
"content_shuffle": {
|
||||
"label": "Mescola contenuto",
|
||||
"scale_factor": "Fattore di scala",
|
||||
"description": "Mescola il contenuto del livello selezionato, in modo simile all'effetto \"liquefa\"."
|
||||
},
|
||||
"mediapipe_face_detection": {
|
||||
"min_confidence": "Confidenza minima",
|
||||
"label": "Rilevamento del volto MediaPipe",
|
||||
"max_faces": "Max volti",
|
||||
"description": "Rileva i volti nel livello selezionato utilizzando il modello di rilevamento dei volti MediaPipe."
|
||||
},
|
||||
"dw_openpose_detection": {
|
||||
"draw_face": "Disegna il volto",
|
||||
"description": "Rileva le pose umane nel livello selezionato utilizzando il modello DW Openpose.",
|
||||
"label": "Rilevamento DW Openpose",
|
||||
"draw_hands": "Disegna le mani",
|
||||
"draw_body": "Disegna il corpo"
|
||||
},
|
||||
"normal_map": {
|
||||
"description": "Genera una mappa delle normali dal livello selezionato.",
|
||||
"label": "Mappa delle normali"
|
||||
},
|
||||
"lineart_edge_detection": {
|
||||
"label": "Rilevamento bordi Lineart",
|
||||
"coarse": "Grossolano",
|
||||
"description": "Genera una mappa dei bordi dal livello selezionato utilizzando il modello di rilevamento dei bordi Lineart."
|
||||
},
|
||||
"depth_anything_depth_estimation": {
|
||||
"model_size_small": "Piccolo",
|
||||
"model_size_small_v2": "Piccolo v2",
|
||||
"model_size": "Dimensioni modello",
|
||||
"model_size_large": "Grande",
|
||||
"model_size_base": "Base",
|
||||
"description": "Genera una mappa di profondità dal livello selezionato utilizzando un modello Depth Anything."
|
||||
},
|
||||
"color_map": {
|
||||
"label": "Mappa colore",
|
||||
"description": "Crea una mappa dei colori dal livello selezionato.",
|
||||
"tile_size": "Dimens. Piastrella"
|
||||
},
|
||||
"canny_edge_detection": {
|
||||
"high_threshold": "Soglia superiore",
|
||||
"low_threshold": "Soglia inferiore",
|
||||
"description": "Genera una mappa dei bordi dal livello selezionato utilizzando l'algoritmo di rilevamento dei bordi Canny.",
|
||||
"label": "Rilevamento bordi Canny"
|
||||
},
|
||||
"spandrel_filter": {
|
||||
"scale": "Scala di destinazione",
|
||||
"autoScaleDesc": "Il modello selezionato verrà eseguito fino al raggiungimento della scala di destinazione.",
|
||||
"description": "Esegue un modello immagine-a-immagine sul livello selezionato.",
|
||||
"label": "Modello Immagine-a-Immagine",
|
||||
"model": "Modello",
|
||||
"autoScale": "Auto Scala"
|
||||
},
|
||||
"pidi_edge_detection": {
|
||||
"quantize_edges": "Quantizza i bordi",
|
||||
"scribble": "Scarabocchio",
|
||||
"description": "Genera una mappa dei bordi dal livello selezionato utilizzando il modello di rilevamento dei bordi PiDiNet.",
|
||||
"label": "Rilevamento bordi PiDiNet"
|
||||
},
|
||||
"hed_edge_detection": {
|
||||
"label": "Rilevamento bordi HED",
|
||||
"description": "Genera una mappa dei bordi dal livello selezionato utilizzando il modello di rilevamento dei bordi HED.",
|
||||
"scribble": "Scarabocchio"
|
||||
},
|
||||
"lineart_anime_edge_detection": {
|
||||
"description": "Genera una mappa dei bordi dal livello selezionato utilizzando il modello di rilevamento dei bordi Lineart Anime.",
|
||||
"label": "Rilevamento bordi Lineart Anime"
|
||||
}
|
||||
},
|
||||
"controlLayers_withCount_hidden": "Livelli di controllo ({{count}} nascosti)",
|
||||
"regionalGuidance_withCount_hidden": "Guida regionale ({{count}} nascosti)",
|
||||
"fill": {
|
||||
"grid": "Griglia",
|
||||
"crosshatch": "Tratteggio incrociato",
|
||||
"fillColor": "Colore di riempimento",
|
||||
"fillStyle": "Stile riempimento",
|
||||
"solid": "Solido",
|
||||
"vertical": "Verticale",
|
||||
"horizontal": "Orizzontale",
|
||||
"diagonal": "Diagonale"
|
||||
},
|
||||
"rasterLayers_withCount_hidden": "Livelli raster ({{count}} nascosti)",
|
||||
"inpaintMasks_withCount_hidden": "Maschere Inpaint ({{count}} nascoste)",
|
||||
"regionalGuidance_withCount_visible": "Guide regionali ({{count}})",
|
||||
"locked": "Bloccato",
|
||||
"hidingType": "Nascondere {{type}}",
|
||||
"logDebugInfo": "Registro Info Debug",
|
||||
"inpaintMasks_withCount_visible": "Maschere Inpaint ({{count}})",
|
||||
"layer_one": "Livello",
|
||||
"layer_many": "Livelli",
|
||||
"layer_other": "Livelli",
|
||||
"disableTransparencyEffect": "Disabilita l'effetto trasparenza",
|
||||
"controlLayers_withCount_visible": "Livelli di controllo ({{count}})",
|
||||
"transparency": "Trasparenza",
|
||||
"newCanvasSessionDesc": "Questo cancellerà la tela e tutte le impostazioni, eccetto la selezione del modello. Le generazioni saranno effettuate sulla tela.",
|
||||
"rasterLayers_withCount_visible": "Livelli raster ({{count}})",
|
||||
"globalReferenceImages_withCount_visible": "Immagini di riferimento Globali ({{count}})",
|
||||
"globalReferenceImages_withCount_hidden": "Immagini di riferimento globali ({{count}} nascoste)",
|
||||
"layer_withCount_one": "Livello ({{count}})",
|
||||
"layer_withCount_many": "Livelli ({{count}})",
|
||||
"layer_withCount_other": "Livelli ({{count}})",
|
||||
"convertToControlLayer": "Converti in livello di controllo",
|
||||
"convertToRasterLayer": "Converti in livello raster",
|
||||
"unlocked": "Sbloccato",
|
||||
"enableTransparencyEffect": "Abilita l'effetto trasparenza",
|
||||
"replaceLayer": "Sostituisci livello",
|
||||
"pullBboxIntoLayer": "Carica l'immagine delimitata nel riquadro",
|
||||
"pullBboxIntoReferenceImage": "Carica l'immagine delimitata nel riquadro",
|
||||
"showProgressOnCanvas": "Mostra i progressi sulla Tela",
|
||||
"weight": "Peso",
|
||||
"newGallerySession": "Nuova sessione Galleria",
|
||||
"newGallerySessionDesc": "Questo cancellerà la tela e tutte le impostazioni, eccetto la selezione del modello. Le generazioni saranno inviate alla galleria.",
|
||||
"newCanvasSession": "Nuova sessione Tela",
|
||||
"deleteSelected": "Elimina selezione",
|
||||
"settings": {
|
||||
"isolatedFilteringPreview": "Anteprima del filtraggio isolata",
|
||||
"isolatedStagingPreview": "Anteprima di generazione isolata",
|
||||
"isolatedTransformingPreview": "Anteprima di trasformazione isolata",
|
||||
"isolatedPreview": "Anteprima isolata",
|
||||
"invertBrushSizeScrollDirection": "Inverti scorrimento per dimensione pennello",
|
||||
"snapToGrid": {
|
||||
"label": "Aggancia alla griglia",
|
||||
"on": "Acceso",
|
||||
"off": "Spento"
|
||||
},
|
||||
"pressureSensitivity": "Sensibilità alla pressione",
|
||||
"preserveMask": {
|
||||
"alert": "Preservare la regione mascherata",
|
||||
"label": "Preserva la regione mascherata"
|
||||
}
|
||||
},
|
||||
"transform": {
|
||||
"reset": "Reimposta",
|
||||
"fitToBbox": "Adatta al Riquadro",
|
||||
"transform": "Trasforma",
|
||||
"apply": "Applica",
|
||||
"cancel": "Annulla"
|
||||
},
|
||||
"stagingArea": {
|
||||
"next": "Successiva",
|
||||
"discard": "Scarta",
|
||||
"discardAll": "Scarta tutto",
|
||||
"accept": "Accetta",
|
||||
"saveToGallery": "Salva nella Galleria",
|
||||
"previous": "Precedente",
|
||||
"showResultsOn": "Risultati visualizzati",
|
||||
"showResultsOff": "Risultati nascosti"
|
||||
},
|
||||
"HUD": {
|
||||
"bbox": "Riquadro di delimitazione",
|
||||
"entityStatus": {
|
||||
"isHidden": "{{title}} è nascosto",
|
||||
"isLocked": "{{title}} è bloccato",
|
||||
"isTransforming": "{{title}} sta trasformando",
|
||||
"isFiltering": "{{title}} sta filtrando",
|
||||
"isEmpty": "{{title}} è vuoto",
|
||||
"isDisabled": "{{title}} è disabilitato"
|
||||
},
|
||||
"scaledBbox": "Riquadro scalato"
|
||||
},
|
||||
"canvasContextMenu": {
|
||||
"newControlLayer": "Nuovo Livello di Controllo",
|
||||
"newRegionalReferenceImage": "Nuova immagine di riferimento Regionale",
|
||||
"newGlobalReferenceImage": "Nuova immagine di riferimento Globale",
|
||||
"bboxGroup": "Crea dal riquadro di delimitazione",
|
||||
"saveBboxToGallery": "Salva il riquadro nella Galleria",
|
||||
"cropCanvasToBbox": "Ritaglia la Tela al riquadro",
|
||||
"canvasGroup": "Tela",
|
||||
"newRasterLayer": "Nuovo Livello Raster",
|
||||
"saveCanvasToGallery": "Salva la Tela nella Galleria",
|
||||
"saveToGalleryGroup": "Salva nella Galleria"
|
||||
}
|
||||
"opacity": "Opacità"
|
||||
},
|
||||
"ui": {
|
||||
"tabs": {
|
||||
@@ -1916,8 +1545,7 @@
|
||||
"modelsTab": "$t(ui.tabs.models) $t(common.tab)",
|
||||
"queue": "Coda",
|
||||
"upscaling": "Amplia",
|
||||
"upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)",
|
||||
"gallery": "Galleria"
|
||||
"upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)"
|
||||
}
|
||||
},
|
||||
"upscaling": {
|
||||
@@ -1987,45 +1615,5 @@
|
||||
"noTemplates": "Nessun modello",
|
||||
"acceptedColumnsKeys": "Colonne/chiavi accettate:",
|
||||
"promptTemplateCleared": "Modello di prompt cancellato"
|
||||
},
|
||||
"newUserExperience": {
|
||||
"gettingStartedSeries": "Desideri maggiori informazioni? Consulta la nostra <LinkComponent>Getting Started Series</LinkComponent> per suggerimenti su come sfruttare appieno il potenziale di Invoke Studio.",
|
||||
"toGetStarted": "Per iniziare, inserisci un prompt nella casella e fai clic su <StrongComponent>Invoke</StrongComponent> per generare la tua prima immagine. Seleziona un modello di prompt per migliorare i risultati. Puoi scegliere di salvare le tue immagini direttamente nella <StrongComponent>Galleria</StrongComponent> o modificarle nella <StrongComponent>Tela</StrongComponent>."
|
||||
},
|
||||
"whatsNew": {
|
||||
"canvasV2Announcement": {
|
||||
"readReleaseNotes": "Leggi le Note di Rilascio",
|
||||
"fluxSupport": "Supporto per la famiglia di modelli Flux",
|
||||
"newCanvas": "Una nuova potente tela di controllo",
|
||||
"watchReleaseVideo": "Guarda il video di rilascio",
|
||||
"watchUiUpdatesOverview": "Guarda le novità dell'interfaccia",
|
||||
"newLayerTypes": "Nuovi tipi di livello per un miglior controllo"
|
||||
},
|
||||
"whatsNewInInvoke": "Novità in Invoke"
|
||||
},
|
||||
"system": {
|
||||
"logLevel": {
|
||||
"info": "Info",
|
||||
"warn": "Avviso",
|
||||
"fatal": "Fatale",
|
||||
"error": "Errore",
|
||||
"debug": "Debug",
|
||||
"trace": "Traccia",
|
||||
"logLevel": "Livello di registro"
|
||||
},
|
||||
"logNamespaces": {
|
||||
"workflows": "Flussi di lavoro",
|
||||
"generation": "Generazione",
|
||||
"canvas": "Tela",
|
||||
"config": "Configurazione",
|
||||
"models": "Modelli",
|
||||
"gallery": "Galleria",
|
||||
"queue": "Coda",
|
||||
"events": "Eventi",
|
||||
"system": "Sistema",
|
||||
"metadata": "Metadati",
|
||||
"logNamespaces": "Elementi del registro"
|
||||
},
|
||||
"enableLogging": "Abilita la registrazione"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,8 +93,7 @@
|
||||
"placeholderSelectAModel": "Выбрать модель",
|
||||
"reset": "Сброс",
|
||||
"none": "Ничего",
|
||||
"new": "Новый",
|
||||
"ok": "Ok"
|
||||
"new": "Новый"
|
||||
},
|
||||
"gallery": {
|
||||
"galleryImageSize": "Размер изображений",
|
||||
@@ -228,118 +227,6 @@
|
||||
"selectBrushTool": {
|
||||
"title": "Инструмент кисть",
|
||||
"desc": "Выбирает кисть."
|
||||
},
|
||||
"selectBboxTool": {
|
||||
"title": "Инструмент рамка",
|
||||
"desc": "Выбрать инструмент «Ограничительная рамка»."
|
||||
},
|
||||
"incrementToolWidth": {
|
||||
"desc": "Increment the brush or eraser tool width, whichever is selected.",
|
||||
"title": "Increment Tool Width"
|
||||
},
|
||||
"selectColorPickerTool": {
|
||||
"title": "Color Picker Tool",
|
||||
"desc": "Select the color picker tool."
|
||||
},
|
||||
"prevEntity": {
|
||||
"title": "Prev Layer",
|
||||
"desc": "Select the previous layer in the list."
|
||||
},
|
||||
"filterSelected": {
|
||||
"title": "Filter",
|
||||
"desc": "Filter the selected layer. Only applies to Raster and Control layers."
|
||||
},
|
||||
"undo": {
|
||||
"desc": "Отменяет последнее действие на холсте.",
|
||||
"title": "Отменить"
|
||||
},
|
||||
"transformSelected": {
|
||||
"title": "Transform",
|
||||
"desc": "Transform the selected layer."
|
||||
},
|
||||
"setZoomTo400Percent": {
|
||||
"title": "Zoom to 400%",
|
||||
"desc": "Set the canvas zoom to 400%."
|
||||
},
|
||||
"setZoomTo200Percent": {
|
||||
"title": "Zoom to 200%",
|
||||
"desc": "Set the canvas zoom to 200%."
|
||||
},
|
||||
"deleteSelected": {
|
||||
"desc": "Delete the selected layer.",
|
||||
"title": "Delete Layer"
|
||||
},
|
||||
"resetSelected": {
|
||||
"title": "Reset Layer",
|
||||
"desc": "Reset the selected layer. Only applies to Inpaint Mask and Regional Guidance."
|
||||
},
|
||||
"redo": {
|
||||
"desc": "Возвращает последнее отмененное действие.",
|
||||
"title": "Вернуть"
|
||||
},
|
||||
"nextEntity": {
|
||||
"title": "Next Layer",
|
||||
"desc": "Select the next layer in the list."
|
||||
},
|
||||
"setFillToWhite": {
|
||||
"title": "Set Color to White",
|
||||
"desc": "Set the current tool color to white."
|
||||
},
|
||||
"applyFilter": {
|
||||
"title": "Apply Filter",
|
||||
"desc": "Apply the pending filter to the selected layer."
|
||||
},
|
||||
"cancelFilter": {
|
||||
"title": "Cancel Filter",
|
||||
"desc": "Cancel the pending filter."
|
||||
},
|
||||
"applyTransform": {
|
||||
"desc": "Apply the pending transform to the selected layer.",
|
||||
"title": "Apply Transform"
|
||||
},
|
||||
"cancelTransform": {
|
||||
"title": "Cancel Transform",
|
||||
"desc": "Cancel the pending transform."
|
||||
},
|
||||
"selectEraserTool": {
|
||||
"title": "Eraser Tool",
|
||||
"desc": "Select the eraser tool."
|
||||
},
|
||||
"fitLayersToCanvas": {
|
||||
"desc": "Scale and position the view to fit all visible layers.",
|
||||
"title": "Fit Layers to Canvas"
|
||||
},
|
||||
"decrementToolWidth": {
|
||||
"title": "Decrement Tool Width",
|
||||
"desc": "Decrement the brush or eraser tool width, whichever is selected."
|
||||
},
|
||||
"setZoomTo800Percent": {
|
||||
"title": "Zoom to 800%",
|
||||
"desc": "Set the canvas zoom to 800%."
|
||||
},
|
||||
"quickSwitch": {
|
||||
"title": "Layer Quick Switch",
|
||||
"desc": "Switch between the last two selected layers. If a layer is bookmarked, always switch between it and the last non-bookmarked layer."
|
||||
},
|
||||
"fitBboxToCanvas": {
|
||||
"title": "Fit Bbox to Canvas",
|
||||
"desc": "Scale and position the view to fit the bbox."
|
||||
},
|
||||
"setZoomTo100Percent": {
|
||||
"title": "Zoom to 100%",
|
||||
"desc": "Set the canvas zoom to 100%."
|
||||
},
|
||||
"selectMoveTool": {
|
||||
"desc": "Select the move tool.",
|
||||
"title": "Move Tool"
|
||||
},
|
||||
"selectRectTool": {
|
||||
"title": "Rect Tool",
|
||||
"desc": "Select the rect tool."
|
||||
},
|
||||
"selectViewTool": {
|
||||
"title": "View Tool",
|
||||
"desc": "Select the view tool."
|
||||
}
|
||||
},
|
||||
"hotkeys": "Горячие клавиши",
|
||||
@@ -349,33 +236,11 @@
|
||||
"desc": "Отменить последнее действие в рабочем процессе."
|
||||
},
|
||||
"deleteSelection": {
|
||||
"desc": "Удалить выделенные узлы и ребра.",
|
||||
"title": "Delete"
|
||||
"desc": "Удалить выделенные узлы и ребра."
|
||||
},
|
||||
"redo": {
|
||||
"title": "Вернуть",
|
||||
"desc": "Вернуть последнее действие в рабочем процессе."
|
||||
},
|
||||
"copySelection": {
|
||||
"title": "Copy",
|
||||
"desc": "Copy selected nodes and edges."
|
||||
},
|
||||
"pasteSelection": {
|
||||
"title": "Paste",
|
||||
"desc": "Paste copied nodes and edges."
|
||||
},
|
||||
"addNode": {
|
||||
"desc": "Open the add node menu.",
|
||||
"title": "Add Node"
|
||||
},
|
||||
"title": "Workflows",
|
||||
"pasteSelectionWithEdges": {
|
||||
"title": "Paste with Edges",
|
||||
"desc": "Paste copied nodes, edges, and all edges connected to copied nodes."
|
||||
},
|
||||
"selectAll": {
|
||||
"desc": "Select all nodes and edges.",
|
||||
"title": "Select All"
|
||||
}
|
||||
},
|
||||
"viewer": {
|
||||
@@ -392,84 +257,12 @@
|
||||
"title": "Восстановить все метаданные"
|
||||
},
|
||||
"swapImages": {
|
||||
"desc": "Поменять местами сравниваемые изображения.",
|
||||
"title": "Swap Comparison Images"
|
||||
"desc": "Поменять местами сравниваемые изображения."
|
||||
},
|
||||
"title": "Просмотрщик изображений",
|
||||
"toggleViewer": {
|
||||
"title": "Открыть/закрыть просмотрщик",
|
||||
"desc": "Показать или скрыть просмотрщик изображений. Доступно только на вкладке «Холст»."
|
||||
},
|
||||
"recallSeed": {
|
||||
"title": "Recall Seed",
|
||||
"desc": "Recall the seed for the current image."
|
||||
},
|
||||
"recallPrompts": {
|
||||
"desc": "Recall the positive and negative prompts for the current image.",
|
||||
"title": "Recall Prompts"
|
||||
},
|
||||
"remix": {
|
||||
"title": "Remix",
|
||||
"desc": "Recall all metadata except for the seed for the current image."
|
||||
},
|
||||
"useSize": {
|
||||
"desc": "Use the current image's size as the bbox size.",
|
||||
"title": "Use Size"
|
||||
},
|
||||
"runPostprocessing": {
|
||||
"title": "Run Postprocessing",
|
||||
"desc": "Run the selected postprocessing on the current image."
|
||||
},
|
||||
"toggleMetadata": {
|
||||
"title": "Show/Hide Metadata",
|
||||
"desc": "Show or hide the current image's metadata overlay."
|
||||
}
|
||||
},
|
||||
"gallery": {
|
||||
"galleryNavRightAlt": {
|
||||
"desc": "Same as Navigate Right, but selects the compare image, opening compare mode if it isn't already open.",
|
||||
"title": "Navigate Right (Compare Image)"
|
||||
},
|
||||
"galleryNavRight": {
|
||||
"desc": "Navigate right in the gallery grid, selecting that image. If at the last image of the row, go to the next row. If at the last image of the page, go to the next page.",
|
||||
"title": "Navigate Right"
|
||||
},
|
||||
"galleryNavUp": {
|
||||
"desc": "Navigate up in the gallery grid, selecting that image. If at the top of the page, go to the previous page.",
|
||||
"title": "Navigate Up"
|
||||
},
|
||||
"galleryNavDown": {
|
||||
"title": "Navigate Down",
|
||||
"desc": "Navigate down in the gallery grid, selecting that image. If at the bottom of the page, go to the next page."
|
||||
},
|
||||
"galleryNavLeft": {
|
||||
"title": "Navigate Left",
|
||||
"desc": "Navigate left in the gallery grid, selecting that image. If at the first image of the row, go to the previous row. If at the first image of the page, go to the previous page."
|
||||
},
|
||||
"galleryNavDownAlt": {
|
||||
"title": "Navigate Down (Compare Image)",
|
||||
"desc": "Same as Navigate Down, but selects the compare image, opening compare mode if it isn't already open."
|
||||
},
|
||||
"galleryNavLeftAlt": {
|
||||
"desc": "Same as Navigate Left, but selects the compare image, opening compare mode if it isn't already open.",
|
||||
"title": "Navigate Left (Compare Image)"
|
||||
},
|
||||
"clearSelection": {
|
||||
"desc": "Clear the current selection, if any.",
|
||||
"title": "Clear Selection"
|
||||
},
|
||||
"deleteSelection": {
|
||||
"title": "Delete",
|
||||
"desc": "Delete all selected images. By default, you will be prompted to confirm deletion. If the images are currently in use in the app, you will be warned."
|
||||
},
|
||||
"galleryNavUpAlt": {
|
||||
"title": "Navigate Up (Compare Image)",
|
||||
"desc": "Same as Navigate Up, but selects the compare image, opening compare mode if it isn't already open."
|
||||
},
|
||||
"title": "Gallery",
|
||||
"selectAllOnPage": {
|
||||
"title": "Select All On Page",
|
||||
"desc": "Select all images on the current page."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -579,9 +372,7 @@
|
||||
"ipAdapters": "IP адаптеры",
|
||||
"starterModelsInModelManager": "Стартовые модели можно найти в Менеджере моделей",
|
||||
"learnMoreAboutSupportedModels": "Подробнее о поддерживаемых моделях",
|
||||
"t5Encoder": "T5 энкодер",
|
||||
"spandrelImageToImage": "Image to Image (Spandrel)",
|
||||
"clipEmbed": "CLIP Embed"
|
||||
"t5Encoder": "T5 энкодер"
|
||||
},
|
||||
"parameters": {
|
||||
"images": "Изображения",
|
||||
@@ -641,16 +432,12 @@
|
||||
"rgNoRegion": "регион не выбран",
|
||||
"rgNoPromptsOrIPAdapters": "нет текстовых запросов или IP-адаптеров",
|
||||
"ipAdapterIncompatibleBaseModel": "несовместимая базовая модель IP-адаптера",
|
||||
"ipAdapterNoImageSelected": "изображение IP-адаптера не выбрано",
|
||||
"t2iAdapterIncompatibleScaledBboxWidth": "$t(parameters.invoke.layer.t2iAdapterRequiresDimensionsToBeMultipleOf) {{multiple}}, масштабированная ширина рамки {{width}}",
|
||||
"t2iAdapterIncompatibleBboxHeight": "$t(parameters.invoke.layer.t2iAdapterRequiresDimensionsToBeMultipleOf) {{multiple}}, высота рамки {{height}}",
|
||||
"t2iAdapterIncompatibleBboxWidth": "$t(parameters.invoke.layer.t2iAdapterRequiresDimensionsToBeMultipleOf) {{multiple}}, ширина рамки {{width}}",
|
||||
"t2iAdapterIncompatibleScaledBboxHeight": "$t(parameters.invoke.layer.t2iAdapterRequiresDimensionsToBeMultipleOf) {{multiple}}, масштабированная высота рамки {{height}}"
|
||||
"ipAdapterNoImageSelected": "изображение IP-адаптера не выбрано"
|
||||
},
|
||||
"fluxModelIncompatibleBboxWidth": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), ширина рамки {{width}}",
|
||||
"fluxModelIncompatibleBboxHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), высота рамки {{height}}",
|
||||
"fluxModelIncompatibleScaledBboxHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), масштабированная высота рамки {{height}}",
|
||||
"fluxModelIncompatibleScaledBboxWidth": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16) масштабированная ширина рамки {{width}}",
|
||||
"fluxModelIncompatibleBboxWidth": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), ширина bbox {{width}}",
|
||||
"fluxModelIncompatibleBboxHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), высота bbox {{height}}",
|
||||
"fluxModelIncompatibleScaledBboxHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), масштабированная высота bbox {{height}}",
|
||||
"fluxModelIncompatibleScaledBboxWidth": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16) масштабированная ширина bbox {{width}}",
|
||||
"noFLUXVAEModelSelected": "Для генерации FLUX не выбрана модель VAE",
|
||||
"noT5EncoderModelSelected": "Для генерации FLUX не выбрана модель T5 энкодера",
|
||||
"canvasIsFiltering": "Холст фильтруется",
|
||||
@@ -683,8 +470,7 @@
|
||||
"staged": "Инсценировка",
|
||||
"optimizedImageToImage": "Оптимизированное img2img",
|
||||
"sendToCanvas": "Отправить на холст",
|
||||
"guidance": "Точность",
|
||||
"boxBlur": "Box Blur"
|
||||
"guidance": "Точность"
|
||||
},
|
||||
"settings": {
|
||||
"models": "Модели",
|
||||
@@ -718,8 +504,7 @@
|
||||
"intermediatesClearedFailed": "Проблема очистки промежуточных",
|
||||
"reloadingIn": "Перезагрузка через",
|
||||
"informationalPopoversDisabled": "Информационные всплывающие окна отключены",
|
||||
"informationalPopoversDisabledDesc": "Информационные всплывающие окна были отключены. Включите их в Настройках.",
|
||||
"confirmOnNewSession": "Подтверждение нового сеанса"
|
||||
"informationalPopoversDisabledDesc": "Информационные всплывающие окна были отключены. Включите их в Настройках."
|
||||
},
|
||||
"toast": {
|
||||
"uploadFailed": "Загрузка не удалась",
|
||||
@@ -733,8 +518,8 @@
|
||||
"parameterSet": "Параметр задан",
|
||||
"problemCopyingImage": "Не удается скопировать изображение",
|
||||
"baseModelChangedCleared_one": "Очищена или отключена {{count}} несовместимая подмодель",
|
||||
"baseModelChangedCleared_few": "Очищено или отключено {{count}} несовместимых подмодели",
|
||||
"baseModelChangedCleared_many": "Очищено или отключено {{count}} несовместимых подмоделей",
|
||||
"baseModelChangedCleared_few": "Очищены или отключены {{count}} несовместимые подмодели",
|
||||
"baseModelChangedCleared_many": "Очищены или отключены {{count}} несовместимых подмоделей",
|
||||
"loadedWithWarnings": "Рабочий процесс загружен с предупреждениями",
|
||||
"setControlImage": "Установить как контрольное изображение",
|
||||
"setNodeField": "Установить как поле узла",
|
||||
@@ -788,9 +573,7 @@
|
||||
"resetUI": "$t(accessibility.reset) интерфейс",
|
||||
"createIssue": "Сообщить о проблеме",
|
||||
"about": "Об этом",
|
||||
"submitSupportTicket": "Отправить тикет в службу поддержки",
|
||||
"toggleRightPanel": "Переключить правую панель (G)",
|
||||
"toggleLeftPanel": "Переключить левую панель (T)"
|
||||
"submitSupportTicket": "Отправить тикет в службу поддержки"
|
||||
},
|
||||
"nodes": {
|
||||
"zoomInNodes": "Увеличьте масштаб",
|
||||
@@ -947,16 +730,16 @@
|
||||
"loading": "Загрузка...",
|
||||
"clearSearch": "Очистить поиск",
|
||||
"deleteBoardOnly": "Удалить только доску",
|
||||
"movingImagesToBoard_one": "Перемещение {{count}} изображения на доску:",
|
||||
"movingImagesToBoard_few": "Перемещение {{count}} изображений на доску:",
|
||||
"movingImagesToBoard_many": "Перемещение {{count}} изображений на доску:",
|
||||
"movingImagesToBoard_one": "Перемещаем {{count}} изображение на доску:",
|
||||
"movingImagesToBoard_few": "Перемещаем {{count}} изображения на доску:",
|
||||
"movingImagesToBoard_many": "Перемещаем {{count}} изображений на доску:",
|
||||
"downloadBoard": "Скачать доску",
|
||||
"deleteBoard": "Удалить доску",
|
||||
"deleteBoardAndImages": "Удалить доску и изображения",
|
||||
"deletedBoardsCannotbeRestored": "Удаленные доски не могут быть восстановлены. Выбор «Удалить только доску» переведет изображения в состояние без категории.",
|
||||
"assetsWithCount_one": "{{count}} актив",
|
||||
"assetsWithCount_few": "{{count}} актива",
|
||||
"assetsWithCount_many": "{{count}} активов",
|
||||
"assetsWithCount_one": "{{count}} ассет",
|
||||
"assetsWithCount_few": "{{count}} ассета",
|
||||
"assetsWithCount_many": "{{count}} ассетов",
|
||||
"imagesWithCount_one": "{{count}} изображение",
|
||||
"imagesWithCount_few": "{{count}} изображения",
|
||||
"imagesWithCount_many": "{{count}} изображений",
|
||||
@@ -972,8 +755,7 @@
|
||||
"hideBoards": "Скрыть доски",
|
||||
"viewBoards": "Просмотреть доски",
|
||||
"noBoards": "Нет досок {{boardType}}",
|
||||
"deletedPrivateBoardsCannotbeRestored": "Удаленные доски не могут быть восстановлены. Выбор «Удалить только доску» переведет изображения в приватное состояние без категории для создателя изображения.",
|
||||
"updateBoardError": "Ошибка обновления доски"
|
||||
"deletedPrivateBoardsCannotbeRestored": "Удаленные доски не могут быть восстановлены. Выбор «Удалить только доску» переведет изображения в приватное состояние без категории для создателя изображения."
|
||||
},
|
||||
"dynamicPrompts": {
|
||||
"seedBehaviour": {
|
||||
@@ -1610,15 +1392,15 @@
|
||||
"autoNegative": "Авто негатив",
|
||||
"deletePrompt": "Удалить запрос",
|
||||
"rectangle": "Прямоугольник",
|
||||
"addNegativePrompt": "Добавить $t(controlLayers.negativePrompt)",
|
||||
"addNegativePrompt": "Добавить $t(common.negativePrompt)",
|
||||
"regionalGuidance": "Региональная точность",
|
||||
"opacity": "Непрозрачность",
|
||||
"addLayer": "Добавить слой",
|
||||
"moveToFront": "На передний план",
|
||||
"addPositivePrompt": "Добавить $t(controlLayers.prompt)",
|
||||
"addPositivePrompt": "Добавить $t(common.positivePrompt)",
|
||||
"regional": "Региональный",
|
||||
"bookmark": "Закладка для быстрого переключения",
|
||||
"fitBboxToLayers": "Подогнать рамку к слоям",
|
||||
"fitBboxToLayers": "Подогнать Bbox к слоям",
|
||||
"mergeVisibleOk": "Объединенные видимые слои",
|
||||
"mergeVisibleError": "Ошибка объединения видимых слоев",
|
||||
"clearHistory": "Очистить историю",
|
||||
@@ -1627,7 +1409,7 @@
|
||||
"saveLayerToAssets": "Сохранить слой в активы",
|
||||
"clearCaches": "Очистить кэши",
|
||||
"recalculateRects": "Пересчитать прямоугольники",
|
||||
"saveBboxToGallery": "Сохранить рамку в галерею",
|
||||
"saveBboxToGallery": "Сохранить Bbox в галерею",
|
||||
"resetCanvas": "Сбросить холст",
|
||||
"canvas": "Холст",
|
||||
"global": "Глобальный",
|
||||
@@ -1639,280 +1421,15 @@
|
||||
"newRasterLayerOk": "Создан растровый слой",
|
||||
"newRasterLayerError": "Ошибка создания растрового слоя",
|
||||
"newGlobalReferenceImageOk": "Создано глобальное эталонное изображение",
|
||||
"bboxOverlay": "Показать наложение ограничительной рамки",
|
||||
"bboxOverlay": "Показать наложение Bbox",
|
||||
"saveCanvasToGallery": "Сохранить холст в галерею",
|
||||
"pullBboxIntoReferenceImageOk": "рамка перенесена в эталонное изображение",
|
||||
"pullBboxIntoReferenceImageError": "Ошибка переноса рамки в эталонное изображение",
|
||||
"pullBboxIntoReferenceImageOk": "Bbox перенесен в эталонное изображение",
|
||||
"pullBboxIntoReferenceImageError": "Ошибка переноса BBox в эталонное изображение",
|
||||
"regionIsEmpty": "Выбранный регион пуст",
|
||||
"savedToGalleryOk": "Сохранено в галерею",
|
||||
"savedToGalleryError": "Ошибка сохранения в галерею",
|
||||
"pullBboxIntoLayerOk": "Рамка перенесена в слой",
|
||||
"pullBboxIntoLayerError": "Проблема с переносом рамки в слой",
|
||||
"newLayerFromImage": "Новый слой из изображения",
|
||||
"filter": {
|
||||
"lineart_anime_edge_detection": {
|
||||
"label": "Обнаружение краев Lineart Anime",
|
||||
"description": "Создает карту краев выбранного слоя с помощью модели обнаружения краев Lineart Anime."
|
||||
},
|
||||
"hed_edge_detection": {
|
||||
"scribble": "Штрих",
|
||||
"label": "обнаружение границ HED",
|
||||
"description": "Создает карту границ из выбранного слоя с использованием модели обнаружения границ HED."
|
||||
},
|
||||
"mlsd_detection": {
|
||||
"description": "Генерирует карту сегментов линий из выбранного слоя с помощью модели обнаружения сегментов линий MLSD.",
|
||||
"score_threshold": "Пороговый балл",
|
||||
"distance_threshold": "Порог расстояния",
|
||||
"label": "Обнаружение сегментов линии"
|
||||
},
|
||||
"canny_edge_detection": {
|
||||
"low_threshold": "Низкий порог",
|
||||
"high_threshold": "Высокий порог",
|
||||
"label": "Обнаружение краев",
|
||||
"description": "Создает карту краев выбранного слоя с помощью алгоритма обнаружения краев Canny."
|
||||
},
|
||||
"color_map": {
|
||||
"description": "Создайте цветовую карту из выбранного слоя.",
|
||||
"label": "Цветная карта",
|
||||
"tile_size": "Размер плитки"
|
||||
},
|
||||
"depth_anything_depth_estimation": {
|
||||
"model_size_base": "Базовая",
|
||||
"model_size_large": "Большая",
|
||||
"label": "Анализ глубины",
|
||||
"model_size_small": "Маленькая",
|
||||
"model_size_small_v2": "Маленькая v2",
|
||||
"description": "Создает карту глубины из выбранного слоя с использованием модели Depth Anything.",
|
||||
"model_size": "Размер модели"
|
||||
},
|
||||
"mediapipe_face_detection": {
|
||||
"min_confidence": "Минимальная уверенность",
|
||||
"label": "Распознавание лиц MediaPipe",
|
||||
"description": "Обнаруживает лица в выбранном слое с помощью модели обнаружения лиц MediaPipe.",
|
||||
"max_faces": "Максимум лиц"
|
||||
},
|
||||
"lineart_edge_detection": {
|
||||
"label": "Обнаружение краев Lineart",
|
||||
"description": "Создает карту краев выбранного слоя с помощью модели обнаружения краев Lineart.",
|
||||
"coarse": "Грубый"
|
||||
},
|
||||
"filterType": "Тип фильтра",
|
||||
"autoProcess": "Автообработка",
|
||||
"reset": "Сбросить",
|
||||
"content_shuffle": {
|
||||
"scale_factor": "Коэффициент",
|
||||
"label": "Перетасовка контента",
|
||||
"description": "Перемешивает содержимое выбранного слоя, аналогично эффекту «сжижения»."
|
||||
},
|
||||
"dw_openpose_detection": {
|
||||
"label": "Обнаружение DW Openpose",
|
||||
"draw_hands": "Рисовать руки",
|
||||
"description": "Обнаруживает позы человека в выбранном слое с помощью модели DW Openpose.",
|
||||
"draw_face": "Рисовать лицо",
|
||||
"draw_body": "Рисовать тело"
|
||||
},
|
||||
"normal_map": {
|
||||
"label": "Карта нормалей",
|
||||
"description": "Создает карту нормалей для выбранного слоя."
|
||||
},
|
||||
"spandrel_filter": {
|
||||
"model": "Модель",
|
||||
"label": "Модель img2img",
|
||||
"autoScale": "Авто масштабирование",
|
||||
"scale": "Целевой масштаб",
|
||||
"description": "Запустить модель изображения к изображению на выбранном слое.",
|
||||
"autoScaleDesc": "Выбранная модель будет работать до тех пор, пока не будет достигнут целевой масштаб."
|
||||
},
|
||||
"pidi_edge_detection": {
|
||||
"scribble": "Штрих",
|
||||
"description": "Генерирует карту краев из выбранного слоя с помощью модели обнаружения краев PiDiNet.",
|
||||
"label": "Обнаружение краев PiDiNet",
|
||||
"quantize_edges": "Квантизация краев"
|
||||
},
|
||||
"process": "Обработать",
|
||||
"apply": "Применить",
|
||||
"cancel": "Отменить",
|
||||
"filter": "Фильтр",
|
||||
"filters": "Фильтры"
|
||||
},
|
||||
"HUD": {
|
||||
"entityStatus": {
|
||||
"isHidden": "{{title}} скрыт",
|
||||
"isLocked": "{{title}} заблокирован",
|
||||
"isDisabled": "{{title}} отключен",
|
||||
"isEmpty": "{{title}} пуст",
|
||||
"isFiltering": "{{title}} фильтруется",
|
||||
"isTransforming": "{{title}} трансформируется"
|
||||
},
|
||||
"scaledBbox": "Масштабированная рамка",
|
||||
"bbox": "Ограничительная рамка"
|
||||
},
|
||||
"canvasContextMenu": {
|
||||
"saveBboxToGallery": "Сохранить рамку в галерею",
|
||||
"newGlobalReferenceImage": "Новое глобальное эталонное изображение",
|
||||
"bboxGroup": "Сохдать из рамки",
|
||||
"canvasGroup": "Холст",
|
||||
"newControlLayer": "Новый контрольный слой",
|
||||
"newRasterLayer": "Новый растровый слой",
|
||||
"saveToGalleryGroup": "Сохранить в галерею",
|
||||
"saveCanvasToGallery": "Сохранить холст в галерею",
|
||||
"cropCanvasToBbox": "Обрезать холст по рамке",
|
||||
"newRegionalReferenceImage": "Новое региональное эталонное изображение"
|
||||
},
|
||||
"fill": {
|
||||
"solid": "Сплошной",
|
||||
"fillStyle": "Стиль заполнения",
|
||||
"fillColor": "Цвет заполнения",
|
||||
"grid": "Сетка",
|
||||
"horizontal": "Горизонтальная",
|
||||
"diagonal": "Диагональная",
|
||||
"crosshatch": "Штриховка",
|
||||
"vertical": "Вертикальная"
|
||||
},
|
||||
"showHUD": "Показать HUD",
|
||||
"copyToClipboard": "Копировать в буфер обмена",
|
||||
"ipAdapterMethod": {
|
||||
"composition": "Только композиция",
|
||||
"style": "Только стиль",
|
||||
"ipAdapterMethod": "Метод IP адаптера",
|
||||
"full": "Полный"
|
||||
},
|
||||
"addReferenceImage": "Добавить $t(controlLayers.referenceImage)",
|
||||
"inpaintMask": "Маска перерисовки",
|
||||
"sendToGalleryDesc": "При нажатии кнопки Invoke создается изображение и сохраняется в вашей галерее.",
|
||||
"sendToCanvas": "Отправить на холст",
|
||||
"regionalGuidance_withCount_one": "$t(controlLayers.regionalGuidance)",
|
||||
"regionalGuidance_withCount_few": "Региональных точности",
|
||||
"regionalGuidance_withCount_many": "Региональных точностей",
|
||||
"controlLayer_withCount_one": "$t(controlLayers.controlLayer)",
|
||||
"controlLayer_withCount_few": "Контрольных слоя",
|
||||
"controlLayer_withCount_many": "Контрольных слоев",
|
||||
"newCanvasFromImage": "Новый холст из изображения",
|
||||
"inpaintMask_withCount_one": "$t(controlLayers.inpaintMask)",
|
||||
"inpaintMask_withCount_few": "Маски перерисовки",
|
||||
"inpaintMask_withCount_many": "Масок перерисовки",
|
||||
"globalReferenceImages_withCount_visible": "Глобальные эталонные изображения ({{count}})",
|
||||
"controlMode": {
|
||||
"prompt": "Запрос",
|
||||
"controlMode": "Режим контроля",
|
||||
"megaControl": "Мега контроль",
|
||||
"balanced": "Сбалансированный",
|
||||
"control": "Контроль"
|
||||
},
|
||||
"settings": {
|
||||
"isolatedPreview": "Изолированный предпросмотр",
|
||||
"isolatedTransformingPreview": "Изолированный предпросмотр преобразования",
|
||||
"invertBrushSizeScrollDirection": "Инвертировать прокрутку для размера кисти",
|
||||
"snapToGrid": {
|
||||
"label": "Привязка к сетке",
|
||||
"on": "Вкл",
|
||||
"off": "Выкл"
|
||||
},
|
||||
"isolatedFilteringPreview": "Изолированный предпросмотр фильтрации",
|
||||
"pressureSensitivity": "Чувствительность к давлению",
|
||||
"isolatedStagingPreview": "Изолированный предпросмотр на промежуточной стадии",
|
||||
"preserveMask": {
|
||||
"label": "Сохранить замаскированную область",
|
||||
"alert": "Сохранение замаскированной области"
|
||||
}
|
||||
},
|
||||
"stagingArea": {
|
||||
"discardAll": "Отбросить все",
|
||||
"discard": "Отбросить",
|
||||
"accept": "Принять",
|
||||
"previous": "Предыдущий",
|
||||
"next": "Следующий",
|
||||
"saveToGallery": "Сохранить в галерею",
|
||||
"showResultsOn": "Показать результаты",
|
||||
"showResultsOff": "Скрыть результаты"
|
||||
},
|
||||
"pullBboxIntoReferenceImage": "Поместить рамку в эталонное изображение",
|
||||
"enableAutoNegative": "Включить авто негатив",
|
||||
"maskFill": "Заполнение маски",
|
||||
"viewProgressInViewer": "Просматривайте прогресс и результаты в <Btn>Просмотрщике изображений</Btn>.",
|
||||
"convertToRasterLayer": "Конвертировать в растровый слой",
|
||||
"tool": {
|
||||
"move": "Двигать",
|
||||
"bbox": "Ограничительная рамка",
|
||||
"view": "Смотреть",
|
||||
"brush": "Кисть",
|
||||
"eraser": "Ластик",
|
||||
"rectangle": "Прямоугольник",
|
||||
"colorPicker": "Подборщик цветов"
|
||||
},
|
||||
"rasterLayer": "Растровый слой",
|
||||
"sendingToCanvas": "Постановка генераций на холст",
|
||||
"rasterLayers_withCount_visible": "Растровые слои ({{count}})",
|
||||
"regionalGuidance_withCount_hidden": "Региональная точность ({{count}} скрыто)",
|
||||
"enableTransparencyEffect": "Включить эффект прозрачности",
|
||||
"hidingType": "Скрыть {{type}}",
|
||||
"addRegionalGuidance": "Добавить $t(controlLayers.regionalGuidance)",
|
||||
"sendingToGallery": "Отправка генераций в галерею",
|
||||
"viewProgressOnCanvas": "Просматривайте прогресс и результаты этапов на <Btn>Холсте</Btn>.",
|
||||
"controlLayers_withCount_hidden": "Контрольные слои ({{count}} скрыто)",
|
||||
"rasterLayers_withCount_hidden": "Растровые слои ({{count}} скрыто)",
|
||||
"deleteSelected": "Удалить выбранное",
|
||||
"stagingOnCanvas": "Постановка изображений на",
|
||||
"pullBboxIntoLayer": "Поместить рамку в слой",
|
||||
"locked": "Заблокировано",
|
||||
"replaceLayer": "Заменить слой",
|
||||
"width": "Ширина",
|
||||
"controlLayer": "Слой управления",
|
||||
"addRasterLayer": "Добавить $t(controlLayers.rasterLayer)",
|
||||
"addControlLayer": "Добавить $t(controlLayers.controlLayer)",
|
||||
"addInpaintMask": "Добавить $t(controlLayers.inpaintMask)",
|
||||
"inpaintMasks_withCount_hidden": "Маски перерисовки ({{count}} скрыто)",
|
||||
"regionalGuidance_withCount_visible": "Региональная точность ({{count}})",
|
||||
"newGallerySessionDesc": "Это очистит холст и все настройки, кроме выбранной модели. Генерации будут отправлены в галерею.",
|
||||
"newCanvasSession": "Новая сессия холста",
|
||||
"newCanvasSessionDesc": "Это очистит холст и все настройки, кроме выбора модели. Генерации будут размещены на холсте.",
|
||||
"cropLayerToBbox": "Обрезать слой по ограничительной рамке",
|
||||
"clipToBbox": "Обрезка штрихов в рамке",
|
||||
"outputOnlyMaskedRegions": "Вывод только маскированных областей",
|
||||
"duplicate": "Дублировать",
|
||||
"inpaintMasks_withCount_visible": "Маски перерисовки ({{count}})",
|
||||
"layer_one": "Слой",
|
||||
"layer_few": "",
|
||||
"layer_many": "",
|
||||
"prompt": "Запрос",
|
||||
"negativePrompt": "Исключающий запрос",
|
||||
"beginEndStepPercentShort": "Начало/конец %",
|
||||
"transform": {
|
||||
"transform": "Трансформировать",
|
||||
"fitToBbox": "Вместить в рамку",
|
||||
"reset": "Сбросить",
|
||||
"apply": "Применить",
|
||||
"cancel": "Отменить"
|
||||
},
|
||||
"disableAutoNegative": "Отключить авто негатив",
|
||||
"deleteReferenceImage": "Удалить эталонное изображение",
|
||||
"controlLayers_withCount_visible": "Контрольные слои ({{count}})",
|
||||
"rasterLayer_withCount_one": "$t(controlLayers.rasterLayer)",
|
||||
"rasterLayer_withCount_few": "Растровых слоя",
|
||||
"rasterLayer_withCount_many": "Растровых слоев",
|
||||
"transparency": "Прозрачность",
|
||||
"weight": "Вес",
|
||||
"newGallerySession": "Новая сессия галереи",
|
||||
"sendToCanvasDesc": "Нажатие кнопки Invoke отображает вашу текущую работу на холсте.",
|
||||
"globalReferenceImages_withCount_hidden": "Глобальные эталонные изображения ({{count}} скрыто)",
|
||||
"convertToControlLayer": "Конвертировать в контрольный слой",
|
||||
"layer_withCount_one": "Слой ({{count}})",
|
||||
"layer_withCount_few": "Слои ({{count}})",
|
||||
"layer_withCount_many": "Слои ({{count}})",
|
||||
"disableTransparencyEffect": "Отключить эффект прозрачности",
|
||||
"showingType": "Показать {{type}}",
|
||||
"dynamicGrid": "Динамическая сетка",
|
||||
"logDebugInfo": "Писать отладочную информацию",
|
||||
"unlocked": "Разблокировано",
|
||||
"showProgressOnCanvas": "Показать прогресс на холсте",
|
||||
"globalReferenceImage_withCount_one": "$t(controlLayers.globalReferenceImage)",
|
||||
"globalReferenceImage_withCount_few": "Глобальных эталонных изображения",
|
||||
"globalReferenceImage_withCount_many": "Глобальных эталонных изображений",
|
||||
"regionalReferenceImage": "Региональное эталонное изображение",
|
||||
"globalReferenceImage": "Глобальное эталонное изображение",
|
||||
"sendToGallery": "Отправить в галерею",
|
||||
"referenceImage": "Эталонное изображение",
|
||||
"addGlobalReferenceImage": "Добавить $t(controlLayers.globalReferenceImage)"
|
||||
"pullBboxIntoLayerOk": "Bbox перенесен в слой",
|
||||
"pullBboxIntoLayerError": "Проблема с переносом BBox в слой"
|
||||
},
|
||||
"ui": {
|
||||
"tabs": {
|
||||
@@ -1924,8 +1441,7 @@
|
||||
"modelsTab": "$t(ui.tabs.models) $t(common.tab)",
|
||||
"queue": "Очередь",
|
||||
"upscaling": "Увеличение",
|
||||
"upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)",
|
||||
"gallery": "Галерея"
|
||||
"upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)"
|
||||
}
|
||||
},
|
||||
"upscaling": {
|
||||
@@ -1997,45 +1513,5 @@
|
||||
"professional": "Профессионал",
|
||||
"professionalUpsell": "Доступно в профессиональной версии Invoke. Нажмите здесь или посетите invoke.com/pricing для получения более подробной информации.",
|
||||
"shareAccess": "Поделиться доступом"
|
||||
},
|
||||
"system": {
|
||||
"logNamespaces": {
|
||||
"canvas": "Холст",
|
||||
"config": "Конфигурация",
|
||||
"generation": "Генерация",
|
||||
"workflows": "Рабочие процессы",
|
||||
"gallery": "Галерея",
|
||||
"models": "Модели",
|
||||
"logNamespaces": "Пространства имен логов",
|
||||
"events": "События",
|
||||
"system": "Система",
|
||||
"queue": "Очередь",
|
||||
"metadata": "Метаданные"
|
||||
},
|
||||
"enableLogging": "Включить логи",
|
||||
"logLevel": {
|
||||
"logLevel": "Уровень логов",
|
||||
"fatal": "Фатальное",
|
||||
"debug": "Отладка",
|
||||
"info": "Инфо",
|
||||
"warn": "Предупреждение",
|
||||
"error": "Ошибки",
|
||||
"trace": "Трассировка"
|
||||
}
|
||||
},
|
||||
"whatsNew": {
|
||||
"canvasV2Announcement": {
|
||||
"newLayerTypes": "Новые типы слоев для еще большего контроля",
|
||||
"readReleaseNotes": "Прочитать информацию о выпуске",
|
||||
"watchReleaseVideo": "Смотреть видео о выпуске",
|
||||
"fluxSupport": "Поддержка семейства моделей Flux",
|
||||
"newCanvas": "Новый мощный холст управления",
|
||||
"watchUiUpdatesOverview": "Обзор обновлений пользовательского интерфейса"
|
||||
},
|
||||
"whatsNewInInvoke": "Что нового в Invoke"
|
||||
},
|
||||
"newUserExperience": {
|
||||
"toGetStarted": "Чтобы начать работу, введите в поле запрос и нажмите <StrongComponent>Invoke</StrongComponent>, чтобы сгенерировать первое изображение. Вы можете сохранить изображения непосредственно в <StrongComponent>Галерею</StrongComponent> или отредактировать их на <StrongComponent>Холсте</StrongComponent>.",
|
||||
"gettingStartedSeries": "Хотите получить больше рекомендаций? Ознакомьтесь с нашей серией <LinkComponent>Getting Started Series</LinkComponent> для получения советов по раскрытию всего потенциала Invoke Studio."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,8 +415,7 @@
|
||||
"resetUI": "$t(accessibility.reset) UI",
|
||||
"createIssue": "创建问题",
|
||||
"about": "关于",
|
||||
"submitSupportTicket": "提交支持工单",
|
||||
"toggleRightPanel": "切换右侧面板(G)"
|
||||
"submitSupportTicket": "提交支持工单"
|
||||
},
|
||||
"nodes": {
|
||||
"zoomInNodes": "放大",
|
||||
|
||||
@@ -21,16 +21,12 @@ import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageMo
|
||||
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
|
||||
import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
|
||||
import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterModelsToast';
|
||||
import { ShareWorkflowModal } from 'features/nodes/components/sidePanel/WorkflowListMenu/ShareWorkflowModal';
|
||||
import { ClearQueueConfirmationsAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
|
||||
import { DeleteStylePresetDialog } from 'features/stylePresets/components/DeleteStylePresetDialog';
|
||||
import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal';
|
||||
import RefreshAfterResetModal from 'features/system/components/SettingsModal/RefreshAfterResetModal';
|
||||
import { configChanged } from 'features/system/store/configSlice';
|
||||
import { selectLanguage } from 'features/system/store/systemSelectors';
|
||||
import { AppContent } from 'features/ui/components/AppContent';
|
||||
import { DeleteWorkflowDialog } from 'features/workflowLibrary/components/DeleteLibraryWorkflowConfirmationAlertDialog';
|
||||
import { NewWorkflowConfirmationAlertDialog } from 'features/workflowLibrary/components/NewWorkflowConfirmationAlertDialog';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import i18n from 'i18n';
|
||||
import { size } from 'lodash-es';
|
||||
@@ -111,10 +107,6 @@ const App = ({ config = DEFAULT_CONFIG, studioInitAction }: Props) => {
|
||||
<DynamicPromptsModal />
|
||||
<StylePresetModal />
|
||||
<ClearQueueConfirmationsAlertDialog />
|
||||
<NewWorkflowConfirmationAlertDialog />
|
||||
<DeleteStylePresetDialog />
|
||||
<DeleteWorkflowDialog />
|
||||
<ShareWorkflowModal />
|
||||
<RefreshAfterResetModal />
|
||||
<DeleteBoardModal />
|
||||
<GlobalImageHotkeys />
|
||||
|
||||
@@ -65,10 +65,10 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex gap={4}>
|
||||
<Button leftIcon={<PiArrowCounterClockwiseBold />} onClick={resetErrorBoundary}>
|
||||
<Button leftIcon={<PiArrowCounterClockwiseBold />} onPointerUp={resetErrorBoundary}>
|
||||
{t('accessibility.resetUI')}
|
||||
</Button>
|
||||
<Button leftIcon={<PiCopyBold />} onClick={handleCopy}>
|
||||
<Button leftIcon={<PiCopyBold />} onPointerUp={handleCopy}>
|
||||
{t('common.copyError')}
|
||||
</Button>
|
||||
<Link href={url} isExternal>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useIsRegionFocused } from 'common/hooks/focus';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { useImageActions } from 'features/gallery/hooks/useImageActions';
|
||||
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
||||
@@ -12,7 +11,6 @@ import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
export const GlobalImageHotkeys = memo(() => {
|
||||
useAssertSingleton('GlobalImageHotkeys');
|
||||
const lastSelectedImage = useAppSelector(selectLastSelectedImage);
|
||||
const { currentData: imageDTO } = useGetImageDTOQuery(lastSelectedImage?.image_name ?? skipToken);
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ import { imageDTOToImageObject } from 'features/controlLayers/store/util';
|
||||
import { $imageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||
import { sentImageToCanvas } from 'features/gallery/store/actions';
|
||||
import { parseAndRecallAllMetadata } from 'features/metadata/util/handlers';
|
||||
import { $isWorkflowListMenuIsOpen } from 'features/nodes/store/workflowListMenu';
|
||||
import { $isStylePresetsMenuOpen, activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||
import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow';
|
||||
import { $workflowLibraryModal } from 'features/workflowLibrary/store/isWorkflowLibraryModalOpen';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getImageDTO, getImageMetadata } from 'services/api/endpoints/images';
|
||||
@@ -160,7 +160,7 @@ export const useStudioInitAction = (action?: StudioInitAction) => {
|
||||
case 'viewAllWorkflows':
|
||||
// Go to the workflows tab and open the workflow library modal
|
||||
store.dispatch(setActiveTab('workflows'));
|
||||
$isWorkflowListMenuIsOpen.set(true);
|
||||
$workflowLibraryModal.set(true);
|
||||
break;
|
||||
case 'viewAllStylePresets':
|
||||
// Go to the canvas tab and open the style presets menu
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export const STORAGE_PREFIX = '@@invokeai-';
|
||||
export const EMPTY_ARRAY = [];
|
||||
/** @knipignore */
|
||||
export const EMPTY_OBJECT = {};
|
||||
|
||||
@@ -166,8 +166,8 @@ export const createStore = (uniqueStoreKey?: string, persist = true) =>
|
||||
reducer: rememberedRootReducer,
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
serializableCheck: import.meta.env.MODE === 'development',
|
||||
immutableCheck: import.meta.env.MODE === 'development',
|
||||
serializableCheck: false, // import.meta.env.MODE === 'development',
|
||||
immutableCheck: false, // import.meta.env.MODE === 'development',
|
||||
})
|
||||
.concat(api.middleware)
|
||||
.concat(dynamicMiddlewares)
|
||||
|
||||
@@ -98,8 +98,8 @@ const RgbColorPicker = (props: Props) => {
|
||||
export default memo(RgbColorPicker);
|
||||
|
||||
const ColorSwatch = ({ color, onChange }: { color: RgbColor; onChange: (color: RgbColor) => void }) => {
|
||||
const onClick = useCallback(() => {
|
||||
const onPointerUp = useCallback(() => {
|
||||
onChange(color);
|
||||
}, [color, onChange]);
|
||||
return <Box role="button" onClick={onClick} h={8} w={8} bg={rgbColorToString(color)} borderRadius="base" />;
|
||||
return <Box role="button" onPointerUp={onPointerUp} h={8} w={8} bg={rgbColorToString(color)} borderRadius="base" />;
|
||||
};
|
||||
|
||||
@@ -109,8 +109,8 @@ const RgbaColorPicker = (props: Props) => {
|
||||
export default memo(RgbaColorPicker);
|
||||
|
||||
const ColorSwatch = ({ color, onChange }: { color: RgbaColor; onChange: (color: RgbaColor) => void }) => {
|
||||
const onClick = useCallback(() => {
|
||||
const onPointerUp = useCallback(() => {
|
||||
onChange(color);
|
||||
}, [color, onChange]);
|
||||
return <Box role="button" onClick={onClick} h={8} w={8} bg={rgbaColorToString(color)} borderRadius="base" />;
|
||||
return <Box role="button" onPointerUp={onPointerUp} h={8} w={8} bg={rgbaColorToString(color)} borderRadius="base" />;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@ import { IAILoadingImageFallback, IAINoContentFallback } from 'common/components
|
||||
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
||||
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
|
||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import ImageContextMenu from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
|
||||
import type { MouseEvent, ReactElement, ReactNode, SyntheticEvent } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { PiImageBold, PiUploadSimpleBold } from 'react-icons/pi';
|
||||
@@ -55,7 +54,7 @@ type IAIDndImageProps = FlexProps & {
|
||||
imageDTO: ImageDTO | undefined;
|
||||
onError?: (event: SyntheticEvent<HTMLImageElement>) => void;
|
||||
onLoad?: (event: SyntheticEvent<HTMLImageElement>) => void;
|
||||
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||
onPointerUp?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||
withMetadataOverlay?: boolean;
|
||||
isDragDisabled?: boolean;
|
||||
isDropDisabled?: boolean;
|
||||
@@ -82,7 +81,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
||||
const {
|
||||
imageDTO,
|
||||
onError,
|
||||
onClick,
|
||||
onPointerUp,
|
||||
withMetadataOverlay = false,
|
||||
isDropDisabled = false,
|
||||
isDragDisabled = false,
|
||||
@@ -125,36 +124,6 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
||||
[onMouseOut]
|
||||
);
|
||||
|
||||
const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
|
||||
postUploadAction,
|
||||
isDisabled: isUploadDisabled,
|
||||
});
|
||||
|
||||
const uploadButtonStyles = useMemo<SystemStyleObject>(() => {
|
||||
const styles: SystemStyleObject = {
|
||||
minH: minSize,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 'base',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: '0.1s',
|
||||
color: 'base.500',
|
||||
};
|
||||
if (!isUploadDisabled) {
|
||||
Object.assign(styles, {
|
||||
cursor: 'pointer',
|
||||
bg: 'base.700',
|
||||
_hover: {
|
||||
bg: 'base.650',
|
||||
color: 'base.300',
|
||||
},
|
||||
});
|
||||
}
|
||||
return styles;
|
||||
}, [isUploadDisabled, minSize]);
|
||||
|
||||
const openInNewTab = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
if (!imageDTO) {
|
||||
@@ -169,10 +138,10 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<ImageContextMenu imageDTO={imageDTO}>
|
||||
{(ref) => (
|
||||
// <ImageContextMenu imageDTO={imageDTO}>
|
||||
// {(ref) => (
|
||||
<Flex
|
||||
ref={ref}
|
||||
// ref={ref}
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
width="full"
|
||||
@@ -216,28 +185,77 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
||||
</Flex>
|
||||
)}
|
||||
{!imageDTO && !isUploadDisabled && (
|
||||
<>
|
||||
<Flex sx={uploadButtonStyles} {...getUploadButtonProps()}>
|
||||
<input {...getUploadInputProps()} />
|
||||
{uploadElement}
|
||||
</Flex>
|
||||
</>
|
||||
<UploadButton
|
||||
isUploadDisabled={isUploadDisabled}
|
||||
postUploadAction={postUploadAction}
|
||||
uploadElement={uploadElement}
|
||||
minSize={minSize}
|
||||
/>
|
||||
)}
|
||||
{!imageDTO && isUploadDisabled && noContentFallback}
|
||||
{imageDTO && !isDragDisabled && (
|
||||
<IAIDraggable
|
||||
data={draggableData}
|
||||
disabled={isDragDisabled || !imageDTO}
|
||||
onClick={onClick}
|
||||
onPointerUp={onPointerUp}
|
||||
onAuxClick={openInNewTab}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
{!isDropDisabled && <IAIDroppable data={droppableData} disabled={isDropDisabled} dropLabel={dropLabel} />}
|
||||
</Flex>
|
||||
)}
|
||||
</ImageContextMenu>
|
||||
// )}
|
||||
// </ImageContextMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(IAIDndImage);
|
||||
|
||||
const UploadButton = ({
|
||||
isUploadDisabled,
|
||||
postUploadAction,
|
||||
uploadElement,
|
||||
minSize,
|
||||
}: {
|
||||
isUploadDisabled: boolean;
|
||||
postUploadAction?: PostUploadAction;
|
||||
uploadElement: ReactNode;
|
||||
minSize: number;
|
||||
}) => {
|
||||
const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
|
||||
postUploadAction,
|
||||
isDisabled: isUploadDisabled,
|
||||
});
|
||||
|
||||
const uploadButtonStyles = useMemo<SystemStyleObject>(() => {
|
||||
const styles: SystemStyleObject = {
|
||||
minH: minSize,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 'base',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: '0.1s',
|
||||
color: 'base.500',
|
||||
};
|
||||
if (!isUploadDisabled) {
|
||||
Object.assign(styles, {
|
||||
cursor: 'pointer',
|
||||
bg: 'base.700',
|
||||
_hover: {
|
||||
bg: 'base.650',
|
||||
color: 'base.300',
|
||||
},
|
||||
});
|
||||
}
|
||||
return styles;
|
||||
}, [isUploadDisabled, minSize]);
|
||||
|
||||
return (
|
||||
<Flex sx={uploadButtonStyles} {...getUploadButtonProps()}>
|
||||
<input {...getUploadInputProps()} />
|
||||
{uploadElement}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,17 +16,17 @@ const sx: SystemStyleObject = {
|
||||
},
|
||||
};
|
||||
|
||||
type Props = Omit<IconButtonProps, 'aria-label' | 'onClick' | 'tooltip'> & {
|
||||
onClick: (event: MouseEvent<HTMLButtonElement>) => void;
|
||||
type Props = Omit<IconButtonProps, 'aria-label' | 'onPointerUp' | 'tooltip'> & {
|
||||
onPointerUp: (event: MouseEvent<HTMLButtonElement>) => void;
|
||||
tooltip: string;
|
||||
};
|
||||
|
||||
const IAIDndImageIcon = (props: Props) => {
|
||||
const { onClick, tooltip, icon, ...rest } = props;
|
||||
const { onPointerUp, tooltip, icon, ...rest } = props;
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
onClick={onClick}
|
||||
onPointerUp={onPointerUp}
|
||||
aria-label={tooltip}
|
||||
icon={icon}
|
||||
variant="link"
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import type { MenuItemProps } from '@invoke-ai/ui-library';
|
||||
import { Flex, MenuItem, Tooltip } from '@invoke-ai/ui-library';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
type Props = MenuItemProps & {
|
||||
tooltip?: ReactNode;
|
||||
icon: ReactNode;
|
||||
};
|
||||
|
||||
export const IconMenuItem = ({ tooltip, icon, ...props }: Props) => {
|
||||
return (
|
||||
<Tooltip label={tooltip} placement="top" gutter={12}>
|
||||
<MenuItem
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
w="min-content"
|
||||
aspectRatio="1"
|
||||
borderRadius="base"
|
||||
{...props}
|
||||
>
|
||||
{icon}
|
||||
</MenuItem>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const IconMenuItemGroup = ({ children }: { children: ReactNode }) => {
|
||||
return <Flex gap={2}>{children}</Flex>;
|
||||
};
|
||||
@@ -95,14 +95,14 @@ const Content = ({ data, feature, hideDisable }: ContentProps) => {
|
||||
[feature, t]
|
||||
);
|
||||
|
||||
const onClickLearnMore = useCallback(() => {
|
||||
const onPointerUpLearnMore = useCallback(() => {
|
||||
if (!data?.href) {
|
||||
return;
|
||||
}
|
||||
window.open(data.href);
|
||||
}, [data?.href]);
|
||||
|
||||
const onClickDontShowMeThese = useCallback(() => {
|
||||
const onPointerUpDontShowMeThese = useCallback(() => {
|
||||
dispatch(setShouldEnableInformationalPopovers(false));
|
||||
toast({
|
||||
title: t('settings.informationalPopoversDisabled'),
|
||||
@@ -135,13 +135,13 @@ const Content = ({ data, feature, hideDisable }: ContentProps) => {
|
||||
<Divider />
|
||||
<Flex alignItems="center" justifyContent="space-between" w="full">
|
||||
{!hideDisable && (
|
||||
<Button onClick={onClickDontShowMeThese} variant="link" size="sm">
|
||||
<Button onPointerUp={onPointerUpDontShowMeThese} variant="link" size="sm">
|
||||
{t('common.dontShowMeThese')}
|
||||
</Button>
|
||||
)}
|
||||
<Spacer />
|
||||
{data?.href && (
|
||||
<Button onClick={onClickLearnMore} leftIcon={<PiArrowSquareOutBold />} variant="link" size="sm">
|
||||
<Button onPointerUp={onPointerUpLearnMore} leftIcon={<PiArrowSquareOutBold />} variant="link" size="sm">
|
||||
{t('common.learnMore') ?? heading}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const buildUseBoolean = (initialValue: boolean): [() => UseBoolean, Writa
|
||||
* Hook to manage a boolean state. Use this for a local boolean state.
|
||||
* @param initialValue Initial value of the boolean
|
||||
*/
|
||||
export const useBoolean = (initialValue: boolean): UseBoolean => {
|
||||
export const useBoolean = (initialValue: boolean) => {
|
||||
const [isTrue, set] = useState(initialValue);
|
||||
|
||||
const setTrue = useCallback(() => {
|
||||
@@ -72,82 +72,3 @@ export const useBoolean = (initialValue: boolean): UseBoolean => {
|
||||
toggle,
|
||||
};
|
||||
};
|
||||
|
||||
type UseDisclosure = {
|
||||
isOpen: boolean;
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
set: (isOpen: boolean) => void;
|
||||
toggle: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the same as `buildUseBoolean`, but the method names are more descriptive,
|
||||
* serving the semantics of a disclosure state.
|
||||
*
|
||||
* Creates a hook to manage a boolean state. The boolean is stored in a nanostores atom.
|
||||
* Returns a tuple containing the hook and the atom. Use this for global boolean state.
|
||||
*
|
||||
* @param defaultIsOpen Initial state of the disclosure
|
||||
*/
|
||||
export const buildUseDisclosure = (defaultIsOpen: boolean): [() => UseDisclosure, WritableAtom<boolean>] => {
|
||||
const $isOpen = atom(defaultIsOpen);
|
||||
|
||||
const open = () => {
|
||||
$isOpen.set(true);
|
||||
};
|
||||
const close = () => {
|
||||
$isOpen.set(false);
|
||||
};
|
||||
const set = (isOpen: boolean) => {
|
||||
$isOpen.set(isOpen);
|
||||
};
|
||||
const toggle = () => {
|
||||
$isOpen.set(!$isOpen.get());
|
||||
};
|
||||
|
||||
const useDisclosure = () => {
|
||||
const isOpen = useStore($isOpen);
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
open,
|
||||
close,
|
||||
set,
|
||||
toggle,
|
||||
};
|
||||
};
|
||||
|
||||
return [useDisclosure, $isOpen] as const;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the same as `useBoolean`, but the method names are more descriptive,
|
||||
* serving the semantics of a disclosure state.
|
||||
*
|
||||
* Hook to manage a boolean state. Use this for a local boolean state.
|
||||
* @param defaultIsOpen Initial state of the disclosure
|
||||
*
|
||||
* @knipignore
|
||||
*/
|
||||
export const useDisclosure = (defaultIsOpen: boolean): UseDisclosure => {
|
||||
const [isOpen, set] = useState(defaultIsOpen);
|
||||
|
||||
const open = useCallback(() => {
|
||||
set(true);
|
||||
}, [set]);
|
||||
const close = useCallback(() => {
|
||||
set(false);
|
||||
}, [set]);
|
||||
const toggle = useCallback(() => {
|
||||
set((val) => !val);
|
||||
}, [set]);
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
open,
|
||||
close,
|
||||
set,
|
||||
toggle,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Combobox, ConfirmationAlertDialog, Flex, FormControl, Text } from '@inv
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import {
|
||||
changeBoardReset,
|
||||
isModalOpenChanged,
|
||||
@@ -26,7 +25,6 @@ const selectIsModalOpen = createSelector(
|
||||
);
|
||||
|
||||
const ChangeBoardModal = () => {
|
||||
useAssertSingleton('ChangeBoardModal');
|
||||
const dispatch = useAppDispatch();
|
||||
const [selectedBoard, setSelectedBoard] = useState<string | null>();
|
||||
const queryArgs = useAppSelector(selectListBoardsQueryArgs);
|
||||
|
||||
@@ -33,7 +33,7 @@ export const CanvasAddEntityButtons = memo(() => {
|
||||
variant="ghost"
|
||||
justifyContent="flex-start"
|
||||
leftIcon={<PiPlusBold />}
|
||||
onClick={addGlobalReferenceImage}
|
||||
onPointerUp={addGlobalReferenceImage}
|
||||
isDisabled={isFLUX}
|
||||
>
|
||||
{t('controlLayers.globalReferenceImage')}
|
||||
@@ -46,7 +46,7 @@ export const CanvasAddEntityButtons = memo(() => {
|
||||
variant="ghost"
|
||||
justifyContent="flex-start"
|
||||
leftIcon={<PiPlusBold />}
|
||||
onClick={addInpaintMask}
|
||||
onPointerUp={addInpaintMask}
|
||||
>
|
||||
{t('controlLayers.inpaintMask')}
|
||||
</Button>
|
||||
@@ -55,7 +55,7 @@ export const CanvasAddEntityButtons = memo(() => {
|
||||
variant="ghost"
|
||||
justifyContent="flex-start"
|
||||
leftIcon={<PiPlusBold />}
|
||||
onClick={addRegionalGuidance}
|
||||
onPointerUp={addRegionalGuidance}
|
||||
isDisabled={isFLUX}
|
||||
>
|
||||
{t('controlLayers.regionalGuidance')}
|
||||
@@ -65,7 +65,7 @@ export const CanvasAddEntityButtons = memo(() => {
|
||||
variant="ghost"
|
||||
justifyContent="flex-start"
|
||||
leftIcon={<PiPlusBold />}
|
||||
onClick={addRegionalReferenceImage}
|
||||
onPointerUp={addRegionalReferenceImage}
|
||||
isDisabled={isFLUX}
|
||||
>
|
||||
{t('controlLayers.regionalReferenceImage')}
|
||||
@@ -79,7 +79,7 @@ export const CanvasAddEntityButtons = memo(() => {
|
||||
variant="ghost"
|
||||
justifyContent="flex-start"
|
||||
leftIcon={<PiPlusBold />}
|
||||
onClick={addControlLayer}
|
||||
onPointerUp={addControlLayer}
|
||||
isDisabled={isFLUX}
|
||||
>
|
||||
{t('controlLayers.controlLayer')}
|
||||
@@ -89,7 +89,7 @@ export const CanvasAddEntityButtons = memo(() => {
|
||||
variant="ghost"
|
||||
justifyContent="flex-start"
|
||||
leftIcon={<PiPlusBold />}
|
||||
onClick={addRasterLayer}
|
||||
onPointerUp={addRasterLayer}
|
||||
>
|
||||
{t('controlLayers.rasterLayer')}
|
||||
</Button>
|
||||
|
||||
@@ -17,12 +17,12 @@ import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
const ActivateImageViewerButton = (props: PropsWithChildren) => {
|
||||
const imageViewer = useImageViewer();
|
||||
const onClick = useCallback(() => {
|
||||
const onPointerUp = useCallback(() => {
|
||||
imageViewer.open();
|
||||
selectCanvasRightPanelGalleryTab();
|
||||
}, [imageViewer]);
|
||||
return (
|
||||
<Button onClick={onClick} size="sm" variant="link" color="base.50">
|
||||
<Button onPointerUp={onPointerUp} size="sm" variant="link" color="base.50">
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
@@ -58,13 +58,13 @@ export const CanvasAlertsSendingToGallery = () => {
|
||||
const ActivateCanvasButton = (props: PropsWithChildren) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const imageViewer = useImageViewer();
|
||||
const onClick = useCallback(() => {
|
||||
const onPointerUp = useCallback(() => {
|
||||
dispatch(setActiveTab('canvas'));
|
||||
selectCanvasRightPanelLayersTab();
|
||||
imageViewer.close();
|
||||
}, [dispatch, imageViewer]);
|
||||
return (
|
||||
<Button onClick={onClick} size="sm" variant="link" color="base.50">
|
||||
<Button onPointerUp={onPointerUp} size="sm" variant="link" color="base.50">
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -30,24 +30,24 @@ export const CanvasContextMenuGlobalMenuItems = memo(() => {
|
||||
<CanvasContextMenuItemsCropCanvasToBbox />
|
||||
</MenuGroup>
|
||||
<MenuGroup title={t('controlLayers.canvasContextMenu.saveToGalleryGroup')}>
|
||||
<MenuItem icon={<PiFloppyDiskBold />} isDisabled={isBusy} onClick={saveCanvasToGallery}>
|
||||
<MenuItem icon={<PiFloppyDiskBold />} isDisabled={isBusy} onPointerUp={saveCanvasToGallery}>
|
||||
{t('controlLayers.canvasContextMenu.saveCanvasToGallery')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiFloppyDiskBold />} isDisabled={isBusy} onClick={saveBboxToGallery}>
|
||||
<MenuItem icon={<PiFloppyDiskBold />} isDisabled={isBusy} onPointerUp={saveBboxToGallery}>
|
||||
{t('controlLayers.canvasContextMenu.saveBboxToGallery')}
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
<MenuGroup title={t('controlLayers.canvasContextMenu.bboxGroup')}>
|
||||
<MenuItem icon={<NewLayerIcon />} isDisabled={isBusy} onClick={newGlobalReferenceImageFromBbox}>
|
||||
<MenuItem icon={<NewLayerIcon />} isDisabled={isBusy} onPointerUp={newGlobalReferenceImageFromBbox}>
|
||||
{t('controlLayers.canvasContextMenu.newGlobalReferenceImage')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<NewLayerIcon />} isDisabled={isBusy} onClick={newRegionalReferenceImageFromBbox}>
|
||||
<MenuItem icon={<NewLayerIcon />} isDisabled={isBusy} onPointerUp={newRegionalReferenceImageFromBbox}>
|
||||
{t('controlLayers.canvasContextMenu.newRegionalReferenceImage')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<NewLayerIcon />} isDisabled={isBusy} onClick={newControlLayerFromBbox}>
|
||||
<MenuItem icon={<NewLayerIcon />} isDisabled={isBusy} onPointerUp={newControlLayerFromBbox}>
|
||||
{t('controlLayers.canvasContextMenu.newControlLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<NewLayerIcon />} isDisabled={isBusy} onClick={newRasterLayerFromBbox}>
|
||||
<MenuItem icon={<NewLayerIcon />} isDisabled={isBusy} onPointerUp={newRasterLayerFromBbox}>
|
||||
{t('controlLayers.canvasContextMenu.newRasterLayer')}
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
|
||||
@@ -17,7 +17,7 @@ export const CanvasContextMenuItemsCropCanvasToBbox = memo(() => {
|
||||
}, [canvasManager]);
|
||||
|
||||
return (
|
||||
<MenuItem icon={<PiCropBold />} isDisabled={isBusy} onClick={cropCanvasToBbox}>
|
||||
<MenuItem icon={<PiCropBold />} isDisabled={isBusy} onPointerUp={cropCanvasToBbox}>
|
||||
{t('controlLayers.canvasContextMenu.cropCanvasToBbox')}
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,6 @@ import type {
|
||||
} from 'features/dnd/types';
|
||||
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const addRasterLayerFromImageDropData: AddRasterLayerFromImageDropData = {
|
||||
id: 'add-raster-layer-from-image-drop-data',
|
||||
@@ -31,7 +30,6 @@ const addGlobalReferenceImageFromImageDropData: AddGlobalReferenceImageFromImage
|
||||
};
|
||||
|
||||
export const CanvasDropArea = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const imageViewer = useImageViewer();
|
||||
|
||||
if (imageViewer.isOpen) {
|
||||
@@ -51,28 +49,16 @@ export const CanvasDropArea = memo(() => {
|
||||
pointerEvents="none"
|
||||
>
|
||||
<GridItem position="relative">
|
||||
<IAIDroppable
|
||||
dropLabel={t('controlLayers.canvasContextMenu.newRasterLayer')}
|
||||
data={addRasterLayerFromImageDropData}
|
||||
/>
|
||||
<IAIDroppable dropLabel="New Raster Layer" data={addRasterLayerFromImageDropData} />
|
||||
</GridItem>
|
||||
<GridItem position="relative">
|
||||
<IAIDroppable
|
||||
dropLabel={t('controlLayers.canvasContextMenu.newControlLayer')}
|
||||
data={addControlLayerFromImageDropData}
|
||||
/>
|
||||
<IAIDroppable dropLabel="New Control Layer" data={addControlLayerFromImageDropData} />
|
||||
</GridItem>
|
||||
<GridItem position="relative">
|
||||
<IAIDroppable
|
||||
dropLabel={t('controlLayers.canvasContextMenu.newRegionalReferenceImage')}
|
||||
data={addRegionalReferenceImageFromImageDropData}
|
||||
/>
|
||||
<IAIDroppable dropLabel="New Regional Reference Image" data={addRegionalReferenceImageFromImageDropData} />
|
||||
</GridItem>
|
||||
<GridItem position="relative">
|
||||
<IAIDroppable
|
||||
dropLabel={t('controlLayers.canvasContextMenu.newGlobalReferenceImage')}
|
||||
data={addGlobalReferenceImageFromImageDropData}
|
||||
/>
|
||||
<IAIDroppable dropLabel="New Global Reference Image" data={addGlobalReferenceImageFromImageDropData} />
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
@@ -40,26 +40,26 @@ export const EntityListGlobalActionBarAddLayerMenu = memo(() => {
|
||||
/>
|
||||
<MenuList>
|
||||
<MenuGroup title={t('controlLayers.global')}>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addGlobalReferenceImage} isDisabled={isFLUX}>
|
||||
<MenuItem icon={<PiPlusBold />} onPointerUp={addGlobalReferenceImage} isDisabled={isFLUX}>
|
||||
{t('controlLayers.globalReferenceImage')}
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
<MenuGroup title={t('controlLayers.regional')}>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addInpaintMask}>
|
||||
<MenuItem icon={<PiPlusBold />} onPointerUp={addInpaintMask}>
|
||||
{t('controlLayers.inpaintMask')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addRegionalGuidance} isDisabled={isFLUX}>
|
||||
<MenuItem icon={<PiPlusBold />} onPointerUp={addRegionalGuidance} isDisabled={isFLUX}>
|
||||
{t('controlLayers.regionalGuidance')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addRegionalReferenceImage} isDisabled={isFLUX}>
|
||||
<MenuItem icon={<PiPlusBold />} onPointerUp={addRegionalReferenceImage} isDisabled={isFLUX}>
|
||||
{t('controlLayers.regionalReferenceImage')}
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
<MenuGroup title={t('controlLayers.layer_other')}>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addControlLayer} isDisabled={isFLUX}>
|
||||
<MenuItem icon={<PiPlusBold />} onPointerUp={addControlLayer} isDisabled={isFLUX}>
|
||||
{t('controlLayers.controlLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addRasterLayer}>
|
||||
<MenuItem icon={<PiPlusBold />} onPointerUp={addRasterLayer}>
|
||||
{t('controlLayers.rasterLayer')}
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
|
||||
@@ -12,7 +12,7 @@ export const EntityListSelectedEntityActionBarDuplicateButton = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isBusy = useCanvasIsBusy();
|
||||
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
||||
const onClick = useCallback(() => {
|
||||
const onPointerUp = useCallback(() => {
|
||||
if (!selectedEntityIdentifier) {
|
||||
return;
|
||||
}
|
||||
@@ -21,7 +21,7 @@ export const EntityListSelectedEntityActionBarDuplicateButton = memo(() => {
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
onClick={onClick}
|
||||
onPointerUp={onPointerUp}
|
||||
isDisabled={!selectedEntityIdentifier || isBusy}
|
||||
size="sm"
|
||||
variant="link"
|
||||
|
||||
@@ -22,7 +22,7 @@ export const EntityListSelectedEntityActionBarFilterButton = memo(() => {
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
onClick={filter.start}
|
||||
onPointerUp={filter.start}
|
||||
isDisabled={filter.isDisabled}
|
||||
size="sm"
|
||||
variant="link"
|
||||
|
||||
@@ -157,7 +157,7 @@ export const EntityListSelectedEntityActionBarOpacity = memo(() => {
|
||||
clampValueOnBlur={false}
|
||||
variant="outline"
|
||||
>
|
||||
<NumberInputField paddingInlineEnd={7} _focusVisible={{ zIndex: 0 }} title="" />
|
||||
<NumberInputField paddingInlineEnd={7} _focusVisible={{ zIndex: 0 }} />
|
||||
<PopoverTrigger>
|
||||
<IconButton
|
||||
aria-label="open-slider"
|
||||
|
||||
@@ -15,7 +15,7 @@ export const EntityListSelectedEntityActionBarSaveToAssetsButton = memo(() => {
|
||||
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
||||
const adapter = useEntityAdapterSafe(selectedEntityIdentifier);
|
||||
const saveLayerToAssets = useSaveLayerToAssets();
|
||||
const onClick = useCallback(() => {
|
||||
const onPointerUp = useCallback(() => {
|
||||
saveLayerToAssets(adapter);
|
||||
}, [saveLayerToAssets, adapter]);
|
||||
|
||||
@@ -29,7 +29,7 @@ export const EntityListSelectedEntityActionBarSaveToAssetsButton = memo(() => {
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
onClick={onClick}
|
||||
onPointerUp={onPointerUp}
|
||||
isDisabled={!selectedEntityIdentifier || isBusy}
|
||||
size="sm"
|
||||
variant="link"
|
||||
|
||||
@@ -22,7 +22,7 @@ export const EntityListSelectedEntityActionBarTransformButton = memo(() => {
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
onClick={transform.start}
|
||||
onPointerUp={transform.start}
|
||||
isDisabled={transform.isDisabled}
|
||||
size="sm"
|
||||
variant="link"
|
||||
|
||||
@@ -20,7 +20,7 @@ export const CanvasRightPanel = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const tabIndex = useStore($canvasRightPanelTabIndex);
|
||||
const imageViewer = useImageViewer();
|
||||
const onClickViewerToggleButton = useCallback(() => {
|
||||
const onPointerUpViewerToggleButton = useCallback(() => {
|
||||
if ($canvasRightPanelTabIndex.get() !== 1) {
|
||||
$canvasRightPanelTabIndex.set(1);
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export const CanvasRightPanel = memo(() => {
|
||||
<TabList alignItems="center">
|
||||
<PanelTabs />
|
||||
<Spacer />
|
||||
<Button size="sm" variant="ghost" onClick={onClickViewerToggleButton}>
|
||||
<Button size="sm" variant="ghost" onPointerUp={onPointerUpViewerToggleButton}>
|
||||
{imageViewer.isOpen ? t('gallery.closeViewer') : t('gallery.openViewer')}
|
||||
</Button>
|
||||
</TabList>
|
||||
@@ -99,7 +99,6 @@ const PanelTabs = memo(() => {
|
||||
<Box as="span" w="full">
|
||||
{layersTabLabel}
|
||||
</Box>
|
||||
{dndCtx.active && <Box position="absolute" top={0} left={0} right={0} bottom={0} border="2px solid red" />}
|
||||
</Tab>
|
||||
<Tab position="relative" onMouseOver={onOnMouseOverGalleryTab} onMouseOut={onMouseOut}>
|
||||
{t('gallery.gallery')}
|
||||
|
||||
@@ -84,7 +84,7 @@ export const ControlLayerControlAdapter = memo(() => {
|
||||
<Flex w="full" gap={2}>
|
||||
<ControlLayerControlAdapterModel modelKey={controlAdapter.model?.key ?? null} onChange={onChangeModel} />
|
||||
<IconButton
|
||||
onClick={filter.start}
|
||||
onPointerUp={filter.start}
|
||||
isDisabled={filter.isDisabled}
|
||||
size="sm"
|
||||
alignSelf="stretch"
|
||||
@@ -94,7 +94,7 @@ export const ControlLayerControlAdapter = memo(() => {
|
||||
icon={<PiShootingStarBold />}
|
||||
/>
|
||||
<IconButton
|
||||
onClick={pullBboxIntoLayer}
|
||||
onPointerUp={pullBboxIntoLayer}
|
||||
isDisabled={isBusy}
|
||||
size="sm"
|
||||
alignSelf="stretch"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { MenuDivider } from '@invoke-ai/ui-library';
|
||||
import { IconMenuItemGroup } from 'common/components/IconMenuItem';
|
||||
import { CanvasEntityMenuItemsArrange } from 'features/controlLayers/components/common/CanvasEntityMenuItemsArrange';
|
||||
import { CanvasEntityMenuItemsCopyToClipboard } from 'features/controlLayers/components/common/CanvasEntityMenuItemsCopyToClipboard';
|
||||
import { CanvasEntityMenuItemsCropToBbox } from 'features/controlLayers/components/common/CanvasEntityMenuItemsCropToBbox';
|
||||
@@ -15,20 +14,18 @@ import { memo } from 'react';
|
||||
export const ControlLayerMenuItems = memo(() => {
|
||||
return (
|
||||
<>
|
||||
<IconMenuItemGroup>
|
||||
<CanvasEntityMenuItemsArrange />
|
||||
<CanvasEntityMenuItemsDuplicate />
|
||||
<CanvasEntityMenuItemsDelete />
|
||||
</IconMenuItemGroup>
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsTransform />
|
||||
<CanvasEntityMenuItemsFilter />
|
||||
<ControlLayerMenuItemsConvertControlToRaster />
|
||||
<ControlLayerMenuItemsTransparencyEffect />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsArrange />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsCropToBbox />
|
||||
<CanvasEntityMenuItemsDuplicate />
|
||||
<CanvasEntityMenuItemsCopyToClipboard />
|
||||
<CanvasEntityMenuItemsSave />
|
||||
<CanvasEntityMenuItemsDelete />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ export const ControlLayerMenuItemsConvertControlToRaster = memo(() => {
|
||||
}, [dispatch, entityIdentifier]);
|
||||
|
||||
return (
|
||||
<MenuItem onClick={convertControlLayerToRasterLayer} icon={<PiLightningBold />} isDisabled={!isInteractable}>
|
||||
<MenuItem onPointerUp={convertControlLayerToRasterLayer} icon={<PiLightningBold />} isDisabled={!isInteractable}>
|
||||
{t('controlLayers.convertToRasterLayer')}
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ export const ControlLayerMenuItemsTransparencyEffect = memo(() => {
|
||||
}, [dispatch, entityIdentifier]);
|
||||
|
||||
return (
|
||||
<MenuItem onClick={onToggle} icon={<PiDropHalfBold />} isDisabled={!isInteractable}>
|
||||
<MenuItem onPointerUp={onToggle} icon={<PiDropHalfBold />} isDisabled={!isInteractable}>
|
||||
{withTransparencyEffect
|
||||
? t('controlLayers.disableTransparencyEffect')
|
||||
: t('controlLayers.enableTransparencyEffect')}
|
||||
|
||||
@@ -109,7 +109,7 @@ const FilterContent = memo(
|
||||
<Button
|
||||
variant="ghost"
|
||||
leftIcon={<PiShootingStarBold />}
|
||||
onClick={adapter.filterer.processImmediate}
|
||||
onPointerUp={adapter.filterer.processImmediate}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.filter.process')}
|
||||
isDisabled={!isValid || autoProcessFilter}
|
||||
@@ -119,7 +119,7 @@ const FilterContent = memo(
|
||||
<Spacer />
|
||||
<Button
|
||||
leftIcon={<PiArrowsCounterClockwiseBold />}
|
||||
onClick={adapter.filterer.reset}
|
||||
onPointerUp={adapter.filterer.reset}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.filter.reset')}
|
||||
variant="ghost"
|
||||
@@ -129,7 +129,7 @@ const FilterContent = memo(
|
||||
<Button
|
||||
variant="ghost"
|
||||
leftIcon={<PiCheckBold />}
|
||||
onClick={adapter.filterer.apply}
|
||||
onPointerUp={adapter.filterer.apply}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.filter.apply')}
|
||||
isDisabled={!isValid || !hasProcessed}
|
||||
@@ -139,7 +139,7 @@ const FilterContent = memo(
|
||||
<Button
|
||||
variant="ghost"
|
||||
leftIcon={<PiXBold />}
|
||||
onClick={adapter.filterer.cancel}
|
||||
onPointerUp={adapter.filterer.cancel}
|
||||
loadingText={t('controlLayers.filter.cancel')}
|
||||
>
|
||||
{t('controlLayers.filter.cancel')}
|
||||
|
||||
@@ -69,7 +69,7 @@ export const IPAdapterImagePreview = memo(({ image, onChangeImage, droppableData
|
||||
{controlImage && (
|
||||
<Flex position="absolute" flexDir="column" top={2} insetInlineEnd={2} gap={1}>
|
||||
<IAIDndImageIcon
|
||||
onClick={handleResetControlImage}
|
||||
onPointerUp={handleResetControlImage}
|
||||
icon={<PiArrowCounterClockwiseBold size={16} />}
|
||||
tooltip={t('common.reset')}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IconMenuItemGroup } from 'common/components/IconMenuItem';
|
||||
import { MenuDivider } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityMenuItemsArrange } from 'features/controlLayers/components/common/CanvasEntityMenuItemsArrange';
|
||||
import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDelete';
|
||||
import { CanvasEntityMenuItemsDuplicate } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDuplicate';
|
||||
@@ -6,11 +6,12 @@ import { memo } from 'react';
|
||||
|
||||
export const IPAdapterMenuItems = memo(() => {
|
||||
return (
|
||||
<IconMenuItemGroup>
|
||||
<>
|
||||
<CanvasEntityMenuItemsArrange />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsDuplicate />
|
||||
<CanvasEntityMenuItemsDelete />
|
||||
</IconMenuItemGroup>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ export const IPAdapterSettings = memo(() => {
|
||||
/>
|
||||
</Box>
|
||||
<IconButton
|
||||
onClick={pullBboxIntoIPAdapter}
|
||||
onPointerUp={pullBboxIntoIPAdapter}
|
||||
isDisabled={isBusy}
|
||||
variant="ghost"
|
||||
aria-label={t('controlLayers.pullBboxIntoReferenceImage')}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { MenuDivider } from '@invoke-ai/ui-library';
|
||||
import { IconMenuItemGroup } from 'common/components/IconMenuItem';
|
||||
import { CanvasEntityMenuItemsArrange } from 'features/controlLayers/components/common/CanvasEntityMenuItemsArrange';
|
||||
import { CanvasEntityMenuItemsCropToBbox } from 'features/controlLayers/components/common/CanvasEntityMenuItemsCropToBbox';
|
||||
import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDelete';
|
||||
@@ -10,15 +9,13 @@ import { memo } from 'react';
|
||||
export const InpaintMaskMenuItems = memo(() => {
|
||||
return (
|
||||
<>
|
||||
<IconMenuItemGroup>
|
||||
<CanvasEntityMenuItemsArrange />
|
||||
<CanvasEntityMenuItemsDuplicate />
|
||||
<CanvasEntityMenuItemsDelete />
|
||||
</IconMenuItemGroup>
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsTransform />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsArrange />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsCropToBbox />
|
||||
<CanvasEntityMenuItemsDuplicate />
|
||||
<CanvasEntityMenuItemsDelete />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Checkbox, ConfirmationAlertDialog, Flex, FormControl, FormLabel, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import { buildUseBoolean } from 'common/hooks/useBoolean';
|
||||
import { newCanvasSessionRequested, newGallerySessionRequested } from 'features/controlLayers/store/actions';
|
||||
import {
|
||||
@@ -66,7 +65,6 @@ export const useNewCanvasSession = () => {
|
||||
};
|
||||
|
||||
export const NewGallerySessionDialog = memo(() => {
|
||||
useAssertSingleton('NewGallerySessionDialog');
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -102,7 +100,6 @@ export const NewGallerySessionDialog = memo(() => {
|
||||
NewGallerySessionDialog.displayName = 'NewGallerySessionDialog';
|
||||
|
||||
export const NewCanvasSessionDialog = memo(() => {
|
||||
useAssertSingleton('NewCanvasSessionDialog');
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { MenuDivider } from '@invoke-ai/ui-library';
|
||||
import { IconMenuItemGroup } from 'common/components/IconMenuItem';
|
||||
import { CanvasEntityMenuItemsArrange } from 'features/controlLayers/components/common/CanvasEntityMenuItemsArrange';
|
||||
import { CanvasEntityMenuItemsCopyToClipboard } from 'features/controlLayers/components/common/CanvasEntityMenuItemsCopyToClipboard';
|
||||
import { CanvasEntityMenuItemsCropToBbox } from 'features/controlLayers/components/common/CanvasEntityMenuItemsCropToBbox';
|
||||
@@ -14,19 +13,17 @@ import { memo } from 'react';
|
||||
export const RasterLayerMenuItems = memo(() => {
|
||||
return (
|
||||
<>
|
||||
<IconMenuItemGroup>
|
||||
<CanvasEntityMenuItemsArrange />
|
||||
<CanvasEntityMenuItemsDuplicate />
|
||||
<CanvasEntityMenuItemsDelete />
|
||||
</IconMenuItemGroup>
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsTransform />
|
||||
<CanvasEntityMenuItemsFilter />
|
||||
<RasterLayerMenuItemsConvertRasterToControl />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsArrange />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsCropToBbox />
|
||||
<CanvasEntityMenuItemsDuplicate />
|
||||
<CanvasEntityMenuItemsCopyToClipboard />
|
||||
<CanvasEntityMenuItemsSave />
|
||||
<CanvasEntityMenuItemsDelete />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ export const RasterLayerMenuItemsConvertRasterToControl = memo(() => {
|
||||
const defaultControlAdapter = useAppSelector(selectDefaultControlAdapter);
|
||||
const isInteractable = useIsEntityInteractable(entityIdentifier);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
const onPointerUp = useCallback(() => {
|
||||
dispatch(
|
||||
rasterLayerConvertedToControlLayer({
|
||||
entityIdentifier,
|
||||
@@ -27,7 +27,7 @@ export const RasterLayerMenuItemsConvertRasterToControl = memo(() => {
|
||||
}, [defaultControlAdapter, dispatch, entityIdentifier]);
|
||||
|
||||
return (
|
||||
<MenuItem onClick={onClick} icon={<PiLightningBold />} isDisabled={!isInteractable}>
|
||||
<MenuItem onPointerUp={onPointerUp} icon={<PiLightningBold />} isDisabled={!isInteractable}>
|
||||
{t('controlLayers.convertToControlLayer')}
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
@@ -30,7 +30,7 @@ export const RegionalGuidanceAddPromptsIPAdapterButtons = () => {
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
leftIcon={<PiPlusBold />}
|
||||
onClick={addRegionalGuidancePositivePrompt}
|
||||
onPointerUp={addRegionalGuidancePositivePrompt}
|
||||
isDisabled={!validActions.canAddPositivePrompt}
|
||||
>
|
||||
{t('controlLayers.prompt')}
|
||||
@@ -39,12 +39,12 @@ export const RegionalGuidanceAddPromptsIPAdapterButtons = () => {
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
leftIcon={<PiPlusBold />}
|
||||
onClick={addRegionalGuidanceNegativePrompt}
|
||||
onPointerUp={addRegionalGuidanceNegativePrompt}
|
||||
isDisabled={!validActions.canAddNegativePrompt}
|
||||
>
|
||||
{t('controlLayers.negativePrompt')}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" leftIcon={<PiPlusBold />} onClick={addRegionalGuidanceIPAdapter}>
|
||||
<Button size="sm" variant="ghost" leftIcon={<PiPlusBold />} onPointerUp={addRegionalGuidanceIPAdapter}>
|
||||
{t('controlLayers.referenceImage')}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
@@ -15,7 +15,7 @@ export const RegionalGuidanceDeletePromptButton = memo(({ onDelete }: Props) =>
|
||||
variant="link"
|
||||
aria-label={t('controlLayers.deletePrompt')}
|
||||
icon={<PiTrashSimpleFill />}
|
||||
onClick={onDelete}
|
||||
onPointerUp={onDelete}
|
||||
flexGrow={0}
|
||||
size="sm"
|
||||
p={0}
|
||||
|
||||
@@ -120,7 +120,7 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ referenceImageId }: Pro
|
||||
icon={<PiTrashSimpleFill />}
|
||||
tooltip={t('controlLayers.deleteReferenceImage')}
|
||||
aria-label={t('controlLayers.deleteReferenceImage')}
|
||||
onClick={onDeleteIPAdapter}
|
||||
onPointerUp={onDeleteIPAdapter}
|
||||
colorScheme="error"
|
||||
/>
|
||||
</Flex>
|
||||
@@ -135,7 +135,7 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ referenceImageId }: Pro
|
||||
/>
|
||||
</Box>
|
||||
<IconButton
|
||||
onClick={pullBboxIntoIPAdapter}
|
||||
onPointerUp={pullBboxIntoIPAdapter}
|
||||
isDisabled={isBusy}
|
||||
variant="ghost"
|
||||
aria-label={t('controlLayers.pullBboxIntoReferenceImage')}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Flex, MenuDivider } from '@invoke-ai/ui-library';
|
||||
import { MenuDivider } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityMenuItemsArrange } from 'features/controlLayers/components/common/CanvasEntityMenuItemsArrange';
|
||||
import { CanvasEntityMenuItemsCropToBbox } from 'features/controlLayers/components/common/CanvasEntityMenuItemsCropToBbox';
|
||||
import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDelete';
|
||||
@@ -11,18 +11,16 @@ import { memo } from 'react';
|
||||
export const RegionalGuidanceMenuItems = memo(() => {
|
||||
return (
|
||||
<>
|
||||
<Flex gap={2}>
|
||||
<CanvasEntityMenuItemsArrange />
|
||||
<CanvasEntityMenuItemsDuplicate />
|
||||
<CanvasEntityMenuItemsDelete />
|
||||
</Flex>
|
||||
<MenuDivider />
|
||||
<RegionalGuidanceMenuItemsAddPromptsAndIPAdapter />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsTransform />
|
||||
<RegionalGuidanceMenuItemsAutoNegative />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsArrange />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsCropToBbox />
|
||||
<CanvasEntityMenuItemsDuplicate />
|
||||
<CanvasEntityMenuItemsDelete />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -26,13 +26,19 @@ export const RegionalGuidanceMenuItemsAddPromptsAndIPAdapter = memo(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem onClick={addRegionalGuidancePositivePrompt} isDisabled={!validActions.canAddPositivePrompt || isBusy}>
|
||||
<MenuItem
|
||||
onPointerUp={addRegionalGuidancePositivePrompt}
|
||||
isDisabled={!validActions.canAddPositivePrompt || isBusy}
|
||||
>
|
||||
{t('controlLayers.addPositivePrompt')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={addRegionalGuidanceNegativePrompt} isDisabled={!validActions.canAddNegativePrompt || isBusy}>
|
||||
<MenuItem
|
||||
onPointerUp={addRegionalGuidanceNegativePrompt}
|
||||
isDisabled={!validActions.canAddNegativePrompt || isBusy}
|
||||
>
|
||||
{t('controlLayers.addNegativePrompt')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={addRegionalGuidanceIPAdapter} isDisabled={isBusy}>
|
||||
<MenuItem onPointerUp={addRegionalGuidanceIPAdapter} isDisabled={isBusy}>
|
||||
{t('controlLayers.addReferenceImage')}
|
||||
</MenuItem>
|
||||
</>
|
||||
|
||||
@@ -17,12 +17,12 @@ export const RegionalGuidanceMenuItemsAutoNegative = memo(() => {
|
||||
[entityIdentifier]
|
||||
);
|
||||
const autoNegative = useAppSelector(selectAutoNegative);
|
||||
const onClick = useCallback(() => {
|
||||
const onPointerUp = useCallback(() => {
|
||||
dispatch(rgAutoNegativeToggled({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
|
||||
return (
|
||||
<MenuItem icon={<PiSelectionInverseBold />} onClick={onClick}>
|
||||
<MenuItem icon={<PiSelectionInverseBold />} onPointerUp={onPointerUp}>
|
||||
{autoNegative ? t('controlLayers.disableAutoNegative') : t('controlLayers.enableAutoNegative')}
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ export const CanvasSettingsClearCachesButton = memo(() => {
|
||||
canvasManager.cache.clearAll();
|
||||
}, [canvasManager]);
|
||||
return (
|
||||
<Button onClick={clearCaches} size="sm" colorScheme="warning">
|
||||
<Button onPointerUp={clearCaches} size="sm" colorScheme="warning">
|
||||
{t('controlLayers.clearCaches')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -7,11 +7,11 @@ import { useTranslation } from 'react-i18next';
|
||||
export const CanvasSettingsClearHistoryButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const onClick = useCallback(() => {
|
||||
const onPointerUp = useCallback(() => {
|
||||
dispatch(canvasClearHistory());
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<Button onClick={onClick} size="sm">
|
||||
<Button onPointerUp={onPointerUp} size="sm">
|
||||
{t('controlLayers.clearHistory')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -6,11 +6,11 @@ import { useTranslation } from 'react-i18next';
|
||||
export const CanvasSettingsLogDebugInfoButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const canvasManager = useCanvasManager();
|
||||
const onClick = useCallback(() => {
|
||||
const onPointerUp = useCallback(() => {
|
||||
canvasManager.logDebugInfo();
|
||||
}, [canvasManager]);
|
||||
return (
|
||||
<Button onClick={onClick} size="sm">
|
||||
<Button onPointerUp={onPointerUp} size="sm">
|
||||
{t('controlLayers.logDebugInfo')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -6,14 +6,14 @@ import { useTranslation } from 'react-i18next';
|
||||
export const CanvasSettingsRecalculateRectsButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const canvasManager = useCanvasManager();
|
||||
const onClick = useCallback(() => {
|
||||
const onPointerUp = useCallback(() => {
|
||||
for (const adapter of canvasManager.getAllAdapters()) {
|
||||
adapter.transformer.requestRectCalculation();
|
||||
}
|
||||
}, [canvasManager]);
|
||||
|
||||
return (
|
||||
<Button onClick={onClick} size="sm" colorScheme="warning">
|
||||
<Button onPointerUp={onPointerUp} size="sm" colorScheme="warning">
|
||||
{t('controlLayers.recalculateRects')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -60,7 +60,7 @@ export const StagingAreaToolbarAcceptButton = memo(() => {
|
||||
tooltip={`${t('common.accept')} (Enter)`}
|
||||
aria-label={`${t('common.accept')} (Enter)`}
|
||||
icon={<PiCheckBold />}
|
||||
onClick={acceptSelected}
|
||||
onPointerUp={acceptSelected}
|
||||
colorScheme="invokeBlue"
|
||||
isDisabled={!selectedImage}
|
||||
/>
|
||||
|
||||
@@ -18,7 +18,7 @@ export const StagingAreaToolbarDiscardAllButton = memo(() => {
|
||||
tooltip={`${t('controlLayers.stagingArea.discardAll')} (Esc)`}
|
||||
aria-label={t('controlLayers.stagingArea.discardAll')}
|
||||
icon={<PiTrashSimpleBold />}
|
||||
onClick={discardAll}
|
||||
onPointerUp={discardAll}
|
||||
colorScheme="error"
|
||||
fontSize={16}
|
||||
/>
|
||||
|
||||
@@ -35,7 +35,7 @@ export const StagingAreaToolbarDiscardSelectedButton = memo(() => {
|
||||
tooltip={t('controlLayers.stagingArea.discard')}
|
||||
aria-label={t('controlLayers.stagingArea.discard')}
|
||||
icon={<PiXBold />}
|
||||
onClick={discardSelected}
|
||||
onPointerUp={discardSelected}
|
||||
colorScheme="invokeBlue"
|
||||
fontSize={16}
|
||||
isDisabled={!selectedImage}
|
||||
|
||||
@@ -40,7 +40,7 @@ export const StagingAreaToolbarNextButton = memo(() => {
|
||||
tooltip={`${t('controlLayers.stagingArea.next')} (Right)`}
|
||||
aria-label={`${t('controlLayers.stagingArea.next')} (Right)`}
|
||||
icon={<PiArrowRightBold />}
|
||||
onClick={selectNext}
|
||||
onPointerUp={selectNext}
|
||||
colorScheme="invokeBlue"
|
||||
isDisabled={imageCount <= 1 || !shouldShowStagedImage}
|
||||
/>
|
||||
|
||||
@@ -40,7 +40,7 @@ export const StagingAreaToolbarPrevButton = memo(() => {
|
||||
tooltip={`${t('controlLayers.stagingArea.previous')} (Left)`}
|
||||
aria-label={`${t('controlLayers.stagingArea.previous')} (Left)`}
|
||||
icon={<PiArrowLeftBold />}
|
||||
onClick={selectPrev}
|
||||
onPointerUp={selectPrev}
|
||||
colorScheme="invokeBlue"
|
||||
isDisabled={imageCount <= 1 || !shouldShowStagedImage}
|
||||
/>
|
||||
|
||||
@@ -40,7 +40,7 @@ export const StagingAreaToolbarSaveSelectedToGalleryButton = memo(() => {
|
||||
tooltip={t('controlLayers.stagingArea.saveToGallery')}
|
||||
aria-label={t('controlLayers.stagingArea.saveToGallery')}
|
||||
icon={<PiFloppyDiskBold />}
|
||||
onClick={saveSelectedImageToGallery}
|
||||
onPointerUp={saveSelectedImageToGallery}
|
||||
colorScheme="invokeBlue"
|
||||
isDisabled={!selectedImage || !selectedImage.imageDTO.is_intermediate}
|
||||
/>
|
||||
|
||||
@@ -29,7 +29,7 @@ export const StagingAreaToolbarToggleShowResultsButton = memo(() => {
|
||||
}
|
||||
data-alert={!shouldShowStagedImage}
|
||||
icon={shouldShowStagedImage ? <PiEyeBold /> : <PiEyeSlashBold />}
|
||||
onClick={toggleShowResults}
|
||||
onPointerUp={toggleShowResults}
|
||||
colorScheme="invokeBlue"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ export const ToolBboxButton = memo(() => {
|
||||
icon={<PiBoundingBoxBold />}
|
||||
colorScheme={isSelected ? 'invokeBlue' : 'base'}
|
||||
variant="solid"
|
||||
onClick={selectBbox}
|
||||
onPointerUp={selectBbox}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ export const ToolBrushButton = memo(() => {
|
||||
icon={<PiPaintBrushBold />}
|
||||
colorScheme={isSelected ? 'invokeBlue' : 'base'}
|
||||
variant="solid"
|
||||
onClick={selectBrush}
|
||||
onPointerUp={selectBrush}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -164,7 +164,7 @@ export const ToolBrushWidth = memo(() => {
|
||||
onKeyDown={onKeyDown}
|
||||
clampValueOnBlur={false}
|
||||
>
|
||||
<NumberInputField _focusVisible={{ zIndex: 0 }} title="" paddingInlineEnd={7} />
|
||||
<NumberInputField paddingInlineEnd={7} />
|
||||
<PopoverTrigger>
|
||||
<IconButton
|
||||
aria-label="open-slider"
|
||||
|
||||
@@ -27,7 +27,7 @@ export const ToolColorPickerButton = memo(() => {
|
||||
icon={<PiEyedropperBold />}
|
||||
colorScheme={isSelected ? 'invokeBlue' : 'base'}
|
||||
variant="solid"
|
||||
onClick={selectColorPicker}
|
||||
onPointerUp={selectColorPicker}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ export const ToolEraserButton = memo(() => {
|
||||
icon={<PiEraserBold />}
|
||||
colorScheme={isSelected ? 'invokeBlue' : 'base'}
|
||||
variant="solid"
|
||||
onClick={selectEraser}
|
||||
onPointerUp={selectEraser}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -167,7 +167,7 @@ export const ToolEraserWidth = memo(() => {
|
||||
onKeyDown={onKeyDown}
|
||||
clampValueOnBlur={false}
|
||||
>
|
||||
<NumberInputField _focusVisible={{ zIndex: 0 }} title="" paddingInlineEnd={7} />
|
||||
<NumberInputField paddingInlineEnd={7} />
|
||||
<PopoverTrigger>
|
||||
<IconButton
|
||||
aria-label="open-slider"
|
||||
|
||||
@@ -27,7 +27,7 @@ export const ToolMoveButton = memo(() => {
|
||||
icon={<PiCursorBold />}
|
||||
colorScheme={isSelected ? 'invokeBlue' : 'base'}
|
||||
variant="solid"
|
||||
onClick={selectMove}
|
||||
onPointerUp={selectMove}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ export const ToolRectButton = memo(() => {
|
||||
icon={<PiRectangleBold />}
|
||||
colorScheme={isSelected ? 'invokeBlue' : 'base'}
|
||||
variant="solid"
|
||||
onClick={selectRect}
|
||||
onPointerUp={selectRect}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user