Compare commits

..

1 Commits

Author SHA1 Message Date
Millun Atluri
7487f96da2 Update diffuses to 0.25 2023-12-29 11:34:02 +11:00
1073 changed files with 25429 additions and 30870 deletions

8
.github/CODEOWNERS vendored
View File

@@ -1,5 +1,5 @@
# continuous integration
/.github/workflows/ @lstein @blessedcoolant @hipsterusername @ebr
/.github/workflows/ @lstein @blessedcoolant @hipsterusername
# documentation
/docs/ @lstein @blessedcoolant @hipsterusername @Millu
@@ -10,7 +10,7 @@
# installation and configuration
/pyproject.toml @lstein @blessedcoolant @hipsterusername
/docker/ @lstein @blessedcoolant @hipsterusername @ebr
/docker/ @lstein @blessedcoolant @hipsterusername
/scripts/ @ebr @lstein @hipsterusername
/installer/ @lstein @ebr @hipsterusername
/invokeai/assets @lstein @ebr @hipsterusername
@@ -26,7 +26,9 @@
# front ends
/invokeai/frontend/CLI @lstein @hipsterusername
/invokeai/frontend/install @lstein @ebr @hipsterusername
/invokeai/frontend/install @lstein @ebr @hipsterusername
/invokeai/frontend/merge @lstein @blessedcoolant @hipsterusername
/invokeai/frontend/training @lstein @blessedcoolant @hipsterusername
/invokeai/frontend/web @psychedelicious @blessedcoolant @maryhipp @hipsterusername

View File

@@ -40,14 +40,10 @@ jobs:
- name: Free up more disk space on the runner
# https://github.com/actions/runner-images/issues/2840#issuecomment-1284059930
run: |
echo "----- Free space before cleanup"
df -h
sudo rm -rf /usr/share/dotnet
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
sudo swapoff /mnt/swapfile
sudo rm -rf /mnt/swapfile
echo "----- Free space after cleanup"
df -h
- name: Checkout
uses: actions/checkout@v3
@@ -95,7 +91,6 @@ jobs:
# password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build container
timeout-minutes: 40
id: docker_build
uses: docker/build-push-action@v4
with:

View File

@@ -1,10 +1,10 @@
<div align="center">
![project hero](https://github.com/invoke-ai/InvokeAI/assets/31807370/6e3728c7-e90e-4711-905c-3b55844ff5be)
![project hero](https://github.com/invoke-ai/InvokeAI/assets/31807370/1a917d94-e099-4fa1-a70f-7dd8d0691018)
# Invoke - Professional Creative AI Tools for Visual Media
## To learn more about Invoke, or implement our Business solutions, visit [invoke.com](https://www.invoke.com/about)
# Invoke AI - Generative AI for Professional Creatives
## Professional Creative Tools for Stable Diffusion, Custom-Trained Models, and more.
To learn more about Invoke AI, get started instantly, or implement our Business solutions, visit [invoke.ai](https://invoke.ai)
[![discord badge]][discord link]
@@ -56,9 +56,7 @@ the foundation for multiple commercial products.
<div align="center">
![Highlighted Features - Canvas and Workflows](https://github.com/invoke-ai/InvokeAI/assets/31807370/708f7a82-084f-4860-bfbe-e2588c53548d)
![canvas preview](https://github.com/invoke-ai/InvokeAI/raw/main/docs/assets/canvas_preview.png)
</div>

View File

@@ -2,13 +2,10 @@
## Any environment variables supported by InvokeAI can be specified here,
## in addition to the examples below.
# HOST_INVOKEAI_ROOT is the path on the docker host's filesystem where InvokeAI will store data.
# INVOKEAI_ROOT is the path to a path on the local filesystem where InvokeAI will store data.
# Outputs will also be stored here by default.
# If relative, it will be relative to the docker directory in which the docker-compose.yml file is located
#HOST_INVOKEAI_ROOT=../../invokeai-data
# INVOKEAI_ROOT is the path to the root of the InvokeAI repository within the container.
# INVOKEAI_ROOT=~/invokeai
# This **must** be an absolute path.
INVOKEAI_ROOT=
# Get this value from your HuggingFace account settings page.
# HUGGING_FACE_HUB_TOKEN=

View File

@@ -59,7 +59,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \
# #### Build the Web UI ------------------------------------
FROM node:20-slim AS web-builder
FROM node:18-slim AS web-builder
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
@@ -68,7 +68,7 @@ WORKDIR /build
COPY invokeai/frontend/web/ ./
RUN --mount=type=cache,target=/pnpm/store \
pnpm install --frozen-lockfile
RUN npx vite build
RUN pnpm run build
#### Runtime stage ---------------------------------------

View File

@@ -21,9 +21,7 @@ x-invokeai: &invokeai
ports:
- "${INVOKEAI_PORT:-9090}:9090"
volumes:
- type: bind
source: ${HOST_INVOKEAI_ROOT:-${INVOKEAI_ROOT:-~/invokeai}}
target: ${INVOKEAI_ROOT:-/invokeai}
- ${INVOKEAI_ROOT:-~/invokeai}:${INVOKEAI_ROOT:-/invokeai}
- ${HF_HOME:-~/.cache/huggingface}:${HF_HOME:-/invokeai/.cache/huggingface}
# - ${INVOKEAI_MODELS_DIR:-${INVOKEAI_ROOT:-/invokeai/models}}
# - ${INVOKEAI_MODELS_CONFIG_PATH:-${INVOKEAI_ROOT:-/invokeai/configs/models.yaml}}

View File

@@ -36,8 +36,7 @@ To use a community workflow, download the the `.json` node graph file and load i
+ [Mask Operations](#mask-operations)
+ [Match Histogram](#match-histogram)
+ [Metadata-Linked](#metadata-linked-nodes)
+ [Negative Image](#negative-image)
+ [Nightmare Promptgen](#nightmare-promptgen)
+ [Negative Image](#negative-image)
+ [Oobabooga](#oobabooga)
+ [Prompt Tools](#prompt-tools)
+ [Remote Image](#remote-image)
@@ -347,13 +346,6 @@ Node Link: https://github.com/VeyDlin/negative-image-node
View:
</br><img src="https://raw.githubusercontent.com/VeyDlin/negative-image-node/master/.readme/node.png" width="500" />
--------------------------------
### Nightmare Promptgen
**Description:** Nightmare Prompt Generator - Uses a local text generation model to create unique imaginative (but usually nightmarish) prompts for InvokeAI. By default, it allows you to choose from some gpt-neo models I finetuned on over 2500 of my own InvokeAI prompts in Compel format, but you're able to add your own, as well. Offers support for replacing any troublesome words with a random choice from list you can also define.
**Node Link:** [https://github.com/gogurtenjoyer/nightmare-promptgen](https://github.com/gogurtenjoyer/nightmare-promptgen)
--------------------------------
### Oobabooga

View File

@@ -241,12 +241,12 @@ class InvokeAiInstance:
pip[
"install",
"--require-virtualenv",
"numpy==1.26.3", # choose versions that won't be uninstalled during phase 2
"numpy~=1.24.0", # choose versions that won't be uninstalled during phase 2
"urllib3~=1.26.0",
"requests~=2.28.0",
"torch==2.1.2",
"torchmetrics==0.11.4",
"torchvision==0.16.2",
"torchvision>=0.16.2",
"--force-reinstall",
"--find-links" if find_links is not None else None,
find_links,

View File

@@ -23,11 +23,10 @@ class DynamicPromptsResponse(BaseModel):
)
async def parse_dynamicprompts(
prompt: str = Body(description="The prompt to parse with dynamicprompts"),
max_prompts: int = Body(ge=1, le=10000, default=1000, description="The max number of prompts to generate"),
max_prompts: int = Body(default=1000, description="The max number of prompts to generate"),
combinatorial: bool = Body(default=True, description="Whether to use the combinatorial generator"),
) -> DynamicPromptsResponse:
"""Creates a batch process"""
max_prompts = min(max_prompts, 10000)
generator: Union[RandomPromptGenerator, CombinatorialPromptGenerator]
try:
error: Optional[str] = None

View File

@@ -24,10 +24,9 @@ from controlnet_aux import (
)
from controlnet_aux.util import HWC3, ade_palette
from PIL import Image
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from pydantic import BaseModel, ConfigDict, Field, field_validator
from invokeai.app.invocations.primitives import ImageField, ImageOutput
from invokeai.app.invocations.util import validate_begin_end_step, validate_weights
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
from invokeai.app.shared.fields import FieldDescriptions
@@ -76,16 +75,17 @@ class ControlField(BaseModel):
resize_mode: CONTROLNET_RESIZE_VALUES = Field(default="just_resize", description="The resize mode to use")
@field_validator("control_weight")
@classmethod
def validate_control_weight(cls, v):
validate_weights(v)
"""Validate that all control weights in the valid range"""
if isinstance(v, list):
for i in v:
if i < -1 or i > 2:
raise ValueError("Control weights must be within -1 to 2 range")
else:
if v < -1 or v > 2:
raise ValueError("Control weights must be within -1 to 2 range")
return v
@model_validator(mode="after")
def validate_begin_end_step_percent(self):
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
return self
@invocation_output("control_output")
class ControlOutput(BaseInvocationOutput):
@@ -95,17 +95,17 @@ class ControlOutput(BaseInvocationOutput):
control: ControlField = OutputField(description=FieldDescriptions.control)
@invocation("controlnet", title="ControlNet", tags=["controlnet"], category="controlnet", version="1.1.1")
@invocation("controlnet", title="ControlNet", tags=["controlnet"], category="controlnet", version="1.1.0")
class ControlNetInvocation(BaseInvocation):
"""Collects ControlNet info to pass to other nodes"""
image: ImageField = InputField(description="The control image")
control_model: ControlNetModelField = InputField(description=FieldDescriptions.controlnet_model, input=Input.Direct)
control_weight: Union[float, List[float]] = InputField(
default=1.0, ge=-1, le=2, description="The weight given to the ControlNet"
default=1.0, description="The weight given to the ControlNet"
)
begin_step_percent: float = InputField(
default=0, ge=0, le=1, description="When the ControlNet is first applied (% of total steps)"
default=0, ge=-1, le=2, description="When the ControlNet is first applied (% of total steps)"
)
end_step_percent: float = InputField(
default=1, ge=0, le=1, description="When the ControlNet is last applied (% of total steps)"
@@ -113,17 +113,6 @@ class ControlNetInvocation(BaseInvocation):
control_mode: CONTROLNET_MODE_VALUES = InputField(default="balanced", description="The control mode used")
resize_mode: CONTROLNET_RESIZE_VALUES = InputField(default="just_resize", description="The resize mode used")
@field_validator("control_weight")
@classmethod
def validate_control_weight(cls, v):
validate_weights(v)
return v
@model_validator(mode="after")
def validate_begin_end_step_percent(self) -> "ControlNetInvocation":
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
return self
def invoke(self, context: InvocationContext) -> ControlOutput:
return ControlOutput(
control=ControlField(

View File

@@ -2,7 +2,7 @@ import os
from builtins import float
from typing import List, Union
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from pydantic import BaseModel, ConfigDict, Field
from invokeai.app.invocations.baseinvocation import (
BaseInvocation,
@@ -15,7 +15,6 @@ from invokeai.app.invocations.baseinvocation import (
invocation_output,
)
from invokeai.app.invocations.primitives import ImageField
from invokeai.app.invocations.util import validate_begin_end_step, validate_weights
from invokeai.app.shared.fields import FieldDescriptions
from invokeai.backend.model_management.models.base import BaseModelType, ModelType
from invokeai.backend.model_management.models.ip_adapter import get_ip_adapter_image_encoder_model_id
@@ -40,6 +39,7 @@ class IPAdapterField(BaseModel):
ip_adapter_model: IPAdapterModelField = Field(description="The IP-Adapter model to use.")
image_encoder_model: CLIPVisionModelField = Field(description="The name of the CLIP image encoder model.")
weight: Union[float, List[float]] = Field(default=1, description="The weight given to the ControlNet")
# weight: float = Field(default=1.0, ge=0, description="The weight of the IP-Adapter.")
begin_step_percent: float = Field(
default=0, ge=0, le=1, description="When the IP-Adapter is first applied (% of total steps)"
)
@@ -47,17 +47,6 @@ class IPAdapterField(BaseModel):
default=1, ge=0, le=1, description="When the IP-Adapter is last applied (% of total steps)"
)
@field_validator("weight")
@classmethod
def validate_ip_adapter_weight(cls, v):
validate_weights(v)
return v
@model_validator(mode="after")
def validate_begin_end_step_percent(self):
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
return self
@invocation_output("ip_adapter_output")
class IPAdapterOutput(BaseInvocationOutput):
@@ -65,7 +54,7 @@ class IPAdapterOutput(BaseInvocationOutput):
ip_adapter: IPAdapterField = OutputField(description=FieldDescriptions.ip_adapter, title="IP-Adapter")
@invocation("ip_adapter", title="IP-Adapter", tags=["ip_adapter", "control"], category="ip_adapter", version="1.1.1")
@invocation("ip_adapter", title="IP-Adapter", tags=["ip_adapter", "control"], category="ip_adapter", version="1.1.0")
class IPAdapterInvocation(BaseInvocation):
"""Collects IP-Adapter info to pass to other nodes."""
@@ -75,27 +64,18 @@ class IPAdapterInvocation(BaseInvocation):
description="The IP-Adapter model.", title="IP-Adapter Model", input=Input.Direct, ui_order=-1
)
# weight: float = InputField(default=1.0, description="The weight of the IP-Adapter.", ui_type=UIType.Float)
weight: Union[float, List[float]] = InputField(
default=1, description="The weight given to the IP-Adapter", title="Weight"
default=1, ge=-1, description="The weight given to the IP-Adapter", title="Weight"
)
begin_step_percent: float = InputField(
default=0, ge=0, le=1, description="When the IP-Adapter is first applied (% of total steps)"
default=0, ge=-1, le=2, description="When the IP-Adapter is first applied (% of total steps)"
)
end_step_percent: float = InputField(
default=1, ge=0, le=1, description="When the IP-Adapter is last applied (% of total steps)"
)
@field_validator("weight")
@classmethod
def validate_ip_adapter_weight(cls, v):
validate_weights(v)
return v
@model_validator(mode="after")
def validate_begin_end_step_percent(self):
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
return self
def invoke(self, context: InvocationContext) -> IPAdapterOutput:
# Lookup the CLIP Vision encoder that is intended to be used with the IP-Adapter model.
ip_adapter_info = context.services.model_manager.model_info(

View File

@@ -220,7 +220,7 @@ def get_scheduler(
title="Denoise Latents",
tags=["latents", "denoise", "txt2img", "t2i", "t2l", "img2img", "i2i", "l2l"],
category="latents",
version="1.5.1",
version="1.5.0",
)
class DenoiseLatentsInvocation(BaseInvocation):
"""Denoises noisy latents to decodable images"""
@@ -279,7 +279,7 @@ class DenoiseLatentsInvocation(BaseInvocation):
ui_order=7,
)
cfg_rescale_multiplier: float = InputField(
title="CFG Rescale Multiplier", default=0, ge=0, lt=1, description=FieldDescriptions.cfg_rescale_multiplier
default=0, ge=0, lt=1, description=FieldDescriptions.cfg_rescale_multiplier
)
latents: Optional[LatentsField] = InputField(
default=None,

View File

@@ -1,6 +1,6 @@
from typing import Union
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from pydantic import BaseModel, ConfigDict, Field
from invokeai.app.invocations.baseinvocation import (
BaseInvocation,
@@ -14,7 +14,6 @@ from invokeai.app.invocations.baseinvocation import (
)
from invokeai.app.invocations.controlnet_image_processors import CONTROLNET_RESIZE_VALUES
from invokeai.app.invocations.primitives import ImageField
from invokeai.app.invocations.util import validate_begin_end_step, validate_weights
from invokeai.app.shared.fields import FieldDescriptions
from invokeai.backend.model_management.models.base import BaseModelType
@@ -38,17 +37,6 @@ class T2IAdapterField(BaseModel):
)
resize_mode: CONTROLNET_RESIZE_VALUES = Field(default="just_resize", description="The resize mode to use")
@field_validator("weight")
@classmethod
def validate_ip_adapter_weight(cls, v):
validate_weights(v)
return v
@model_validator(mode="after")
def validate_begin_end_step_percent(self):
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
return self
@invocation_output("t2i_adapter_output")
class T2IAdapterOutput(BaseInvocationOutput):
@@ -56,7 +44,7 @@ class T2IAdapterOutput(BaseInvocationOutput):
@invocation(
"t2i_adapter", title="T2I-Adapter", tags=["t2i_adapter", "control"], category="t2i_adapter", version="1.0.1"
"t2i_adapter", title="T2I-Adapter", tags=["t2i_adapter", "control"], category="t2i_adapter", version="1.0.0"
)
class T2IAdapterInvocation(BaseInvocation):
"""Collects T2I-Adapter info to pass to other nodes."""
@@ -73,7 +61,7 @@ class T2IAdapterInvocation(BaseInvocation):
default=1, ge=0, description="The weight given to the T2I-Adapter", title="Weight"
)
begin_step_percent: float = InputField(
default=0, ge=0, le=1, description="When the T2I-Adapter is first applied (% of total steps)"
default=0, ge=-1, le=2, description="When the T2I-Adapter is first applied (% of total steps)"
)
end_step_percent: float = InputField(
default=1, ge=0, le=1, description="When the T2I-Adapter is last applied (% of total steps)"
@@ -83,17 +71,6 @@ class T2IAdapterInvocation(BaseInvocation):
description="The resize mode applied to the T2I-Adapter input image so that it matches the target output size.",
)
@field_validator("weight")
@classmethod
def validate_ip_adapter_weight(cls, v):
validate_weights(v)
return v
@model_validator(mode="after")
def validate_begin_end_step_percent(self):
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
return self
def invoke(self, context: InvocationContext) -> T2IAdapterOutput:
return T2IAdapterOutput(
t2i_adapter=T2IAdapterField(

View File

@@ -1,14 +0,0 @@
from typing import Union
def validate_weights(weights: Union[float, list[float]]) -> None:
"""Validate that all control weights in the valid range"""
to_validate = weights if isinstance(weights, list) else [weights]
if any(i < -1 or i > 2 for i in to_validate):
raise ValueError("Control weights must be within -1 to 2 range")
def validate_begin_end_step(begin_step_percent: float, end_step_percent: float) -> None:
"""Validate that begin_step_percent is less than end_step_percent"""
if begin_step_percent >= end_step_percent:
raise ValueError("Begin step percent must be less than or equal to end step percent")

View File

@@ -1,31 +0,0 @@
# Copyright (c) 2024 Lincoln Stein and the InvokeAI Development Team
"""
This module exports the function has_baked_in_sdxl_vae().
It returns True if an SDXL checkpoint model has the original SDXL 1.0 VAE,
which doesn't work properly in fp16 mode.
"""
import hashlib
from pathlib import Path
from safetensors.torch import load_file
SDXL_1_0_VAE_HASH = "bc40b16c3a0fa4625abdfc01c04ffc21bf3cefa6af6c7768ec61eb1f1ac0da51"
def has_baked_in_sdxl_vae(checkpoint_path: Path) -> bool:
"""Return true if the checkpoint contains a custom (non SDXL-1.0) VAE."""
hash = _vae_hash(checkpoint_path)
return hash != SDXL_1_0_VAE_HASH
def _vae_hash(checkpoint_path: Path) -> str:
checkpoint = load_file(checkpoint_path, device="cpu")
vae_keys = [x for x in checkpoint.keys() if x.startswith("first_stage_model.")]
hash = hashlib.new("sha256")
for key in vae_keys:
value = checkpoint[key]
hash.update(bytes(key, "UTF-8"))
hash.update(bytes(str(value), "UTF-8"))
return hash.hexdigest()

View File

@@ -13,7 +13,6 @@ from safetensors.torch import load_file
from transformers import CLIPTextModel, CLIPTokenizer
from invokeai.app.shared.models import FreeUConfig
from invokeai.backend.model_management.model_load_optimizations import skip_torch_weight_init
from .models.lora import LoRAModel
@@ -212,12 +211,8 @@ class ModelPatcher:
for i in range(ti_embedding.shape[0]):
new_tokens_added += ti_tokenizer.add_tokens(_get_trigger(ti_name, i))
# Modify text_encoder.
# resize_token_embeddings(...) constructs a new torch.nn.Embedding internally. Initializing the weights of
# this embedding is slow and unnecessary, so we wrap this step in skip_torch_weight_init() to save some
# time.
with skip_torch_weight_init():
text_encoder.resize_token_embeddings(init_tokens_count + new_tokens_added, pad_to_multiple_of)
# modify text_encoder
text_encoder.resize_token_embeddings(init_tokens_count + new_tokens_added, pad_to_multiple_of)
model_embeddings = text_encoder.get_input_embeddings()
for ti_name, ti in ti_list:

View File

@@ -370,8 +370,6 @@ class LoRACheckpointProbe(CheckpointProbeBase):
return BaseModelType.StableDiffusion1
elif token_vector_length == 1024:
return BaseModelType.StableDiffusion2
elif token_vector_length == 1280:
return BaseModelType.StableDiffusionXL # recognizes format at https://civitai.com/models/224641
elif token_vector_length == 2048:
return BaseModelType.StableDiffusionXL
else:

View File

@@ -1,16 +1,11 @@
import json
import os
from enum import Enum
from pathlib import Path
from typing import Literal, Optional
from omegaconf import OmegaConf
from pydantic import Field
from invokeai.app.services.config import InvokeAIAppConfig
from invokeai.backend.model_management.detect_baked_in_vae import has_baked_in_sdxl_vae
from invokeai.backend.util.logging import InvokeAILogger
from .base import (
BaseModelType,
DiffusersModel,
@@ -121,28 +116,14 @@ class StableDiffusionXLModel(DiffusersModel):
# The convert script adapted from the diffusers package uses
# strings for the base model type. To avoid making too many
# source code changes, we simply translate here
if Path(output_path).exists():
return output_path
if isinstance(config, cls.CheckpointConfig):
from invokeai.backend.model_management.models.stable_diffusion import _convert_ckpt_and_cache
# Hack in VAE-fp16 fix - If model sdxl-vae-fp16-fix is installed,
# then we bake it into the converted model unless there is already
# a nonstandard VAE installed.
kwargs = {}
app_config = InvokeAIAppConfig.get_config()
vae_path = app_config.models_path / "sdxl/vae/sdxl-vae-fp16-fix"
if vae_path.exists() and not has_baked_in_sdxl_vae(Path(model_path)):
InvokeAILogger.get_logger().warning("No baked-in VAE detected. Inserting sdxl-vae-fp16-fix.")
kwargs["vae_path"] = vae_path
return _convert_ckpt_and_cache(
version=base_model,
model_config=config,
output_path=output_path,
use_safetensors=False, # corrupts sdxl models for some reason
**kwargs,
)
else:
return model_path

View File

@@ -44,7 +44,7 @@ def torch_dtype(device: torch.device) -> torch.dtype:
if config.full_precision:
return torch.float32
if choose_precision(device) == "float16":
return torch.bfloat16 if device.type == "cuda" else torch.float16
return torch.float16
else:
return torch.float32

View File

@@ -68,9 +68,12 @@ def welcome(latest_release: str, latest_prerelease: str):
yield ""
yield "This script will update InvokeAI to the latest release, or to the development version of your choice."
yield ""
yield "When updating to an arbitrary tag or branch, be aware that the front end may be mismatched to the backend,"
yield "making the web frontend unusable. Please downgrade to the latest release if this happens."
yield ""
yield "[bold yellow]Options:"
yield f"""[1] Update to the latest [bold]official release[/bold] ([italic]{latest_release}[/italic])
[2] Update to the latest [bold]pre-release[/bold] (may be buggy, database backups are recommended before installation; caveat emptor!) ([italic]{latest_prerelease}[/italic])
[2] Update to the latest [bold]pre-release[/bold] (may be buggy; caveat emptor!) ([italic]{latest_prerelease}[/italic])
[3] Manually enter the [bold]version[/bold] you wish to update to"""
console.rule()

View File

@@ -7,4 +7,4 @@ stats.html
index.html
.yarn/
*.scss
src/services/api/schema.ts
src/services/api/schema.d.ts

View File

@@ -28,16 +28,12 @@ module.exports = {
'i18next',
'path',
'unused-imports',
'simple-import-sort',
'eslint-plugin-import',
// These rules are too strict for normal usage, but are useful for optimizing rerenders
// '@arthurgeron/react-usememo',
],
root: true,
rules: {
'path/no-relative-imports': ['error', { maxDepth: 0 }],
curly: 'error',
'i18next/no-literal-string': 'warn',
'i18next/no-literal-string': 2,
'react/jsx-no-bind': ['error', { allowBind: true }],
'react/jsx-curly-brace-presence': [
'error',
@@ -47,7 +43,6 @@ module.exports = {
'no-var': 'error',
'brace-style': 'error',
'prefer-template': 'error',
'import/no-duplicates': 'error',
radix: 'error',
'space-before-blocks': 'error',
'import/prefer-default-export': 'off',
@@ -62,18 +57,6 @@ module.exports = {
argsIgnorePattern: '^_',
},
],
// These rules are too strict for normal usage, but are useful for optimizing rerenders
// '@arthurgeron/react-usememo/require-usememo': [
// 'warn',
// {
// strict: false,
// checkHookReturnObject: false,
// fix: { addImports: true },
// checkHookCalls: false,
// },
// ],
// '@arthurgeron/react-usememo/require-memo': 'warn',
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-empty-interface': [
@@ -82,26 +65,7 @@ module.exports = {
allowSingleExtends: true,
},
],
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
fixStyle: 'separate-type-imports',
disallowTypeAnnotations: true,
},
],
'@typescript-eslint/no-import-type-side-effects': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
},
overrides: [
{
files: ['*.stories.tsx'],
rules: {
'i18next/no-literal-string': 'off',
},
},
],
settings: {
react: {
version: 'detect',

View File

@@ -8,7 +8,6 @@ pnpm-debug.log*
lerna-debug.log*
node_modules
.pnpm-store
# We want to distribute the repo
dist
dist/**

View File

@@ -9,8 +9,7 @@ index.html
.yarn/
.yalc/
*.scss
src/services/api/schema.ts
src/services/api/schema.d.ts
static/
src/theme/css/overlayscrollbars.css
src/theme_/css/overlayscrollbars.css
pnpm-lock.yaml

View File

@@ -1,25 +0,0 @@
import { PropsWithChildren, memo, useEffect } from 'react';
import { modelChanged } from '../src/features/parameters/store/generationSlice';
import { useAppDispatch } from '../src/app/store/storeHooks';
import { useGlobalModifiersInit } from '../src/common/hooks/useGlobalModifiers';
/**
* Initializes some state for storybook. Must be in a different component
* so that it is run inside the redux context.
*/
export const ReduxInit = memo((props: PropsWithChildren) => {
const dispatch = useAppDispatch();
useGlobalModifiersInit();
useEffect(() => {
dispatch(
modelChanged({
model_name: 'test_model',
base_model: 'sd-1',
model_type: 'main',
})
);
}, []);
return props.children;
});
ReduxInit.displayName = 'ReduxInit';

View File

@@ -6,7 +6,6 @@ const config: StorybookConfig = {
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-storysource',
],
framework: {
name: '@storybook/react-vite',

View File

@@ -1,17 +1,16 @@
import { Preview } from '@storybook/react';
import { themes } from '@storybook/theming';
import i18n from 'i18next';
import React from 'react';
import { initReactI18next } from 'react-i18next';
import { Provider } from 'react-redux';
import GlobalHotkeys from '../src/app/components/GlobalHotkeys';
import ThemeLocaleProvider from '../src/app/components/ThemeLocaleProvider';
import { $baseUrl } from '../src/app/store/nanostores/baseUrl';
import { createStore } from '../src/app/store/store';
import { Container } from '@chakra-ui/react';
// TODO: Disabled for IDE performance issues with our translation JSON
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import translationEN from '../public/locales/en.json';
import { ReduxInit } from './ReduxInit';
i18n.use(initReactI18next).init({
lng: 'en',
@@ -26,21 +25,17 @@ i18n.use(initReactI18next).init({
});
const store = createStore(undefined, false);
$baseUrl.set('http://localhost:9090');
const preview: Preview = {
decorators: [
(Story) => {
return (
<Provider store={store}>
<ThemeLocaleProvider>
<ReduxInit>
<Story />
</ReduxInit>
</ThemeLocaleProvider>
</Provider>
);
},
(Story) => (
<Provider store={store}>
<ThemeLocaleProvider>
<GlobalHotkeys />
<Story />
</ThemeLocaleProvider>
</Provider>
),
],
parameters: {
docs: {

View File

@@ -1,15 +0,0 @@
{
"entry": ["src/main.tsx"],
"extensions": [".ts", ".tsx"],
"ignorePatterns": [
"**/node_modules/**",
"dist/**",
"public/**",
"**/*.stories.tsx",
"config/**"
],
"ignoreUnresolved": [],
"ignoreUnimported": ["src/i18.d.ts", "vite.config.ts", "src/vite-env.d.ts"],
"respectGitignore": true,
"ignoreUnused": []
}

View File

@@ -1,6 +1,6 @@
import react from '@vitejs/plugin-react-swc';
import { visualizer } from 'rollup-plugin-visualizer';
import type { PluginOption, UserConfig } from 'vite';
import { PluginOption, UserConfig } from 'vite';
import eslint from 'vite-plugin-eslint';
import tsconfigPaths from 'vite-tsconfig-paths';

View File

@@ -1,6 +1,5 @@
import type { UserConfig } from 'vite';
import { commonPlugins } from './common.mjs';
import { UserConfig } from 'vite';
import { commonPlugins } from './common';
export const appConfig: UserConfig = {
base: './',

View File

@@ -1,9 +1,8 @@
import path from 'path';
import type { UserConfig } from 'vite';
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
import { UserConfig } from 'vite';
import dts from 'vite-plugin-dts';
import { commonPlugins } from './common.mjs';
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
import { commonPlugins } from './common';
export const packageConfig: UserConfig = {
base: './',

View File

@@ -1,4 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="6" fill="#E6FD13"/>
<path d="M19.2378 10.9H25V7H7V10.9H12.7622L19.2378 21.1H25V25H7V21.1H12.7622" stroke="#181818" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 272 B

View File

@@ -1,3 +0,0 @@
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M29.1951 10.6667H42V2H2V10.6667H14.8049L29.1951 33.3333H42V42H2V33.3333H14.8049" stroke="#E6FD13" stroke-width="2.8"/>
</svg>

Before

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View File

@@ -1,27 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>InvokeAI - A Stable Diffusion Toolkit</title>
<link rel="shortcut icon" type="icon" href="favicon.ico" />
<style>
html,
body {
padding: 0;
margin: 0;
}
</style>
</head>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>InvokeAI - A Stable Diffusion Toolkit</title>
<link rel="mask-icon" type="icon" href="favicon-outline.svg" color="#E6FD13" sizes="any" />
<link rel="icon" type="icon" href="favicon-key.svg" />
<style>
html,
body {
padding: 0;
margin: 0;
}
</style>
</head>
<body dir="ltr">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
<body dir="ltr">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -31,17 +31,13 @@
"lint": "concurrently -g -n eslint,prettier,tsc,madge -c cyan,green,magenta,yellow \"pnpm run lint:eslint\" \"pnpm run lint:prettier\" \"pnpm run lint:tsc\" \"pnpm run lint:madge\"",
"fix": "eslint --fix . && prettier --log-level warn --write .",
"preinstall": "npx only-allow pnpm",
"postinstall": "pnpm run theme",
"postinstall": "patch-package && pnpm run theme",
"theme": "chakra-cli tokens src/theme/theme.ts",
"theme:watch": "chakra-cli tokens src/theme/theme.ts --watch",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"unimported": "npx unimported"
"build-storybook": "storybook build"
},
"madge": {
"excludeRegExp": [
"^index.ts$"
],
"detectiveOptions": {
"ts": {
"skipTypeImports": true
@@ -57,57 +53,56 @@
"@chakra-ui/layout": "^2.3.1",
"@chakra-ui/portal": "^2.1.0",
"@chakra-ui/react": "^2.8.2",
"@chakra-ui/react-use-size": "^2.1.0",
"@chakra-ui/styled-system": "^2.9.2",
"@chakra-ui/theme-tools": "^2.1.2",
"@dagrejs/graphlib": "^2.1.13",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/utilities": "^3.2.2",
"@emotion/react": "^11.11.3",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@fontsource-variable/inter": "^5.0.16",
"@mantine/form": "6.0.21",
"@mantine/core": "^6.0.19",
"@mantine/form": "^6.0.19",
"@mantine/hooks": "^6.0.19",
"@nanostores/react": "^0.7.1",
"@reduxjs/toolkit": "2.0.1",
"@reduxjs/toolkit": "^2.0.1",
"@roarr/browser-log-writer": "^1.3.0",
"chakra-react-select": "^4.7.6",
"@storybook/manager-api": "^7.6.4",
"@storybook/theming": "^7.6.4",
"compare-versions": "^6.1.0",
"dateformat": "^5.0.3",
"framer-motion": "^10.17.9",
"i18next": "^23.7.16",
"framer-motion": "^10.16.15",
"i18next": "^23.7.8",
"i18next-http-backend": "^2.4.2",
"idb-keyval": "^6.2.1",
"jsondiffpatch": "^0.6.0",
"konva": "^9.3.0",
"konva": "^9.2.3",
"lodash-es": "^4.17.21",
"nanostores": "^0.9.5",
"new-github-issue-url": "^1.0.0",
"overlayscrollbars": "^2.4.6",
"overlayscrollbars": "^2.4.5",
"overlayscrollbars-react": "^0.5.3",
"patch-package": "^8.0.0",
"query-string": "^8.1.0",
"react": "^18.2.0",
"react-colorful": "^5.6.1",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-error-boundary": "^4.0.12",
"react-hook-form": "^7.49.2",
"react-hotkeys-hook": "4.4.3",
"react-i18next": "^14.0.0",
"react-error-boundary": "^4.0.11",
"react-hotkeys-hook": "4.4.1",
"react-i18next": "^13.5.0",
"react-icons": "^4.12.0",
"react-konva": "^18.2.10",
"react-redux": "9.0.4",
"react-resizable-panels": "^1.0.7",
"react-select": "5.8.0",
"react-textarea-autosize": "^8.5.3",
"react-redux": "^9.0.2",
"react-resizable-panels": "^0.0.55",
"react-use": "^17.4.2",
"react-virtuoso": "^4.6.2",
"reactflow": "^11.10.1",
"redux-dynamic-middlewares": "^2.2.0",
"redux-remember": "^5.1.0",
"redux-remember": "^5.0.0",
"roarr": "^7.21.0",
"serialize-error": "^11.0.3",
"socket.io-client": "^4.7.3",
"type-fest": "^4.9.0",
"socket.io-client": "^4.7.2",
"type-fest": "^4.8.3",
"use-debounce": "^10.0.0",
"use-image": "^1.1.1",
"uuid": "^9.0.1",
@@ -122,56 +117,44 @@
"ts-toolbelt": "^9.6.0"
},
"devDependencies": {
"@arthurgeron/eslint-plugin-react-usememo": "^2.2.3",
"@chakra-ui/cli": "^2.4.1",
"@storybook/addon-docs": "^7.6.7",
"@storybook/addon-essentials": "^7.6.7",
"@storybook/addon-interactions": "^7.6.7",
"@storybook/addon-links": "^7.6.7",
"@storybook/addon-storysource": "^7.6.7",
"@storybook/blocks": "^7.6.7",
"@storybook/manager-api": "^7.6.7",
"@storybook/react": "^7.6.7",
"@storybook/react-vite": "^7.6.7",
"@storybook/test": "^7.6.7",
"@storybook/theming": "^7.6.7",
"@storybook/addon-essentials": "^7.6.4",
"@storybook/addon-interactions": "^7.6.4",
"@storybook/addon-links": "^7.6.4",
"@storybook/blocks": "^7.6.4",
"@storybook/react": "^7.6.4",
"@storybook/react-vite": "^7.6.4",
"@storybook/test": "^7.6.4",
"@types/dateformat": "^5.0.2",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.10.7",
"@types/react": "^18.2.47",
"@types/react-dom": "^18.2.18",
"@types/node": "^20.9.0",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.17",
"@types/uuid": "^9.0.7",
"@typescript-eslint/eslint-plugin": "^6.18.0",
"@typescript-eslint/parser": "^6.18.0",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"@vitejs/plugin-react-swc": "^3.5.0",
"concurrently": "^8.2.2",
"eslint": "^8.56.0",
"eslint": "^8.55.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-i18next": "^6.0.3",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-path": "^1.2.3",
"eslint-plugin-path": "^1.2.2",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-storybook": "^0.6.15",
"eslint-plugin-unused-imports": "^3.0.0",
"madge": "^6.1.0",
"openapi-types": "^12.1.3",
"openapi-typescript": "^6.7.3",
"prettier": "^3.1.1",
"rollup-plugin-visualizer": "^5.12.0",
"storybook": "^7.6.7",
"openapi-typescript": "^6.7.2",
"prettier": "^3.1.0",
"rollup-plugin-visualizer": "^5.10.0",
"storybook": "^7.6.4",
"ts-toolbelt": "^9.6.0",
"typescript": "^5.3.3",
"vite": "^5.0.11",
"vite-plugin-css-injected-by-js": "^3.3.1",
"vite-plugin-dts": "^3.7.0",
"vite": "^4.5.1",
"vite-plugin-css-injected-by-js": "^3.3.0",
"vite-plugin-dts": "^3.6.4",
"vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.2.3"
},
"pnpm": {
"patchedDependencies": {
"reselect@5.0.1": "patches/reselect@5.0.1.patch"
}
"vite-tsconfig-paths": "^4.2.2"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -83,6 +83,10 @@
"title": "خيارات التثبيت",
"desc": "ثبت لوحة الخيارات"
},
"toggleViewer": {
"title": "تبديل العارض",
"desc": "فتح وإغلاق مشاهد الصور"
},
"toggleGallery": {
"title": "تبديل المعرض",
"desc": "فتح وإغلاق درابزين المعرض"
@@ -143,6 +147,10 @@
"title": "الصورة التالية",
"desc": "عرض الصورة التالية في الصالة"
},
"toggleGalleryPin": {
"title": "تبديل تثبيت الصالة",
"desc": "يثبت ويفتح تثبيت الصالة على الواجهة الرسومية"
},
"increaseGalleryThumbSize": {
"title": "زيادة حجم صورة الصالة",
"desc": "يزيد حجم الصور المصغرة في الصالة"

View File

@@ -168,6 +168,10 @@
"title": "Optionen anheften",
"desc": "Anheften des Optionsfeldes"
},
"toggleViewer": {
"title": "Bildbetrachter umschalten",
"desc": "Bildbetrachter öffnen und schließen"
},
"toggleGallery": {
"title": "Galerie umschalten",
"desc": "Öffnen und Schließen des Galerie-Schubfachs"
@@ -228,6 +232,10 @@
"title": "Nächstes Bild",
"desc": "Nächstes Bild in Galerie anzeigen"
},
"toggleGalleryPin": {
"title": "Galerie anheften umschalten",
"desc": "Heftet die Galerie an die Benutzeroberfläche bzw. löst die sie"
},
"increaseGalleryThumbSize": {
"title": "Größe der Galeriebilder erhöhen",
"desc": "Vergrößert die Galerie-Miniaturansichten"
@@ -752,6 +760,7 @@
"w": "W",
"addControlNet": "$t(common.controlNet) hinzufügen",
"none": "Kein",
"incompatibleBaseModel": "Inkompatibles Basismodell:",
"enableControlnet": "Aktiviere ControlNet",
"detectResolution": "Auflösung erkennen",
"controlNetT2IMutexDesc": "$t(common.controlNet) und $t(common.t2iAdapter) zur gleichen Zeit wird nicht unterstützt.",

View File

@@ -50,33 +50,9 @@
"uncategorized": "Uncategorized",
"downloadBoard": "Download Board"
},
"accordions": {
"generation": {
"title": "Generation",
"modelTab": "Model",
"conceptsTab": "Concepts"
},
"image": {
"title": "Image"
},
"advanced": {
"title": "Advanced"
},
"control": {
"title": "Control",
"controlAdaptersTab": "Control Adapters",
"ipTab": "Image Prompts"
},
"compositing": {
"title": "Compositing",
"coherenceTab": "Coherence Pass",
"infillTab": "Infill"
}
},
"common": {
"accept": "Accept",
"advanced": "Advanced",
"advancedOptions": "Advanced Options",
"ai": "ai",
"areYouSure": "Are you sure?",
"auto": "Auto",
@@ -86,7 +62,6 @@
"copyError": "$t(gallery.copy) Error",
"close": "Close",
"on": "On",
"or": "or",
"checkpoint": "Checkpoint",
"communityLabel": "Community",
"controlNet": "ControlNet",
@@ -104,7 +79,6 @@
"file": "File",
"folder": "Folder",
"format": "format",
"free": "Free",
"generate": "Generate",
"githubLabel": "Github",
"hotkeysLabel": "Hotkeys",
@@ -157,7 +131,6 @@
"save": "Save",
"saveAs": "Save As",
"settingsLabel": "Settings",
"preferencesLabel": "Preferences",
"simple": "Simple",
"somethingWentWrong": "Something went wrong",
"statusConnected": "Connected",
@@ -248,6 +221,7 @@
"colorMapTileSize": "Tile Size",
"importImageFromCanvas": "Import Image From Canvas",
"importMaskFromCanvas": "Import Mask From Canvas",
"incompatibleBaseModel": "Incompatible base model:",
"lineart": "Lineart",
"lineartAnime": "Lineart Anime",
"lineartAnimeDescription": "Anime-style lineart processing",
@@ -272,7 +246,6 @@
"prompt": "Prompt",
"resetControlImage": "Reset Control Image",
"resize": "Resize",
"resizeSimple": "Resize (Simple)",
"resizeMode": "Resize Mode",
"safe": "Safe",
"saveControlImage": "Save Control Image",
@@ -311,7 +284,7 @@
"queue": "Queue",
"queueFront": "Add to Front of Queue",
"queueBack": "Add to Queue",
"queueCountPrediction": "{{promptsCount}} prompts × {{iterations}} iterations -> {{count}} generations",
"queueCountPrediction": "Add {{predicted}} to Queue",
"queueMaxExceeded": "Max of {{max_queue_size}} exceeded, would skip {{skip}}",
"queuedCount": "{{pending}} Pending",
"queueTotal": "{{total}} Total",
@@ -367,8 +340,7 @@
"back": "back",
"batchFailedToQueue": "Failed to Queue Batch",
"graphQueued": "Graph queued",
"graphFailedToQueue": "Failed to queue graph",
"openQueue": "Open Queue"
"graphFailedToQueue": "Failed to queue graph"
},
"invocationCache": {
"invocationCache": "Invocation Cache",
@@ -428,9 +400,6 @@
"problemDeletingImagesDesc": "One or more images could not be deleted"
},
"hotkeys": {
"searchHotkeys": "Search Hotkeys",
"clearSearch": "Clear Search",
"noHotkeysFound": "No Hotkeys Found",
"acceptStagingImage": {
"desc": "Accept Current Staging Area Image",
"title": "Accept Staging Image"
@@ -439,15 +408,11 @@
"desc": "Opens the add node menu",
"title": "Add Nodes"
},
"appHotkeys": "App",
"appHotkeys": "App Hotkeys",
"cancel": {
"desc": "Cancel current queue item",
"desc": "Cancel image generation",
"title": "Cancel"
},
"cancelAndClear": {
"desc": "Cancel current queue item and clear all pending items",
"title": "Cancel and Clear"
},
"changeTabs": {
"desc": "Switch to another workspace",
"title": "Change Tabs"
@@ -504,8 +469,8 @@
"desc": "Focus the prompt input area",
"title": "Focus Prompt"
},
"galleryHotkeys": "Gallery",
"generalHotkeys": "General",
"galleryHotkeys": "Gallery Hotkeys",
"generalHotkeys": "General Hotkeys",
"hideMask": {
"desc": "Hide and unhide mask",
"title": "Hide Mask"
@@ -526,7 +491,7 @@
"desc": "Generate an image",
"title": "Invoke"
},
"keyboardShortcuts": "Hotkeys",
"keyboardShortcuts": "Keyboard Shortcuts",
"maximizeWorkSpace": {
"desc": "Close panels and maximize work area",
"title": "Maximize Workspace"
@@ -547,7 +512,7 @@
"desc": "Next Staging Area Image",
"title": "Next Staging Image"
},
"nodesHotkeys": "Nodes",
"nodesHotkeys": "Nodes Hotkeys",
"pinOptions": {
"desc": "Pin the options panel",
"title": "Pin Options"
@@ -616,31 +581,31 @@
"desc": "Open and close the gallery drawer",
"title": "Toggle Gallery"
},
"toggleOptions": {
"desc": "Open and close the options panel",
"title": "Toggle Options"
},
"toggleOptionsAndGallery": {
"desc": "Open and close the options and gallery panels",
"title": "Toggle Options and Gallery"
},
"resetOptionsAndGallery": {
"desc": "Resets the options and gallery panels",
"title": "Reset Options and Gallery"
"toggleGalleryPin": {
"desc": "Pins and unpins the gallery to the UI",
"title": "Toggle Gallery Pin"
},
"toggleLayer": {
"desc": "Toggles mask/base layer selection",
"title": "Toggle Layer"
},
"toggleOptions": {
"desc": "Open and close the options panel",
"title": "Toggle Options"
},
"toggleSnap": {
"desc": "Toggles Snap to Grid",
"title": "Toggle Snap"
},
"toggleViewer": {
"desc": "Open and close Image Viewer",
"title": "Toggle Viewer"
},
"undoStroke": {
"desc": "Undo a brush stroke",
"title": "Undo Stroke"
},
"unifiedCanvasHotkeys": "Unified Canvas",
"unifiedCanvasHotkeys": "Unified Canvas Hotkeys",
"upscale": {
"desc": "Upscale the current image",
"title": "Upscale"
@@ -823,23 +788,17 @@
},
"models": {
"addLora": "Add LoRA",
"allLoRAsAdded": "All LoRAs added",
"loraAlreadyAdded": "LoRA already added",
"esrganModel": "ESRGAN Model",
"loading": "loading",
"incompatibleBaseModel": "Incompatible base model",
"noMainModelSelected": "No main model selected",
"noLoRAsAvailable": "No LoRAs available",
"noLoRAsLoaded": "No LoRAs Loaded",
"noMatchingLoRAs": "No matching LoRAs",
"noMatchingModels": "No matching Models",
"noModelsAvailable": "No models available",
"lora": "LoRA",
"selectLoRA": "Select a LoRA",
"selectModel": "Select a Model",
"noLoRAsInstalled": "No LoRAs installed",
"noRefinerModelsInstalled": "No SDXL Refiner models installed",
"defaultVAE": "Default VAE"
"noRefinerModelsInstalled": "No SDXL Refiner models installed"
},
"nodes": {
"addNode": "Add Node",
@@ -1078,14 +1037,8 @@
"prototypeDesc": "This invocation is a prototype. It may have breaking changes during app updates and may be removed at any time."
},
"parameters": {
"aspect": "Aspect",
"aspectRatio": "Aspect Ratio",
"aspectRatioFree": "Free",
"lockAspectRatio": "Lock Aspect Ratio",
"swapDimensions": "Swap Dimensions",
"setToOptimalSize": "Optimize size for model",
"setToOptimalSizeTooSmall": "$t(parameters.setToOptimalSize) (may be too small)",
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (may be too large)",
"boundingBoxHeader": "Bounding Box",
"boundingBoxHeight": "Bounding Box Height",
"boundingBoxWidth": "Bounding Box Width",
@@ -1124,7 +1077,6 @@
"imageFit": "Fit Initial Image To Output Size",
"images": "Images",
"imageToImage": "Image to Image",
"imageSize": "Image Size",
"img2imgStrength": "Image To Image Strength",
"infillMethod": "Infill Method",
"infillScalingHeader": "Infill and Scaling",
@@ -1139,7 +1091,7 @@
"noControlImageForControlAdapter": "Control Adapter #{{number}} has no control image",
"noInitialImageSelected": "No initial image selected",
"noModelForControlAdapter": "Control Adapter #{{number}} has no model selected.",
"incompatibleBaseModelForControlAdapter": "Control Adapter #{{number}} model is incompatible with main model.",
"incompatibleBaseModelForControlAdapter": "Control Adapter #{{number}} model is invalid with main model.",
"noModelSelected": "No model selected",
"noPrompts": "No prompts generated",
"noNodesInGraph": "No nodes in graph",
@@ -1175,8 +1127,8 @@
"seamCorrectionHeader": "Seam Correction",
"seamHighThreshold": "High",
"seamlessTiling": "Seamless Tiling",
"seamlessXAxis": "Seamless Tiling X Axis",
"seamlessYAxis": "Seamless Tiling Y Axis",
"seamlessXAxis": "X Axis",
"seamlessYAxis": "Y Axis",
"seamlessX": "Seamless X",
"seamlessY": "Seamless Y",
"seamlessX&Y": "Seamless X & Y",
@@ -1219,7 +1171,6 @@
},
"dynamicPrompts": {
"combinatorial": "Combinatorial Generation",
"showDynamicPrompts": "Show Dynamic Prompts",
"dynamicPrompts": "Dynamic Prompts",
"enableDynamicPrompts": "Enable Dynamic Prompts",
"maxPrompts": "Max Prompts",
@@ -1232,13 +1183,11 @@
"perIterationDesc": "Use a different seed for each iteration",
"perPromptLabel": "Seed per Image",
"perPromptDesc": "Use a different seed for each image"
},
"loading": "Generating Dynamic Prompts..."
}
},
"sdxl": {
"cfgScale": "CFG Scale",
"concatPromptStyle": "Linking Prompt & Style",
"freePromptStyle": "Manual Style Prompting",
"concatPromptStyle": "Concatenate Prompt & Style",
"denoisingStrength": "Denoising Strength",
"loading": "Loading...",
"negAestheticScore": "Negative Aesthetic Score",

View File

@@ -127,6 +127,10 @@
"title": "Fijar opciones",
"desc": "Fijar el panel de opciones"
},
"toggleViewer": {
"title": "Alternar visor",
"desc": "Mostar y ocultar el visor de imágenes"
},
"toggleGallery": {
"title": "Alternar galería",
"desc": "Mostar y ocultar la galería de imágenes"
@@ -187,6 +191,10 @@
"title": "Imagen siguiente",
"desc": "Muetra la imagen siguiente en la galería"
},
"toggleGalleryPin": {
"title": "Alternar fijado de galería",
"desc": "Fijar o desfijar la galería en la interfaz"
},
"increaseGalleryThumbSize": {
"title": "Aumentar imagen en galería",
"desc": "Aumenta el tamaño de las miniaturas de la galería"

View File

@@ -96,6 +96,10 @@
"title": "Epinglage des options",
"desc": "Epingler le panneau d'options"
},
"toggleViewer": {
"title": "Affichage de la visionneuse",
"desc": "Afficher et masquer la visionneuse d'image"
},
"toggleGallery": {
"title": "Affichage de la galerie",
"desc": "Afficher et masquer la galerie"
@@ -156,6 +160,10 @@
"title": "Image suivante",
"desc": "Afficher l'image suivante dans la galerie"
},
"toggleGalleryPin": {
"title": "Activer/désactiver l'épinglage de la galerie",
"desc": "Épingle ou dépingle la galerie à l'interface"
},
"increaseGalleryThumbSize": {
"title": "Augmenter la taille des miniatures de la galerie",
"desc": "Augmente la taille des miniatures de la galerie"

View File

@@ -192,6 +192,10 @@
"title": "הצמד הגדרות",
"desc": "הצמד את פאנל ההגדרות"
},
"toggleViewer": {
"title": "הצג את חלון ההצגה",
"desc": "פתח וסגור את מציג התמונות"
},
"changeTabs": {
"title": "החלף לשוניות",
"desc": "החלף לאיזור עבודה אחר"
@@ -232,6 +236,10 @@
"title": "תמונה קודמת",
"desc": "הצג את התמונה הקודמת בגלריה"
},
"toggleGalleryPin": {
"title": "הצג את מצמיד הגלריה",
"desc": "הצמדה וביטול הצמדה של הגלריה לממשק המשתמש"
},
"decreaseGalleryThumbSize": {
"title": "הקטנת גודל תמונת גלריה",
"desc": "מקטין את גודל התמונות הממוזערות של הגלריה"

View File

@@ -114,9 +114,7 @@
"nextPage": "Pagina successiva",
"saveAs": "Salva come",
"unsaved": "Non salvato",
"direction": "Direzione",
"advancedOptions": "Opzioni avanzate",
"free": "Libero"
"direction": "Direzione"
},
"gallery": {
"generations": "Generazioni",
@@ -134,7 +132,7 @@
"noImagesInGallery": "Nessuna immagine da visualizzare",
"deleteImage": "Elimina l'immagine",
"deleteImagePermanent": "Le immagini eliminate non possono essere ripristinate.",
"deleteImageBin": "Le immagini eliminate verranno spostate nel cestino del tuo sistema operativo.",
"deleteImageBin": "Le immagini eliminate verranno spostate nel Cestino del tuo sistema operativo.",
"assets": "Risorse",
"autoAssignBoardOnClick": "Assegna automaticamente la bacheca al clic",
"featuresWillReset": "Se elimini questa immagine, quelle funzionalità verranno immediatamente ripristinate.",
@@ -159,18 +157,18 @@
"problemDeletingImages": "Problema durante l'eliminazione delle immagini"
},
"hotkeys": {
"keyboardShortcuts": "Tasti di scelta rapida",
"appHotkeys": "Applicazione",
"generalHotkeys": "Generale",
"galleryHotkeys": "Galleria",
"unifiedCanvasHotkeys": "Tela Unificata",
"keyboardShortcuts": "Tasti rapidi",
"appHotkeys": "Tasti di scelta rapida dell'applicazione",
"generalHotkeys": "Tasti di scelta rapida generali",
"galleryHotkeys": "Tasti di scelta rapida della galleria",
"unifiedCanvasHotkeys": "Tasti di scelta rapida Tela Unificata",
"invoke": {
"title": "Invoke",
"desc": "Genera un'immagine"
},
"cancel": {
"title": "Annulla",
"desc": "Annulla l'elemento della coda corrente"
"desc": "Annulla la generazione dell'immagine"
},
"focusPrompt": {
"title": "Metti a fuoco il Prompt",
@@ -184,8 +182,12 @@
"title": "Appunta le opzioni",
"desc": "Blocca il pannello delle opzioni"
},
"toggleViewer": {
"title": "Attiva/disattiva visualizzatore",
"desc": "Apre e chiude il visualizzatore immagini"
},
"toggleGallery": {
"title": "Attiva/disattiva galleria",
"title": "Attiva/disattiva Galleria",
"desc": "Apre e chiude il pannello della galleria"
},
"maximizeWorkSpace": {
@@ -244,6 +246,10 @@
"title": "Immagine successiva",
"desc": "Visualizza l'immagine successiva nella galleria"
},
"toggleGalleryPin": {
"title": "Attiva/disattiva il blocco della galleria",
"desc": "Blocca/sblocca la galleria dall'interfaccia utente"
},
"increaseGalleryThumbSize": {
"title": "Aumenta dimensione immagini nella galleria",
"desc": "Aumenta la dimensione delle miniature della galleria"
@@ -356,26 +362,11 @@
"title": "Accetta l'immagine della sessione",
"desc": "Accetta l'immagine dell'area della sessione corrente"
},
"nodesHotkeys": "Nodi",
"nodesHotkeys": "Tasti di scelta rapida dei Nodi",
"addNodes": {
"title": "Aggiungi Nodi",
"desc": "Apre il menu Aggiungi Nodi"
},
"cancelAndClear": {
"desc": "Annulla l'elemento della coda corrente e cancella tutti gli elementi in sospeso",
"title": "Annulla e cancella"
},
"resetOptionsAndGallery": {
"title": "Ripristina Opzioni e Galleria",
"desc": "Reimposta le opzioni e i pannelli della galleria"
},
"searchHotkeys": "Cerca tasti di scelta rapida",
"noHotkeysFound": "Nessun tasto di scelta rapida trovato",
"toggleOptionsAndGallery": {
"desc": "Apre e chiude le opzioni e i pannelli della galleria",
"title": "Attiva/disattiva le Opzioni e la Galleria"
},
"clearSearch": "Cancella ricerca"
}
},
"modelManager": {
"modelManager": "Gestione Modelli",
@@ -590,8 +581,8 @@
"hidePreview": "Nascondi l'anteprima",
"showPreview": "Mostra l'anteprima",
"noiseSettings": "Rumore",
"seamlessXAxis": "Piastrella senza cucitura Asse X",
"seamlessYAxis": "Piastrella senza cucitura Asse Y",
"seamlessXAxis": "Asse X",
"seamlessYAxis": "Asse Y",
"scheduler": "Campionatore",
"boundingBoxWidth": "Larghezza riquadro di delimitazione",
"boundingBoxHeight": "Altezza riquadro di delimitazione",
@@ -651,14 +642,7 @@
"unmasked": "No maschera",
"cfgRescaleMultiplier": "Moltiplicatore riscala CFG",
"cfgRescale": "Riscala CFG",
"useSize": "Usa Dimensioni",
"setToOptimalSize": "Ottimizza le dimensioni per il modello",
"setToOptimalSizeTooSmall": "$t(parameters.setToOptimalSize) (potrebbe essere troppo piccolo)",
"imageSize": "Dimensione dell'immagine",
"lockAspectRatio": "Blocca proporzioni",
"swapDimensions": "Scambia dimensioni",
"aspect": "Aspetto",
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (potrebbe essere troppo grande)"
"useSize": "Usa Dimensioni"
},
"settings": {
"models": "Modelli",
@@ -1194,6 +1178,7 @@
"w": "W",
"processor": "Processore",
"none": "Nessuno",
"incompatibleBaseModel": "Modello base incompatibile:",
"pidiDescription": "Elaborazione immagini PIDI",
"fill": "Riempie",
"colorMapDescription": "Genera una mappa dei colori dall'immagine",
@@ -1229,13 +1214,12 @@
"minConfidence": "Confidenza minima",
"scribble": "Scribble",
"amult": "Angolo di illuminazione",
"coarse": "Approssimativo",
"resizeSimple": "Ridimensiona (semplice)"
"coarse": "Approssimativo"
},
"queue": {
"queueFront": "Aggiungi all'inizio della coda",
"queueBack": "Aggiungi alla coda",
"queueCountPrediction": "{{promptsCount}} prompt × {{iterations}} iterazioni -> {{count}} generazioni",
"queueCountPrediction": "Aggiungi {{predicted}} alla coda",
"queue": "Coda",
"status": "Stato",
"pruneSucceeded": "Rimossi {{item_count}} elementi completati dalla coda",
@@ -1293,8 +1277,7 @@
"graphFailedToQueue": "Impossibile mettere in coda il grafico",
"queueMaxExceeded": "È stato superato il limite massimo di {{max_queue_size}} e {{skip}} elementi verrebbero saltati",
"batchFieldValues": "Valori Campi Lotto",
"time": "Tempo",
"openQueue": "Apri coda"
"time": "Tempo"
},
"embedding": {
"noMatchingEmbedding": "Nessun Incorporamento corrispondente",
@@ -1314,12 +1297,7 @@
"noLoRAsInstalled": "Nessun LoRA installato",
"esrganModel": "Modello ESRGAN",
"addLora": "Aggiungi LoRA",
"noLoRAsLoaded": "Nessun LoRA caricato",
"noMainModelSelected": "Nessun modello principale selezionato",
"allLoRAsAdded": "Tutti i LoRA aggiunti",
"defaultVAE": "VAE predefinito",
"incompatibleBaseModel": "Modello base incompatibile",
"loraAlreadyAdded": "LoRA già aggiunto"
"noLoRAsLoaded": "Nessuna LoRA caricata"
},
"invocationCache": {
"disable": "Disabilita",
@@ -1353,9 +1331,7 @@
"promptsWithCount_many": "{{count}} Prompt",
"promptsWithCount_other": "{{count}} Prompt",
"dynamicPrompts": "Prompt dinamici",
"promptsPreview": "Anteprima dei prompt",
"showDynamicPrompts": "Mostra prompt dinamici",
"loading": "Generazione prompt dinamici..."
"promptsPreview": "Anteprima dei prompt"
},
"popovers": {
"paramScheduler": {
@@ -1581,7 +1557,7 @@
"scheduler": "Campionatore",
"noModelsAvailable": "Nessun modello disponibile",
"denoisingStrength": "Forza di riduzione del rumore",
"concatPromptStyle": "Collega Prompt & Stile",
"concatPromptStyle": "Concatena Prompt & Stile",
"loading": "Caricamento...",
"steps": "Passi",
"refinerStart": "Inizio Affinamento",
@@ -1592,8 +1568,7 @@
"useRefiner": "Utilizza l'affinatore",
"refinermodel": "Modello Affinatore",
"posAestheticScore": "Punteggio estetico positivo",
"posStylePrompt": "Prompt Stile positivo",
"freePromptStyle": "Prompt di stile manuale"
"posStylePrompt": "Prompt Stile positivo"
},
"metadata": {
"initImage": "Immagine iniziale",
@@ -1666,28 +1641,5 @@
},
"app": {
"storeNotInitialized": "Il negozio non è inizializzato"
},
"accordions": {
"compositing": {
"infillTab": "Riempimento",
"coherenceTab": "Passaggio di coerenza",
"title": "Composizione"
},
"control": {
"controlAdaptersTab": "Adattatori di Controllo",
"ipTab": "Prompt immagine",
"title": "Controllo"
},
"generation": {
"title": "Generazione",
"conceptsTab": "Concetti",
"modelTab": "Modello"
},
"advanced": {
"title": "Avanzate"
},
"image": {
"title": "Immagine"
}
}
}

View File

@@ -133,6 +133,10 @@
"title": "ピン",
"desc": "オプションパネルを固定"
},
"toggleViewer": {
"title": "ビュワーのトグル",
"desc": "ビュワーを開閉"
},
"toggleGallery": {
"title": "ギャラリーのトグル",
"desc": "ギャラリードロワーの開閉"
@@ -193,6 +197,10 @@
"title": "次の画像",
"desc": "ギャラリー内の1つ後の画像を表示"
},
"toggleGalleryPin": {
"title": "ギャラリードロワーの固定",
"desc": "ギャラリーをUIにピン留め/解除"
},
"increaseGalleryThumbSize": {
"title": "ギャラリーの画像を拡大",
"desc": "ギャラリーのサムネイル画像を拡大"
@@ -582,6 +590,7 @@
"processor": "プロセッサー",
"addControlNet": "$t(common.controlNet)を追加",
"none": "なし",
"incompatibleBaseModel": "互換性のないベースモデル:",
"enableControlnet": "コントロールネットを有効化",
"detectResolution": "検出解像度",
"controlNetT2IMutexDesc": "$t(common.controlNet)と$t(common.t2iAdapter)の同時使用は現在サポートされていません。",

View File

@@ -363,6 +363,7 @@
"processor": "프로세서",
"addControlNet": "$t(common.controlNet) 추가",
"none": "해당없음",
"incompatibleBaseModel": "호환되지 않는 기본 모델:",
"enableControlnet": "사용 가능한 ControlNet",
"detectResolution": "해상도 탐지",
"controlNetT2IMutexDesc": "$t(common.controlNet)와 $t(common.t2iAdapter)는 현재 동시에 지원되지 않습니다.",
@@ -407,6 +408,10 @@
"maxFaces": "Max Faces"
},
"hotkeys": {
"toggleGalleryPin": {
"title": "Gallery Pin 전환",
"desc": "갤러리를 UI에 고정했다가 풉니다"
},
"toggleSnap": {
"desc": "Snap을 Grid로 전환",
"title": "Snap 전환"
@@ -596,6 +601,10 @@
"desc": "노드 추가 메뉴 열기",
"title": "노드 추가"
},
"toggleViewer": {
"desc": "이미지 뷰어 열기 및 닫기",
"title": "Viewer 전환"
},
"undoStroke": {
"title": "Stroke 실행 취소",
"desc": "brush stroke 실행 취소"

View File

@@ -148,6 +148,10 @@
"title": "Zet Opties vast",
"desc": "Zet het deelscherm Opties vast"
},
"toggleViewer": {
"title": "Zet Viewer vast",
"desc": "Opent of sluit Afbeeldingsviewer"
},
"toggleGallery": {
"title": "Zet Galerij vast",
"desc": "Opent of sluit het deelscherm Galerij"
@@ -208,6 +212,10 @@
"title": "Volgende afbeelding",
"desc": "Toont de volgende afbeelding in de galerij"
},
"toggleGalleryPin": {
"title": "Zet galerij vast/los",
"desc": "Zet de galerij vast of los aan de gebruikersinterface"
},
"increaseGalleryThumbSize": {
"title": "Vergroot afbeeldingsgrootte galerij",
"desc": "Vergroot de grootte van de galerijminiaturen"
@@ -1063,6 +1071,7 @@
"processor": "Verwerker",
"addControlNet": "Voeg $t(common.controlNet) toe",
"none": "Geen",
"incompatibleBaseModel": "Niet-compatibel basismodel:",
"enableControlnet": "Schakel ControlNet in",
"detectResolution": "Herken resolutie",
"controlNetT2IMutexDesc": "Gelijktijdig gebruik van $t(common.controlNet) en $t(common.t2iAdapter) wordt op dit moment niet ondersteund.",

View File

@@ -86,6 +86,10 @@
"title": "Przypnij opcje",
"desc": "Przypina panel opcji"
},
"toggleViewer": {
"title": "Przełącz podgląd",
"desc": "Otwiera lub zamyka widok podglądu"
},
"toggleGallery": {
"title": "Przełącz galerię",
"desc": "Wysuwa lub chowa galerię"
@@ -146,6 +150,10 @@
"title": "Następny obraz",
"desc": "Aktywuje następny obraz z galerii"
},
"toggleGalleryPin": {
"title": "Przypnij galerię",
"desc": "Przypina lub odpina widok galerii"
},
"increaseGalleryThumbSize": {
"title": "Powiększ obrazy",
"desc": "Powiększa rozmiar obrazów w galerii"

View File

@@ -81,6 +81,10 @@
"hotkeys": {
"generalHotkeys": "Atalhos Gerais",
"galleryHotkeys": "Atalhos da Galeria",
"toggleViewer": {
"title": "Ativar Visualizador",
"desc": "Abrir e fechar o Visualizador de Imagens"
},
"maximizeWorkSpace": {
"desc": "Fechar painéis e maximixar área de trabalho",
"title": "Maximizar a Área de Trabalho"
@@ -228,6 +232,10 @@
"title": "Apagar Imagem",
"desc": "Apaga a imagem atual"
},
"toggleGalleryPin": {
"title": "Ativar Fixar Galeria",
"desc": "Fixa e desafixa a galeria na interface"
},
"increaseGalleryThumbSize": {
"title": "Aumentar Tamanho da Galeria de Imagem",
"desc": "Aumenta o tamanho das thumbs na galeria"

View File

@@ -103,6 +103,10 @@
"title": "Fixar Opções",
"desc": "Fixar o painel de opções"
},
"toggleViewer": {
"title": "Ativar Visualizador",
"desc": "Abrir e fechar o Visualizador de Imagens"
},
"toggleGallery": {
"title": "Ativar Galeria",
"desc": "Abrir e fechar a gaveta da galeria"
@@ -163,6 +167,10 @@
"title": "Próxima Imagem",
"desc": "Mostra a próxima imagem na galeria"
},
"toggleGalleryPin": {
"title": "Ativar Fixar Galeria",
"desc": "Fixa e desafixa a galeria na interface"
},
"increaseGalleryThumbSize": {
"title": "Aumentar Tamanho da Galeria de Imagem",
"desc": "Aumenta o tamanho das thumbs na galeria"

View File

@@ -121,11 +121,7 @@
"unsaved": "несохраненный",
"input": "Вход",
"details": "Детали",
"notInstalled": "Нет $t(common.installed)",
"preferencesLabel": "Предпочтения",
"or": "или",
"advancedOptions": "Расширенные настройки",
"free": "Свободно"
"notInstalled": "Нет $t(common.installed)"
},
"gallery": {
"generations": "Генерации",
@@ -193,6 +189,10 @@
"title": "Закрепить параметры",
"desc": "Закрепить панель параметров"
},
"toggleViewer": {
"title": "Показать просмотр",
"desc": "Открывать и закрывать просмотрщик изображений"
},
"toggleGallery": {
"title": "Показать галерею",
"desc": "Открывать и закрывать ящик галереи"
@@ -253,6 +253,10 @@
"title": "Следующее изображение",
"desc": "Отображение следующего изображения в галерее"
},
"toggleGalleryPin": {
"title": "Закрепить галерею",
"desc": "Закрепляет и открепляет галерею"
},
"increaseGalleryThumbSize": {
"title": "Увеличить размер миниатюр галереи",
"desc": "Увеличивает размер миниатюр галереи"
@@ -369,22 +373,7 @@
"desc": "Открывает меню добавления узла",
"title": "Добавление узлов"
},
"nodesHotkeys": "Горячие клавиши узлов",
"cancelAndClear": {
"desc": "Отмена текущего элемента очереди и очистка всех ожидающих элементов",
"title": "Отменить и очистить"
},
"resetOptionsAndGallery": {
"title": "Сброс настроек и галереи",
"desc": "Сброс панелей галереи и настроек"
},
"searchHotkeys": "Поиск горячих клавиш",
"noHotkeysFound": "Горячие клавиши не найдены",
"toggleOptionsAndGallery": {
"desc": "Открытие и закрытие панели опций и галереи",
"title": "Переключить опции и галерею"
},
"clearSearch": "Очистить поиск"
"nodesHotkeys": "Горячие клавиши узлов"
},
"modelManager": {
"modelManager": "Менеджер моделей",
@@ -534,7 +523,7 @@
"parameters": {
"images": "Изображения",
"steps": "Шаги",
"cfgScale": "Шкала точности (CFG)",
"cfgScale": "Точность следования запросу (CFG)",
"width": "Ширина",
"height": "Высота",
"seed": "Сид",
@@ -1173,6 +1162,7 @@
"processor": "Процессор",
"addControlNet": "Добавить $t(common.controlNet)",
"none": "ничего",
"incompatibleBaseModel": "Несовместимая базовая модель:",
"controlNetT2IMutexDesc": "$t(common.controlNet) и $t(common.t2iAdapter) одновременно в настоящее время не поддерживаются.",
"ip_adapter": "$t(controlnet.controlAdapter_one) №{{number}} $t(common.ipAdapter)",
"pidiDescription": "PIDI-обработка изображений",
@@ -1207,8 +1197,7 @@
"handAndFace": "Руки и Лицо",
"enableIPAdapter": "Включить IP Adapter",
"maxFaces": "Макс Лица",
"mlsdDescription": "Минималистичный детектор отрезков линии",
"resizeSimple": "Изменить размер (простой)"
"mlsdDescription": "Минималистичный детектор отрезков линии"
},
"boards": {
"autoAddBoard": "Авто добавление Доски",
@@ -1560,8 +1549,7 @@
"cancelBatchFailed": "Проблема с отменой пакета",
"clearQueueAlertDialog2": "Вы уверены, что хотите очистить очередь?",
"item": "Элемент",
"graphFailedToQueue": "Не удалось поставить график в очередь",
"openQueue": "Открыть очередь"
"graphFailedToQueue": "Не удалось поставить график в очередь"
},
"sdxl": {
"refinerStart": "Запуск перерисовщика",
@@ -1656,38 +1644,9 @@
"selectModel": "Выберите модель",
"noRefinerModelsInstalled": "Модели SDXL Refiner не установлены",
"noLoRAsInstalled": "Нет установленных LoRA",
"selectLoRA": "Выберите LoRA",
"noMainModelSelected": "Базовая модель не выбрана",
"lora": "LoRA",
"allLoRAsAdded": "Все LoRA добавлены",
"defaultVAE": "Стандартное VAE",
"incompatibleBaseModel": "Несовместимая базовая модель",
"loraAlreadyAdded": "LoRA уже добавлена"
"selectLoRA": "Выберите LoRA"
},
"app": {
"storeNotInitialized": "Магазин не инициализирован"
},
"accordions": {
"compositing": {
"infillTab": "Заполнение",
"coherenceTab": "Согласованность",
"title": "Композиция"
},
"control": {
"controlAdaptersTab": "Адаптеры контроля",
"ipTab": "Запросы изображений",
"title": "Контроль"
},
"generation": {
"title": "Генерация",
"conceptsTab": "Концепты",
"modelTab": "Модель"
},
"advanced": {
"title": "Расширенные"
},
"image": {
"title": "Изображение"
}
}
}

View File

@@ -129,6 +129,10 @@
"title": "Växla inställningar",
"desc": "Öppna och stäng alternativpanelen"
},
"toggleViewer": {
"title": "Växla visaren",
"desc": "Öppna och stäng bildvisaren"
},
"toggleGallery": {
"title": "Växla galleri",
"desc": "Öppna eller stäng galleribyrån"
@@ -189,6 +193,10 @@
"title": "Nästa bild",
"desc": "Visa nästa bild"
},
"toggleGalleryPin": {
"title": "Växla gallerinål",
"desc": "Nålar fast eller nålar av galleriet i gränssnittet"
},
"increaseGalleryThumbSize": {
"title": "Förstora galleriets bildstorlek",
"desc": "Förstora miniatyrbildernas storlek"

View File

@@ -111,6 +111,10 @@
"title": "Закріпити параметри",
"desc": "Закріпити панель параметрів"
},
"toggleViewer": {
"title": "Показати перегляд",
"desc": "Відкривати і закривати переглядач зображень"
},
"toggleGallery": {
"title": "Показати галерею",
"desc": "Відкривати і закривати скриньку галереї"
@@ -171,6 +175,10 @@
"title": "Наступне зображення",
"desc": "Відображення наступного зображення в галереї"
},
"toggleGalleryPin": {
"title": "Закріпити галерею",
"desc": "Закріплює і відкріплює галерею"
},
"increaseGalleryThumbSize": {
"title": "Збільшити розмір мініатюр галереї",
"desc": "Збільшує розмір мініатюр галереї"

View File

@@ -121,11 +121,7 @@
"nextPage": "下一页",
"saveAs": "保存为",
"unsaved": "未保存",
"ai": "ai",
"preferencesLabel": "首选项",
"or": "或",
"advancedOptions": "高级选项",
"free": "自由"
"ai": "ai"
},
"gallery": {
"generations": "生成的图像",
@@ -168,18 +164,18 @@
"starImage": "收藏图像"
},
"hotkeys": {
"keyboardShortcuts": "快捷键",
"appHotkeys": "应用",
"generalHotkeys": "一般",
"galleryHotkeys": "图库",
"unifiedCanvasHotkeys": "统一画布",
"keyboardShortcuts": "键盘快捷键",
"appHotkeys": "应用快捷键",
"generalHotkeys": "一般快捷键",
"galleryHotkeys": "图库快捷键",
"unifiedCanvasHotkeys": "统一画布快捷键",
"invoke": {
"title": "Invoke",
"desc": "生成图像"
},
"cancel": {
"title": "取消",
"desc": "取消当前队列项目"
"desc": "取消图像生成"
},
"focusPrompt": {
"title": "打开提示词框",
@@ -193,6 +189,10 @@
"title": "常开选项卡",
"desc": "保持选项浮窗常开"
},
"toggleViewer": {
"title": "切换图像查看器",
"desc": "打开或关闭图像查看器"
},
"toggleGallery": {
"title": "切换图库",
"desc": "打开或关闭图库"
@@ -253,6 +253,10 @@
"title": "下一张图像",
"desc": "显示图库中的下一张图像"
},
"toggleGalleryPin": {
"title": "切换图库常开",
"desc": "开关图库在界面中的常开模式"
},
"increaseGalleryThumbSize": {
"title": "增大预览尺寸",
"desc": "增大图库中预览的尺寸"
@@ -365,26 +369,11 @@
"title": "接受暂存图像",
"desc": "接受当前暂存区中的图像"
},
"nodesHotkeys": "节点",
"nodesHotkeys": "节点快捷键",
"addNodes": {
"title": "添加节点",
"desc": "打开添加节点菜单"
},
"cancelAndClear": {
"desc": "取消当前队列项目并且清除所有待定项目",
"title": "取消和清除"
},
"resetOptionsAndGallery": {
"title": "重置选项和图库",
"desc": "重置选项和图库面板"
},
"searchHotkeys": "检索快捷键",
"noHotkeysFound": "未找到快捷键",
"toggleOptionsAndGallery": {
"desc": "打开和关闭选项和图库面板",
"title": "开关选项和图库"
},
"clearSearch": "清除检索项"
}
},
"modelManager": {
"modelManager": "模型管理器",
@@ -582,8 +571,8 @@
"info": "信息",
"initialImage": "初始图像",
"showOptionsPanel": "显示侧栏浮窗 (O 或 T)",
"seamlessYAxis": "无缝平铺 Y 轴",
"seamlessXAxis": "无缝平铺 X 轴",
"seamlessYAxis": "Y 轴",
"seamlessXAxis": "X 轴",
"boundingBoxWidth": "边界框宽度",
"boundingBoxHeight": "边界框高度",
"denoisingStrength": "去噪强度",
@@ -630,7 +619,7 @@
"readyToInvoke": "准备调用",
"noControlImageForControlAdapter": "有 #{{number}} 个 Control Adapter 缺失控制图像",
"noModelForControlAdapter": "有 #{{number}} 个 Control Adapter 没有选择模型。",
"incompatibleBaseModelForControlAdapter": "有 #{{number}} 个 Control Adapter 模型与主模型不兼容。"
"incompatibleBaseModelForControlAdapter": "有 #{{number}} 个 Control Adapter 模型与主模型不匹配。"
},
"patchmatchDownScaleSize": "缩小",
"coherenceSteps": "步数",
@@ -661,14 +650,7 @@
"unmasked": "取消遮罩",
"cfgRescaleMultiplier": "CFG 重缩放倍数",
"cfgRescale": "CFG 重缩放",
"useSize": "使用尺寸",
"setToOptimalSize": "优化模型大小",
"setToOptimalSizeTooSmall": "$t(parameters.setToOptimalSize) (可能过小)",
"imageSize": "图像尺寸",
"lockAspectRatio": "锁定纵横比",
"swapDimensions": "交换尺寸",
"aspect": "纵横",
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (可能过大)"
"useSize": "使用尺寸"
},
"settings": {
"models": "模型",
@@ -1167,6 +1149,7 @@
"crop": "裁剪",
"processor": "处理器",
"none": "无",
"incompatibleBaseModel": "不兼容的基础模型:",
"enableControlnet": "启用 ControlNet",
"detectResolution": "检测分辨率",
"pidiDescription": "像素差分 (PIDI) 图像处理",
@@ -1227,8 +1210,7 @@
"openPose": "Openpose",
"controlAdapter_other": "Control Adapters",
"lineartAnime": "Lineart Anime",
"canny": "Canny",
"resizeSimple": "缩放(简单)"
"canny": "Canny"
},
"queue": {
"status": "状态",
@@ -1275,7 +1257,7 @@
"notReady": "无法排队",
"batchFailedToQueue": "批次加入队列失败",
"batchValues": "批次数",
"queueCountPrediction": "{{promptsCount}} 提示词 × {{iterations}} 迭代次数 -> {{count}} 次生成",
"queueCountPrediction": "添加 {{predicted}} 到队列",
"batchQueued": "加入队列的批次",
"queuedCount": "{{pending}} 待处理",
"front": "前",
@@ -1289,8 +1271,7 @@
"queueMaxExceeded": "超出最大值 {{max_queue_size}},将跳过 {{skip}}",
"graphFailedToQueue": "节点图加入队列失败",
"batchFieldValues": "批处理值",
"time": "时间",
"openQueue": "打开队列"
"time": "时间"
},
"sdxl": {
"refinerStart": "Refiner 开始作用时机",
@@ -1304,12 +1285,11 @@
"denoisingStrength": "去噪强度",
"refinermodel": "Refiner 模型",
"posAestheticScore": "正向美学评分",
"concatPromptStyle": "接提示词 & 样式",
"concatPromptStyle": "接提示词 & 样式",
"loading": "加载中...",
"steps": "步数",
"posStylePrompt": "正向样式提示词",
"refiner": "Refiner",
"freePromptStyle": "手动输入样式提示词"
"refiner": "Refiner"
},
"metadata": {
"positivePrompt": "正向提示词",
@@ -1353,13 +1333,7 @@
"noLoRAsInstalled": "无已安装的 LoRA",
"esrganModel": "ESRGAN 模型",
"addLora": "添加 LoRA",
"noLoRAsLoaded": "无已加载的 LoRA",
"noMainModelSelected": "未选择主模型",
"lora": "LoRA",
"allLoRAsAdded": "已添加所有 LoRA",
"defaultVAE": "默认 VAE",
"incompatibleBaseModel": "不兼容基础模型",
"loraAlreadyAdded": "LoRA 已经被添加"
"noLoRAsLoaded": "无已加载的 LoRA"
},
"boards": {
"autoAddBoard": "自动添加面板",
@@ -1403,9 +1377,7 @@
"maxPrompts": "最大提示词数",
"dynamicPrompts": "动态提示词",
"promptsWithCount_other": "{{count}} 个提示词",
"promptsPreview": "提示词预览",
"showDynamicPrompts": "显示动态提示词",
"loading": "生成动态提示词中..."
"promptsPreview": "提示词预览"
},
"popovers": {
"compositingMaskAdjustments": {
@@ -1687,28 +1659,5 @@
},
"app": {
"storeNotInitialized": "商店尚未初始化"
},
"accordions": {
"compositing": {
"infillTab": "内补",
"coherenceTab": "一致性层",
"title": "合成"
},
"control": {
"controlAdaptersTab": "Control Adapters",
"ipTab": "图像提示",
"title": "Control"
},
"generation": {
"title": "生成",
"conceptsTab": "概念",
"modelTab": "模型"
},
"advanced": {
"title": "高级"
},
"image": {
"title": "图像"
}
}
}

View File

@@ -1,9 +1,8 @@
import fs from 'node:fs';
import openapiTS from 'openapi-typescript';
const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json';
const OUTPUT_FILE = 'src/services/api/schema.ts';
const OUTPUT_FILE = 'src/services/api/schema.d.ts';
async function main() {
process.stdout.write(

View File

@@ -1,29 +1,27 @@
import { Box } from '@chakra-ui/react';
import { useSocketIO } from 'app/hooks/useSocketIO';
import { Flex, Grid } from '@chakra-ui/react';
import { useStore } from '@nanostores/react';
import { useLogger } from 'app/logging/useLogger';
import { appStarted } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
import { $headerComponent } from 'app/store/nanostores/headerComponent';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import type { PartialAppConfig } from 'app/types/invokeai';
import ImageUploadOverlay from 'common/components/ImageUploadOverlay';
import { useClearStorage } from 'common/hooks/useClearStorage';
import { useFullscreenDropzone } from 'common/hooks/useFullscreenDropzone';
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
import { useGlobalModifiersInit } from 'common/hooks/useGlobalModifiers';
import { PartialAppConfig } from 'app/types/invokeai';
import ImageUploader from 'common/components/ImageUploader';
import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardModal';
import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal';
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
import SiteHeader from 'features/system/components/SiteHeader';
import { configChanged } from 'features/system/store/configSlice';
import { languageSelector } from 'features/system/store/systemSelectors';
import InvokeTabs from 'features/ui/components/InvokeTabs';
import { AnimatePresence } from 'framer-motion';
import i18n from 'i18n';
import { size } from 'lodash-es';
import { memo, useCallback, useEffect } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
import GlobalHotkeys from './GlobalHotkeys';
import PreselectedImage from './PreselectedImage';
import Toaster from './Toaster';
import { useSocketIO } from 'app/hooks/useSocketIO';
import { useClearStorage } from 'common/hooks/useClearStorage';
const DEFAULT_CONFIG = {};
@@ -43,11 +41,6 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
// singleton!
useSocketIO();
useGlobalModifiersInit();
useGlobalHotkeys();
const { dropzone, isHandlingUpload, setIsHandlingUpload } =
useFullscreenDropzone();
const handleReset = useCallback(() => {
clearStorage();
@@ -70,34 +63,41 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
dispatch(appStarted());
}, [dispatch]);
const headerComponent = useStore($headerComponent);
return (
<ErrorBoundary
onReset={handleReset}
FallbackComponent={AppErrorBoundaryFallback}
>
<Box
id="invoke-app-wrapper"
w="100vw"
h="100vh"
position="relative"
overflow="hidden"
{...dropzone.getRootProps()}
>
<input {...dropzone.getInputProps()} />
<InvokeTabs />
<AnimatePresence>
{dropzone.isDragActive && isHandlingUpload && (
<ImageUploadOverlay
dropzone={dropzone}
setIsHandlingUpload={setIsHandlingUpload}
/>
)}
</AnimatePresence>
</Box>
<Grid w="100vw" h="100vh" position="relative" overflow="hidden">
<ImageUploader>
<Grid
sx={{
gap: 4,
p: 4,
gridAutoRows: 'min-content auto',
w: 'full',
h: 'full',
}}
>
{headerComponent || <SiteHeader />}
<Flex
sx={{
gap: 4,
w: 'full',
h: 'full',
}}
>
<InvokeTabs />
</Flex>
</Grid>
</ImageUploader>
</Grid>
<DeleteImageModal />
<ChangeBoardModal />
<DynamicPromptsModal />
<Toaster />
<GlobalHotkeys />
<PreselectedImage selectedImage={selectedImage} />
</ErrorBoundary>
);

View File

@@ -1,6 +1,5 @@
import { Flex, Heading, Link, useToast } from '@chakra-ui/react';
import { InvButton } from 'common/components/InvButton/InvButton';
import { InvText } from 'common/components/InvText/wrapper';
import { Flex, Heading, Link, Text, useToast } from '@chakra-ui/react';
import IAIButton from 'common/components/IAIButton';
import newGithubIssueUrl from 'new-github-issue-url';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -38,48 +37,60 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
return (
<Flex
layerStyle="body"
w="100vw"
h="100vh"
alignItems="center"
justifyContent="center"
p={4}
sx={{
w: '100vw',
h: '100vh',
alignItems: 'center',
justifyContent: 'center',
p: 4,
}}
>
<Flex
layerStyle="first"
flexDir="column"
borderRadius="base"
justifyContent="center"
gap={8}
p={16}
sx={{
flexDir: 'column',
borderRadius: 'base',
justifyContent: 'center',
gap: 8,
p: 16,
}}
>
<Heading>{t('common.somethingWentWrong')}</Heading>
<Flex
layerStyle="second"
px={8}
py={4}
gap={4}
borderRadius="base"
justifyContent="space-between"
alignItems="center"
sx={{
px: 8,
py: 4,
borderRadius: 'base',
gap: 4,
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<InvText fontWeight="semibold" color="error.400">
<Text
sx={{
fontWeight: 600,
color: 'error.500',
_dark: { color: 'error.400' },
}}
>
{error.name}: {error.message}
</InvText>
</Text>
</Flex>
<Flex gap={4}>
<InvButton
<Flex sx={{ gap: 4 }}>
<IAIButton
leftIcon={<FaArrowRotateLeft />}
onClick={resetErrorBoundary}
>
{t('accessibility.resetUI')}
</InvButton>
<InvButton leftIcon={<FaCopy />} onClick={handleCopy}>
</IAIButton>
<IAIButton leftIcon={<FaCopy />} onClick={handleCopy}>
{t('common.copyError')}
</InvButton>
</IAIButton>
<Link href={url} isExternal>
<InvButton leftIcon={<FaExternalLinkAlt />}>
<IAIButton leftIcon={<FaExternalLinkAlt />}>
{t('accessibility.createIssue')}
</InvButton>
</IAIButton>
</Link>
</Flex>
</Flex>

View File

@@ -0,0 +1,112 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useQueueBack } from 'features/queue/hooks/useQueueBack';
import { useQueueFront } from 'features/queue/hooks/useQueueFront';
import {
ctrlKeyPressed,
metaKeyPressed,
shiftKeyPressed,
} from 'features/ui/store/hotkeysSlice';
import { setActiveTab } from 'features/ui/store/uiSlice';
import React, { memo } from 'react';
import { isHotkeyPressed, useHotkeys } from 'react-hotkeys-hook';
const globalHotkeysSelector = createMemoizedSelector(
[stateSelector],
({ hotkeys }) => {
const { shift, ctrl, meta } = hotkeys;
return { shift, ctrl, meta };
}
);
// TODO: Does not catch keypresses while focused in an input. Maybe there is a way?
/**
* Logical component. Handles app-level global hotkeys.
* @returns null
*/
const GlobalHotkeys: React.FC = () => {
const dispatch = useAppDispatch();
const { shift, ctrl, meta } = useAppSelector(globalHotkeysSelector);
const {
queueBack,
isDisabled: isDisabledQueueBack,
isLoading: isLoadingQueueBack,
} = useQueueBack();
useHotkeys(
['ctrl+enter', 'meta+enter'],
queueBack,
{
enabled: () => !isDisabledQueueBack && !isLoadingQueueBack,
preventDefault: true,
enableOnFormTags: ['input', 'textarea', 'select'],
},
[queueBack, isDisabledQueueBack, isLoadingQueueBack]
);
const {
queueFront,
isDisabled: isDisabledQueueFront,
isLoading: isLoadingQueueFront,
} = useQueueFront();
useHotkeys(
['ctrl+shift+enter', 'meta+shift+enter'],
queueFront,
{
enabled: () => !isDisabledQueueFront && !isLoadingQueueFront,
preventDefault: true,
enableOnFormTags: ['input', 'textarea', 'select'],
},
[queueFront, isDisabledQueueFront, isLoadingQueueFront]
);
useHotkeys(
'*',
() => {
if (isHotkeyPressed('shift')) {
!shift && dispatch(shiftKeyPressed(true));
} else {
shift && dispatch(shiftKeyPressed(false));
}
if (isHotkeyPressed('ctrl')) {
!ctrl && dispatch(ctrlKeyPressed(true));
} else {
ctrl && dispatch(ctrlKeyPressed(false));
}
if (isHotkeyPressed('meta')) {
!meta && dispatch(metaKeyPressed(true));
} else {
meta && dispatch(metaKeyPressed(false));
}
},
{ keyup: true, keydown: true },
[shift, ctrl, meta]
);
useHotkeys('1', () => {
dispatch(setActiveTab('txt2img'));
});
useHotkeys('2', () => {
dispatch(setActiveTab('img2img'));
});
useHotkeys('3', () => {
dispatch(setActiveTab('unifiedCanvas'));
});
useHotkeys('4', () => {
dispatch(setActiveTab('nodes'));
});
useHotkeys('5', () => {
dispatch(setActiveTab('modelManager'));
});
return null;
};
export default memo(GlobalHotkeys);

View File

@@ -1,27 +1,29 @@
import 'i18n';
import type { Middleware } from '@reduxjs/toolkit';
import { Middleware } from '@reduxjs/toolkit';
import { $socketOptions } from 'app/hooks/useSocketIO';
import { $authToken } from 'app/store/nanostores/authToken';
import { $baseUrl } from 'app/store/nanostores/baseUrl';
import { $customNavComponent } from 'app/store/nanostores/customNavComponent';
import type { CustomStarUi } from 'app/store/nanostores/customStarUI';
import { $customStarUI } from 'app/store/nanostores/customStarUI';
import { $galleryHeader } from 'app/store/nanostores/galleryHeader';
import { $customStarUI, CustomStarUi } from 'app/store/nanostores/customStarUI';
import { $headerComponent } from 'app/store/nanostores/headerComponent';
import { $isDebugging } from 'app/store/nanostores/isDebugging';
import { $logo } from 'app/store/nanostores/logo';
import { $projectId } from 'app/store/nanostores/projectId';
import { $queueId, DEFAULT_QUEUE_ID } from 'app/store/nanostores/queueId';
import { $store } from 'app/store/nanostores/store';
import { createStore } from 'app/store/store';
import type { PartialAppConfig } from 'app/types/invokeai';
import { PartialAppConfig } from 'app/types/invokeai';
import Loading from 'common/components/Loading/Loading';
import AppDndContext from 'features/dnd/components/AppDndContext';
import type { PropsWithChildren, ReactNode } from 'react';
import React, { lazy, memo, useEffect, useMemo } from 'react';
import 'i18n';
import React, {
PropsWithChildren,
ReactNode,
lazy,
memo,
useEffect,
useMemo,
} from 'react';
import { Provider } from 'react-redux';
import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
import type { ManagerOptions, SocketOptions } from 'socket.io-client';
import { ManagerOptions, SocketOptions } from 'socket.io-client';
const App = lazy(() => import('./App'));
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
@@ -30,10 +32,9 @@ interface Props extends PropsWithChildren {
apiUrl?: string;
token?: string;
config?: PartialAppConfig;
customNavComponent?: ReactNode;
headerComponent?: ReactNode;
middleware?: Middleware[];
projectId?: string;
galleryHeader?: ReactNode;
queueId?: string;
selectedImage?: {
imageName: string;
@@ -42,23 +43,20 @@ interface Props extends PropsWithChildren {
customStarUi?: CustomStarUi;
socketOptions?: Partial<ManagerOptions & SocketOptions>;
isDebugging?: boolean;
logo?: ReactNode;
}
const InvokeAIUI = ({
apiUrl,
token,
config,
customNavComponent,
headerComponent,
middleware,
projectId,
galleryHeader,
queueId,
selectedImage,
customStarUi,
socketOptions,
isDebugging = false,
logo,
}: Props) => {
useEffect(() => {
// configure API client token
@@ -114,34 +112,14 @@ const InvokeAIUI = ({
}, [customStarUi]);
useEffect(() => {
if (customNavComponent) {
$customNavComponent.set(customNavComponent);
if (headerComponent) {
$headerComponent.set(headerComponent);
}
return () => {
$customNavComponent.set(undefined);
$headerComponent.set(undefined);
};
}, [customNavComponent]);
useEffect(() => {
if (galleryHeader) {
$galleryHeader.set(galleryHeader);
}
return () => {
$galleryHeader.set(undefined);
};
}, [galleryHeader]);
useEffect(() => {
if (logo) {
$logo.set(logo);
}
return () => {
$logo.set(undefined);
};
}, [logo]);
}, [headerComponent]);
useEffect(() => {
if (socketOptions) {
@@ -167,15 +145,6 @@ const InvokeAIUI = ({
useEffect(() => {
$store.set(store);
if (import.meta.env.MODE === 'development') {
window.$store = $store;
}
() => {
$store.set(undefined);
if (import.meta.env.MODE === 'development') {
window.$store = undefined;
}
};
}, [store]);
return (

View File

@@ -1,17 +1,24 @@
import '@fontsource-variable/inter';
import 'overlayscrollbars/overlayscrollbars.css';
import 'common/components/OverlayScrollbars/overlayscrollbars.css';
import { ChakraProvider, extendTheme } from '@chakra-ui/react';
import type { ReactNode } from 'react';
import { memo, useEffect, useMemo } from 'react';
import {
ChakraProvider,
createLocalStorageManager,
extendTheme,
} from '@chakra-ui/react';
import { ReactNode, memo, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { theme as invokeAITheme, TOAST_OPTIONS } from 'theme/theme';
import { TOAST_OPTIONS, theme as invokeAITheme } from 'theme/theme';
import '@fontsource-variable/inter';
import { MantineProvider } from '@mantine/core';
import { useMantineTheme } from 'mantine-theme/theme';
import 'overlayscrollbars/overlayscrollbars.css';
import 'theme/css/overlayscrollbars.css';
type ThemeLocaleProviderProps = {
children: ReactNode;
};
const manager = createLocalStorageManager('@@invokeai-color-mode');
function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) {
const { i18n } = useTranslation();
@@ -28,10 +35,18 @@ function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) {
document.body.dir = direction;
}, [direction]);
const mantineTheme = useMantineTheme();
return (
<ChakraProvider theme={theme} toastOptions={TOAST_OPTIONS}>
{children}
</ChakraProvider>
<MantineProvider theme={mantineTheme}>
<ChakraProvider
theme={theme}
colorModeManager={manager}
toastOptions={TOAST_OPTIONS}
>
{children}
</ChakraProvider>
</MantineProvider>
);
}

View File

@@ -1,8 +1,7 @@
import { useToast } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { addToast, clearToastQueue } from 'features/system/store/systemSlice';
import type { MakeToastArg } from 'features/system/util/makeToast';
import { makeToast } from 'features/system/util/makeToast';
import { MakeToastArg, makeToast } from 'features/system/util/makeToast';
import { memo, useCallback, useEffect } from 'react';
/**
@@ -11,7 +10,7 @@ import { memo, useCallback, useEffect } from 'react';
*/
const Toaster = () => {
const dispatch = useAppDispatch();
const toastQueue = useAppSelector((s) => s.system.toastQueue);
const toastQueue = useAppSelector((state) => state.system.toastQueue);
const toast = useToast();
useEffect(() => {
toastQueue.forEach((t) => {

View File

@@ -0,0 +1,94 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
type FeatureHelpInfo = {
text: string;
href: string;
guideImage: string;
};
export enum Feature {
PROMPT,
GALLERY,
OTHER,
SEED,
VARIATIONS,
UPSCALE,
FACE_CORRECTION,
IMAGE_TO_IMAGE,
BOUNDING_BOX,
SEAM_CORRECTION,
INFILL_AND_SCALING,
}
/** For each tooltip in the UI, the below feature definitions & props will pull relevant information into the tooltip.
*
* To-do: href & GuideImages are placeholders, and are not currently utilized, but will be updated (along with the tooltip UI) as feature and UI develop and we get a better idea on where things "forever homes" will be .
*/
const useFeatures = (): Record<Feature, FeatureHelpInfo> => {
const { t } = useTranslation();
return useMemo(
() => ({
[Feature.PROMPT]: {
text: t('tooltip.feature.prompt'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.GALLERY]: {
text: t('tooltip.feature.gallery'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.OTHER]: {
text: t('tooltip.feature.other'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.SEED]: {
text: t('tooltip.feature.seed'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.VARIATIONS]: {
text: t('tooltip.feature.variations'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.UPSCALE]: {
text: t('tooltip.feature.upscale'),
href: 'link/to/docs/feature1.html',
guideImage: 'asset/path.gif',
},
[Feature.FACE_CORRECTION]: {
text: t('tooltip.feature.faceCorrection'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.IMAGE_TO_IMAGE]: {
text: t('tooltip.feature.imageToImage'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.BOUNDING_BOX]: {
text: t('tooltip.feature.boundingBox'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.SEAM_CORRECTION]: {
text: t('tooltip.feature.seamCorrection'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.INFILL_AND_SCALING]: {
text: t('tooltip.feature.infillAndScaling'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
}),
[t]
);
};
export const useFeatureHelpInfo = (feature: Feature): FeatureHelpInfo => {
const features = useFeatures();
return features[feature];
};

View File

@@ -3,16 +3,14 @@ import { $authToken } from 'app/store/nanostores/authToken';
import { $baseUrl } from 'app/store/nanostores/baseUrl';
import { $isDebugging } from 'app/store/nanostores/isDebugging';
import { useAppDispatch } from 'app/store/storeHooks';
import type { MapStore } from 'nanostores';
import { atom, map } from 'nanostores';
import { MapStore, atom, map } from 'nanostores';
import { useEffect, useMemo } from 'react';
import type {
import {
ClientToServerEvents,
ServerToClientEvents,
} from 'services/events/types';
import { setEventListeners } from 'services/events/util/setEventListeners';
import type { ManagerOptions, Socket, SocketOptions } from 'socket.io-client';
import { io } from 'socket.io-client';
import { ManagerOptions, Socket, SocketOptions, io } from 'socket.io-client';
// Inject socket options and url into window for debugging
declare global {
@@ -43,7 +41,7 @@ export const useSocketIO = () => {
}, [baseUrl]);
const socketOptions = useMemo(() => {
const options: Partial<ManagerOptions & SocketOptions> = {
const options: Parameters<typeof io>[0] = {
timeout: 60000,
path: '/ws/socket.io',
autoConnect: false, // achtung! removing this breaks the dynamic middleware
@@ -71,7 +69,7 @@ export const useSocketIO = () => {
setEventListeners({ dispatch, socket });
socket.connect();
if ($isDebugging.get() || import.meta.env.MODE === 'development') {
if ($isDebugging.get()) {
window.$socketOptions = $socketOptions;
console.log('Socket initialized', socket);
}
@@ -79,7 +77,7 @@ export const useSocketIO = () => {
$isSocketInitialized.set(true);
return () => {
if ($isDebugging.get() || import.meta.env.MODE === 'development') {
if ($isDebugging.get()) {
window.$socketOptions = undefined;
console.log('Socket teardown', socket);
}

View File

@@ -1,14 +1,7 @@
import { createLogWriter } from '@roarr/browser-log-writer';
import { atom } from 'nanostores';
import type { Logger, MessageSerializer } from 'roarr';
import { ROARR, Roarr } from 'roarr';
import { z } from 'zod';
import { Logger, ROARR, Roarr } from 'roarr';
const serializeMessage: MessageSerializer = (message) => {
return JSON.stringify(message);
};
ROARR.serializeMessage = serializeMessage;
ROARR.write = createLogWriter();
export const BASE_CONTEXT = {};
@@ -33,20 +26,19 @@ export type LoggerNamespace =
export const logger = (namespace: LoggerNamespace) =>
$logger.get().child({ namespace });
export const zLogLevel = z.enum([
export const VALID_LOG_LEVELS = [
'trace',
'debug',
'info',
'warn',
'error',
'fatal',
]);
export type LogLevel = z.infer<typeof zLogLevel>;
export const isLogLevel = (v: unknown): v is LogLevel =>
zLogLevel.safeParse(v).success;
] as const;
export type InvokeLogLevel = (typeof VALID_LOG_LEVELS)[number];
// Translate human-readable log levels to numbers, used for log filtering
export const LOG_LEVEL_MAP: Record<LogLevel, number> = {
export const LOG_LEVEL_MAP: Record<InvokeLogLevel, number> = {
trace: 10,
debug: 20,
info: 30,

View File

@@ -1,14 +1,28 @@
import { createLogWriter } from '@roarr/browser-log-writer';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { useEffect, useMemo } from 'react';
import { ROARR, Roarr } from 'roarr';
import {
$logger,
BASE_CONTEXT,
LOG_LEVEL_MAP,
LoggerNamespace,
logger,
} from './logger';
import type { LoggerNamespace } from './logger';
import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP, logger } from './logger';
const selector = createMemoizedSelector(stateSelector, ({ system }) => {
const { consoleLogLevel, shouldLogToConsole } = system;
return {
consoleLogLevel,
shouldLogToConsole,
};
});
export const useLogger = (namespace: LoggerNamespace) => {
const consoleLogLevel = useAppSelector((s) => s.system.consoleLogLevel);
const shouldLogToConsole = useAppSelector((s) => s.system.shouldLogToConsole);
const { consoleLogLevel, shouldLogToConsole } = useAppSelector(selector);
// The provided Roarr browser log writer uses localStorage to config logging to console
useEffect(() => {

View File

@@ -1,6 +1,6 @@
import { createAction } from '@reduxjs/toolkit';
import type { InvokeTabName } from 'features/ui/store/tabMap';
import type { BatchConfig } from 'services/api/types';
import { InvokeTabName } from 'features/ui/store/tabMap';
import { BatchConfig } from 'services/api/types';
export const enqueueRequested = createAction<{
tabName: InvokeTabName;

View File

@@ -1,9 +1,4 @@
import {
createDraftSafeSelectorCreator,
createSelectorCreator,
lruMemoize,
} from '@reduxjs/toolkit';
import type { GetSelectorsOptions } from '@reduxjs/toolkit/dist/entities/state_selectors';
import { createSelectorCreator, lruMemoize } from '@reduxjs/toolkit';
import { isEqual } from 'lodash-es';
/**
@@ -14,22 +9,4 @@ export const createMemoizedSelector = createSelectorCreator({
memoizeOptions: {
resultEqualityCheck: isEqual,
},
argsMemoize: lruMemoize,
});
/**
* A memoized selector creator that uses LRU cache default shallow equality check.
*/
export const createLruSelector = createSelectorCreator({
memoize: lruMemoize,
argsMemoize: lruMemoize,
});
export const createLruDraftSafeSelector = createDraftSafeSelectorCreator({
memoize: lruMemoize,
argsMemoize: lruMemoize,
});
export const getSelectorsOptions: GetSelectorsOptions = {
createSelector: createLruDraftSafeSelector,
};

View File

@@ -1,37 +0,0 @@
import { StorageError } from 'app/store/enhancers/reduxRemember/errors';
import type { UseStore } from 'idb-keyval';
import {
clear,
createStore as createIDBKeyValStore,
get,
set,
} from 'idb-keyval';
import { action, atom } from 'nanostores';
import type { Driver } from 'redux-remember';
// Create a custom idb-keyval store (just needed to customize the name)
export const $idbKeyValStore = atom<UseStore>(
createIDBKeyValStore('invoke', 'invoke-store')
);
export const clearIdbKeyValStore = action($idbKeyValStore, 'clear', (store) => {
clear(store.get());
});
// Create redux-remember driver, wrapping idb-keyval
export const idbKeyValDriver: Driver = {
getItem: (key) => {
try {
return get(key, $idbKeyValStore.get());
} catch (originalError) {
throw new StorageError({ key, originalError });
}
},
setItem: (key, value) => {
try {
return set(key, value, $idbKeyValStore.get());
} catch (originalError) {
throw new StorageError({ key, value, originalError });
}
},
};

View File

@@ -1,41 +0,0 @@
import { logger } from 'app/logging/logger';
import { parseify } from 'common/util/serialize';
import { PersistError, RehydrateError } from 'redux-remember';
import { serializeError } from 'serialize-error';
export type StorageErrorArgs = {
key: string;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ // any is correct
value?: any;
originalError?: unknown;
};
export class StorageError extends Error {
key: string;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ // any is correct
value?: any;
originalError?: Error;
constructor({ key, value, originalError }: StorageErrorArgs) {
super(`Error setting ${key}`);
this.name = 'StorageSetError';
this.key = key;
if (value !== undefined) {
this.value = value;
}
if (originalError instanceof Error) {
this.originalError = originalError;
}
}
}
export const errorHandler = (err: PersistError | RehydrateError) => {
const log = logger('system');
if (err instanceof PersistError) {
log.error({ error: serializeError(err) }, 'Problem persisting state');
} else if (err instanceof RehydrateError) {
log.error({ error: serializeError(err) }, 'Problem rehydrating state');
} else {
log.error({ error: parseify(err) }, 'Problem in persistence layer');
}
};

View File

@@ -0,0 +1,30 @@
import { canvasPersistDenylist } from 'features/canvas/store/canvasPersistDenylist';
import { controlAdaptersPersistDenylist } from 'features/controlAdapters/store/controlAdaptersPersistDenylist';
import { dynamicPromptsPersistDenylist } from 'features/dynamicPrompts/store/dynamicPromptsPersistDenylist';
import { galleryPersistDenylist } from 'features/gallery/store/galleryPersistDenylist';
import { nodesPersistDenylist } from 'features/nodes/store/nodesPersistDenylist';
import { generationPersistDenylist } from 'features/parameters/store/generationPersistDenylist';
import { postprocessingPersistDenylist } from 'features/parameters/store/postprocessingPersistDenylist';
import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist';
import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist';
import { omit } from 'lodash-es';
import { SerializeFunction } from 'redux-remember';
const serializationDenylist: {
[key: string]: string[];
} = {
canvas: canvasPersistDenylist,
gallery: galleryPersistDenylist,
generation: generationPersistDenylist,
nodes: nodesPersistDenylist,
postprocessing: postprocessingPersistDenylist,
system: systemPersistDenylist,
ui: uiPersistDenylist,
controlNet: controlAdaptersPersistDenylist,
dynamicPrompts: dynamicPromptsPersistDenylist,
};
export const serialize: SerializeFunction = (data, key) => {
const result = omit(data, serializationDenylist[key] ?? []);
return JSON.stringify(result);
};

View File

@@ -0,0 +1,36 @@
import { initialCanvasState } from 'features/canvas/store/canvasSlice';
import { initialControlAdapterState } from 'features/controlAdapters/store/controlAdaptersSlice';
import { initialDynamicPromptsState } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { initialGalleryState } from 'features/gallery/store/gallerySlice';
import { initialNodesState } from 'features/nodes/store/nodesSlice';
import { initialGenerationState } from 'features/parameters/store/generationSlice';
import { initialPostprocessingState } from 'features/parameters/store/postprocessingSlice';
import { initialSDXLState } from 'features/sdxl/store/sdxlSlice';
import { initialConfigState } from 'features/system/store/configSlice';
import { initialSystemState } from 'features/system/store/systemSlice';
import { initialHotkeysState } from 'features/ui/store/hotkeysSlice';
import { initialUIState } from 'features/ui/store/uiSlice';
import { defaultsDeep } from 'lodash-es';
import { UnserializeFunction } from 'redux-remember';
const initialStates: {
[key: string]: object; // TODO: type this properly
} = {
canvas: initialCanvasState,
gallery: initialGalleryState,
generation: initialGenerationState,
nodes: initialNodesState,
postprocessing: initialPostprocessingState,
system: initialSystemState,
config: initialConfigState,
ui: initialUIState,
hotkeys: initialHotkeysState,
controlAdapters: initialControlAdapterState,
dynamicPrompts: initialDynamicPromptsState,
sdxl: initialSDXLState,
};
export const unserialize: UnserializeFunction = (data, key) => {
const result = defaultsDeep(JSON.parse(data), initialStates[key]);
return result;
};

View File

@@ -1,16 +0,0 @@
import type { Middleware, MiddlewareAPI } from '@reduxjs/toolkit';
import { diff } from 'jsondiffpatch';
/**
* Super simple logger middleware. Useful for debugging when the redux devtools are awkward.
*/
export const debugLoggerMiddleware: Middleware =
(api: MiddlewareAPI) => (next) => (action) => {
const originalState = api.getState();
console.log('REDUX: dispatching', action);
const result = next(action);
const nextState = api.getState();
console.log('REDUX: next state', nextState);
console.log('REDUX: diff', diff(originalState, nextState));
return result;
};

View File

@@ -1,10 +1,8 @@
import type { UnknownAction } from '@reduxjs/toolkit';
import { UnknownAction } from '@reduxjs/toolkit';
import { isAnyGraphBuilt } from 'features/nodes/store/actions';
import { nodeTemplatesBuilt } from 'features/nodes/store/nodeTemplatesSlice';
import { cloneDeep } from 'lodash-es';
import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice';
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import type { Graph } from 'services/api/types';
import { socketGeneratorProgress } from 'services/events/actions';
import { Graph } from 'services/api/types';
export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
if (isAnyGraphBuilt(action)) {
@@ -32,14 +30,5 @@ export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
};
}
if (socketGeneratorProgress.match(action)) {
const sanitized = cloneDeep(action);
if (sanitized.payload.data.progress_image) {
sanitized.payload.data.progress_image.dataURL =
'<Progress image omitted>';
}
return sanitized;
}
return action;
};

View File

@@ -1,16 +1,21 @@
/**
* This is a list of actions that should be excluded in the Redux DevTools.
*/
export const actionsDenylist: string[] = [
export const actionsDenylist = [
// very spammy canvas actions
// 'canvas/setStageCoordinates',
// 'canvas/setStageScale',
// 'canvas/setBoundingBoxCoordinates',
// 'canvas/setBoundingBoxDimensions',
// 'canvas/addPointToCurrentLine',
'canvas/setCursorPosition',
'canvas/setStageCoordinates',
'canvas/setStageScale',
'canvas/setIsDrawing',
'canvas/setBoundingBoxCoordinates',
'canvas/setBoundingBoxDimensions',
'canvas/setIsDrawing',
'canvas/addPointToCurrentLine',
// bazillions during generation
// 'socket/socketGeneratorProgress',
// 'socket/appSocketGeneratorProgress',
'socket/socketGeneratorProgress',
'socket/appSocketGeneratorProgress',
// every time user presses shift
// 'hotkeys/shiftKeyPressed',
// this happens after every state change
// '@@REMEMBER_PERSISTED',
'@@REMEMBER_PERSISTED',
];

View File

@@ -1,13 +1,11 @@
import type {
ListenerEffect,
TypedAddListener,
TypedStartListening,
import type { TypedAddListener, TypedStartListening } from '@reduxjs/toolkit';
import {
UnknownAction,
ListenerEffect,
addListener,
createListenerMiddleware,
} from '@reduxjs/toolkit';
import { addListener, createListenerMiddleware } from '@reduxjs/toolkit';
import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
import type { AppDispatch, RootState } from 'app/store/store';
import { addCommitStagingAreaImageListener } from './listeners/addCommitStagingAreaImageListener';
import { addFirstListImagesListener } from './listeners/addFirstListImagesListener.ts';
import { addAnyEnqueuedListener } from './listeners/anyEnqueued';
@@ -44,13 +42,13 @@ import {
addImageRemovedFromBoardFulfilledListener,
addImageRemovedFromBoardRejectedListener,
} from './listeners/imageRemovedFromBoard';
import { addImagesStarredListener } from './listeners/imagesStarred';
import { addImagesUnstarredListener } from './listeners/imagesUnstarred';
import { addImageToDeleteSelectedListener } from './listeners/imageToDeleteSelected';
import {
addImageUploadedFulfilledListener,
addImageUploadedRejectedListener,
} from './listeners/imageUploaded';
import { addImagesStarredListener } from './listeners/imagesStarred';
import { addImagesUnstarredListener } from './listeners/imagesUnstarred';
import { addInitialImageSelectedListener } from './listeners/initialImageSelected';
import { addModelSelectedListener } from './listeners/modelSelected';
import { addModelsLoadedListener } from './listeners/modelsLoaded';
@@ -70,9 +68,10 @@ import { addSessionRetrievalErrorEventListener } from './listeners/socketio/sock
import { addSocketSubscribedEventListener as addSocketSubscribedListener } from './listeners/socketio/socketSubscribed';
import { addSocketUnsubscribedEventListener as addSocketUnsubscribedListener } from './listeners/socketio/socketUnsubscribed';
import { addStagingAreaImageSavedListener } from './listeners/stagingAreaImageSaved';
import { addUpdateAllNodesRequestedListener } from './listeners/updateAllNodesRequested';
import { addTabChangedListener } from './listeners/tabChanged';
import { addUpscaleRequestedListener } from './listeners/upscaleRequested';
import { addWorkflowLoadRequestedListener } from './listeners/workflowLoadRequested';
import { addUpdateAllNodesRequestedListener } from './listeners/updateAllNodesRequested';
export const listenerMiddleware = createListenerMiddleware();
@@ -118,9 +117,6 @@ addImageToDeleteSelectedListener();
addImagesStarredListener();
addImagesUnstarredListener();
// Gallery
addGalleryImageClickedListener();
// User Invoked
addEnqueueRequestedCanvasListener();
addEnqueueRequestedNodes();
@@ -139,7 +135,19 @@ addCanvasMergedListener();
addStagingAreaImageSavedListener();
addCommitStagingAreaImageListener();
// Socket.IO
/**
* Socket.IO Events - these handle SIO events directly and pass on internal application actions.
* We don't handle SIO events in slices via `extraReducers` because some of these events shouldn't
* actually be handled at all.
*
* For example, we don't want to respond to progress events for canceled sessions. To avoid
* duplicating the logic to determine if an event should be responded to, we handle all of that
* "is this session canceled?" logic in these listeners.
*
* The `socketGeneratorProgress` listener will then only dispatch the `appSocketGeneratorProgress`
* action if it should be handled by the rest of the application. It is this `appSocketGeneratorProgress`
* action that is handled by reducers in slices.
*/
addGeneratorProgressListener();
addGraphExecutionStateCompleteListener();
addInvocationCompleteListener();
@@ -187,5 +195,8 @@ addFirstListImagesListener();
// Ad-hoc upscale workflwo
addUpscaleRequestedListener();
// Tab Change
addTabChangedListener();
// Dynamic prompts
addDynamicPromptsListener();

View File

@@ -8,7 +8,6 @@ import {
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { queueApi } from 'services/api/endpoints/queue';
import { startAppListening } from '..';
const matcher = isAnyOf(commitStagingAreaImage, discardStagedImages);

View File

@@ -2,10 +2,9 @@ import { createAction } from '@reduxjs/toolkit';
import { imageSelected } from 'features/gallery/store/gallerySlice';
import { IMAGE_CATEGORIES } from 'features/gallery/store/types';
import { imagesApi } from 'services/api/endpoints/images';
import type { ImageCache } from 'services/api/types';
import { getListImagesUrl, imagesSelectors } from 'services/api/util';
import { startAppListening } from '..';
import { getListImagesUrl, imagesAdapter } from 'services/api/util';
import { ImageCache } from 'services/api/types';
export const appStarted = createAction('app/appStarted');
@@ -33,7 +32,7 @@ export const addFirstListImagesListener = () => {
if (data.ids.length > 0) {
// Select the first image
const firstImage = imagesSelectors.selectAll(data)[0];
const firstImage = imagesAdapter.getSelectors().selectAll(data)[0];
dispatch(imageSelected(firstImage ?? null));
}
},

View File

@@ -1,5 +1,4 @@
import { queueApi } from 'services/api/endpoints/queue';
import { startAppListening } from '..';
export const addAnyEnqueuedListener = () => {

View File

@@ -4,7 +4,6 @@ import {
shouldUseWatermarkerChanged,
} from 'features/system/store/systemSlice';
import { appInfoApi } from 'services/api/endpoints/appInfo';
import { startAppListening } from '..';
export const addAppConfigReceivedListener = () => {

View File

@@ -1,5 +1,4 @@
import { createAction } from '@reduxjs/toolkit';
import { startAppListening } from '..';
export const appStarted = createAction('app/appStarted');

View File

@@ -5,8 +5,7 @@ import { zPydanticValidationError } from 'features/system/store/zodSchemas';
import { t } from 'i18next';
import { truncate, upperFirst } from 'lodash-es';
import { queueApi } from 'services/api/endpoints/queue';
import { theme, TOAST_OPTIONS } from 'theme/theme';
import { TOAST_OPTIONS, theme } from 'theme/theme';
import { startAppListening } from '..';
const { toast } = createStandaloneToast({

View File

@@ -4,7 +4,6 @@ import { getImageUsage } from 'features/deleteImageModal/store/selectors';
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
import { clearInitialImage } from 'features/parameters/store/generationSlice';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addDeleteBoardAndImagesFulfilledListener = () => {
@@ -20,15 +19,9 @@ export const addDeleteBoardAndImagesFulfilledListener = () => {
let wasNodeEditorReset = false;
let wereControlAdaptersReset = false;
const { generation, canvas, nodes, controlAdapters } = getState();
const state = getState();
deleted_images.forEach((image_name) => {
const imageUsage = getImageUsage(
generation,
canvas,
nodes,
controlAdapters,
image_name
);
const imageUsage = getImageUsage(state, image_name);
if (imageUsage.isInitialImage && !wasInitialImageReset) {
dispatch(clearInitialImage());

View File

@@ -9,9 +9,8 @@ import {
IMAGE_CATEGORIES,
} from 'features/gallery/store/types';
import { imagesApi } from 'services/api/endpoints/images';
import { imagesSelectors } from 'services/api/util';
import { startAppListening } from '..';
import { imagesSelectors } from 'services/api/util';
export const addBoardIdSelectedListener = () => {
startAppListening({

View File

@@ -1,12 +1,11 @@
import { $logger } from 'app/logging/logger';
import { canvasCopiedToClipboard } from 'features/canvas/store/actions';
import { startAppListening } from '..';
import { $logger } from 'app/logging/logger';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice';
import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard';
import { t } from 'i18next';
import { startAppListening } from '..';
export const addCanvasCopiedToClipboardListener = () => {
startAppListening({
actionCreator: canvasCopiedToClipboard,

View File

@@ -1,12 +1,11 @@
import { $logger } from 'app/logging/logger';
import { canvasDownloadedAsImage } from 'features/canvas/store/actions';
import { startAppListening } from '..';
import { $logger } from 'app/logging/logger';
import { downloadBlob } from 'features/canvas/util/downloadBlob';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { startAppListening } from '..';
export const addCanvasDownloadedAsImageListener = () => {
startAppListening({
actionCreator: canvasDownloadedAsImage,

View File

@@ -1,12 +1,11 @@
import { logger } from 'app/logging/logger';
import { canvasImageToControlAdapter } from 'features/canvas/store/actions';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { canvasImageToControlAdapter } from 'features/canvas/store/actions';
export const addCanvasImageToControlNetListener = () => {
startAppListening({

View File

@@ -2,10 +2,9 @@ import { logger } from 'app/logging/logger';
import { canvasMaskSavedToGallery } from 'features/canvas/store/actions';
import { getCanvasData } from 'features/canvas/util/getCanvasData';
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { t } from 'i18next';
export const addCanvasMaskSavedToGalleryListener = () => {
startAppListening({

View File

@@ -5,7 +5,6 @@ import { controlAdapterImageChanged } from 'features/controlAdapters/store/contr
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addCanvasMaskToControlNetListener = () => {

View File

@@ -1,13 +1,12 @@
import { $logger } from 'app/logging/logger';
import { canvasMerged } from 'features/canvas/store/actions';
import { $canvasBaseLayer } from 'features/canvas/store/canvasNanostore';
import { setMergedCanvas } from 'features/canvas/store/canvasSlice';
import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { t } from 'i18next';
export const addCanvasMergedListener = () => {
startAppListening({
@@ -30,7 +29,7 @@ export const addCanvasMergedListener = () => {
return;
}
const canvasBaseLayer = $canvasBaseLayer.get();
const canvasBaseLayer = getCanvasBaseLayer();
if (!canvasBaseLayer) {
moduleLog.error('Problem getting canvas base layer');

View File

@@ -2,10 +2,9 @@ import { logger } from 'app/logging/logger';
import { canvasSavedToGallery } from 'features/canvas/store/actions';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { t } from 'i18next';
export const addCanvasSavedToGalleryListener = () => {
startAppListening({

View File

@@ -1,6 +1,6 @@
import type { AnyListenerPredicate } from '@reduxjs/toolkit';
import { AnyListenerPredicate } from '@reduxjs/toolkit';
import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import { RootState } from 'app/store/store';
import { controlAdapterImageProcessed } from 'features/controlAdapters/store/actions';
import {
controlAdapterAutoConfigToggled,
@@ -10,9 +10,8 @@ import {
controlAdapterProcessortTypeChanged,
selectControlAdapterById,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { startAppListening } from '..';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
type AnyControlAdapterParamChangeAction =
| ReturnType<typeof controlAdapterProcessorParamsChanged>
@@ -69,12 +68,10 @@ const predicate: AnyListenerPredicate<RootState> = (
return isProcessorSelected && hasControlImage;
};
const DEBOUNCE_MS = 300;
/**
* Listener that automatically processes a ControlNet image when its processor parameters are changed.
*
* The network request is debounced.
* The network request is debounced by 1 second.
*/
export const addControlNetAutoProcessListener = () => {
startAppListening({
@@ -87,7 +84,7 @@ export const addControlNetAutoProcessListener = () => {
cancelActiveListeners();
log.trace('ControlNet auto-process triggered');
// Delay before starting actual work
await delay(DEBOUNCE_MS);
await delay(300);
dispatch(controlAdapterImageProcessed({ id }));
},

View File

@@ -8,15 +8,14 @@ import {
selectControlAdapterById,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { isImageOutput } from 'features/nodes/types/common';
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { queueApi } from 'services/api/endpoints/queue';
import type { BatchConfig, ImageDTO } from 'services/api/types';
import { BatchConfig, ImageDTO } from 'services/api/types';
import { socketInvocationComplete } from 'services/events/actions';
import { startAppListening } from '..';
import { isImageOutput } from 'features/nodes/types/common';
export const addControlNetImageProcessedListener = () => {
startAppListening({

View File

@@ -14,8 +14,7 @@ import { buildCanvasGraph } from 'features/nodes/util/graph/buildCanvasGraph';
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
import { imagesApi } from 'services/api/endpoints/images';
import { queueApi } from 'services/api/endpoints/queue';
import type { ImageDTO } from 'services/api/types';
import { ImageDTO } from 'services/api/types';
import { startAppListening } from '..';
/**

View File

@@ -5,7 +5,6 @@ import { buildLinearSDXLImageToImageGraph } from 'features/nodes/util/graph/buil
import { buildLinearSDXLTextToImageGraph } from 'features/nodes/util/graph/buildLinearSDXLTextToImageGraph';
import { buildLinearTextToImageGraph } from 'features/nodes/util/graph/buildLinearTextToImageGraph';
import { queueApi } from 'services/api/endpoints/queue';
import { startAppListening } from '..';
export const addEnqueueRequestedLinear = () => {

View File

@@ -1,9 +1,8 @@
import { enqueueRequested } from 'app/store/actions';
import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph';
import { buildWorkflowWithValidation } from 'features/nodes/util/workflow/buildWorkflow';
import { buildWorkflow } from 'features/nodes/util/workflow/buildWorkflow';
import { queueApi } from 'services/api/endpoints/queue';
import type { BatchConfig } from 'services/api/types';
import { BatchConfig } from 'services/api/types';
import { startAppListening } from '..';
export const addEnqueueRequestedNodes = () => {
@@ -15,16 +14,14 @@ export const addEnqueueRequestedNodes = () => {
const { nodes, edges } = state.nodes;
const workflow = state.workflow;
const graph = buildNodesGraph(state.nodes);
const builtWorkflow = buildWorkflowWithValidation({
const builtWorkflow = buildWorkflow({
nodes,
edges,
workflow,
});
if (builtWorkflow) {
// embedded workflows don't have an id
delete builtWorkflow.id;
}
// embedded workflows don't have an id
delete builtWorkflow.id;
const batchConfig: BatchConfig = {
batch: {

View File

@@ -1,80 +0,0 @@
import { createAction } from '@reduxjs/toolkit';
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
import { selectionChanged } from 'features/gallery/store/gallerySlice';
import { imagesApi } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types';
import { imagesSelectors } from 'services/api/util';
import { startAppListening } from '..';
export const galleryImageClicked = createAction<{
imageDTO: ImageDTO;
shiftKey: boolean;
ctrlKey: boolean;
metaKey: boolean;
}>('gallery/imageClicked');
/**
* This listener handles the logic for selecting images in the gallery.
*
* Previously, this logic was in a `useCallback` with the whole gallery selection as a dependency. Every time
* the selection changed, the callback got recreated and all images rerendered. This could easily block for
* hundreds of ms, more for lower end devices.
*
* Moving this logic into a listener means we don't need to recalculate anything dynamically and the gallery
* is much more responsive.
*/
export const addGalleryImageClickedListener = () => {
startAppListening({
actionCreator: galleryImageClicked,
effect: async (action, { dispatch, getState }) => {
const { imageDTO, shiftKey, ctrlKey, metaKey } = action.payload;
const state = getState();
const queryArgs = selectListImagesQueryArgs(state);
const { data: listImagesData } =
imagesApi.endpoints.listImages.select(queryArgs)(state);
if (!listImagesData) {
// Should never happen if we have clicked a gallery image
return;
}
const imageDTOs = imagesSelectors.selectAll(listImagesData);
const selection = state.gallery.selection;
if (shiftKey) {
const rangeEndImageName = imageDTO.image_name;
const lastSelectedImage = selection[selection.length - 1]?.image_name;
const lastClickedIndex = imageDTOs.findIndex(
(n) => n.image_name === lastSelectedImage
);
const currentClickedIndex = imageDTOs.findIndex(
(n) => n.image_name === rangeEndImageName
);
if (lastClickedIndex > -1 && currentClickedIndex > -1) {
// We have a valid range!
const start = Math.min(lastClickedIndex, currentClickedIndex);
const end = Math.max(lastClickedIndex, currentClickedIndex);
const imagesToSelect = imageDTOs.slice(start, end + 1);
dispatch(selectionChanged(selection.concat(imagesToSelect)));
}
} else if (ctrlKey || metaKey) {
if (
selection.some((i) => i.image_name === imageDTO.image_name) &&
selection.length > 1
) {
dispatch(
selectionChanged(
selection.filter((n) => n.image_name !== imageDTO.image_name)
)
);
} else {
dispatch(selectionChanged(selection.concat(imageDTO)));
}
} else {
dispatch(selectionChanged([imageDTO]));
}
},
});
};

View File

@@ -1,6 +1,5 @@
import { logger } from 'app/logging/logger';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addImageAddedToBoardFulfilledListener = () => {

View File

@@ -8,7 +8,7 @@ import {
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors';
import { imageSelected } from 'features/gallery/store/gallerySlice';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { isImageFieldInputInstance } from 'features/nodes/types/field';
@@ -17,8 +17,7 @@ import { clearInitialImage } from 'features/parameters/store/generationSlice';
import { clamp, forEach } from 'lodash-es';
import { api } from 'services/api';
import { imagesApi } from 'services/api/endpoints/images';
import { imagesSelectors } from 'services/api/util';
import { imagesAdapter } from 'services/api/util';
import { startAppListening } from '..';
export const addRequestedSingleImageDeletionListener = () => {
@@ -49,11 +48,13 @@ export const addRequestedSingleImageDeletionListener = () => {
if (imageDTO && imageDTO?.image_name === lastSelectedImage) {
const { image_name } = imageDTO;
const baseQueryArgs = selectListImagesQueryArgs(state);
const baseQueryArgs = selectListImagesBaseQueryArgs(state);
const { data } =
imagesApi.endpoints.listImages.select(baseQueryArgs)(state);
const cachedImageDTOs = data ? imagesSelectors.selectAll(data) : [];
const cachedImageDTOs = data
? imagesAdapter.getSelectors().selectAll(data)
: [];
const deletedImageIndex = cachedImageDTOs.findIndex(
(i) => i.image_name === image_name
@@ -180,12 +181,12 @@ export const addRequestedMultipleImageDeletionListener = () => {
imagesApi.endpoints.deleteImages.initiate({ imageDTOs })
).unwrap();
const state = getState();
const queryArgs = selectListImagesQueryArgs(state);
const baseQueryArgs = selectListImagesBaseQueryArgs(state);
const { data } =
imagesApi.endpoints.listImages.select(queryArgs)(state);
imagesApi.endpoints.listImages.select(baseQueryArgs)(state);
const newSelectedImageDTO = data
? imagesSelectors.selectAll(data)[0]
? imagesAdapter.getSelectors().selectAll(data)[0]
: undefined;
if (newSelectedImageDTO) {

Some files were not shown because too many files have changed in this diff Show More