mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-01-27 03:48:11 -05:00
Compare commits
337 Commits
next-rebas
...
maryhipp/d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0bfa7f0e0 | ||
|
|
c46b2b6fa6 | ||
|
|
058cc715d4 | ||
|
|
f69e3ee01c | ||
|
|
6e0665e3d7 | ||
|
|
5a35550144 | ||
|
|
8926a1a424 | ||
|
|
8566c1c7ff | ||
|
|
6eb4c1ccb6 | ||
|
|
ef474a3196 | ||
|
|
16b3718d6a | ||
|
|
30228ce2a4 | ||
|
|
efb5f2d202 | ||
|
|
4418c118db | ||
|
|
110b0bc8fe | ||
|
|
175cfe41a4 | ||
|
|
a12d54afb9 | ||
|
|
18af5348a2 | ||
|
|
b18c8e1c96 | ||
|
|
ea1e647174 | ||
|
|
af059f2cff | ||
|
|
d8e21091e7 | ||
|
|
344041fd3a | ||
|
|
588a220dd4 | ||
|
|
770d4092b9 | ||
|
|
33fe02bdff | ||
|
|
8a353bc1e3 | ||
|
|
240f4801db | ||
|
|
da50507b2d | ||
|
|
67d150ab66 | ||
|
|
40d70add76 | ||
|
|
7bd9bf3ba5 | ||
|
|
c94d607089 | ||
|
|
ad801e54d4 | ||
|
|
fb4db83911 | ||
|
|
cc229c3ea0 | ||
|
|
ca00fabd79 | ||
|
|
b361fabf81 | ||
|
|
00669200c7 | ||
|
|
fa07e82d2a | ||
|
|
3632c5cd57 | ||
|
|
daef68d3c1 | ||
|
|
ba29376fba | ||
|
|
3efd9465eb | ||
|
|
a3b11c04cb | ||
|
|
8f9e3ac795 | ||
|
|
2367f53367 | ||
|
|
8b9f0a9551 | ||
|
|
ab57976e42 | ||
|
|
3c103c89f3 | ||
|
|
0f19176944 | ||
|
|
fc09a954b5 | ||
|
|
e7eee29825 | ||
|
|
2c1ba23f61 | ||
|
|
58ef6dc6ce | ||
|
|
8faefa89fe | ||
|
|
02f59a3831 | ||
|
|
2555be3058 | ||
|
|
e174ce038f | ||
|
|
0f10faf0d4 | ||
|
|
393e32f8a7 | ||
|
|
70412464c8 | ||
|
|
30fdb9dbfd | ||
|
|
66f6013436 | ||
|
|
49b04f7db8 | ||
|
|
253dc5d43d | ||
|
|
3ccb4e6ff9 | ||
|
|
200a9d1801 | ||
|
|
b09a76ea0d | ||
|
|
8a2030e78a | ||
|
|
dfa5505ed8 | ||
|
|
f8b731b900 | ||
|
|
fd9ab0fb7d | ||
|
|
f504a5c96e | ||
|
|
afe6639b9c | ||
|
|
1f1bf15099 | ||
|
|
8fa238f100 | ||
|
|
30b6a0ee23 | ||
|
|
784878c300 | ||
|
|
b51b163400 | ||
|
|
7e13224ec8 | ||
|
|
7bc454209c | ||
|
|
cc7f6c7048 | ||
|
|
8b8d950137 | ||
|
|
24fd7f41ff | ||
|
|
7c5e458372 | ||
|
|
a5dba4b0d9 | ||
|
|
72fb1cefff | ||
|
|
a64f1c0b20 | ||
|
|
974658107d | ||
|
|
07fb5d5c19 | ||
|
|
20c75e7a7e | ||
|
|
cfcb68696c | ||
|
|
7b1b6d3235 | ||
|
|
aefba52a0a | ||
|
|
6af46f9c5f | ||
|
|
190702d011 | ||
|
|
7785e8ff79 | ||
|
|
b3beaefa04 | ||
|
|
98be81354a | ||
|
|
2a2a5eb775 | ||
|
|
4a42b15b42 | ||
|
|
f24d5e5e31 | ||
|
|
4b106bc903 | ||
|
|
135ef9066f | ||
|
|
0567f98e4a | ||
|
|
5b66baa3ec | ||
|
|
a022aaf258 | ||
|
|
94065b090a | ||
|
|
091bf9220b | ||
|
|
8d243b1fca | ||
|
|
23c412e011 | ||
|
|
66692f02aa | ||
|
|
38af1c3a81 | ||
|
|
7b4b7e3781 | ||
|
|
02a3472505 | ||
|
|
909d354a38 | ||
|
|
7801b8c42f | ||
|
|
4fd259bb89 | ||
|
|
b8b3ef9725 | ||
|
|
3a8d5dc349 | ||
|
|
358cac9674 | ||
|
|
bdc2b8069b | ||
|
|
09295ae43b | ||
|
|
80ad14d89f | ||
|
|
c674eb3168 | ||
|
|
63138640a7 | ||
|
|
d103ff0d6e | ||
|
|
94931e8ac0 | ||
|
|
b409f3aaf9 | ||
|
|
f96b7f2e11 | ||
|
|
de3be4bd30 | ||
|
|
cc12f57a5a | ||
|
|
613f11a3ac | ||
|
|
a6e2d2c5e0 | ||
|
|
ae14df97d6 | ||
|
|
a6e1ac6096 | ||
|
|
8530635540 | ||
|
|
b2b7aed030 | ||
|
|
970d45f691 | ||
|
|
19b9a22d93 | ||
|
|
c0d9990344 | ||
|
|
4ac5e307c4 | ||
|
|
2815f737fe | ||
|
|
63e96fd1ea | ||
|
|
66ab56246a | ||
|
|
20a56bc757 | ||
|
|
82925e1539 | ||
|
|
0137a0db7b | ||
|
|
b410793684 | ||
|
|
894e9f127b | ||
|
|
dd9b1c8eec | ||
|
|
8d9c566656 | ||
|
|
9db7e073a3 | ||
|
|
5f64ed5bd5 | ||
|
|
7f75f6226b | ||
|
|
6dc819fd47 | ||
|
|
0cc81e5d63 | ||
|
|
daecc54153 | ||
|
|
4c31c7f9f1 | ||
|
|
d709c5519f | ||
|
|
5d84ecef49 | ||
|
|
641d246213 | ||
|
|
2e53aa48c9 | ||
|
|
ef12631450 | ||
|
|
d9eb626b62 | ||
|
|
8033589629 | ||
|
|
124075ae7a | ||
|
|
0bde933c89 | ||
|
|
fc5c5b6bdd | ||
|
|
ff53563152 | ||
|
|
12b0d735e7 | ||
|
|
d06ee94fd3 | ||
|
|
9dbdb6cf7c | ||
|
|
7c091570fe | ||
|
|
e99f3482cc | ||
|
|
d999c9ffd6 | ||
|
|
888db8ac46 | ||
|
|
7deef2cb27 | ||
|
|
ada807af0c | ||
|
|
aa132fb9e3 | ||
|
|
98a01368b8 | ||
|
|
fc9a62dbf5 | ||
|
|
4d8bec1605 | ||
|
|
cf9dad83bc | ||
|
|
0d0a2a5c91 | ||
|
|
0cab636ab0 | ||
|
|
de097ec58a | ||
|
|
bb6f426162 | ||
|
|
663f135b3c | ||
|
|
2f2097662a | ||
|
|
458c29cfa5 | ||
|
|
4bec01d6f2 | ||
|
|
9d79ee8dc4 | ||
|
|
78dd460348 | ||
|
|
9d27d354cf | ||
|
|
e8725a1099 | ||
|
|
479d65b6e1 | ||
|
|
5d4b388dfd | ||
|
|
4956fa282b | ||
|
|
51133522b7 | ||
|
|
6d5cc8b1ff | ||
|
|
08a5bb90e2 | ||
|
|
39bdf5c4e9 | ||
|
|
f31e4205aa | ||
|
|
4d05c4ff66 | ||
|
|
7e88d2a7f1 | ||
|
|
556f6aa174 | ||
|
|
6a74048af8 | ||
|
|
2cb51bff11 | ||
|
|
851e835e0e | ||
|
|
fe04f28841 | ||
|
|
258fc006ec | ||
|
|
dcb4ee47d5 | ||
|
|
1a56f5aaf9 | ||
|
|
5fc745653a | ||
|
|
47b5a90177 | ||
|
|
81518ee1af | ||
|
|
b06d63fb34 | ||
|
|
5278a64301 | ||
|
|
4de4473c0f | ||
|
|
2c28a850ca | ||
|
|
6dada3326d | ||
|
|
2dfdc02ec8 | ||
|
|
1f19db4c6a | ||
|
|
7c150c27f2 | ||
|
|
248916c190 | ||
|
|
be8b99eed5 | ||
|
|
2ad0752582 | ||
|
|
ba1f8878dd | ||
|
|
bc524026f9 | ||
|
|
ad7c571983 | ||
|
|
8559c6a392 | ||
|
|
c7904a32f4 | ||
|
|
17f5484f5b | ||
|
|
86a372b02f | ||
|
|
2e9aa9391d | ||
|
|
0c8112cf28 | ||
|
|
019898c7be | ||
|
|
2b1ff8d196 | ||
|
|
79fb691b4d | ||
|
|
560ae17e21 | ||
|
|
2bd1ab2f1c | ||
|
|
ed43472582 | ||
|
|
6e5e9176c0 | ||
|
|
4c6bcdbc18 | ||
|
|
20e6d4fa3c | ||
|
|
8e51392910 | ||
|
|
0b1c2acd61 | ||
|
|
86ac55ab5f | ||
|
|
3e82f63c7e | ||
|
|
631f6cae19 | ||
|
|
0845a0ed84 | ||
|
|
46c8ce9fed | ||
|
|
13a9ea35b5 | ||
|
|
94e8d1b6d5 | ||
|
|
2b1dc74080 | ||
|
|
f7e558d165 | ||
|
|
d959276217 | ||
|
|
dfcf38be91 | ||
|
|
fbded1c0f2 | ||
|
|
ad2926a24c | ||
|
|
34d5cad4c9 | ||
|
|
60aa3d4893 | ||
|
|
5c2884569e | ||
|
|
a1307b9f2e | ||
|
|
f505ec64ba | ||
|
|
f22eb368a3 | ||
|
|
96ae22c7e0 | ||
|
|
f5447cdc23 | ||
|
|
c76a6bd65f | ||
|
|
6c4eeaa569 | ||
|
|
1bbd13ead7 | ||
|
|
321b939d0e | ||
|
|
8fb77e431e | ||
|
|
083a4f3faa | ||
|
|
2005411f7e | ||
|
|
ba7b1b2665 | ||
|
|
b7ffd36cc6 | ||
|
|
199ddd6623 | ||
|
|
a7207ed8cf | ||
|
|
6bb2dda3f1 | ||
|
|
c1e5cd5893 | ||
|
|
ff249a2315 | ||
|
|
c58f8c3269 | ||
|
|
ed772a7107 | ||
|
|
cb0b389b4b | ||
|
|
8892df1d97 | ||
|
|
bc5f356390 | ||
|
|
bcb85e100d | ||
|
|
1f27ddc07d | ||
|
|
7a2b606001 | ||
|
|
83ddcc5f3a | ||
|
|
55fa785561 | ||
|
|
06429028c8 | ||
|
|
8b6e322697 | ||
|
|
54a67459bf | ||
|
|
7fe5283e74 | ||
|
|
fe0391c86b | ||
|
|
25386a76ef | ||
|
|
fd30cb4d90 | ||
|
|
0266946d3d | ||
|
|
a7f91b3e01 | ||
|
|
de0b72528c | ||
|
|
2932652787 | ||
|
|
db6bc7305a | ||
|
|
a5db204629 | ||
|
|
8e2b61e19f | ||
|
|
a3faa3792a | ||
|
|
c16eba78ab | ||
|
|
1a191c4655 | ||
|
|
e36d925bce | ||
|
|
b1ba18b3d1 | ||
|
|
aff46759f9 | ||
|
|
d7b7dcc7fe | ||
|
|
889a26c5b6 | ||
|
|
b4c774896a | ||
|
|
afbe889d35 | ||
|
|
9c1e52b1ef | ||
|
|
3f5ab02da9 | ||
|
|
bf48e8a03a | ||
|
|
e52434cb99 | ||
|
|
483bdbcb9f | ||
|
|
ae421fb4ab | ||
|
|
cc295a9f0a | ||
|
|
a7e23af9c6 | ||
|
|
3de4390711 | ||
|
|
3ceee2b2b2 | ||
|
|
5c7ed24aab | ||
|
|
183c9c4799 | ||
|
|
8baf3f78a2 | ||
|
|
ac2eb16a65 | ||
|
|
4aa7bee4b9 | ||
|
|
7e5ba2795e | ||
|
|
97a6c6eea7 | ||
|
|
f0e60a4ba2 | ||
|
|
aa089e8108 |
@@ -32,7 +32,6 @@ To use a community workflow, download the the `.json` node graph file and load i
|
||||
+ [Image to Character Art Image Nodes](#image-to-character-art-image-nodes)
|
||||
+ [Image Picker](#image-picker)
|
||||
+ [Image Resize Plus](#image-resize-plus)
|
||||
+ [Latent Upscale](#latent-upscale)
|
||||
+ [Load Video Frame](#load-video-frame)
|
||||
+ [Make 3D](#make-3d)
|
||||
+ [Mask Operations](#mask-operations)
|
||||
@@ -291,13 +290,6 @@ View:
|
||||
</br><img src="https://raw.githubusercontent.com/VeyDlin/image-resize-plus-node/master/.readme/node.png" width="500" />
|
||||
|
||||
|
||||
--------------------------------
|
||||
### Latent Upscale
|
||||
|
||||
**Description:** This node uses a small (~2.4mb) model to upscale the latents used in a Stable Diffusion 1.5 or Stable Diffusion XL image generation, rather than the typical interpolation method, avoiding the traditional downsides of the latent upscale technique.
|
||||
|
||||
**Node Link:** [https://github.com/gogurtenjoyer/latent-upscale](https://github.com/gogurtenjoyer/latent-upscale)
|
||||
|
||||
--------------------------------
|
||||
### Load Video Frame
|
||||
|
||||
@@ -354,21 +346,12 @@ See full docs here: https://github.com/skunkworxdark/Prompt-tools-nodes/edit/mai
|
||||
|
||||
**Description:** A set of nodes for Metadata. Collect Metadata from within an `iterate` node & extract metadata from an image.
|
||||
|
||||
- `Metadata Item Linked` - Allows collecting of metadata while within an iterate node with no need for a collect node or conversion to metadata node
|
||||
- `Metadata From Image` - Provides Metadata from an image
|
||||
- `Metadata To String` - Extracts a String value of a label from metadata
|
||||
- `Metadata To Integer` - Extracts an Integer value of a label from metadata
|
||||
- `Metadata To Float` - Extracts a Float value of a label from metadata
|
||||
- `Metadata To Scheduler` - Extracts a Scheduler value of a label from metadata
|
||||
- `Metadata To Bool` - Extracts Bool types from metadata
|
||||
- `Metadata To Model` - Extracts model types from metadata
|
||||
- `Metadata To SDXL Model` - Extracts SDXL model types from metadata
|
||||
- `Metadata To LoRAs` - Extracts Loras from metadata.
|
||||
- `Metadata To SDXL LoRAs` - Extracts SDXL Loras from metadata
|
||||
- `Metadata To ControlNets` - Extracts ControNets from metadata
|
||||
- `Metadata To IP-Adapters` - Extracts IP-Adapters from metadata
|
||||
- `Metadata To T2I-Adapters` - Extracts T2I-Adapters from metadata
|
||||
- `Denoise Latents + Metadata` - This is an inherited version of the existing `Denoise Latents` node but with a metadata input and output.
|
||||
- `Metadata Item Linked` - Allows collecting of metadata while within an iterate node with no need for a collect node or conversion to metadata node.
|
||||
- `Metadata From Image` - Provides Metadata from an image.
|
||||
- `Metadata To String` - Extracts a String value of a label from metadata.
|
||||
- `Metadata To Integer` - Extracts an Integer value of a label from metadata.
|
||||
- `Metadata To Float` - Extracts a Float value of a label from metadata.
|
||||
- `Metadata To Scheduler` - Extracts a Scheduler value of a label from metadata.
|
||||
|
||||
**Node Link:** https://github.com/skunkworxdark/metadata-linked-nodes
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import pathlib
|
||||
import shutil
|
||||
from hashlib import sha1
|
||||
from random import randbytes
|
||||
import traceback
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
|
||||
from fastapi import Body, Path, Query, Response
|
||||
@@ -14,6 +15,7 @@ from starlette.exceptions import HTTPException
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from invokeai.app.services.model_install import ModelInstallJob
|
||||
from invokeai.app.services.model_metadata.metadata_store_base import ModelMetadataChanges
|
||||
from invokeai.app.services.model_records import (
|
||||
DuplicateModelException,
|
||||
InvalidModelException,
|
||||
@@ -32,6 +34,7 @@ from invokeai.backend.model_manager.config import (
|
||||
)
|
||||
from invokeai.backend.model_manager.merge import MergeInterpolationMethod, ModelMerger
|
||||
from invokeai.backend.model_manager.metadata import AnyModelRepoMetadata
|
||||
from invokeai.backend.model_manager.metadata.metadata_base import BaseMetadata
|
||||
from invokeai.backend.model_manager.search import ModelSearch
|
||||
|
||||
from ..dependencies import ApiDependencies
|
||||
@@ -242,6 +245,47 @@ async def get_model_metadata(
|
||||
|
||||
return result
|
||||
|
||||
@model_manager_router.patch(
|
||||
"/i/{key}/metadata",
|
||||
operation_id="update_model_metadata",
|
||||
responses={
|
||||
201: {
|
||||
"description": "The model metadata was updated successfully",
|
||||
"content": {"application/json": {"example": example_model_metadata}},
|
||||
},
|
||||
400: {"description": "Bad request"},
|
||||
},
|
||||
)
|
||||
async def update_model_metadata(
|
||||
key: str = Path(description="Key of the model repo metadata to fetch."),
|
||||
changes: ModelMetadataChanges = Body(description="The changes")
|
||||
) -> Optional[AnyModelRepoMetadata]:
|
||||
"""Updates or creates a model metadata object."""
|
||||
record_store = ApiDependencies.invoker.services.model_manager.store
|
||||
metadata_store = ApiDependencies.invoker.services.model_manager.store.metadata_store
|
||||
|
||||
try:
|
||||
original_metadata = record_store.get_metadata(key)
|
||||
if original_metadata:
|
||||
if changes.trigger_phrases:
|
||||
original_metadata.trigger_phrases = changes.trigger_phrases
|
||||
|
||||
if changes.default_settings:
|
||||
original_metadata.default_settings = changes.default_settings
|
||||
|
||||
metadata_store.update_metadata(key, original_metadata)
|
||||
else:
|
||||
metadata_store.add_metadata(key, BaseMetadata(name="", author="",trigger_phrases=changes.trigger_phrases, default_settings=changes.default_settings))
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"An error occurred while updating the model metadata: {e}",
|
||||
)
|
||||
|
||||
result: Optional[AnyModelRepoMetadata] = record_store.get_metadata(key)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@model_manager_router.get(
|
||||
"/tags",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# which are imported/used before parse_args() is called will get the default config values instead of the
|
||||
# values from the command line or config file.
|
||||
import sys
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from invokeai.app.api.no_cache_staticfiles import NoCacheStaticFiles
|
||||
from invokeai.version.invokeai_version import __version__
|
||||
@@ -72,25 +71,9 @@ logger = InvokeAILogger.get_logger(config=app_config)
|
||||
mimetypes.add_type("application/javascript", ".js")
|
||||
mimetypes.add_type("text/css", ".css")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# Add startup event to load dependencies
|
||||
ApiDependencies.initialize(config=app_config, event_handler_id=event_handler_id, logger=logger)
|
||||
yield
|
||||
# Shut down threads
|
||||
ApiDependencies.shutdown()
|
||||
|
||||
|
||||
# Create the app
|
||||
# TODO: create this all in a method so configuration/etc. can be passed in?
|
||||
app = FastAPI(
|
||||
title="Invoke - Community Edition",
|
||||
docs_url=None,
|
||||
redoc_url=None,
|
||||
separate_input_output_schemas=False,
|
||||
lifespan=lifespan,
|
||||
)
|
||||
app = FastAPI(title="Invoke - Community Edition", docs_url=None, redoc_url=None, separate_input_output_schemas=False)
|
||||
|
||||
# Add event handler
|
||||
event_handler_id: int = id(app)
|
||||
@@ -113,6 +96,18 @@ app.add_middleware(
|
||||
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
||||
|
||||
|
||||
# Add startup event to load dependencies
|
||||
@app.on_event("startup")
|
||||
async def startup_event() -> None:
|
||||
ApiDependencies.initialize(config=app_config, event_handler_id=event_handler_id, logger=logger)
|
||||
|
||||
|
||||
# Shut down threads
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown_event() -> None:
|
||||
ApiDependencies.shutdown()
|
||||
|
||||
|
||||
# Include all routers
|
||||
app.include_router(utilities.utilities_router, prefix="/api")
|
||||
app.include_router(model_manager.model_manager_router, prefix="/api")
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
from typing import Iterator, List, Optional, Tuple, Union, cast
|
||||
from typing import Iterator, List, Optional, Tuple, Union
|
||||
|
||||
import torch
|
||||
from compel import Compel, ReturnedEmbeddingsType
|
||||
from compel.prompt_parser import Blend, Conjunction, CrossAttentionControlSubstitute, FlattenedPrompt, Fragment
|
||||
from transformers import CLIPTextModel, CLIPTextModelWithProjection, CLIPTokenizer
|
||||
from transformers import CLIPTokenizer
|
||||
|
||||
from invokeai.app.invocations.fields import FieldDescriptions, Input, InputField, OutputField, UIComponent
|
||||
import invokeai.backend.util.logging as logger
|
||||
from invokeai.app.invocations.fields import (
|
||||
FieldDescriptions,
|
||||
Input,
|
||||
InputField,
|
||||
OutputField,
|
||||
UIComponent,
|
||||
)
|
||||
from invokeai.app.invocations.primitives import ConditioningOutput
|
||||
from invokeai.app.services.model_records import UnknownModelException
|
||||
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||
from invokeai.app.util.ti_utils import generate_ti_list
|
||||
from invokeai.app.util.ti_utils import extract_ti_triggers_from_prompt
|
||||
from invokeai.backend.lora import LoRAModelRaw
|
||||
from invokeai.backend.model_manager import ModelType
|
||||
from invokeai.backend.model_patcher import ModelPatcher
|
||||
from invokeai.backend.stable_diffusion.diffusion.conditioning_data import (
|
||||
BasicConditioningInfo,
|
||||
@@ -17,9 +26,15 @@ from invokeai.backend.stable_diffusion.diffusion.conditioning_data import (
|
||||
ExtraConditioningInfo,
|
||||
SDXLConditioningInfo,
|
||||
)
|
||||
from invokeai.backend.textual_inversion import TextualInversionModelRaw
|
||||
from invokeai.backend.util.devices import torch_dtype
|
||||
|
||||
from .baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output
|
||||
from .baseinvocation import (
|
||||
BaseInvocation,
|
||||
BaseInvocationOutput,
|
||||
invocation,
|
||||
invocation_output,
|
||||
)
|
||||
from .model import ClipField
|
||||
|
||||
# unconditioned: Optional[torch.Tensor]
|
||||
@@ -55,11 +70,7 @@ class CompelInvocation(BaseInvocation):
|
||||
@torch.no_grad()
|
||||
def invoke(self, context: InvocationContext) -> ConditioningOutput:
|
||||
tokenizer_info = context.models.load(**self.clip.tokenizer.model_dump())
|
||||
tokenizer_model = tokenizer_info.model
|
||||
assert isinstance(tokenizer_model, CLIPTokenizer)
|
||||
text_encoder_info = context.models.load(**self.clip.text_encoder.model_dump())
|
||||
text_encoder_model = text_encoder_info.model
|
||||
assert isinstance(text_encoder_model, CLIPTextModel)
|
||||
|
||||
def _lora_loader() -> Iterator[Tuple[LoRAModelRaw, float]]:
|
||||
for lora in self.clip.loras:
|
||||
@@ -71,10 +82,21 @@ class CompelInvocation(BaseInvocation):
|
||||
|
||||
# loras = [(context.models.get(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.clip.loras]
|
||||
|
||||
ti_list = generate_ti_list(self.prompt, text_encoder_info.config.base, context)
|
||||
ti_list = []
|
||||
for trigger in extract_ti_triggers_from_prompt(self.prompt):
|
||||
name = trigger[1:-1]
|
||||
try:
|
||||
loaded_model = context.models.load(key=name).model
|
||||
assert isinstance(loaded_model, TextualInversionModelRaw)
|
||||
ti_list.append((name, loaded_model))
|
||||
except UnknownModelException:
|
||||
# print(e)
|
||||
# import traceback
|
||||
# print(traceback.format_exc())
|
||||
print(f'Warn: trigger: "{trigger}" not found')
|
||||
|
||||
with (
|
||||
ModelPatcher.apply_ti(tokenizer_model, text_encoder_model, ti_list) as (
|
||||
ModelPatcher.apply_ti(tokenizer_info.model, text_encoder_info.model, ti_list) as (
|
||||
tokenizer,
|
||||
ti_manager,
|
||||
),
|
||||
@@ -82,9 +104,8 @@ class CompelInvocation(BaseInvocation):
|
||||
# Apply the LoRA after text_encoder has been moved to its target device for faster patching.
|
||||
ModelPatcher.apply_lora_text_encoder(text_encoder, _lora_loader()),
|
||||
# Apply CLIP Skip after LoRA to prevent LoRA application from failing on skipped layers.
|
||||
ModelPatcher.apply_clip_skip(text_encoder_model, self.clip.skipped_layers),
|
||||
ModelPatcher.apply_clip_skip(text_encoder_info.model, self.clip.skipped_layers),
|
||||
):
|
||||
assert isinstance(text_encoder, CLIPTextModel)
|
||||
compel = Compel(
|
||||
tokenizer=tokenizer,
|
||||
text_encoder=text_encoder,
|
||||
@@ -134,11 +155,7 @@ class SDXLPromptInvocationBase:
|
||||
zero_on_empty: bool,
|
||||
) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[ExtraConditioningInfo]]:
|
||||
tokenizer_info = context.models.load(**clip_field.tokenizer.model_dump())
|
||||
tokenizer_model = tokenizer_info.model
|
||||
assert isinstance(tokenizer_model, CLIPTokenizer)
|
||||
text_encoder_info = context.models.load(**clip_field.text_encoder.model_dump())
|
||||
text_encoder_model = text_encoder_info.model
|
||||
assert isinstance(text_encoder_model, (CLIPTextModel, CLIPTextModelWithProjection))
|
||||
|
||||
# return zero on empty
|
||||
if prompt == "" and zero_on_empty:
|
||||
@@ -172,10 +189,25 @@ class SDXLPromptInvocationBase:
|
||||
|
||||
# loras = [(context.models.get(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.clip.loras]
|
||||
|
||||
ti_list = generate_ti_list(prompt, text_encoder_info.config.base, context)
|
||||
ti_list = []
|
||||
for trigger in extract_ti_triggers_from_prompt(prompt):
|
||||
name = trigger[1:-1]
|
||||
try:
|
||||
ti_model = context.models.load_by_attrs(
|
||||
model_name=name, base_model=text_encoder_info.config.base, model_type=ModelType.TextualInversion
|
||||
).model
|
||||
assert isinstance(ti_model, TextualInversionModelRaw)
|
||||
ti_list.append((name, ti_model))
|
||||
except UnknownModelException:
|
||||
# print(e)
|
||||
# import traceback
|
||||
# print(traceback.format_exc())
|
||||
logger.warning(f'trigger: "{trigger}" not found')
|
||||
except ValueError:
|
||||
logger.warning(f'trigger: "{trigger}" more than one similarly-named textual inversion models')
|
||||
|
||||
with (
|
||||
ModelPatcher.apply_ti(tokenizer_model, text_encoder_model, ti_list) as (
|
||||
ModelPatcher.apply_ti(tokenizer_info.model, text_encoder_info.model, ti_list) as (
|
||||
tokenizer,
|
||||
ti_manager,
|
||||
),
|
||||
@@ -183,10 +215,8 @@ class SDXLPromptInvocationBase:
|
||||
# Apply the LoRA after text_encoder has been moved to its target device for faster patching.
|
||||
ModelPatcher.apply_lora(text_encoder, _lora_loader(), lora_prefix),
|
||||
# Apply CLIP Skip after LoRA to prevent LoRA application from failing on skipped layers.
|
||||
ModelPatcher.apply_clip_skip(text_encoder_model, clip_field.skipped_layers),
|
||||
ModelPatcher.apply_clip_skip(text_encoder_info.model, clip_field.skipped_layers),
|
||||
):
|
||||
assert isinstance(text_encoder, (CLIPTextModel, CLIPTextModelWithProjection))
|
||||
text_encoder = cast(CLIPTextModel, text_encoder)
|
||||
compel = Compel(
|
||||
tokenizer=tokenizer,
|
||||
text_encoder=text_encoder,
|
||||
|
||||
@@ -4,9 +4,27 @@ Storage for Model Metadata
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List, Set, Tuple
|
||||
from typing import List, Optional, Set, Tuple
|
||||
|
||||
from pydantic import Field
|
||||
from invokeai.app.util.model_exclude_null import BaseModelExcludeNull
|
||||
|
||||
from invokeai.backend.model_manager.metadata import AnyModelRepoMetadata
|
||||
from invokeai.backend.model_manager.metadata.metadata_base import ModelDefaultSettings
|
||||
|
||||
class ModelMetadataChanges(BaseModelExcludeNull, extra="allow"):
|
||||
"""A set of changes to apply to model metadata.
|
||||
|
||||
Only limited changes are valid:
|
||||
- `trigger_phrases`: the list of trigger phrases for this model
|
||||
- `default_settings`: the user-configured default settings for this model
|
||||
"""
|
||||
|
||||
trigger_phrases: Optional[List[str]] = Field(default=None, description="The model's list of trigger phrases")
|
||||
"""The model's list of trigger phrases"""
|
||||
|
||||
default_settings: Optional[ModelDefaultSettings] = Field(default=None, description="The user-configured default settings for this model")
|
||||
"""The user-configured default settings for this model"""
|
||||
|
||||
|
||||
class ModelMetadataStoreBase(ABC):
|
||||
|
||||
@@ -115,6 +115,8 @@ class ModelMetadataStoreSQL(ModelMetadataStoreBase):
|
||||
except sqlite3.Error as e:
|
||||
self._db.conn.rollback()
|
||||
raise e
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
return self.get_metadata(model_key)
|
||||
|
||||
@@ -179,44 +181,45 @@ class ModelMetadataStoreSQL(ModelMetadataStoreBase):
|
||||
)
|
||||
return {x[0] for x in self._cursor.fetchall()}
|
||||
|
||||
def _update_tags(self, model_key: str, tags: Set[str]) -> None:
|
||||
def _update_tags(self, model_key: str, tags: Optional[Set[str]]) -> None:
|
||||
"""Update tags for the model referenced by model_key."""
|
||||
if tags:
|
||||
# remove previous tags from this model
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
DELETE FROM model_tags
|
||||
WHERE model_id=?;
|
||||
""",
|
||||
(model_key,),
|
||||
)
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
DELETE FROM model_tags
|
||||
WHERE model_id=?;
|
||||
""",
|
||||
(model_key,),
|
||||
)
|
||||
|
||||
for tag in tags:
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
INSERT OR IGNORE INTO tags (
|
||||
tag_text
|
||||
)
|
||||
VALUES (?);
|
||||
""",
|
||||
(tag,),
|
||||
)
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
SELECT tag_id
|
||||
FROM tags
|
||||
WHERE tag_text = ?
|
||||
LIMIT 1;
|
||||
""",
|
||||
(tag,),
|
||||
)
|
||||
tag_id = self._cursor.fetchone()[0]
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
INSERT OR IGNORE INTO model_tags (
|
||||
model_id,
|
||||
tag_id
|
||||
)
|
||||
VALUES (?,?);
|
||||
""",
|
||||
(model_key, tag_id),
|
||||
)
|
||||
for tag in tags:
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
INSERT OR IGNORE INTO tags (
|
||||
tag_text
|
||||
)
|
||||
VALUES (?);
|
||||
""",
|
||||
(tag,),
|
||||
)
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
SELECT tag_id
|
||||
FROM tags
|
||||
WHERE tag_text = ?
|
||||
LIMIT 1;
|
||||
""",
|
||||
(tag,),
|
||||
)
|
||||
tag_id = self._cursor.fetchone()[0]
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
INSERT OR IGNORE INTO model_tags (
|
||||
model_id,
|
||||
tag_id
|
||||
)
|
||||
VALUES (?,?);
|
||||
""",
|
||||
(model_key, tag_id),
|
||||
)
|
||||
|
||||
@@ -1,47 +1,8 @@
|
||||
import re
|
||||
from typing import List, Tuple
|
||||
|
||||
import invokeai.backend.util.logging as logger
|
||||
from invokeai.app.services.model_records import UnknownModelException
|
||||
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||
from invokeai.backend.model_manager.config import BaseModelType, ModelType
|
||||
from invokeai.backend.textual_inversion import TextualInversionModelRaw
|
||||
|
||||
|
||||
def extract_ti_triggers_from_prompt(prompt: str) -> List[str]:
|
||||
ti_triggers: List[str] = []
|
||||
def extract_ti_triggers_from_prompt(prompt: str) -> list[str]:
|
||||
ti_triggers = []
|
||||
for trigger in re.findall(r"<[a-zA-Z0-9., _-]+>", prompt):
|
||||
ti_triggers.append(str(trigger))
|
||||
ti_triggers.append(trigger)
|
||||
return ti_triggers
|
||||
|
||||
|
||||
def generate_ti_list(
|
||||
prompt: str, base: BaseModelType, context: InvocationContext
|
||||
) -> List[Tuple[str, TextualInversionModelRaw]]:
|
||||
ti_list: List[Tuple[str, TextualInversionModelRaw]] = []
|
||||
for trigger in extract_ti_triggers_from_prompt(prompt):
|
||||
name_or_key = trigger[1:-1]
|
||||
try:
|
||||
loaded_model = context.models.load(key=name_or_key)
|
||||
model = loaded_model.model
|
||||
assert isinstance(model, TextualInversionModelRaw)
|
||||
assert loaded_model.config.base == base
|
||||
ti_list.append((name_or_key, model))
|
||||
except UnknownModelException:
|
||||
try:
|
||||
loaded_model = context.models.load_by_attrs(
|
||||
model_name=name_or_key, base_model=base, model_type=ModelType.TextualInversion
|
||||
)
|
||||
model = loaded_model.model
|
||||
assert isinstance(model, TextualInversionModelRaw)
|
||||
assert loaded_model.config.base == base
|
||||
ti_list.append((name_or_key, model))
|
||||
except UnknownModelException:
|
||||
pass
|
||||
except ValueError:
|
||||
logger.warning(f'trigger: "{trigger}" more than one similarly-named textual inversion models')
|
||||
except AssertionError:
|
||||
logger.warning(f'trigger: "{trigger}" not a valid textual inversion model for this graph')
|
||||
except Exception:
|
||||
logger.warning(f'Failed to load TI model for trigger: "{trigger}"')
|
||||
return ti_list
|
||||
|
||||
@@ -160,10 +160,11 @@ class CivitaiMetadataFetch(ModelMetadataFetchBase):
|
||||
nsfw=model_json["nsfw"],
|
||||
restrictions=LicenseRestrictions(
|
||||
AllowNoCredit=model_json["allowNoCredit"],
|
||||
AllowCommercialUse={CommercialUsage(x) for x in model_json["allowCommercialUse"]},
|
||||
AllowCommercialUse=CommercialUsage(model_json["allowCommercialUse"]),
|
||||
AllowDerivatives=model_json["allowDerivatives"],
|
||||
AllowDifferentLicense=model_json["allowDifferentLicense"],
|
||||
),
|
||||
trigger_phrases=version_json["trainedWords"],
|
||||
)
|
||||
|
||||
def from_civitai_versionid(self, version_id: int, model_id: Optional[int] = None) -> CivitaiMetadata:
|
||||
|
||||
@@ -24,6 +24,7 @@ from pydantic import BaseModel, Field, TypeAdapter
|
||||
from pydantic.networks import AnyHttpUrl
|
||||
from requests.sessions import Session
|
||||
from typing_extensions import Annotated
|
||||
from invokeai.app.invocations.constants import SCHEDULER_NAME_VALUES
|
||||
|
||||
from invokeai.backend.model_manager import ModelRepoVariant
|
||||
|
||||
@@ -54,8 +55,8 @@ class LicenseRestrictions(BaseModel):
|
||||
AllowDifferentLicense: bool = Field(
|
||||
description="if true, derivatives of this model be redistributed under a different license", default=False
|
||||
)
|
||||
AllowCommercialUse: Optional[Set[CommercialUsage] | CommercialUsage] = Field(
|
||||
description="Type of commercial use allowed if no commercial use is allowed.", default=None
|
||||
AllowCommercialUse: Optional[CommercialUsage] = Field(
|
||||
description="Type of commercial use allowed or 'No' if no commercial use is allowed.", default=None
|
||||
)
|
||||
|
||||
|
||||
@@ -68,12 +69,22 @@ class RemoteModelFile(BaseModel):
|
||||
sha256: Optional[str] = Field(description="SHA256 hash of this model (not always available)", default=None)
|
||||
|
||||
|
||||
class ModelDefaultSettings(BaseModel):
|
||||
vae: str | None
|
||||
vae_precision: str | None
|
||||
scheduler: SCHEDULER_NAME_VALUES | None
|
||||
steps: int | None
|
||||
cfg_scale: float | None
|
||||
cfg_rescale_multiplier: float | None
|
||||
|
||||
class ModelMetadataBase(BaseModel):
|
||||
"""Base class for model metadata information."""
|
||||
|
||||
name: str = Field(description="model's name")
|
||||
author: str = Field(description="model's author")
|
||||
tags: Set[str] = Field(description="tags provided by model source")
|
||||
tags: Optional[Set[str]] = Field(description="tags provided by model source", default=None)
|
||||
trigger_phrases: Optional[List[str]] = Field(description="trigger phrases for this model", default=None)
|
||||
default_settings: Optional[ModelDefaultSettings] = Field(description="default settings for this model", default=None)
|
||||
|
||||
|
||||
class BaseMetadata(ModelMetadataBase):
|
||||
@@ -142,10 +153,7 @@ class CivitaiMetadata(ModelMetadataWithFiles):
|
||||
if self.restrictions.AllowCommercialUse is None:
|
||||
return False
|
||||
else:
|
||||
# accommodate schema change
|
||||
acu = self.restrictions.AllowCommercialUse
|
||||
commercial_usage = acu if isinstance(acu, set) else {acu}
|
||||
return CommercialUsage.No not in commercial_usage
|
||||
return self.restrictions.AllowCommercialUse != CommercialUsage("None")
|
||||
|
||||
@property
|
||||
def allow_derivatives(self) -> bool:
|
||||
|
||||
@@ -28,7 +28,6 @@ from typing import Callable, Optional, Set, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from invokeai.app.services.config import InvokeAIAppConfig
|
||||
from invokeai.backend.util.logging import InvokeAILogger
|
||||
|
||||
default_logger: Logger = InvokeAILogger.get_logger()
|
||||
@@ -118,10 +117,13 @@ class ModelSearch(ModelSearchBase):
|
||||
"""
|
||||
|
||||
models_found: Set[Path] = Field(default_factory=set)
|
||||
config: InvokeAIAppConfig = InvokeAIAppConfig.get_config()
|
||||
scanned_dirs: Set[Path] = Field(default_factory=set)
|
||||
pruned_paths: Set[Path] = Field(default_factory=set)
|
||||
|
||||
def search_started(self) -> None:
|
||||
self.models_found = set()
|
||||
self.scanned_dirs = set()
|
||||
self.pruned_paths = set()
|
||||
if self.on_search_started:
|
||||
self.on_search_started(self._directory)
|
||||
|
||||
@@ -137,53 +139,53 @@ class ModelSearch(ModelSearchBase):
|
||||
|
||||
def search(self, directory: Union[Path, str]) -> Set[Path]:
|
||||
self._directory = Path(directory)
|
||||
if not self._directory.is_absolute():
|
||||
self._directory = self.config.models_path / self._directory
|
||||
self.stats = SearchStats() # zero out
|
||||
self.search_started() # This will initialize _models_found to empty
|
||||
self._walk_directory(self._directory)
|
||||
self._walk_directory(directory)
|
||||
self.search_completed()
|
||||
return self.models_found
|
||||
|
||||
def _walk_directory(self, path: Union[Path, str], max_depth: int = 20) -> None:
|
||||
absolute_path = Path(path)
|
||||
if (
|
||||
len(absolute_path.parts) - len(self._directory.parts) > max_depth
|
||||
or not absolute_path.exists()
|
||||
or absolute_path.parent in self.models_found
|
||||
):
|
||||
return
|
||||
entries = os.scandir(absolute_path.as_posix())
|
||||
entries = [entry for entry in entries if not entry.name.startswith(".")]
|
||||
dirs = [entry for entry in entries if entry.is_dir()]
|
||||
file_names = [entry.name for entry in entries if entry.is_file()]
|
||||
if any(
|
||||
x in file_names
|
||||
for x in [
|
||||
"config.json",
|
||||
"model_index.json",
|
||||
"learned_embeds.bin",
|
||||
"pytorch_lora_weights.bin",
|
||||
"image_encoder.txt",
|
||||
]
|
||||
):
|
||||
try:
|
||||
self.model_found(absolute_path)
|
||||
return
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception as e:
|
||||
self.logger.warning(str(e))
|
||||
return
|
||||
def _walk_directory(self, path: Union[Path, str]) -> None:
|
||||
for root, dirs, files in os.walk(path, followlinks=True):
|
||||
# don't descend into directories that start with a "."
|
||||
# to avoid the Mac .DS_STORE issue.
|
||||
if str(Path(root).name).startswith("."):
|
||||
self.pruned_paths.add(Path(root))
|
||||
if any(Path(root).is_relative_to(x) for x in self.pruned_paths):
|
||||
continue
|
||||
|
||||
for n in file_names:
|
||||
if n.endswith((".ckpt", ".bin", ".pth", ".safetensors", ".pt")):
|
||||
try:
|
||||
self.model_found(absolute_path / n)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception as e:
|
||||
self.logger.warning(str(e))
|
||||
self.stats.items_scanned += len(dirs) + len(files)
|
||||
for d in dirs:
|
||||
path = Path(root) / d
|
||||
if path.parent in self.scanned_dirs:
|
||||
self.scanned_dirs.add(path)
|
||||
continue
|
||||
if any(
|
||||
(path / x).exists()
|
||||
for x in [
|
||||
"config.json",
|
||||
"model_index.json",
|
||||
"learned_embeds.bin",
|
||||
"pytorch_lora_weights.bin",
|
||||
"image_encoder.txt",
|
||||
]
|
||||
):
|
||||
self.scanned_dirs.add(path)
|
||||
try:
|
||||
self.model_found(path)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception as e:
|
||||
self.logger.warning(str(e))
|
||||
|
||||
for d in dirs:
|
||||
self._walk_directory(absolute_path / d)
|
||||
for f in files:
|
||||
path = Path(root) / f
|
||||
if path.parent in self.scanned_dirs:
|
||||
continue
|
||||
if path.suffix in {".ckpt", ".bin", ".pth", ".safetensors", ".pt"}:
|
||||
try:
|
||||
self.model_found(path)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception as e:
|
||||
self.logger.warning(str(e))
|
||||
|
||||
@@ -4,12 +4,12 @@ from __future__ import annotations
|
||||
|
||||
import pickle
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
|
||||
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from diffusers import OnnxRuntimeModel, UNet2DConditionModel
|
||||
from transformers import CLIPTextModel, CLIPTextModelWithProjection, CLIPTokenizer
|
||||
from transformers import CLIPTextModel, CLIPTokenizer
|
||||
|
||||
from invokeai.app.shared.models import FreeUConfig
|
||||
from invokeai.backend.model_manager import AnyModel
|
||||
@@ -168,7 +168,7 @@ class ModelPatcher:
|
||||
def apply_ti(
|
||||
cls,
|
||||
tokenizer: CLIPTokenizer,
|
||||
text_encoder: Union[CLIPTextModel, CLIPTextModelWithProjection],
|
||||
text_encoder: CLIPTextModel,
|
||||
ti_list: List[Tuple[str, TextualInversionModelRaw]],
|
||||
) -> Iterator[Tuple[CLIPTokenizer, TextualInversionManager]]:
|
||||
init_tokens_count = None
|
||||
@@ -265,7 +265,7 @@ class ModelPatcher:
|
||||
@contextmanager
|
||||
def apply_clip_skip(
|
||||
cls,
|
||||
text_encoder: Union[CLIPTextModel, CLIPTextModelWithProjection],
|
||||
text_encoder: CLIPTextModel,
|
||||
clip_skip: int,
|
||||
) -> None:
|
||||
skipped_layers = []
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
"outputs": "Ausgabe",
|
||||
"data": "Daten",
|
||||
"safetensors": "Safe-Tensors",
|
||||
"outpaint": "Outpaint (Außen ausmalen)",
|
||||
"outpaint": "Ausmalen",
|
||||
"details": "Details",
|
||||
"format": "Format",
|
||||
"unknown": "Unbekannt",
|
||||
@@ -110,18 +110,17 @@
|
||||
"nextPage": "Nächste Seite",
|
||||
"unknownError": "Unbekannter Fehler",
|
||||
"unsaved": "Nicht gespeichert",
|
||||
"aboutDesc": "Verwenden Sie Invoke für die Arbeit? Siehe hier:",
|
||||
"aboutDesc": "Verwenden Sie Invoke für die Arbeit? Dann siehe hier:",
|
||||
"localSystem": "Lokales System",
|
||||
"orderBy": "Ordnen nach",
|
||||
"saveAs": "Speichern als",
|
||||
"saveAs": "Speicher als",
|
||||
"updated": "Aktualisiert",
|
||||
"copy": "Kopieren",
|
||||
"aboutHeading": "Nutzen Sie Ihre kreative Energie",
|
||||
"toResolve": "Lösen"
|
||||
"aboutHeading": "Nutzen Sie Ihre kreative Energie"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Erzeugungen",
|
||||
"showGenerations": "Zeige Ergebnisse",
|
||||
"showGenerations": "Zeige Erzeugnisse",
|
||||
"uploads": "Uploads",
|
||||
"showUploads": "Zeige Uploads",
|
||||
"galleryImageSize": "Bildgröße",
|
||||
@@ -151,9 +150,9 @@
|
||||
"problemDeletingImagesDesc": "Ein oder mehrere Bilder konnten nicht gelöscht werden",
|
||||
"starImage": "Bild markieren",
|
||||
"assets": "Ressourcen",
|
||||
"unstarImage": "Markierung entfernen",
|
||||
"unstarImage": "Markierung Entfernen",
|
||||
"image": "Bild",
|
||||
"deleteSelection": "Lösche Auswahl",
|
||||
"deleteSelection": "Lösche markierte",
|
||||
"dropToUpload": "$t(gallery.drop) zum hochladen",
|
||||
"dropOrUpload": "$t(gallery.drop) oder hochladen",
|
||||
"drop": "Ablegen",
|
||||
@@ -591,21 +590,10 @@
|
||||
"general": "Allgemein",
|
||||
"hiresStrength": "High Res Stärke",
|
||||
"hidePreview": "Verstecke Vorschau",
|
||||
"showPreview": "Zeige Vorschau",
|
||||
"aspect": "Seitenverhältnis",
|
||||
"aspectRatio": "Seitenverhältnis",
|
||||
"scheduler": "Planer",
|
||||
"aspectRatioFree": "Frei",
|
||||
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (kann zu groß sein)",
|
||||
"lockAspectRatio": "Seitenverhältnis sperren",
|
||||
"swapDimensions": "Seitenverhältnis umkehren",
|
||||
"setToOptimalSize": "Optimiere Größe für Modell",
|
||||
"useSize": "Maße übernehmen",
|
||||
"remixImage": "Remix des Bilds erstellen",
|
||||
"imageActions": "Weitere Bildaktionen"
|
||||
"showPreview": "Zeige Vorschau"
|
||||
},
|
||||
"settings": {
|
||||
"displayInProgress": "Zwischenbilder anzeigen",
|
||||
"displayInProgress": "Bilder in Bearbeitung anzeigen",
|
||||
"saveSteps": "Speichern der Bilder alle n Schritte",
|
||||
"confirmOnDelete": "Bestätigen beim Löschen",
|
||||
"displayHelpIcons": "Hilfesymbole anzeigen",
|
||||
@@ -618,34 +606,7 @@
|
||||
"useSlidersForAll": "Schieberegler für alle Optionen verwenden",
|
||||
"showAdvancedOptions": "Erweiterte Optionen anzeigen",
|
||||
"alternateCanvasLayout": "Alternatives Leinwand-Layout",
|
||||
"clearIntermediatesDesc1": "Das Löschen der Zwischenbilder setzt Leinwand und ControlNet zurück.",
|
||||
"favoriteSchedulers": "Lieblings-Planer",
|
||||
"favoriteSchedulersPlaceholder": "Keine Planer favorisiert",
|
||||
"generation": "Erzeugung",
|
||||
"enableInformationalPopovers": "Info-Popouts anzeigen",
|
||||
"shouldLogToConsole": "Konsole loggen",
|
||||
"showProgressInViewer": "Zwischenbilder im Viewer anzeigen",
|
||||
"clearIntermediatesDesc3": "Ihre Bilder werden nicht gelöscht.",
|
||||
"clearIntermediatesWithCount_one": "Lösche {{count}} Zwischenbilder",
|
||||
"clearIntermediatesWithCount_other": "Lösche {{count}} Zwischenbilder",
|
||||
"reloadingIn": "Neuladen in",
|
||||
"enableNodesEditor": "Nodes Editor aktivieren",
|
||||
"autoChangeDimensions": "Breite/Höhe auf Modellstandard setzen",
|
||||
"experimental": "Experimentell",
|
||||
"intermediatesCleared_one": "{{count}} Zwischenbilder gelöscht",
|
||||
"intermediatesCleared_other": "{{count}} Zwischenbilder gelöscht",
|
||||
"enableInvisibleWatermark": "Unsichtbares Wasserzeichen aktivieren",
|
||||
"general": "Allgemein",
|
||||
"consoleLogLevel": "Protokollierungsstufe",
|
||||
"clearIntermediatesDisabled": "Warteschlange muss leer sein, um Zwischenbilder zu löschen",
|
||||
"developer": "Entwickler",
|
||||
"antialiasProgressImages": "Zwischenbilder mit Anti-Alias",
|
||||
"beta": "Beta",
|
||||
"ui": "Benutzeroberfläche",
|
||||
"clearIntermediatesDesc2": "Zwischenbilder sind Nebenprodukte der Erstellung. Sie zu löschen macht Festplattenspeicher frei.",
|
||||
"clearIntermediates": "Zwischenbilder löschen",
|
||||
"intermediatesClearedFailed": "Problem beim Löschen der Zwischenbilder",
|
||||
"enableNSFWChecker": "Auf unangemessene Inhalte prüfen"
|
||||
"clearIntermediatesDesc1": "Das Löschen der Zwischenprodukte setzt Leinwand und ControlNet zurück."
|
||||
},
|
||||
"toast": {
|
||||
"tempFoldersEmptied": "Temp-Ordner geleert",
|
||||
@@ -690,9 +651,7 @@
|
||||
"problemCopyingCanvas": "Problem beim Kopieren der Leinwand",
|
||||
"problemCopyingCanvasDesc": "Kann Basis-Layer nicht exportieren",
|
||||
"problemDownloadingCanvas": "Problem beim Herunterladen der Leinwand",
|
||||
"setAsCanvasInitialImage": "Als Ausgangsbild gesetzt",
|
||||
"addedToBoard": "Dem Board hinzugefügt",
|
||||
"loadedWithWarnings": "Workflow mit Warnungen geladen"
|
||||
"setAsCanvasInitialImage": "Als Ausgangsbild gesetzt"
|
||||
},
|
||||
"tooltip": {
|
||||
"feature": {
|
||||
@@ -774,23 +733,23 @@
|
||||
"accessibility": {
|
||||
"modelSelect": "Modell-Auswahl",
|
||||
"uploadImage": "Bild hochladen",
|
||||
"previousImage": "Vorheriges Bild",
|
||||
"previousImage": "Voriges Bild",
|
||||
"useThisParameter": "Benutze diesen Parameter",
|
||||
"copyMetadataJson": "Kopiere JSON-Metadaten",
|
||||
"copyMetadataJson": "Kopiere Metadaten JSON",
|
||||
"zoomIn": "Vergrößern",
|
||||
"rotateClockwise": "Im Uhrzeigersinn drehen",
|
||||
"flipHorizontally": "Horizontal drehen",
|
||||
"flipVertically": "Vertikal drehen",
|
||||
"modifyConfig": "Optionen einstellen",
|
||||
"toggleAutoscroll": "Auroscroll ein/ausschalten",
|
||||
"toggleLogViewer": "Log-Betrachter ein/ausschalten",
|
||||
"toggleLogViewer": "Log Betrachter ein/ausschalten",
|
||||
"showOptionsPanel": "Seitenpanel anzeigen",
|
||||
"reset": "Zurücksetzten",
|
||||
"nextImage": "Nächstes Bild",
|
||||
"zoomOut": "Verkleinern",
|
||||
"rotateCounterClockwise": "Gegen den Uhrzeigersinn drehen",
|
||||
"showGalleryPanel": "Galerie-Panel anzeigen",
|
||||
"exitViewer": "Betrachter beenden",
|
||||
"showGalleryPanel": "Galeriefenster anzeigen",
|
||||
"exitViewer": "Betrachten beenden",
|
||||
"menu": "Menü",
|
||||
"loadMore": "Mehr laden",
|
||||
"invokeProgressBar": "Invoke Fortschrittsanzeige",
|
||||
@@ -800,7 +759,7 @@
|
||||
"about": "Über"
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "Automatisches Hinzufügen zum Board",
|
||||
"autoAddBoard": "Automatisches Hinzufügen zum Ordner",
|
||||
"topMessage": "Dieser Ordner enthält Bilder die in den folgenden Funktionen verwendet werden:",
|
||||
"move": "Bewegen",
|
||||
"menuItemAutoAdd": "Auto-Hinzufügen zu diesem Ordner",
|
||||
@@ -809,13 +768,13 @@
|
||||
"noMatching": "Keine passenden Ordner",
|
||||
"selectBoard": "Ordner aussuchen",
|
||||
"cancel": "Abbrechen",
|
||||
"addBoard": "Board hinzufügen",
|
||||
"addBoard": "Ordner hinzufügen",
|
||||
"uncategorized": "Ohne Kategorie",
|
||||
"downloadBoard": "Ordner runterladen",
|
||||
"changeBoard": "Ordner wechseln",
|
||||
"loading": "Laden...",
|
||||
"clearSearch": "Suche leeren",
|
||||
"bottomMessage": "Löschen des Boards und seiner Bilder setzt alle Funktionen zurück, die sie gerade verwenden.",
|
||||
"bottomMessage": "Durch das Löschen dieses Ordners und seiner Bilder werden alle Funktionen zurückgesetzt, die sie derzeit verwenden.",
|
||||
"deleteBoardOnly": "Nur Ordner löschen",
|
||||
"deleteBoard": "Löschen Ordner",
|
||||
"deleteBoardAndImages": "Löschen Ordner und Bilder",
|
||||
@@ -861,7 +820,7 @@
|
||||
"colorMap": "Farbe",
|
||||
"lowThreshold": "Niedrige Schwelle",
|
||||
"highThreshold": "Hohe Schwelle",
|
||||
"toggleControlNet": "Dieses ControlNet ein- oder ausschalten",
|
||||
"toggleControlNet": "Schalten ControlNet um",
|
||||
"delete": "Löschen",
|
||||
"controlAdapter_one": "Control Adapter",
|
||||
"controlAdapter_other": "Control Adapter",
|
||||
@@ -906,23 +865,18 @@
|
||||
"maxFaces": "Maximale Anzahl Gesichter",
|
||||
"resizeSimple": "Größe ändern (einfach)",
|
||||
"large": "Groß",
|
||||
"modelSize": "Modellgröße",
|
||||
"modelSize": "Modell Größe",
|
||||
"small": "Klein",
|
||||
"base": "Basis",
|
||||
"depthAnything": "Depth Anything",
|
||||
"depthAnythingDescription": "Erstellung einer Tiefenkarte mit der Depth-Anything-Technik",
|
||||
"face": "Gesicht",
|
||||
"body": "Körper",
|
||||
"hands": "Hände",
|
||||
"dwOpenpose": "DW Openpose",
|
||||
"dwOpenposeDescription": "Posenschätzung mit DW Openpose"
|
||||
"depthAnything": "Depth Anything / \"Tiefe irgendwas\"",
|
||||
"depthAnythingDescription": "Erstellung einer Tiefenkarte mit der Depth Anything-Technik"
|
||||
},
|
||||
"queue": {
|
||||
"status": "Status",
|
||||
"cancelTooltip": "Aktuellen Aufgabe abbrechen",
|
||||
"queueEmpty": "Warteschlange leer",
|
||||
"in_progress": "In Arbeit",
|
||||
"queueFront": "Am Anfang der Warteschlange einreihen",
|
||||
"queueFront": "An den Anfang der Warteschlange tun",
|
||||
"completed": "Fertig",
|
||||
"queueBack": "In die Warteschlange",
|
||||
"clearFailed": "Probleme beim leeren der Warteschlange",
|
||||
@@ -950,7 +904,7 @@
|
||||
"batchValues": "Stapel Werte",
|
||||
"queueCountPrediction": "{{promptsCount}} Prompts × {{iterations}} Iterationen -> {{count}} Generationen",
|
||||
"queuedCount": "{{pending}} wartenden Elemente",
|
||||
"clearQueueAlertDialog": "\"Die Warteschlange leeren\" stoppt den aktuellen Prozess und leert die Warteschlange komplett.",
|
||||
"clearQueueAlertDialog": "Die Warteschlange leeren, stoppt den aktuellen Prozess und leert die Warteschlange komplett.",
|
||||
"completedIn": "Fertig in",
|
||||
"cancelBatchSucceeded": "Stapel abgebrochen",
|
||||
"cancelBatch": "Stapel stoppen",
|
||||
@@ -959,20 +913,20 @@
|
||||
"cancelBatchFailed": "Problem beim Abbruch vom Stapel",
|
||||
"clearQueueAlertDialog2": "Warteschlange wirklich leeren?",
|
||||
"pruneSucceeded": "{{item_count}} abgeschlossene Elemente aus der Warteschlange entfernt",
|
||||
"pauseSucceeded": "Prozess angehalten",
|
||||
"pauseSucceeded": "Prozessor angehalten",
|
||||
"cancelFailed": "Problem beim Stornieren des Auftrags",
|
||||
"pauseFailed": "Problem beim Anhalten des Prozesses",
|
||||
"pauseFailed": "Problem beim Anhalten des Prozessors",
|
||||
"front": "Vorne",
|
||||
"pruneTooltip": "Bereinigen Sie {{item_count}} abgeschlossene Aufträge",
|
||||
"resumeFailed": "Problem beim Fortsetzen des Prozesses",
|
||||
"resumeFailed": "Problem beim wieder aufnehmen von Prozessor",
|
||||
"pruneFailed": "Problem beim leeren der Warteschlange",
|
||||
"pauseTooltip": "Prozess anhalten",
|
||||
"pauseTooltip": "Pause von Prozessor",
|
||||
"back": "Hinten",
|
||||
"resumeSucceeded": "Prozess wird fortgesetzt",
|
||||
"resumeTooltip": "Prozess wieder aufnehmen",
|
||||
"resumeSucceeded": "Prozessor wieder aufgenommen",
|
||||
"resumeTooltip": "Prozessor wieder aufnehmen",
|
||||
"time": "Zeit",
|
||||
"batchQueuedDesc_one": "{{count}} Eintrag an {{direction}} der Wartschlange hinzugefügt",
|
||||
"batchQueuedDesc_other": "{{count}} Einträge an {{direction}} der Wartschlange hinzugefügt",
|
||||
"batchQueuedDesc_one": "{{count}} Eintrag ans {{direction}} der Wartschlange hinzugefügt",
|
||||
"batchQueuedDesc_other": "{{count}} Einträge ans {{direction}} der Wartschlange hinzugefügt",
|
||||
"openQueue": "Warteschlange öffnen",
|
||||
"batchFailedToQueue": "Fehler beim Einreihen in die Stapelverarbeitung",
|
||||
"batchFieldValues": "Stapelverarbeitungswerte",
|
||||
@@ -1007,12 +961,11 @@
|
||||
"workflow": "Workflow",
|
||||
"scheduler": "Planer",
|
||||
"noRecallParameters": "Es wurden keine Parameter zum Abrufen gefunden",
|
||||
"recallParameters": "Parameter wiederherstellen",
|
||||
"cfgRescaleMultiplier": "$t(parameters.cfgRescaleMultiplier)"
|
||||
"recallParameters": "Parameter wiederherstellen"
|
||||
},
|
||||
"popovers": {
|
||||
"noiseUseCPU": {
|
||||
"heading": "Nutze CPU-Rauschen",
|
||||
"heading": "Nutze Prozessor rauschen",
|
||||
"paragraphs": [
|
||||
"Entscheidet, ob auf der CPU oder GPU Rauschen erzeugt wird.",
|
||||
"Mit aktiviertem CPU-Rauschen wird ein bestimmter Seedwert das gleiche Bild auf jeder Maschine erzeugen.",
|
||||
@@ -1022,7 +975,8 @@
|
||||
"paramModel": {
|
||||
"heading": "Modell",
|
||||
"paragraphs": [
|
||||
"Modell für die Entrauschungsschritte."
|
||||
"Modell für die Entrauschungsschritte.",
|
||||
"Verschiedene Modelle werden in der Regel so trainiert, dass sie sich auf die Erzeugung bestimmter Ästhetik und/oder Inhalte spezialisiert."
|
||||
]
|
||||
},
|
||||
"paramIterations": {
|
||||
@@ -1130,23 +1084,12 @@
|
||||
"Wie stark wird das ControlNet das generierte Bild beeinflussen wird."
|
||||
],
|
||||
"heading": "Einfluss"
|
||||
},
|
||||
"paramScheduler": {
|
||||
"paragraphs": [
|
||||
"\"Planer\" definiert, wie iterativ Rauschen zu einem Bild hinzugefügt wird, oder wie ein Sample bei der Ausgabe eines Modells aktualisiert wird."
|
||||
],
|
||||
"heading": "Planer"
|
||||
},
|
||||
"imageFit": {
|
||||
"paragraphs": [
|
||||
"Reduziert das Ausgangsbild auf die Breite und Höhe des Ausgangsbildes. Empfohlen zu aktivieren."
|
||||
]
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
"lockRatio": "Verhältnis sperren",
|
||||
"hideProgressImages": "Fortschrittsbilder verbergen",
|
||||
"showProgressImages": "Fortschrittsbilder anzeigen",
|
||||
"hideProgressImages": "Verstecke Prozess Bild",
|
||||
"showProgressImages": "Zeige Prozess Bild",
|
||||
"swapSizes": "Tausche Größen"
|
||||
},
|
||||
"invocationCache": {
|
||||
@@ -1344,19 +1287,7 @@
|
||||
"vaeFieldDescription": "VAE Submodell.",
|
||||
"unknownInput": "Unbekannte Eingabe: {{name}}",
|
||||
"unknownNodeType": "Unbekannter Knotentyp",
|
||||
"float": "Kommazahlen",
|
||||
"latentsPolymorphic": "Latents Polymorph",
|
||||
"integerPolymorphicDescription": "Eine Sammlung von ganzen Zahlen.",
|
||||
"integerPolymorphic": "Ganze Zahl Polymorph",
|
||||
"ipAdapterPolymorphic": "IP-Adapter Polymorph",
|
||||
"floatPolymorphic": "Fließkommazahl Polymorph",
|
||||
"enumDescription": "Aufzählungen sind Werte, die eine von mehreren Optionen sein können.",
|
||||
"floatCollection": "Fließkommazahl Sammlung",
|
||||
"enum": "Aufzählung",
|
||||
"floatPolymorphicDescription": "Eine Sammlung von Fließkommazahlen",
|
||||
"fullyContainNodes": "Vollständig ausgewählte Nodes auswählen",
|
||||
"editMode": "Im Workflow-Editor bearbeiten",
|
||||
"floatCollectionDescription": "Eine Sammlung von Fließkommazahlen"
|
||||
"float": "Kommazahlen"
|
||||
},
|
||||
"hrf": {
|
||||
"enableHrf": "Korrektur für hohe Auflösungen",
|
||||
@@ -1405,12 +1336,12 @@
|
||||
},
|
||||
"control": {
|
||||
"title": "Kontrolle",
|
||||
"controlAdaptersTab": "Kontroll-Adapter",
|
||||
"ipTab": "Bild-Prompts"
|
||||
"controlAdaptersTab": "Kontroll Adapter",
|
||||
"ipTab": "Bild Beschreibung"
|
||||
},
|
||||
"compositing": {
|
||||
"coherenceTab": "Kohärenzpass",
|
||||
"infillTab": "Füllung / Infill",
|
||||
"infillTab": "Füllung",
|
||||
"title": "Compositing"
|
||||
}
|
||||
},
|
||||
@@ -1448,15 +1379,5 @@
|
||||
},
|
||||
"app": {
|
||||
"storeNotInitialized": "App-Store ist nicht initialisiert"
|
||||
},
|
||||
"sdxl": {
|
||||
"concatPromptStyle": "Verknüpfen von Prompt & Stil",
|
||||
"scheduler": "Planer",
|
||||
"steps": "Schritte",
|
||||
"useRefiner": "Refiner verwenden",
|
||||
"selectAModel": "Modell auswählen"
|
||||
},
|
||||
"dynamicPrompts": {
|
||||
"showDynamicPrompts": "Dynamische Prompts anzeigen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
"aboutDesc": "Using Invoke for work? Check out:",
|
||||
"aboutHeading": "Own Your Creative Power",
|
||||
"accept": "Accept",
|
||||
"add": "Add",
|
||||
"advanced": "Advanced",
|
||||
"advancedOptions": "Advanced Options",
|
||||
"ai": "ai",
|
||||
@@ -303,6 +304,12 @@
|
||||
"method": "High Resolution Fix Method"
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
"addPromptTrigger": "Add Prompt Trigger",
|
||||
"compatibleEmbeddings": "Compatible Embeddings",
|
||||
"noPromptTriggers": "No triggers available",
|
||||
"noMatchingTriggers": "No matching triggers"
|
||||
},
|
||||
"embedding": {
|
||||
"addEmbedding": "Add Embedding",
|
||||
"incompatibleModel": "Incompatible base model:",
|
||||
@@ -734,6 +741,8 @@
|
||||
"customConfig": "Custom Config",
|
||||
"customConfigFileLocation": "Custom Config File Location",
|
||||
"customSaveLocation": "Custom Save Location",
|
||||
"defaultSettings": "Default Settings",
|
||||
"defaultSettingsSaved": "Default Settings Saved",
|
||||
"delete": "Delete",
|
||||
"deleteConfig": "Delete Config",
|
||||
"deleteModel": "Delete Model",
|
||||
@@ -768,6 +777,7 @@
|
||||
"mergedModelName": "Merged Model Name",
|
||||
"mergedModelSaveLocation": "Save Location",
|
||||
"mergeModels": "Merge Models",
|
||||
"metadata": "Metadata",
|
||||
"model": "Model",
|
||||
"modelAdded": "Model Added",
|
||||
"modelConversionFailed": "Model Conversion Failed",
|
||||
@@ -839,9 +849,12 @@
|
||||
"statusConverting": "Converting",
|
||||
"syncModels": "Sync Models",
|
||||
"syncModelsDesc": "If your models are out of sync with the backend, you can refresh them up using this option. This is generally handy in cases where you add models to the InvokeAI root folder or autoimport directory after the application has booted.",
|
||||
"triggerPhrases": "Trigger Phrases",
|
||||
"typePhraseHere": "Type phrase here",
|
||||
"upcastAttention": "Upcast Attention",
|
||||
"updateModel": "Update Model",
|
||||
"useCustomConfig": "Use Custom Config",
|
||||
"useDefaultSettings": "Use Default Settings",
|
||||
"v1": "v1",
|
||||
"v2_768": "v2 (768px)",
|
||||
"v2_base": "v2 (512px)",
|
||||
@@ -1456,8 +1469,9 @@
|
||||
"clipSkip": {
|
||||
"heading": "CLIP Skip",
|
||||
"paragraphs": [
|
||||
"How many layers of the CLIP model to skip.",
|
||||
"Certain models are better suited to be used with CLIP Skip."
|
||||
"Choose how many layers of the CLIP model to skip.",
|
||||
"Some models work better with certain CLIP Skip settings.",
|
||||
"A higher value typically results in a less detailed image."
|
||||
]
|
||||
},
|
||||
"paramNegativeConditioning": {
|
||||
@@ -1477,8 +1491,7 @@
|
||||
"paramScheduler": {
|
||||
"heading": "Scheduler",
|
||||
"paragraphs": [
|
||||
"Scheduler used during the generation process.",
|
||||
"Each scheduler defines how to iteratively add noise to an image or how to update a sample based on a model's output."
|
||||
"Scheduler defines how to iteratively add noise to an image or how to update a sample based on a model's output."
|
||||
]
|
||||
},
|
||||
"compositingMaskBlur": {
|
||||
@@ -1495,15 +1508,7 @@
|
||||
},
|
||||
"compositingCoherenceMode": {
|
||||
"heading": "Mode",
|
||||
"paragraphs": ["Method used to create a coherent image with the newly generated masked area."]
|
||||
},
|
||||
"compositingCoherenceSteps": {
|
||||
"heading": "Steps",
|
||||
"paragraphs": ["Number of steps in the Coherence Pass.", "Similar to Generation Steps."]
|
||||
},
|
||||
"compositingStrength": {
|
||||
"heading": "Strength",
|
||||
"paragraphs": ["Amount of noise added for the Coherence Pass.", "Similar to Denoising Strength."]
|
||||
"paragraphs": ["The mode of the Coherence Pass."]
|
||||
},
|
||||
"compositingCoherenceEdgeSize": {
|
||||
"heading": "Edge Size",
|
||||
@@ -1520,38 +1525,30 @@
|
||||
"heading": "Mask Adjustments",
|
||||
"paragraphs": ["Adjust the mask."]
|
||||
},
|
||||
"controlNetBeginEnd": {
|
||||
"heading": "Begin / End Step Percentage",
|
||||
"paragraphs": [
|
||||
"Which steps of the denoising process will have the ControlNet applied.",
|
||||
"ControlNets applied at the beginning of the process guide composition, and ControlNets applied at the end guide details."
|
||||
]
|
||||
},
|
||||
"controlNetControlMode": {
|
||||
"heading": "Control Mode",
|
||||
"paragraphs": ["Lends more weight to either the prompt or ControlNet."]
|
||||
},
|
||||
"controlNetResizeMode": {
|
||||
"heading": "Resize Mode",
|
||||
"paragraphs": ["How the ControlNet image will be fit to the image output size."]
|
||||
},
|
||||
"controlNet": {
|
||||
"heading": "ControlNet",
|
||||
"paragraphs": [
|
||||
"ControlNets provide guidance to the generation process, helping create images with controlled composition, structure, or style, depending on the model selected."
|
||||
]
|
||||
},
|
||||
"controlNetBeginEnd": {
|
||||
"heading": "Begin / End Step Percentage",
|
||||
"paragraphs": [
|
||||
"The part of the of the denoising process that will have the Control Adapter applied.",
|
||||
"Generally, Control Adapters applied at the start of the process guide composition, and Control Adapters applied at the end guide details."
|
||||
]
|
||||
},
|
||||
"controlNetControlMode": {
|
||||
"heading": "Control Mode",
|
||||
"paragraphs": ["Lend more weight to either the prompt or ControlNet."]
|
||||
},
|
||||
"controlNetProcessor": {
|
||||
"heading": "Processor",
|
||||
"paragraphs": [
|
||||
"Method of processing the input image to guide the generation process. Different processors will providedifferent effects or styles in your generated images."
|
||||
]
|
||||
},
|
||||
"controlNetResizeMode": {
|
||||
"heading": "Resize Mode",
|
||||
"paragraphs": ["Method to fit Control Adapter's input image size to the output generation size."]
|
||||
},
|
||||
"controlNetWeight": {
|
||||
"heading": "Weight",
|
||||
"paragraphs": [
|
||||
"Weight of the Control Adapter. Higher weight will lead to larger impacts on the final image."
|
||||
]
|
||||
"paragraphs": ["How strongly the ControlNet will impact the generated image."]
|
||||
},
|
||||
"dynamicPrompts": {
|
||||
"heading": "Dynamic Prompts",
|
||||
@@ -1574,23 +1571,13 @@
|
||||
"Per Image will use a unique seed for each image. This provides more variation."
|
||||
]
|
||||
},
|
||||
"imageFit": {
|
||||
"heading": "Fit Initial Image to Output Size",
|
||||
"paragraphs": [
|
||||
"Resizes the initial image to the width and height of the output image. Recommended to enable."
|
||||
]
|
||||
},
|
||||
"infillMethod": {
|
||||
"heading": "Infill Method",
|
||||
"paragraphs": ["Method of infilling during the Outpainting or Inpainting process."]
|
||||
"paragraphs": ["Method to infill the selected area."]
|
||||
},
|
||||
"lora": {
|
||||
"heading": "LoRA",
|
||||
"paragraphs": ["Lightweight models that are used in conjunction with base models."]
|
||||
},
|
||||
"loraWeight": {
|
||||
"heading": "Weight",
|
||||
"paragraphs": ["Weight of the LoRA. Higher weight will lead to larger impacts on the final image."]
|
||||
"heading": "LoRA Weight",
|
||||
"paragraphs": ["Higher LoRA weight will lead to larger impacts on the final image."]
|
||||
},
|
||||
"noiseUseCPU": {
|
||||
"heading": "Use CPU Noise",
|
||||
@@ -1600,25 +1587,14 @@
|
||||
"There is no performance impact to enabling CPU Noise."
|
||||
]
|
||||
},
|
||||
"paramAspect": {
|
||||
"heading": "Aspect",
|
||||
"paragraphs": [
|
||||
"Aspect ratio of the generated image. Changing the ratio will update the Width and Height accordingly.",
|
||||
"“Optimize” will set the Width and Height to optimal dimensions for the chosen model."
|
||||
]
|
||||
},
|
||||
"paramCFGScale": {
|
||||
"heading": "CFG Scale",
|
||||
"paragraphs": [
|
||||
"Controls how much the prompt influences the generation process.",
|
||||
"High CFG Scale values can result in over-saturation and distorted generation results. "
|
||||
]
|
||||
"paragraphs": ["Controls how much your prompt influences the generation process."]
|
||||
},
|
||||
"paramCFGRescaleMultiplier": {
|
||||
"heading": "CFG Rescale Multiplier",
|
||||
"paragraphs": [
|
||||
"Rescale multiplier for CFG guidance, used for models trained using zero-terminal SNR (ztsnr).",
|
||||
"Suggested value of 0.7 for these models."
|
||||
"Rescale multiplier for CFG guidance, used for models trained using zero-terminal SNR (ztsnr). Suggested value 0.7."
|
||||
]
|
||||
},
|
||||
"paramDenoisingStrength": {
|
||||
@@ -1628,16 +1604,6 @@
|
||||
"0 will result in an identical image, while 1 will result in a completely new image."
|
||||
]
|
||||
},
|
||||
"paramHeight": {
|
||||
"heading": "Height",
|
||||
"paragraphs": ["Height of the generated image. Must be a multiple of 8."]
|
||||
},
|
||||
"paramHrf": {
|
||||
"heading": "Enable High Resolution Fix",
|
||||
"paragraphs": [
|
||||
"Generate high quality images at a larger resolution than optimal for the model. Generally used to prevent duplication in the generated image."
|
||||
]
|
||||
},
|
||||
"paramIterations": {
|
||||
"heading": "Iterations",
|
||||
"paragraphs": [
|
||||
@@ -1648,7 +1614,8 @@
|
||||
"paramModel": {
|
||||
"heading": "Model",
|
||||
"paragraphs": [
|
||||
"Model used for generation. Different models are trained to specialize in producing different aesthetic results and content."
|
||||
"Model used for the denoising steps.",
|
||||
"Different models are typically trained to specialize in producing particular aesthetic results and content."
|
||||
]
|
||||
},
|
||||
"paramRatio": {
|
||||
@@ -1662,7 +1629,7 @@
|
||||
"heading": "Seed",
|
||||
"paragraphs": [
|
||||
"Controls the starting noise used for generation.",
|
||||
"Disable the “Random” option to produce identical results with the same generation settings."
|
||||
"Disable “Random Seed” to produce identical results with the same generation settings."
|
||||
]
|
||||
},
|
||||
"paramSteps": {
|
||||
@@ -1672,10 +1639,6 @@
|
||||
"Higher step counts will typically create better images but will require more generation time."
|
||||
]
|
||||
},
|
||||
"paramUpscaleMethod": {
|
||||
"heading": "Upscale Method",
|
||||
"paragraphs": ["Method used to upscale the image for High Resolution Fix."]
|
||||
},
|
||||
"paramVAE": {
|
||||
"heading": "VAE",
|
||||
"paragraphs": ["Model used for translating AI output into the final image."]
|
||||
@@ -1683,82 +1646,14 @@
|
||||
"paramVAEPrecision": {
|
||||
"heading": "VAE Precision",
|
||||
"paragraphs": [
|
||||
"The precision used during VAE encoding and decoding.",
|
||||
"Fp16/Half precision is more efficient, at the expense of minor image variations."
|
||||
]
|
||||
},
|
||||
"paramWidth": {
|
||||
"heading": "Width",
|
||||
"paragraphs": ["Width of the generated image. Must be a multiple of 8."]
|
||||
},
|
||||
"patchmatchDownScaleSize": {
|
||||
"heading": "Downscale",
|
||||
"paragraphs": [
|
||||
"How much downscaling occurs before infilling.",
|
||||
"Higher downscaling will improve performance and reduce quality."
|
||||
]
|
||||
},
|
||||
"refinerModel": {
|
||||
"heading": "Refiner Model",
|
||||
"paragraphs": [
|
||||
"Model used during the refiner portion of the generation process.",
|
||||
"Similar to the Generation Model."
|
||||
]
|
||||
},
|
||||
"refinerPositiveAestheticScore": {
|
||||
"heading": "Positive Aesthetic Score",
|
||||
"paragraphs": [
|
||||
"Weight generations to be more similar to images with a high aesthetic score, based on the training data."
|
||||
]
|
||||
},
|
||||
"refinerNegativeAestheticScore": {
|
||||
"heading": "Negative Aesthetic Score",
|
||||
"paragraphs": [
|
||||
"Weight generations to be more similar to images with a low aesthetic score, based on the training data."
|
||||
]
|
||||
},
|
||||
"refinerScheduler": {
|
||||
"heading": "Scheduler",
|
||||
"paragraphs": [
|
||||
"Scheduler used during the refiner portion of the generation process.",
|
||||
"Similar to the Generation Scheduler."
|
||||
]
|
||||
},
|
||||
"refinerStart": {
|
||||
"heading": "Refiner Start",
|
||||
"paragraphs": [
|
||||
"Where in the generation process the refiner will start to be used.",
|
||||
"0 means the refiner will be used for the entire generation process, 0.8 means the refiner will be used for the last 20% of the generation process."
|
||||
]
|
||||
},
|
||||
"refinerSteps": {
|
||||
"heading": "Steps",
|
||||
"paragraphs": [
|
||||
"Number of steps that will be performed during the refiner portion of the generation process.",
|
||||
"Similar to the Generation Steps."
|
||||
]
|
||||
},
|
||||
"refinerCfgScale": {
|
||||
"heading": "CFG Scale",
|
||||
"paragraphs": [
|
||||
"Controls how much the prompt influences the generation process.",
|
||||
"Similar to the Generation CFG Scale."
|
||||
"The precision used during VAE encoding and decoding. FP16/half precision is more efficient, at the expense of minor image variations."
|
||||
]
|
||||
},
|
||||
"scaleBeforeProcessing": {
|
||||
"heading": "Scale Before Processing",
|
||||
"paragraphs": [
|
||||
"“Auto” scales the selected area to the size best suited for the model before the image generation process.",
|
||||
"“Manual” allows you to choose the width and height the selected area will be scaled to before the image generation process."
|
||||
"Scales the selected area to the size best suited for the model before the image generation process."
|
||||
]
|
||||
},
|
||||
"seamlessTilingXAxis": {
|
||||
"heading": "Seamless Tiling X Axis",
|
||||
"paragraphs": ["Seamlessly tile an image along the horizontal axis."]
|
||||
},
|
||||
"seamlessTilingYAxis": {
|
||||
"heading": "Seamless Tiling Y Axis",
|
||||
"paragraphs": ["Seamlessly tile an image along the vertical axis."]
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"statusModelConverted": "Modello Convertito",
|
||||
"statusConvertingModel": "Conversione Modello",
|
||||
"loading": "Caricamento in corso",
|
||||
"loadingInvokeAI": "Caricamento di Invoke AI",
|
||||
"loadingInvokeAI": "Caricamento Invoke AI",
|
||||
"postprocessing": "Post Elaborazione",
|
||||
"txt2img": "Testo a Immagine",
|
||||
"accept": "Accetta",
|
||||
@@ -61,7 +61,7 @@
|
||||
"imagePrompt": "Prompt Immagine",
|
||||
"darkMode": "Modalità scura",
|
||||
"batch": "Gestione Lotto",
|
||||
"modelManager": "Gestore Modelli",
|
||||
"modelManager": "Gestore modello",
|
||||
"communityLabel": "Comunità",
|
||||
"nodeEditor": "Editor dei nodi",
|
||||
"statusProcessing": "Elaborazione in corso",
|
||||
@@ -81,7 +81,7 @@
|
||||
"error": "Errore",
|
||||
"installed": "Installato",
|
||||
"template": "Schema",
|
||||
"outputs": "Risultati",
|
||||
"outputs": "Uscite",
|
||||
"data": "Dati",
|
||||
"somethingWentWrong": "Qualcosa è andato storto",
|
||||
"copyError": "$t(gallery.copy) Errore",
|
||||
@@ -93,7 +93,7 @@
|
||||
"created": "Creato",
|
||||
"prevPage": "Pagina precedente",
|
||||
"delete": "Elimina",
|
||||
"orderBy": "Ordina per",
|
||||
"orderBy": "Ordinato per",
|
||||
"nextPage": "Pagina successiva",
|
||||
"saveAs": "Salva come",
|
||||
"unsaved": "Non salvato",
|
||||
@@ -109,12 +109,7 @@
|
||||
"green": "Verde",
|
||||
"blue": "Blu",
|
||||
"alpha": "Alfa",
|
||||
"copy": "Copia",
|
||||
"on": "Attivato",
|
||||
"checkpoint": "Checkpoint",
|
||||
"safetensors": "Safetensors",
|
||||
"ai": "ia",
|
||||
"file": "File"
|
||||
"copy": "Copia"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Generazioni",
|
||||
@@ -939,7 +934,7 @@
|
||||
"executionStateCompleted": "Completato",
|
||||
"boardFieldDescription": "Una bacheca della galleria",
|
||||
"addNodeToolTip": "Aggiungi nodo (Shift+A, Space)",
|
||||
"sDXLRefinerModelField": "Modello Affinatore",
|
||||
"sDXLRefinerModelField": "Modello Refiner",
|
||||
"problemReadingMetadata": "Problema durante la lettura dei metadati dall'immagine",
|
||||
"colorCodeEdgesHelp": "Bordi con codice colore in base ai campi collegati",
|
||||
"animatedEdges": "Bordi animati",
|
||||
@@ -1143,11 +1138,7 @@
|
||||
"unsupportedAnyOfLength": "unione di troppi elementi ({{count}})",
|
||||
"clearWorkflowDesc": "Cancellare questo flusso di lavoro e avviarne uno nuovo?",
|
||||
"clearWorkflow": "Cancella il flusso di lavoro",
|
||||
"clearWorkflowDesc2": "Il tuo flusso di lavoro attuale presenta modifiche non salvate.",
|
||||
"viewMode": "Utilizzare nella vista lineare",
|
||||
"reorderLinearView": "Riordina la vista lineare",
|
||||
"editMode": "Modifica nell'editor del flusso di lavoro",
|
||||
"resetToDefaultValue": "Ripristina il valore predefinito"
|
||||
"clearWorkflowDesc2": "Il tuo flusso di lavoro attuale presenta modifiche non salvate."
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "Aggiungi automaticamente bacheca",
|
||||
@@ -1250,16 +1241,7 @@
|
||||
"large": "Grande",
|
||||
"small": "Piccolo",
|
||||
"depthAnythingDescription": "Generazione di mappe di profondità utilizzando la tecnica Depth Anything",
|
||||
"modelSize": "Dimensioni del modello",
|
||||
"dwOpenposeDescription": "Stima della posa umana utilizzando DW Openpose",
|
||||
"face": "Viso",
|
||||
"body": "Corpo",
|
||||
"hands": "Mani",
|
||||
"lineartAnime": "Linea Anime",
|
||||
"base": "Base",
|
||||
"lineart": "Linea",
|
||||
"controlnet": "$t(controlnet.controlAdapter_one) #{{number}} ($t(common.controlNet))",
|
||||
"mediapipeFace": "Mediapipe Volto"
|
||||
"modelSize": "Dimensioni del modello"
|
||||
},
|
||||
"queue": {
|
||||
"queueFront": "Aggiungi all'inizio della coda",
|
||||
@@ -1339,7 +1321,7 @@
|
||||
"noModelsAvailable": "Nessun modello disponibile",
|
||||
"selectModel": "Seleziona un modello",
|
||||
"selectLoRA": "Seleziona un LoRA",
|
||||
"noRefinerModelsInstalled": "Nessun modello affinatore SDXL installato",
|
||||
"noRefinerModelsInstalled": "Nessun modello SDXL Refiner installato",
|
||||
"noLoRAsInstalled": "Nessun LoRA installato",
|
||||
"esrganModel": "Modello ESRGAN",
|
||||
"addLora": "Aggiungi LoRA",
|
||||
@@ -1389,8 +1371,7 @@
|
||||
"popovers": {
|
||||
"paramScheduler": {
|
||||
"paragraphs": [
|
||||
"Il campionatore utilizzato durante il processo di generazione.",
|
||||
"Ciascun campionatore definisce come aggiungere in modo iterativo il rumore a un'immagine o come aggiornare un campione in base all'output di un modello."
|
||||
"Il campionatore definisce come aggiungere in modo iterativo il rumore a un'immagine o come aggiornare un campione in base all'output di un modello."
|
||||
],
|
||||
"heading": "Campionatore"
|
||||
},
|
||||
@@ -1403,8 +1384,8 @@
|
||||
"compositingCoherenceSteps": {
|
||||
"heading": "Passi",
|
||||
"paragraphs": [
|
||||
"Numero di passi utilizzati nel Passaggio di Coerenza.",
|
||||
"Simile ai passi di generazione."
|
||||
"Numero di passi di riduzione del rumore utilizzati nel Passaggio di Coerenza.",
|
||||
"Uguale al parametro principale Passi."
|
||||
]
|
||||
},
|
||||
"compositingBlur": {
|
||||
@@ -1416,13 +1397,14 @@
|
||||
"compositingCoherenceMode": {
|
||||
"heading": "Modalità",
|
||||
"paragraphs": [
|
||||
"Metodo utilizzato per creare un'immagine coerente con l'area mascherata appena generata."
|
||||
"La modalità del Passaggio di Coerenza."
|
||||
]
|
||||
},
|
||||
"clipSkip": {
|
||||
"paragraphs": [
|
||||
"Scegli quanti livelli del modello CLIP saltare.",
|
||||
"Alcuni modelli funzionano meglio con determinate impostazioni di CLIP Skip."
|
||||
"Alcuni modelli funzionano meglio con determinate impostazioni di CLIP Skip.",
|
||||
"Un valore più alto in genere produce un'immagine meno dettagliata."
|
||||
]
|
||||
},
|
||||
"compositingCoherencePass": {
|
||||
@@ -1434,8 +1416,8 @@
|
||||
"compositingStrength": {
|
||||
"heading": "Forza",
|
||||
"paragraphs": [
|
||||
"Quantità di rumore aggiunta per il Passaggio di Coerenza.",
|
||||
"Simile alla forza di riduzione del rumore."
|
||||
"Intensità di riduzione del rumore per il passaggio di coerenza.",
|
||||
"Uguale al parametro intensità di riduzione del rumore da immagine a immagine."
|
||||
]
|
||||
},
|
||||
"paramNegativeConditioning": {
|
||||
@@ -1461,8 +1443,8 @@
|
||||
"controlNetBeginEnd": {
|
||||
"heading": "Percentuale passi Inizio / Fine",
|
||||
"paragraphs": [
|
||||
"La parte del processo di rimozione del rumore in cui verrà applicato l'adattatore di controllo.",
|
||||
"In genere, gli adattatori di controllo applicati all'inizio del processo guidano la composizione, mentre quelli applicati alla fine guidano i dettagli."
|
||||
"A quali passi del processo di rimozione del rumore verrà applicato ControlNet.",
|
||||
"I ControlNet applicati all'inizio del processo guidano la composizione, mentre i ControlNet applicati alla fine guidano i dettagli."
|
||||
]
|
||||
},
|
||||
"noiseUseCPU": {
|
||||
@@ -1475,8 +1457,7 @@
|
||||
},
|
||||
"scaleBeforeProcessing": {
|
||||
"paragraphs": [
|
||||
"\"Auto\" ridimensiona l'area selezionata alla dimensione più adatta al modello prima del processo di generazione dell'immagine.",
|
||||
"\"Manuale\" consente di scegliere la larghezza e l'altezza a cui verrà ridimensionata l'area selezionata prima del processo di generazione dell'immagine."
|
||||
"Ridimensiona l'area selezionata alla dimensione più adatta al modello prima del processo di generazione dell'immagine."
|
||||
],
|
||||
"heading": "Scala prima dell'elaborazione"
|
||||
},
|
||||
@@ -1511,21 +1492,20 @@
|
||||
"paramVAEPrecision": {
|
||||
"heading": "Precisione VAE",
|
||||
"paragraphs": [
|
||||
"La precisione utilizzata durante la codifica e decodifica VAE.",
|
||||
"Fp16/Mezza precisione è più efficiente, a scapito di minori variazioni dell'immagine."
|
||||
"La precisione utilizzata durante la codifica e decodifica VAE. FP16/mezza precisione è più efficiente, a scapito di minori variazioni dell'immagine."
|
||||
]
|
||||
},
|
||||
"paramSeed": {
|
||||
"paragraphs": [
|
||||
"Controlla il rumore iniziale utilizzato per la generazione.",
|
||||
"Disabilita l'opzione \"Casuale\" per produrre risultati identici con le stesse impostazioni di generazione."
|
||||
"Disabilita seme \"Casuale\" per produrre risultati identici con le stesse impostazioni di generazione."
|
||||
],
|
||||
"heading": "Seme"
|
||||
},
|
||||
"controlNetResizeMode": {
|
||||
"heading": "Modalità ridimensionamento",
|
||||
"paragraphs": [
|
||||
"Metodo per adattare le dimensioni dell'immagine in ingresso dell'adattatore di controllo alle dimensioni della generazione di output."
|
||||
"Come l'immagine ControlNet verrà adattata alle dimensioni di output dell'immagine."
|
||||
]
|
||||
},
|
||||
"dynamicPromptsSeedBehaviour": {
|
||||
@@ -1540,7 +1520,8 @@
|
||||
"paramModel": {
|
||||
"heading": "Modello",
|
||||
"paragraphs": [
|
||||
"Modello utilizzato per la generazione. Diversi modelli vengono addestrati per specializzarsi nella produzione di risultati e contenuti estetici diversi."
|
||||
"Modello utilizzato per i passaggi di riduzione del rumore.",
|
||||
"Diversi modelli sono generalmente addestrati per specializzarsi nella produzione di particolari risultati e contenuti estetici."
|
||||
]
|
||||
},
|
||||
"paramDenoisingStrength": {
|
||||
@@ -1558,26 +1539,25 @@
|
||||
},
|
||||
"infillMethod": {
|
||||
"paragraphs": [
|
||||
"Metodo di riempimento durante il processo di Outpainting o Inpainting."
|
||||
"Metodo per riempire l'area selezionata."
|
||||
],
|
||||
"heading": "Metodo di riempimento"
|
||||
},
|
||||
"controlNetWeight": {
|
||||
"heading": "Peso",
|
||||
"paragraphs": [
|
||||
"Peso dell'adattatore di controllo. Un peso maggiore porterà a impatti maggiori sull'immagine finale."
|
||||
"Quanto forte sarà l'impatto di ControlNet sull'immagine generata."
|
||||
]
|
||||
},
|
||||
"paramCFGScale": {
|
||||
"heading": "Scala CFG",
|
||||
"paragraphs": [
|
||||
"Controlla quanto il prompt influenza il processo di generazione.",
|
||||
"Valori elevati della scala CFG possono provocare una saturazione eccessiva e distorsioni nei risultati della generazione. "
|
||||
"Controlla quanto il tuo prompt influenza il processo di generazione."
|
||||
]
|
||||
},
|
||||
"controlNetControlMode": {
|
||||
"paragraphs": [
|
||||
"Attribuisce più peso al prompt oppure a ControlNet."
|
||||
"Attribuisce più peso al prompt o a ControlNet."
|
||||
],
|
||||
"heading": "Modalità di controllo"
|
||||
},
|
||||
@@ -1589,9 +1569,9 @@
|
||||
]
|
||||
},
|
||||
"lora": {
|
||||
"heading": "LoRA",
|
||||
"heading": "Peso LoRA",
|
||||
"paragraphs": [
|
||||
"Modelli leggeri utilizzati insieme ai modelli base."
|
||||
"Un peso LoRA più elevato porterà a impatti maggiori sull'immagine finale."
|
||||
]
|
||||
},
|
||||
"controlNet": {
|
||||
@@ -1603,123 +1583,7 @@
|
||||
"paramCFGRescaleMultiplier": {
|
||||
"heading": "Moltiplicatore di riscala CFG",
|
||||
"paragraphs": [
|
||||
"Moltiplicatore di riscala per la guida CFG, utilizzato per modelli addestrati utilizzando SNR a terminale zero (ztsnr).",
|
||||
"Valore suggerito di 0.7 per questi modelli."
|
||||
]
|
||||
},
|
||||
"controlNetProcessor": {
|
||||
"heading": "Processore",
|
||||
"paragraphs": [
|
||||
"Metodo di elaborazione dell'immagine di input per guidare il processo di generazione. Processori diversi forniranno effetti o stili diversi nelle immagini generate."
|
||||
]
|
||||
},
|
||||
"imageFit": {
|
||||
"heading": "Adatta l'immagine iniziale alle dimensioni di output",
|
||||
"paragraphs": [
|
||||
"Ridimensiona l'immagine iniziale in base alla larghezza e all'altezza dell'immagine di output. Si consiglia di abilitarlo."
|
||||
]
|
||||
},
|
||||
"loraWeight": {
|
||||
"heading": "Peso",
|
||||
"paragraphs": [
|
||||
"Peso del LoRA. Un peso maggiore comporterà un impatto maggiore sull'immagine finale."
|
||||
]
|
||||
},
|
||||
"paramAspect": {
|
||||
"heading": "Aspetto",
|
||||
"paragraphs": [
|
||||
"Proporzioni dell'immagine generata. La modifica del rapporto aggiornerà di conseguenza la larghezza e l'altezza.",
|
||||
"\"Ottimizza\" imposterà la larghezza e l'altezza alle dimensioni ottimali per il modello scelto."
|
||||
]
|
||||
},
|
||||
"paramHeight": {
|
||||
"heading": "Altezza",
|
||||
"paragraphs": [
|
||||
"Altezza dell'immagine generata. Deve essere un multiplo di 8."
|
||||
]
|
||||
},
|
||||
"paramHrf": {
|
||||
"heading": "Abilita correzione alta risoluzione",
|
||||
"paragraphs": [
|
||||
"Genera immagini di alta qualità con una risoluzione maggiore di quella ottimale per il modello. Generalmente utilizzato per impedire la duplicazione nell'immagine generata."
|
||||
]
|
||||
},
|
||||
"paramUpscaleMethod": {
|
||||
"heading": "Metodo di ampliamento",
|
||||
"paragraphs": [
|
||||
"Metodo utilizzato per eseguire l'ampliamento dell'immagine per la correzione ad alta risoluzione."
|
||||
]
|
||||
},
|
||||
"patchmatchDownScaleSize": {
|
||||
"heading": "Ridimensiona",
|
||||
"paragraphs": [
|
||||
"Quanto ridimensionamento avviene prima del riempimento.",
|
||||
"Un ridimensionamento più elevato migliorerà le prestazioni e ridurrà la qualità."
|
||||
]
|
||||
},
|
||||
"paramWidth": {
|
||||
"paragraphs": [
|
||||
"Larghezza dell'immagine generata. Deve essere un multiplo di 8."
|
||||
],
|
||||
"heading": "Larghezza"
|
||||
},
|
||||
"refinerModel": {
|
||||
"heading": "Modello Affinatore",
|
||||
"paragraphs": [
|
||||
"Modello utilizzato durante la parte di affinamento del processo di generazione.",
|
||||
"Simile al modello di generazione."
|
||||
]
|
||||
},
|
||||
"refinerNegativeAestheticScore": {
|
||||
"paragraphs": [
|
||||
"Valuta le generazioni in modo che siano più simili alle immagini con un punteggio estetico basso, in base ai dati di addestramento."
|
||||
],
|
||||
"heading": "Punteggio estetico negativo"
|
||||
},
|
||||
"refinerScheduler": {
|
||||
"paragraphs": [
|
||||
"Campionatore utilizzato durante la parte di affinamento del processo di generazione.",
|
||||
"Simile al campionatore di generazione."
|
||||
],
|
||||
"heading": "Campionatore"
|
||||
},
|
||||
"refinerStart": {
|
||||
"heading": "Inizio affinamento",
|
||||
"paragraphs": [
|
||||
"A che punto nel processo di generazione inizierà ad essere utilizzato l'affinatore.",
|
||||
"0 significa che l'affinatore verrà utilizzato per l'intero processo di generazione, 0.8 significa che l'affinatore verrà utilizzato per l'ultimo 20% del processo di generazione."
|
||||
]
|
||||
},
|
||||
"refinerSteps": {
|
||||
"heading": "Passi",
|
||||
"paragraphs": [
|
||||
"Numero di passi che verranno eseguiti durante la parte di affinamento del processo di generazione.",
|
||||
"Simile ai passi di generazione."
|
||||
]
|
||||
},
|
||||
"refinerCfgScale": {
|
||||
"heading": "Scala CFG",
|
||||
"paragraphs": [
|
||||
"Controlla quanto il prompt influenza il processo di generazione.",
|
||||
"Simile alla scala CFG di generazione."
|
||||
]
|
||||
},
|
||||
"seamlessTilingXAxis": {
|
||||
"heading": "Asse X di piastrellatura senza cuciture",
|
||||
"paragraphs": [
|
||||
"Affianca senza soluzione di continuità un'immagine lungo l'asse orizzontale."
|
||||
]
|
||||
},
|
||||
"seamlessTilingYAxis": {
|
||||
"heading": "Asse Y di piastrellatura senza cuciture",
|
||||
"paragraphs": [
|
||||
"Affianca senza soluzione di continuità un'immagine lungo l'asse verticale."
|
||||
]
|
||||
},
|
||||
"refinerPositiveAestheticScore": {
|
||||
"heading": "Punteggio estetico positivo",
|
||||
"paragraphs": [
|
||||
"Valuta le generazioni in modo che siano più simili alle immagini con un punteggio estetico elevato, in base ai dati di addestramento."
|
||||
"Moltiplicatore di riscala per la guida CFG, utilizzato per modelli addestrati utilizzando SNR a terminale zero (ztsnr). Valore suggerito 0.7."
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -1768,8 +1632,7 @@
|
||||
"steps": "Passi",
|
||||
"scheduler": "Campionatore",
|
||||
"recallParameters": "Richiama i parametri",
|
||||
"noRecallParameters": "Nessun parametro da richiamare trovato",
|
||||
"cfgRescaleMultiplier": "$t(parameters.cfgRescaleMultiplier)"
|
||||
"noRecallParameters": "Nessun parametro da richiamare trovato"
|
||||
},
|
||||
"hrf": {
|
||||
"enableHrf": "Abilita Correzione Alta Risoluzione",
|
||||
|
||||
@@ -1217,14 +1217,16 @@
|
||||
"clipSkip": {
|
||||
"paragraphs": [
|
||||
"Kies hoeveel CLIP-modellagen je wilt overslaan.",
|
||||
"Bepaalde modellen werken beter met bepaalde Overslaan CLIP-instellingen."
|
||||
"Bepaalde modellen werken beter met bepaalde Overslaan CLIP-instellingen.",
|
||||
"Een hogere waarde geeft meestal een minder gedetailleerde afbeelding."
|
||||
],
|
||||
"heading": "Overslaan CLIP"
|
||||
},
|
||||
"paramModel": {
|
||||
"heading": "Model",
|
||||
"paragraphs": [
|
||||
"Model gebruikt voor de ontruisingsstappen."
|
||||
"Model gebruikt voor de ontruisingsstappen.",
|
||||
"Verschillende modellen zijn meestal getraind om zich te specialiseren in het maken van bepaalde esthetische resultaten en materiaal."
|
||||
]
|
||||
},
|
||||
"compositingCoherencePass": {
|
||||
|
||||
@@ -108,16 +108,7 @@
|
||||
"preferencesLabel": "Предпочтения",
|
||||
"or": "или",
|
||||
"advancedOptions": "Расширенные настройки",
|
||||
"free": "Свободно",
|
||||
"aboutHeading": "Владей своей творческой силой",
|
||||
"red": "Красный",
|
||||
"green": "Зеленый",
|
||||
"blue": "Синий",
|
||||
"alpha": "Альфа",
|
||||
"toResolve": "Чтоб решить",
|
||||
"copy": "Копировать",
|
||||
"localSystem": "Локальная система",
|
||||
"aboutDesc": "Используя Invoke для работы? Проверьте это:"
|
||||
"free": "Свободно"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Генерации",
|
||||
@@ -161,17 +152,17 @@
|
||||
},
|
||||
"hotkeys": {
|
||||
"keyboardShortcuts": "Горячие клавиши",
|
||||
"appHotkeys": "Приложение",
|
||||
"generalHotkeys": "Общее",
|
||||
"galleryHotkeys": "Галлерея",
|
||||
"unifiedCanvasHotkeys": "Единый холст",
|
||||
"appHotkeys": "Горячие клавиши приложения",
|
||||
"generalHotkeys": "Общие горячие клавиши",
|
||||
"galleryHotkeys": "Горячие клавиши галереи",
|
||||
"unifiedCanvasHotkeys": "Горячие клавиши Единого холста",
|
||||
"invoke": {
|
||||
"title": "Invoke",
|
||||
"desc": "Сгенерировать изображение"
|
||||
},
|
||||
"cancel": {
|
||||
"title": "Отменить",
|
||||
"desc": "Отменить текущий элемент"
|
||||
"desc": "Отменить генерацию изображения"
|
||||
},
|
||||
"focusPrompt": {
|
||||
"title": "Переключиться на ввод запроса",
|
||||
@@ -361,7 +352,7 @@
|
||||
"desc": "Открывает меню добавления узла",
|
||||
"title": "Добавление узлов"
|
||||
},
|
||||
"nodesHotkeys": "Узлы",
|
||||
"nodesHotkeys": "Горячие клавиши узлов",
|
||||
"cancelAndClear": {
|
||||
"desc": "Отмена текущего элемента очереди и очистка всех ожидающих элементов",
|
||||
"title": "Отменить и очистить"
|
||||
@@ -376,11 +367,7 @@
|
||||
"desc": "Открытие и закрытие панели опций и галереи",
|
||||
"title": "Переключить опции и галерею"
|
||||
},
|
||||
"clearSearch": "Очистить поиск",
|
||||
"remixImage": {
|
||||
"desc": "Используйте все параметры, кроме сида из текущего изображения",
|
||||
"title": "Ремикс изображения"
|
||||
}
|
||||
"clearSearch": "Очистить поиск"
|
||||
},
|
||||
"modelManager": {
|
||||
"modelManager": "Менеджер моделей",
|
||||
@@ -525,8 +512,7 @@
|
||||
"modelType": "Тип модели",
|
||||
"customConfigFileLocation": "Расположение пользовательского файла конфигурации",
|
||||
"vaePrecision": "Точность VAE",
|
||||
"noModelSelected": "Модель не выбрана",
|
||||
"configFile": "Файл конфигурации"
|
||||
"noModelSelected": "Модель не выбрана"
|
||||
},
|
||||
"parameters": {
|
||||
"images": "Изображения",
|
||||
@@ -597,8 +583,8 @@
|
||||
"copyImage": "Скопировать изображение",
|
||||
"showPreview": "Показать предпросмотр",
|
||||
"noiseSettings": "Шум",
|
||||
"seamlessXAxis": "Бесшовность по оси X",
|
||||
"seamlessYAxis": "Бесшовность по оси Y",
|
||||
"seamlessXAxis": "Горизонтальная",
|
||||
"seamlessYAxis": "Вертикальная",
|
||||
"scheduler": "Планировщик",
|
||||
"boundingBoxWidth": "Ширина ограничивающей рамки",
|
||||
"boundingBoxHeight": "Высота ограничивающей рамки",
|
||||
@@ -626,7 +612,7 @@
|
||||
"noControlImageForControlAdapter": "Адаптер контроля #{{number}} не имеет изображения",
|
||||
"noModelForControlAdapter": "Не выбрана модель адаптера контроля #{{number}}.",
|
||||
"unableToInvoke": "Невозможно вызвать",
|
||||
"incompatibleBaseModelForControlAdapter": "Адаптер контроля №{{number}} несовместим с основной моделью.",
|
||||
"incompatibleBaseModelForControlAdapter": "Модель контрольного адаптера №{{number}} недействительна для основной модели.",
|
||||
"systemDisconnected": "Система отключена",
|
||||
"missingNodeTemplate": "Отсутствует шаблон узла",
|
||||
"readyToInvoke": "Готово к вызову",
|
||||
@@ -667,10 +653,7 @@
|
||||
"setToOptimalSize": "Установить оптимальный для модели размер",
|
||||
"setToOptimalSizeTooSmall": "$t(parameters.setToOptimalSize) (может быть слишком маленьким)",
|
||||
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (может быть слишком большим)",
|
||||
"lockAspectRatio": "Заблокировать соотношение",
|
||||
"boxBlur": "Размытие прямоугольника",
|
||||
"gaussianBlur": "Размытие по Гауссу",
|
||||
"remixImage": "Ремикс изображения"
|
||||
"lockAspectRatio": "Заблокировать соотношение"
|
||||
},
|
||||
"settings": {
|
||||
"models": "Модели",
|
||||
@@ -804,10 +787,7 @@
|
||||
"canvasSavedGallery": "Холст сохранен в галерею",
|
||||
"imageUploadFailed": "Не удалось загрузить изображение",
|
||||
"modelAdded": "Добавлена модель: {{modelName}}",
|
||||
"problemImportingMask": "Проблема с импортом маски",
|
||||
"problemDownloadingImage": "Не удается скачать изображение",
|
||||
"uploadInitialImage": "Загрузить начальное изображение",
|
||||
"resetInitialImage": "Сбросить начальное изображение"
|
||||
"problemImportingMask": "Проблема с импортом маски"
|
||||
},
|
||||
"tooltip": {
|
||||
"feature": {
|
||||
@@ -912,8 +892,7 @@
|
||||
"mode": "Режим",
|
||||
"loadMore": "Загрузить больше",
|
||||
"resetUI": "$t(accessibility.reset) интерфейс",
|
||||
"createIssue": "Сообщить о проблеме",
|
||||
"about": "Об этом"
|
||||
"createIssue": "Сообщить о проблеме"
|
||||
},
|
||||
"ui": {
|
||||
"showProgressImages": "Показывать промежуточный итог",
|
||||
@@ -1138,18 +1117,7 @@
|
||||
"unableToParseEdge": "Невозможно разобрать край",
|
||||
"unknownInput": "Неизвестный вход: {{name}}",
|
||||
"oNNXModelFieldDescription": "Поле модели ONNX.",
|
||||
"imageCollection": "Коллекция изображений",
|
||||
"newWorkflow": "Новый рабочий процесс",
|
||||
"newWorkflowDesc": "Создать новый рабочий процесс?",
|
||||
"clearWorkflow": "Очистить рабочий процесс",
|
||||
"newWorkflowDesc2": "Текущий рабочий процесс имеет несохраненные изменения.",
|
||||
"latentsCollection": "Коллекция латентов",
|
||||
"clearWorkflowDesc": "Очистить этот рабочий процесс и создать новый?",
|
||||
"clearWorkflowDesc2": "Текущий рабочий процесс имеет несохраненные измерения.",
|
||||
"reorderLinearView": "Изменить порядок линейного просмотра",
|
||||
"viewMode": "Использовать в линейном представлении",
|
||||
"editMode": "Открыть в редакторе узлов",
|
||||
"resetToDefaultValue": "Сбросить к стандартному значкнию"
|
||||
"imageCollection": "Коллекция изображений"
|
||||
},
|
||||
"controlnet": {
|
||||
"amult": "a_mult",
|
||||
@@ -1230,18 +1198,7 @@
|
||||
"enableIPAdapter": "Включить IP Adapter",
|
||||
"maxFaces": "Макс Лица",
|
||||
"mlsdDescription": "Минималистичный детектор отрезков линии",
|
||||
"resizeSimple": "Изменить размер (простой)",
|
||||
"megaControl": "Mega контроль",
|
||||
"base": "Базовый",
|
||||
"depthAnything": "Глубина всего",
|
||||
"depthAnythingDescription": "Создание карты глубины с использованием метода Depth Anything",
|
||||
"face": "Лицо",
|
||||
"dwOpenposeDescription": "Оценка позы человека с помощью DW Openpose",
|
||||
"large": "Большой",
|
||||
"modelSize": "Размер модели",
|
||||
"small": "Маленький",
|
||||
"body": "Тело",
|
||||
"hands": "Руки"
|
||||
"resizeSimple": "Изменить размер (простой)"
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "Авто добавление Доски",
|
||||
@@ -1324,7 +1281,7 @@
|
||||
"compositingCoherenceSteps": {
|
||||
"heading": "Шаги",
|
||||
"paragraphs": [
|
||||
"Количество шагов снижения шума, используемых при прохождении когерентности.",
|
||||
null,
|
||||
"То же, что и основной параметр «Шаги»."
|
||||
]
|
||||
},
|
||||
@@ -1362,10 +1319,7 @@
|
||||
]
|
||||
},
|
||||
"compositingCoherenceMode": {
|
||||
"heading": "Режим",
|
||||
"paragraphs": [
|
||||
"Режим прохождения когерентности."
|
||||
]
|
||||
"heading": "Режим"
|
||||
},
|
||||
"paramSeed": {
|
||||
"paragraphs": [
|
||||
@@ -1399,14 +1353,16 @@
|
||||
"clipSkip": {
|
||||
"paragraphs": [
|
||||
"Выберите, сколько слоев модели CLIP нужно пропустить.",
|
||||
"Некоторые модели работают лучше с определенными настройками пропуска CLIP."
|
||||
"Некоторые модели работают лучше с определенными настройками пропуска CLIP.",
|
||||
"Более высокое значение обычно приводит к менее детализированному изображению."
|
||||
],
|
||||
"heading": "CLIP пропуск"
|
||||
},
|
||||
"paramModel": {
|
||||
"heading": "Модель",
|
||||
"paragraphs": [
|
||||
"Модель, используемая для шагов шумоподавления."
|
||||
"Модель, используемая для шагов шумоподавления.",
|
||||
"Различные модели обычно обучаются, чтобы специализироваться на достижении определенных эстетических результатов и содержания."
|
||||
]
|
||||
},
|
||||
"compositingCoherencePass": {
|
||||
@@ -1645,7 +1601,7 @@
|
||||
"openWorkflow": "Открытый рабочий процесс",
|
||||
"clearWorkflowSearchFilter": "Очистить фильтр поиска рабочих процессов",
|
||||
"workflowLibrary": "Библиотека",
|
||||
"downloadWorkflow": "Сохранить в файл",
|
||||
"downloadWorkflow": "Скачать рабочий процесс",
|
||||
"noRecentWorkflows": "Нет недавних рабочих процессов",
|
||||
"workflowSaved": "Рабочий процесс сохранен",
|
||||
"workflowIsOpen": "Рабочий процесс открыт",
|
||||
@@ -1658,12 +1614,9 @@
|
||||
"deleteWorkflow": "Удалить рабочий процесс",
|
||||
"workflows": "Рабочие процессы",
|
||||
"noDescription": "Без описания",
|
||||
"uploadWorkflow": "Загрузить из файла",
|
||||
"uploadWorkflow": "Загрузить рабочий процесс",
|
||||
"userWorkflows": "Мои рабочие процессы",
|
||||
"newWorkflowCreated": "Создан новый рабочий процесс",
|
||||
"saveWorkflowToProject": "Сохранить рабочий процесс в проект",
|
||||
"workflowCleared": "Рабочий процесс очищен",
|
||||
"noWorkflows": "Нет рабочих процессов"
|
||||
"newWorkflowCreated": "Создан новый рабочий процесс"
|
||||
},
|
||||
"embedding": {
|
||||
"noEmbeddingsLoaded": "встраивания не загружены",
|
||||
|
||||
@@ -1444,14 +1444,16 @@
|
||||
"clipSkip": {
|
||||
"paragraphs": [
|
||||
"选择要跳过 CLIP 模型多少层。",
|
||||
"部分模型跳过特定数值的层时效果会更好。"
|
||||
"部分模型跳过特定数值的层时效果会更好。",
|
||||
"较高的数值通常会导致图像细节更少。"
|
||||
],
|
||||
"heading": "CLIP 跳过层"
|
||||
},
|
||||
"paramModel": {
|
||||
"heading": "模型",
|
||||
"paragraphs": [
|
||||
"用于去噪过程的模型。"
|
||||
"用于去噪过程的模型。",
|
||||
"不同的模型一般会通过接受训练来专门产生特定的美学内容和结果。"
|
||||
]
|
||||
},
|
||||
"paramIterations": {
|
||||
|
||||
@@ -55,6 +55,8 @@ import { addUpscaleRequestedListener } from 'app/store/middleware/listenerMiddle
|
||||
import { addWorkflowLoadRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested';
|
||||
import type { AppDispatch, RootState } from 'app/store/store';
|
||||
|
||||
import { addSetDefaultSettingsListener } from './listeners/setDefaultSettings';
|
||||
|
||||
export const listenerMiddleware = createListenerMiddleware();
|
||||
|
||||
export type AppStartListening = TypedStartListening<RootState, AppDispatch>;
|
||||
@@ -153,3 +155,5 @@ addUpscaleRequestedListener(startAppListening);
|
||||
|
||||
// Dynamic prompts
|
||||
addDynamicPromptsListener(startAppListening);
|
||||
|
||||
addSetDefaultSettingsListener(startAppListening)
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { setDefaultSettings } from 'features/parameters/store/actions';
|
||||
import { setCfgRescaleMultiplier, setCfgScale, setScheduler, setSteps, vaePrecisionChanged, vaeSelected } from 'features/parameters/store/generationSlice';
|
||||
import { isParameterCFGRescaleMultiplier, isParameterCFGScale, isParameterPrecision, isParameterScheduler, isParameterSteps, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { t } from 'i18next';
|
||||
import { map } from 'lodash-es';
|
||||
import { modelsApi } from 'services/api/endpoints/models';
|
||||
|
||||
|
||||
export const addSetDefaultSettingsListener = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
actionCreator: setDefaultSettings,
|
||||
effect: async (action, { dispatch, getState }) => {
|
||||
const state = getState();
|
||||
|
||||
const currentModel = state.generation.model;
|
||||
|
||||
if (!currentModel) {
|
||||
return
|
||||
}
|
||||
|
||||
const metadata = await dispatch(
|
||||
modelsApi.endpoints.getModelMetadata.initiate(currentModel.key)
|
||||
).unwrap();
|
||||
|
||||
console.log({ metadata })
|
||||
|
||||
|
||||
if (!metadata || !metadata.default_settings) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { vae, vae_precision, cfg_scale, cfg_rescale_multiplier, steps, scheduler } = metadata.default_settings
|
||||
|
||||
if (vae) {
|
||||
// we store this as "default" within default settings
|
||||
// to distinguish it from no default set
|
||||
if (vae === "default") {
|
||||
dispatch(vaeSelected(null))
|
||||
} else {
|
||||
const { data } = modelsApi.endpoints.getVaeModels.select()(state)
|
||||
const vaeArray = map(data?.entities)
|
||||
const validVae = vaeArray.find(model => model.key === vae)
|
||||
|
||||
const result = zParameterVAEModel.safeParse(validVae);
|
||||
if (!result.success) {
|
||||
return;
|
||||
}
|
||||
dispatch(vaeSelected(result.data));
|
||||
}
|
||||
}
|
||||
|
||||
if (vae_precision) {
|
||||
if (isParameterPrecision(vae_precision)) {
|
||||
dispatch(vaePrecisionChanged(vae_precision));
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg_scale) {
|
||||
if (isParameterCFGScale(cfg_scale)) {
|
||||
dispatch(setCfgScale(cfg_scale));
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg_rescale_multiplier) {
|
||||
if (isParameterCFGRescaleMultiplier(cfg_rescale_multiplier)) {
|
||||
dispatch(setCfgRescaleMultiplier(cfg_rescale_multiplier));
|
||||
}
|
||||
}
|
||||
|
||||
if (steps) {
|
||||
if (isParameterSteps(steps)) {
|
||||
dispatch(setSteps(steps));
|
||||
}
|
||||
}
|
||||
|
||||
if (scheduler) {
|
||||
if (isParameterScheduler(scheduler)) {
|
||||
dispatch(setScheduler(scheduler));
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(addToast(makeToast({ title: t('toast.parameterSet', { parameter: "Default settings" }) })))
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
|
||||
import type { ParameterPrecision, ParameterScheduler } from 'features/parameters/types/parameterSchemas';
|
||||
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
||||
import type { O } from 'ts-toolbelt';
|
||||
|
||||
@@ -82,6 +83,8 @@ export type AppConfig = {
|
||||
guidance: NumericalParameterConfig;
|
||||
cfgRescaleMultiplier: NumericalParameterConfig;
|
||||
img2imgStrength: NumericalParameterConfig;
|
||||
scheduler?: ParameterScheduler,
|
||||
vaePrecision?: ParameterPrecision
|
||||
// Canvas
|
||||
boundingBoxHeight: NumericalParameterConfig; // initial value comes from model
|
||||
boundingBoxWidth: NumericalParameterConfig; // initial value comes from model
|
||||
|
||||
@@ -13,46 +13,28 @@ export type Feature =
|
||||
| 'compositingCoherenceEdgeSize'
|
||||
| 'compositingCoherenceMinDenoise'
|
||||
| 'compositingMaskAdjustments'
|
||||
| 'controlNet'
|
||||
| 'controlNetBeginEnd'
|
||||
| 'controlNetControlMode'
|
||||
| 'controlNetProcessor'
|
||||
| 'controlNetResizeMode'
|
||||
| 'controlNet'
|
||||
| 'controlNetWeight'
|
||||
| 'dynamicPrompts'
|
||||
| 'dynamicPromptsMaxPrompts'
|
||||
| 'dynamicPromptsSeedBehaviour'
|
||||
| 'imageFit'
|
||||
| 'infillMethod'
|
||||
| 'lora'
|
||||
| 'loraWeight'
|
||||
| 'noiseUseCPU'
|
||||
| 'paramAspect'
|
||||
| 'paramCFGScale'
|
||||
| 'paramCFGRescaleMultiplier'
|
||||
| 'paramDenoisingStrength'
|
||||
| 'paramHeight'
|
||||
| 'paramHrf'
|
||||
| 'paramIterations'
|
||||
| 'paramModel'
|
||||
| 'paramRatio'
|
||||
| 'paramSeed'
|
||||
| 'paramSteps'
|
||||
| 'paramUpscaleMethod'
|
||||
| 'paramVAE'
|
||||
| 'paramVAEPrecision'
|
||||
| 'paramWidth'
|
||||
| 'patchmatchDownScaleSize'
|
||||
| 'refinerModel'
|
||||
| 'refinerNegativeAestheticScore'
|
||||
| 'refinerPositiveAestheticScore'
|
||||
| 'refinerScheduler'
|
||||
| 'refinerStart'
|
||||
| 'refinerSteps'
|
||||
| 'refinerCfgScale'
|
||||
| 'scaleBeforeProcessing'
|
||||
| 'seamlessTilingXAxis'
|
||||
| 'seamlessTilingYAxis';
|
||||
| 'scaleBeforeProcessing';
|
||||
|
||||
export type PopoverData = PopoverProps & {
|
||||
image?: string;
|
||||
@@ -64,57 +46,21 @@ export const POPOVER_DATA: { [key in Feature]?: PopoverData } = {
|
||||
paramNegativeConditioning: {
|
||||
placement: 'right',
|
||||
},
|
||||
clipSkip: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178161-advanced-settings',
|
||||
},
|
||||
controlNet: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000105880',
|
||||
},
|
||||
controlNetBeginEnd: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178148',
|
||||
},
|
||||
controlNetWeight: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178148',
|
||||
},
|
||||
lora: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000159072',
|
||||
},
|
||||
loraWeight: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000159072-concepts-low-rank-adaptations-loras-',
|
||||
},
|
||||
compositingBlur: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000158838-compositing-settings',
|
||||
},
|
||||
compositingBlurMethod: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000158838-compositing-settings',
|
||||
},
|
||||
compositingCoherenceMode: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000158838-compositing-settings',
|
||||
},
|
||||
compositingCoherenceSteps: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000158838-compositing-settings',
|
||||
},
|
||||
compositingStrength: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000158838-compositing-settings',
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000158838',
|
||||
},
|
||||
infillMethod: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000158841-infill-and-scaling',
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000158841',
|
||||
},
|
||||
scaleBeforeProcessing: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000158841',
|
||||
},
|
||||
paramCFGScale: {
|
||||
href: 'https://www.youtube.com/watch?v=1OeHEJrsTpI',
|
||||
},
|
||||
paramCFGRescaleMultiplier: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178161-advanced-settings',
|
||||
},
|
||||
paramDenoisingStrength: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000094998-image-to-image',
|
||||
},
|
||||
paramHrf: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000096700-how-can-i-get-larger-images-what-does-upscaling-do-',
|
||||
},
|
||||
paramIterations: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000159073',
|
||||
},
|
||||
@@ -124,10 +70,7 @@ export const POPOVER_DATA: { [key in Feature]?: PopoverData } = {
|
||||
},
|
||||
paramScheduler: {
|
||||
placement: 'right',
|
||||
href: 'https://www.youtube.com/watch?v=1OeHEJrsTpI',
|
||||
},
|
||||
paramSeed: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000096684-what-is-a-seed-how-do-i-use-it-to-recreate-the-same-image-',
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000159073',
|
||||
},
|
||||
paramModel: {
|
||||
placement: 'right',
|
||||
@@ -138,53 +81,15 @@ export const POPOVER_DATA: { [key in Feature]?: PopoverData } = {
|
||||
},
|
||||
controlNetControlMode: {
|
||||
placement: 'right',
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178148',
|
||||
},
|
||||
controlNetProcessor: {
|
||||
placement: 'right',
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000105880-using-controlnet',
|
||||
},
|
||||
controlNetResizeMode: {
|
||||
placement: 'right',
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178148',
|
||||
},
|
||||
paramVAE: {
|
||||
placement: 'right',
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178161-advanced-settings',
|
||||
},
|
||||
paramVAEPrecision: {
|
||||
placement: 'right',
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178161-advanced-settings',
|
||||
},
|
||||
paramUpscaleMethod: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000096700-how-can-i-get-larger-images-what-does-upscaling-do-',
|
||||
},
|
||||
refinerModel: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
|
||||
},
|
||||
refinerNegativeAestheticScore: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
|
||||
},
|
||||
refinerPositiveAestheticScore: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
|
||||
},
|
||||
refinerScheduler: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
|
||||
},
|
||||
refinerStart: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
|
||||
},
|
||||
refinerSteps: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
|
||||
},
|
||||
refinerCfgScale: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
|
||||
},
|
||||
seamlessTilingXAxis: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178161-advanced-settings',
|
||||
},
|
||||
seamlessTilingYAxis: {
|
||||
href: 'https://support.invoke.ai/support/solutions/articles/151000178161-advanced-settings',
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CompositeRangeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { useControlAdapterBeginEndStepPct } from 'features/controlAdapters/hooks/useControlAdapterBeginEndStepPct';
|
||||
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
|
||||
import {
|
||||
@@ -62,10 +61,12 @@ export const ParamControlAdapterBeginEnd = memo(({ id }: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl isDisabled={!isEnabled} orientation="vertical">
|
||||
<InformationalPopover feature="controlNetBeginEnd">
|
||||
<FormLabel>{t('controlnet.beginEndStepPercent')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormControl
|
||||
isDisabled={!isEnabled}
|
||||
// feature="controlNetBeginEnd"
|
||||
orientation="vertical"
|
||||
>
|
||||
<FormLabel>{t('controlnet.beginEndStepPercent')}</FormLabel>
|
||||
<CompositeRangeSlider
|
||||
aria-label={ariaLabel}
|
||||
value={value}
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
|
||||
import { useControlAdapterProcessorNode } from 'features/controlAdapters/hooks/useControlAdapterProcessorNode';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
|
||||
@@ -59,9 +58,7 @@ const ParamControlAdapterProcessorSelect = ({ id }: Props) => {
|
||||
}
|
||||
return (
|
||||
<FormControl isDisabled={!isEnabled}>
|
||||
<InformationalPopover feature="controlNetProcessor">
|
||||
<FormLabel>{t('controlnet.processor')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('controlnet.processor')}</FormLabel>
|
||||
<Combobox value={value} options={options} onChange={onChange} />
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { EmbeddingSelect } from './EmbeddingSelect';
|
||||
import type { EmbeddingSelectProps } from './types';
|
||||
|
||||
const meta: Meta<typeof EmbeddingSelect> = {
|
||||
title: 'Feature/Prompt/EmbeddingSelect',
|
||||
tags: ['autodocs'],
|
||||
component: EmbeddingSelect,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof EmbeddingSelect>;
|
||||
|
||||
const Component = (props: EmbeddingSelectProps) => {
|
||||
return <EmbeddingSelect {...props}>Invoke</EmbeddingSelect>;
|
||||
};
|
||||
|
||||
export const Default: Story = {
|
||||
render: Component,
|
||||
};
|
||||
@@ -1,67 +0,0 @@
|
||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
||||
import type { EmbeddingSelectProps } from 'features/embedding/types';
|
||||
import { t } from 'i18next';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
|
||||
import type { TextualInversionModelConfig } from 'services/api/types';
|
||||
|
||||
const noOptionsMessage = () => t('embedding.noMatchingEmbedding');
|
||||
|
||||
export const EmbeddingSelect = memo(({ onSelect, onClose }: EmbeddingSelectProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
|
||||
|
||||
const getIsDisabled = useCallback(
|
||||
(embedding: TextualInversionModelConfig): boolean => {
|
||||
const isCompatible = currentBaseModel === embedding.base;
|
||||
const hasMainModel = Boolean(currentBaseModel);
|
||||
return !hasMainModel || !isCompatible;
|
||||
},
|
||||
[currentBaseModel]
|
||||
);
|
||||
const { data, isLoading } = useGetTextualInversionModelsQuery();
|
||||
|
||||
const _onChange = useCallback(
|
||||
(embedding: TextualInversionModelConfig | null) => {
|
||||
if (!embedding) {
|
||||
return;
|
||||
}
|
||||
onSelect(embedding.name);
|
||||
},
|
||||
[onSelect]
|
||||
);
|
||||
|
||||
const { options, onChange } = useGroupedModelCombobox({
|
||||
modelEntities: data,
|
||||
getIsDisabled,
|
||||
onChange: _onChange,
|
||||
});
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<Combobox
|
||||
placeholder={isLoading ? t('common.loading') : t('embedding.addEmbedding')}
|
||||
defaultMenuIsOpen
|
||||
autoFocus
|
||||
value={null}
|
||||
options={options}
|
||||
noOptionsMessage={noOptionsMessage}
|
||||
onChange={onChange}
|
||||
onMenuClose={onClose}
|
||||
data-testid="add-embedding"
|
||||
sx={selectStyles}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
});
|
||||
|
||||
EmbeddingSelect.displayName = 'EmbeddingSelect';
|
||||
|
||||
const selectStyles: ChakraProps['sx'] = {
|
||||
w: 'full',
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { setHrfMethod } from 'features/hrf/store/hrfSlice';
|
||||
import { isParameterHRFMethod } from 'features/parameters/types/parameterSchemas';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
@@ -31,9 +30,7 @@ const ParamHrfMethodSelect = () => {
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="paramUpscaleMethod">
|
||||
<FormLabel>{t('hrf.upscaleMethod')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('hrf.upscaleMethod')}</FormLabel>
|
||||
<Combobox value={value} options={options} onChange={onChange} />
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { setHrfStrength } from 'features/hrf/store/hrfSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -26,9 +25,7 @@ const ParamHrfStrength = () => {
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="paramDenoisingStrength">
|
||||
<FormLabel>{`${t('parameters.denoisingStrength')}`}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('parameters.denoisingStrength')}</FormLabel>
|
||||
<CompositeSlider
|
||||
min={sliderMin}
|
||||
max={sliderMax}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { setHrfEnabled } from 'features/hrf/store/hrfSlice';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
@@ -19,9 +18,7 @@ const ParamHrfToggle = () => {
|
||||
|
||||
return (
|
||||
<FormControl w="full">
|
||||
<InformationalPopover feature="paramHrf">
|
||||
<FormLabel flexGrow={1}>{t('hrf.enableHrf')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel flexGrow={1}>{t('hrf.enableHrf')}</FormLabel>
|
||||
<Switch isChecked={hrfEnabled} onChange={handleHrfEnabled} />
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
Text,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import type { LoRA } from 'features/lora/store/loraSlice';
|
||||
import { loraIsEnabledChanged, loraRemoved, loraWeightChanged } from 'features/lora/store/loraSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
@@ -60,31 +59,29 @@ export const LoRACard = memo((props: LoRACardProps) => {
|
||||
</Flex>
|
||||
</Flex>
|
||||
</CardHeader>
|
||||
<InformationalPopover feature="loraWeight">
|
||||
<CardBody>
|
||||
<CompositeSlider
|
||||
value={lora.weight}
|
||||
onChange={handleChange}
|
||||
min={-1}
|
||||
max={2}
|
||||
step={0.01}
|
||||
marks={marks}
|
||||
defaultValue={0.75}
|
||||
isDisabled={!lora.isEnabled}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={lora.weight}
|
||||
onChange={handleChange}
|
||||
min={-5}
|
||||
max={5}
|
||||
step={0.01}
|
||||
w={20}
|
||||
flexShrink={0}
|
||||
defaultValue={0.75}
|
||||
isDisabled={!lora.isEnabled}
|
||||
/>
|
||||
</CardBody>
|
||||
</InformationalPopover>
|
||||
<CardBody>
|
||||
<CompositeSlider
|
||||
value={lora.weight}
|
||||
onChange={handleChange}
|
||||
min={-1}
|
||||
max={2}
|
||||
step={0.01}
|
||||
marks={marks}
|
||||
defaultValue={0.75}
|
||||
isDisabled={!lora.isEnabled}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={lora.weight}
|
||||
onChange={handleChange}
|
||||
min={-5}
|
||||
max={5}
|
||||
step={0.01}
|
||||
w={20}
|
||||
flexShrink={0}
|
||||
defaultValue={0.75}
|
||||
isDisabled={!lora.isEnabled}
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
||||
import { loraAdded, selectLoraSlice } from 'features/lora/store/loraSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
@@ -58,9 +57,7 @@ const LoRASelect = () => {
|
||||
|
||||
return (
|
||||
<FormControl isDisabled={!options.length}>
|
||||
<InformationalPopover feature="lora">
|
||||
<FormLabel>{t('models.lora')} </FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('models.lora')} </FormLabel>
|
||||
<Combobox
|
||||
placeholder={placeholder}
|
||||
value={null}
|
||||
|
||||
@@ -8,7 +8,7 @@ export const ModelPane = () => {
|
||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||
return (
|
||||
<Box layerStyle="first" p={2} borderRadius="base" w="50%" h="full">
|
||||
{selectedModelKey ? <Model /> : <ImportModels />}
|
||||
{selectedModelKey ? <Model key={selectedModelKey} /> : <ImportModels />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import Loading from 'common/components/Loading/Loading';
|
||||
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { useMemo } from 'react';
|
||||
import { useGetModelMetadataQuery } from 'services/api/endpoints/models';
|
||||
|
||||
import { DefaultSettingsForm } from './DefaultSettings/DefaultSettingsForm';
|
||||
|
||||
const initialStatesSelector = createMemoizedSelector(selectConfigSlice, (config) => {
|
||||
const { steps, guidance, scheduler, cfgRescaleMultiplier, vaePrecision } = config.sd;
|
||||
|
||||
return {
|
||||
initialSteps: steps.initial,
|
||||
initialCfg: guidance.initial,
|
||||
initialScheduler: scheduler,
|
||||
initialCfgRescaleMultiplier: cfgRescaleMultiplier.initial,
|
||||
initialVaePrecision: vaePrecision,
|
||||
};
|
||||
});
|
||||
|
||||
export const DefaultSettings = () => {
|
||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||
|
||||
const { data, isLoading } = useGetModelMetadataQuery(selectedModelKey ?? skipToken);
|
||||
const { initialSteps, initialCfg, initialScheduler, initialCfgRescaleMultiplier, initialVaePrecision } =
|
||||
useAppSelector(initialStatesSelector);
|
||||
|
||||
const defaultSettingsDefaults = useMemo(() => {
|
||||
return {
|
||||
vae: { isEnabled: !isNil(data?.default_settings?.vae), value: data?.default_settings?.vae || 'default' },
|
||||
vaePrecision: {
|
||||
isEnabled: !isNil(data?.default_settings?.vae_precision),
|
||||
value: data?.default_settings?.vae_precision || initialVaePrecision || 'fp32',
|
||||
},
|
||||
scheduler: {
|
||||
isEnabled: !isNil(data?.default_settings?.scheduler),
|
||||
value: data?.default_settings?.scheduler || initialScheduler || 'euler',
|
||||
},
|
||||
steps: { isEnabled: !isNil(data?.default_settings?.steps), value: data?.default_settings?.steps || initialSteps },
|
||||
cfgScale: {
|
||||
isEnabled: !isNil(data?.default_settings?.cfg_scale),
|
||||
value: data?.default_settings?.cfg_scale || initialCfg,
|
||||
},
|
||||
cfgRescaleMultiplier: {
|
||||
isEnabled: !isNil(data?.default_settings?.cfg_rescale_multiplier),
|
||||
value: data?.default_settings?.cfg_rescale_multiplier || initialCfgRescaleMultiplier,
|
||||
},
|
||||
};
|
||||
}, [
|
||||
data?.default_settings,
|
||||
initialSteps,
|
||||
initialCfg,
|
||||
initialScheduler,
|
||||
initialCfgRescaleMultiplier,
|
||||
initialVaePrecision,
|
||||
]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return <DefaultSettingsForm defaultSettingsDefaults={defaultSettingsDefaults} />;
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import { CompositeNumberInput, CompositeSlider, Flex,FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { useCallback,useMemo } from 'react';
|
||||
import type {UseControllerProps } from 'react-hook-form';
|
||||
import { useController } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
|
||||
|
||||
type DefaultCfgRescaleMultiplierType = DefaultSettingsFormData['cfgRescaleMultiplier'];
|
||||
|
||||
export function DefaultCfgRescaleMultiplier(props: UseControllerProps<DefaultSettingsFormData>) {
|
||||
const { field } = useController(props);
|
||||
|
||||
const sliderMin = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.sliderMin);
|
||||
const sliderMax = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.sliderMax);
|
||||
const numberInputMin = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.numberInputMin);
|
||||
const numberInputMax = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.numberInputMax);
|
||||
const coarseStep = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.coarseStep);
|
||||
const fineStep = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.fineStep);
|
||||
const { t } = useTranslation();
|
||||
const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]);
|
||||
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
const updatedValue = {
|
||||
...(field.value as DefaultCfgRescaleMultiplierType),
|
||||
value: v,
|
||||
};
|
||||
field.onChange(updatedValue);
|
||||
},
|
||||
[field]
|
||||
);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return (field.value as DefaultCfgRescaleMultiplierType).value;
|
||||
}, [field.value]);
|
||||
|
||||
const isDisabled = useMemo(() => {
|
||||
return !(field.value as DefaultCfgRescaleMultiplierType).isEnabled;
|
||||
}, [field.value]);
|
||||
|
||||
return (
|
||||
<FormControl flexDir="column" gap={1} alignItems="flex-start">
|
||||
<InformationalPopover feature="paramCFGRescaleMultiplier">
|
||||
<FormLabel>{t('parameters.cfgRescaleMultiplier')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<Flex w="full" gap={1}>
|
||||
<CompositeSlider
|
||||
value={value}
|
||||
min={sliderMin}
|
||||
max={sliderMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
marks={marks}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={value}
|
||||
min={numberInputMin}
|
||||
max={numberInputMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { CompositeNumberInput, CompositeSlider, Flex,FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { useCallback,useMemo } from 'react';
|
||||
import type {UseControllerProps } from 'react-hook-form';
|
||||
import { useController } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
|
||||
|
||||
type DefaultCfgType = DefaultSettingsFormData['cfgScale'];
|
||||
|
||||
export function DefaultCfgScale(props: UseControllerProps<DefaultSettingsFormData>) {
|
||||
const { field } = useController(props);
|
||||
|
||||
const sliderMin = useAppSelector((s) => s.config.sd.guidance.sliderMin);
|
||||
const sliderMax = useAppSelector((s) => s.config.sd.guidance.sliderMax);
|
||||
const numberInputMin = useAppSelector((s) => s.config.sd.guidance.numberInputMin);
|
||||
const numberInputMax = useAppSelector((s) => s.config.sd.guidance.numberInputMax);
|
||||
const coarseStep = useAppSelector((s) => s.config.sd.guidance.coarseStep);
|
||||
const fineStep = useAppSelector((s) => s.config.sd.guidance.fineStep);
|
||||
const { t } = useTranslation();
|
||||
const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]);
|
||||
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
const updatedValue = {
|
||||
...(field.value as DefaultCfgType),
|
||||
value: v,
|
||||
};
|
||||
field.onChange(updatedValue);
|
||||
},
|
||||
[field]
|
||||
);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return (field.value as DefaultCfgType).value;
|
||||
}, [field.value]);
|
||||
|
||||
const isDisabled = useMemo(() => {
|
||||
return !(field.value as DefaultCfgType).isEnabled;
|
||||
}, [field.value]);
|
||||
|
||||
return (
|
||||
<FormControl flexDir="column" gap={1} alignItems="flex-start">
|
||||
<InformationalPopover feature="paramCFGScale">
|
||||
<FormLabel>{t('parameters.cfgScale')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<Flex w="full" gap={1}>
|
||||
<CompositeSlider
|
||||
value={value}
|
||||
min={sliderMin}
|
||||
max={sliderMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
marks={marks}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={value}
|
||||
min={numberInputMin}
|
||||
max={numberInputMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { SCHEDULER_OPTIONS } from 'features/parameters/types/constants';
|
||||
import { isParameterScheduler } from 'features/parameters/types/parameterSchemas';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type {UseControllerProps } from 'react-hook-form';
|
||||
import { useController } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
|
||||
|
||||
type DefaultSchedulerType = DefaultSettingsFormData['scheduler'];
|
||||
|
||||
export function DefaultScheduler(props: UseControllerProps<DefaultSettingsFormData>) {
|
||||
const { t } = useTranslation();
|
||||
const { field } = useController(props);
|
||||
|
||||
const onChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
if (!isParameterScheduler(v?.value)) {
|
||||
return;
|
||||
}
|
||||
const updatedValue = {
|
||||
...(field.value as DefaultSchedulerType),
|
||||
value: v.value,
|
||||
};
|
||||
field.onChange(updatedValue);
|
||||
},
|
||||
[field]
|
||||
);
|
||||
|
||||
const value = useMemo(
|
||||
() => SCHEDULER_OPTIONS.find((o) => o.value === (field.value as DefaultSchedulerType).value),
|
||||
[field]
|
||||
);
|
||||
|
||||
const isDisabled = useMemo(() => {
|
||||
return !(field.value as DefaultSchedulerType).isEnabled;
|
||||
}, [field.value]);
|
||||
|
||||
return (
|
||||
<FormControl flexDir="column" gap={1} alignItems="flex-start">
|
||||
<InformationalPopover feature="paramScheduler">
|
||||
<FormLabel>{t('parameters.scheduler')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<Combobox isDisabled={isDisabled} value={value} options={SCHEDULER_OPTIONS} onChange={onChange} />
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { Button, Flex, Heading } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import type { ParameterScheduler } from 'features/parameters/types/parameterSchemas';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { useCallback } from 'react';
|
||||
import type { SubmitHandler } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IoPencil } from 'react-icons/io5';
|
||||
import { useUpdateModelMetadataMutation } from 'services/api/endpoints/models';
|
||||
|
||||
import { DefaultCfgRescaleMultiplier } from './DefaultCfgRescaleMultiplier';
|
||||
import { DefaultCfgScale } from './DefaultCfgScale';
|
||||
import { DefaultScheduler } from './DefaultScheduler';
|
||||
import { DefaultSteps } from './DefaultSteps';
|
||||
import { DefaultVae } from './DefaultVae';
|
||||
import { DefaultVaePrecision } from './DefaultVaePrecision';
|
||||
import { SettingToggle } from './SettingToggle';
|
||||
|
||||
export interface FormField<T> {
|
||||
value: T;
|
||||
isEnabled: boolean;
|
||||
}
|
||||
|
||||
export type DefaultSettingsFormData = {
|
||||
vae: FormField<string>;
|
||||
vaePrecision: FormField<string>;
|
||||
scheduler: FormField<ParameterScheduler>;
|
||||
steps: FormField<number>;
|
||||
cfgScale: FormField<number>;
|
||||
cfgRescaleMultiplier: FormField<number>;
|
||||
};
|
||||
|
||||
export const DefaultSettingsForm = ({
|
||||
defaultSettingsDefaults,
|
||||
}: {
|
||||
defaultSettingsDefaults: DefaultSettingsFormData;
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||
|
||||
const [editModelMetadata, { isLoading }] = useUpdateModelMetadataMutation();
|
||||
|
||||
const { handleSubmit, control, formState } = useForm<DefaultSettingsFormData>({
|
||||
defaultValues: defaultSettingsDefaults,
|
||||
});
|
||||
|
||||
const onSubmit = useCallback<SubmitHandler<DefaultSettingsFormData>>(
|
||||
(data) => {
|
||||
if (!selectedModelKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const body = {
|
||||
vae: data.vae.isEnabled ? data.vae.value : null,
|
||||
vae_precision: data.vaePrecision.isEnabled ? data.vaePrecision.value : null,
|
||||
cfg_scale: data.cfgScale.isEnabled ? data.cfgScale.value : null,
|
||||
cfg_rescale_multiplier: data.cfgRescaleMultiplier.isEnabled ? data.cfgRescaleMultiplier.value : null,
|
||||
steps: data.steps.isEnabled ? data.steps.value : null,
|
||||
scheduler: data.scheduler.isEnabled ? data.scheduler.value : null,
|
||||
};
|
||||
|
||||
editModelMetadata({
|
||||
key: selectedModelKey,
|
||||
body: { default_settings: body },
|
||||
})
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('modelManager.defaultSettingsSaved'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
[selectedModelKey, dispatch, editModelMetadata, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex gap="2" justifyContent="space-between" w="full" mb={5}>
|
||||
<Heading fontSize="md">{t('modelManager.defaultSettings')}</Heading>
|
||||
<Button
|
||||
size="sm"
|
||||
leftIcon={<IoPencil />}
|
||||
colorScheme="invokeYellow"
|
||||
isDisabled={!formState.isDirty}
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
type="submit"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
<Flex flexDir="column" gap={8}>
|
||||
<Flex gap={8}>
|
||||
<Flex gap={4} w="full">
|
||||
<SettingToggle control={control} name="vae" />
|
||||
<DefaultVae control={control} name="vae" />
|
||||
</Flex>
|
||||
<Flex gap={4} w="full">
|
||||
<SettingToggle control={control} name="vaePrecision" />
|
||||
<DefaultVaePrecision control={control} name="vaePrecision" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex gap={8}>
|
||||
<Flex gap={4} w="full">
|
||||
<SettingToggle control={control} name="scheduler" />
|
||||
<DefaultScheduler control={control} name="scheduler" />
|
||||
</Flex>
|
||||
<Flex gap={4} w="full">
|
||||
<SettingToggle control={control} name="steps" />
|
||||
<DefaultSteps control={control} name="steps" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex gap={8}>
|
||||
<Flex gap={4} w="full">
|
||||
<SettingToggle control={control} name="cfgScale" />
|
||||
<DefaultCfgScale control={control} name="cfgScale" />
|
||||
</Flex>
|
||||
<Flex gap={4} w="full">
|
||||
<SettingToggle control={control} name="cfgRescaleMultiplier" />
|
||||
<DefaultCfgRescaleMultiplier control={control} name="cfgRescaleMultiplier" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import { CompositeNumberInput, CompositeSlider, Flex,FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { useCallback,useMemo } from 'react';
|
||||
import type {UseControllerProps } from 'react-hook-form';
|
||||
import { useController } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
|
||||
|
||||
type DefaultSteps = DefaultSettingsFormData['steps'];
|
||||
|
||||
export function DefaultSteps(props: UseControllerProps<DefaultSettingsFormData>) {
|
||||
const { field } = useController(props);
|
||||
|
||||
const sliderMin = useAppSelector((s) => s.config.sd.steps.sliderMin);
|
||||
const sliderMax = useAppSelector((s) => s.config.sd.steps.sliderMax);
|
||||
const numberInputMin = useAppSelector((s) => s.config.sd.steps.numberInputMin);
|
||||
const numberInputMax = useAppSelector((s) => s.config.sd.steps.numberInputMax);
|
||||
const coarseStep = useAppSelector((s) => s.config.sd.steps.coarseStep);
|
||||
const fineStep = useAppSelector((s) => s.config.sd.steps.fineStep);
|
||||
const { t } = useTranslation();
|
||||
const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]);
|
||||
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
const updatedValue = {
|
||||
...(field.value as DefaultSteps),
|
||||
value: v,
|
||||
};
|
||||
field.onChange(updatedValue);
|
||||
},
|
||||
[field]
|
||||
);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return (field.value as DefaultSteps).value;
|
||||
}, [field.value]);
|
||||
|
||||
const isDisabled = useMemo(() => {
|
||||
return !(field.value as DefaultSteps).isEnabled;
|
||||
}, [field.value]);
|
||||
|
||||
return (
|
||||
<FormControl flexDir="column" gap={1} alignItems="flex-start">
|
||||
<InformationalPopover feature="paramSteps">
|
||||
<FormLabel>{t('parameters.steps')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<Flex w="full" gap={1}>
|
||||
<CompositeSlider
|
||||
value={value}
|
||||
min={sliderMin}
|
||||
max={sliderMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
marks={marks}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={value}
|
||||
min={numberInputMin}
|
||||
max={numberInputMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { map } from 'lodash-es';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type {UseControllerProps } from 'react-hook-form';
|
||||
import { useController } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetModelConfigQuery, useGetVaeModelsQuery } from 'services/api/endpoints/models';
|
||||
|
||||
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
|
||||
|
||||
type DefaultVaeType = DefaultSettingsFormData['vae'];
|
||||
|
||||
export function DefaultVae(props: UseControllerProps<DefaultSettingsFormData>) {
|
||||
const { t } = useTranslation();
|
||||
const { field } = useController(props);
|
||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||
const { data: modelData } = useGetModelConfigQuery(selectedModelKey ?? skipToken);
|
||||
|
||||
const { compatibleOptions } = useGetVaeModelsQuery(undefined, {
|
||||
selectFromResult: ({ data }) => {
|
||||
const modelArray = map(data?.entities);
|
||||
const compatibleOptions = modelArray
|
||||
.filter((vae) => vae.base === modelData?.base)
|
||||
.map((vae) => ({ label: vae.name, value: vae.key }));
|
||||
|
||||
const defaultOption = { label: 'Default VAE', value: 'default' };
|
||||
|
||||
return { compatibleOptions: [defaultOption, ...compatibleOptions] };
|
||||
},
|
||||
});
|
||||
|
||||
const onChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
const newValue = !v?.value ? 'default' : v.value;
|
||||
|
||||
const updatedValue = {
|
||||
...(field.value as DefaultVaeType),
|
||||
value: newValue,
|
||||
};
|
||||
field.onChange(updatedValue);
|
||||
},
|
||||
[field]
|
||||
);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return compatibleOptions.find((vae) => vae.value === (field.value as DefaultVaeType).value);
|
||||
}, [compatibleOptions, field.value]);
|
||||
|
||||
const isDisabled = useMemo(() => {
|
||||
return !(field.value as DefaultVaeType).isEnabled;
|
||||
}, [field.value]);
|
||||
|
||||
return (
|
||||
<FormControl flexDir="column" gap={1} alignItems="flex-start">
|
||||
<InformationalPopover feature="paramVAE">
|
||||
<FormLabel>{t('modelManager.vae')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<Combobox isDisabled={isDisabled} value={value} options={compatibleOptions} onChange={onChange} />
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { isParameterPrecision } from 'features/parameters/types/parameterSchemas';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type {UseControllerProps } from 'react-hook-form';
|
||||
import { useController } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
|
||||
|
||||
const options = [
|
||||
{ label: 'FP16', value: 'fp16' },
|
||||
{ label: 'FP32', value: 'fp32' },
|
||||
];
|
||||
|
||||
type DefaultVaePrecisionType = DefaultSettingsFormData['vaePrecision'];
|
||||
|
||||
export function DefaultVaePrecision(props: UseControllerProps<DefaultSettingsFormData>) {
|
||||
const { t } = useTranslation();
|
||||
const { field } = useController(props);
|
||||
|
||||
const onChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
if (!isParameterPrecision(v?.value)) {
|
||||
return;
|
||||
}
|
||||
const updatedValue = {
|
||||
...(field.value as DefaultVaePrecisionType),
|
||||
value: v.value,
|
||||
};
|
||||
field.onChange(updatedValue);
|
||||
},
|
||||
[field]
|
||||
);
|
||||
|
||||
const value = useMemo(() => options.find((o) => o.value === (field.value as DefaultVaePrecisionType).value), [field]);
|
||||
|
||||
const isDisabled = useMemo(() => {
|
||||
return !(field.value as DefaultVaePrecisionType).isEnabled;
|
||||
}, [field.value]);
|
||||
|
||||
return (
|
||||
<FormControl flexDir="column" gap={1} alignItems="flex-start">
|
||||
<InformationalPopover feature="paramVAEPrecision">
|
||||
<FormLabel>{t('modelManager.vaePrecision')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<Combobox isDisabled={isDisabled} value={value} options={options} onChange={onChange} />
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Switch } from '@invoke-ai/ui-library';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { useCallback , useMemo } from 'react';
|
||||
import type { UseControllerProps} from 'react-hook-form';
|
||||
import { useController } from 'react-hook-form';
|
||||
|
||||
import type { DefaultSettingsFormData, FormField } from './DefaultSettingsForm';
|
||||
|
||||
interface Props<T> extends UseControllerProps<DefaultSettingsFormData> {
|
||||
name: keyof DefaultSettingsFormData;
|
||||
}
|
||||
|
||||
export function SettingToggle<T>(props: Props<T>) {
|
||||
const { field } = useController(props);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return !!(field.value as FormField<T>).isEnabled;
|
||||
}, [field.value]);
|
||||
|
||||
const onChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
const updatedValue: FormField<T> = {
|
||||
...(field.value as FormField<T>),
|
||||
isEnabled: e.target.checked,
|
||||
};
|
||||
field.onChange(updatedValue);
|
||||
},
|
||||
[field]
|
||||
);
|
||||
|
||||
return <Switch isChecked={value} onChange={onChange} />;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
|
||||
import { useGetModelMetadataQuery } from 'services/api/endpoints/models';
|
||||
|
||||
import { TriggerPhrases } from './TriggerPhrases';
|
||||
|
||||
export const ModelMetadata = () => {
|
||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||
const { data: metadata } = useGetModelMetadataQuery(selectedModelKey ?? skipToken);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" height="full" gap="3">
|
||||
<Box layerStyle="second" borderRadius="base" p={3}>
|
||||
<TriggerPhrases />
|
||||
</Box>
|
||||
<DataViewer label="metadata" data={metadata || {}} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
Input,
|
||||
Tag,
|
||||
TagCloseButton,
|
||||
TagLabel,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { ModelListHeader } from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelListHeader';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetModelMetadataQuery, useUpdateModelMetadataMutation } from 'services/api/endpoints/models';
|
||||
|
||||
export const TriggerPhrases = () => {
|
||||
const { t } = useTranslation();
|
||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||
const { data: metadata } = useGetModelMetadataQuery(selectedModelKey ?? skipToken);
|
||||
const [phrase, setPhrase] = useState('');
|
||||
|
||||
const [editModelMetadata, { isLoading }] = useUpdateModelMetadataMutation();
|
||||
|
||||
const handlePhraseChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setPhrase(e.target.value);
|
||||
}, []);
|
||||
|
||||
const triggerPhrases = useMemo(() => {
|
||||
return metadata?.trigger_phrases || [];
|
||||
}, [metadata?.trigger_phrases]);
|
||||
|
||||
const errors = useMemo(() => {
|
||||
const errors = [];
|
||||
|
||||
if (phrase.length && triggerPhrases.includes(phrase)) {
|
||||
errors.push('Phrase is already in list');
|
||||
}
|
||||
|
||||
return errors;
|
||||
}, [phrase, triggerPhrases]);
|
||||
|
||||
const addTriggerPhrase = useCallback(async () => {
|
||||
if (!selectedModelKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!phrase.length || triggerPhrases.includes(phrase)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await editModelMetadata({
|
||||
key: selectedModelKey,
|
||||
body: { trigger_phrases: [...triggerPhrases, phrase] },
|
||||
}).unwrap();
|
||||
setPhrase('');
|
||||
}, [editModelMetadata, selectedModelKey, phrase, triggerPhrases]);
|
||||
|
||||
const removeTriggerPhrase = useCallback(
|
||||
async (phraseToRemove: string) => {
|
||||
if (!selectedModelKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filteredPhrases = triggerPhrases.filter((p) => p !== phraseToRemove);
|
||||
|
||||
await editModelMetadata({ key: selectedModelKey, body: { trigger_phrases: filteredPhrases } }).unwrap();
|
||||
},
|
||||
[editModelMetadata, selectedModelKey, triggerPhrases]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" w="full" gap="5">
|
||||
<ModelListHeader title={t('modelManager.triggerPhrases')} />
|
||||
<form>
|
||||
<FormControl w="full" isInvalid={Boolean(errors.length)}>
|
||||
<Flex flexDir="column" w="full">
|
||||
<Flex gap="3" alignItems="center" w="full">
|
||||
<Input value={phrase} onChange={handlePhraseChange} placeholder={t('modelManager.typePhraseHere')} />
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={addTriggerPhrase}
|
||||
isDisabled={Boolean(errors.length)}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{t('common.add')}
|
||||
</Button>
|
||||
</Flex>
|
||||
{!!errors.length && errors.map((error) => <FormErrorMessage key={error}>{error}</FormErrorMessage>)}
|
||||
</Flex>
|
||||
</FormControl>
|
||||
</form>
|
||||
|
||||
<Flex gap="4" flexWrap="wrap" mt="3" mb="3">
|
||||
{triggerPhrases.map((phrase, index) => (
|
||||
<Tag size="md" key={index}>
|
||||
<TagLabel>{phrase}</TagLabel>
|
||||
<TagCloseButton onClick={removeTriggerPhrase.bind(null, phrase)} isDisabled={isLoading} />
|
||||
</Tag>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,58 @@
|
||||
import { Box, Flex, Heading, Tab, TabList, TabPanel, TabPanels, Tabs, Text } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetModelConfigQuery } from 'services/api/endpoints/models';
|
||||
|
||||
import { ModelMetadata } from './Metadata/ModelMetadata';
|
||||
import { ModelAttrView } from './ModelAttrView';
|
||||
import { ModelEdit } from './ModelEdit';
|
||||
import { ModelView } from './ModelView';
|
||||
|
||||
export const Model = () => {
|
||||
const { t } = useTranslation();
|
||||
const selectedModelMode = useAppSelector((s) => s.modelmanagerV2.selectedModelMode);
|
||||
return selectedModelMode === 'view' ? <ModelView /> : <ModelEdit />;
|
||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||
const { data, isLoading } = useGetModelConfigQuery(selectedModelKey ?? skipToken);
|
||||
|
||||
if (isLoading) {
|
||||
return <Text>{t('common.loading')}</Text>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <Text>{t('common.somethingWentWrong')}</Text>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex flexDir="column" gap={1} p={2}>
|
||||
<Heading as="h2" fontSize="lg">
|
||||
{data.name}
|
||||
</Heading>
|
||||
|
||||
{data.source && (
|
||||
<Text variant="subtext">
|
||||
{t('modelManager.source')}: {data?.source}
|
||||
</Text>
|
||||
)}
|
||||
<Box mt="4">
|
||||
<ModelAttrView label="Description" value={data.description} />
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Tabs mt="4" h="100%">
|
||||
<TabList>
|
||||
<Tab>{t('modelManager.settings')}</Tab>
|
||||
<Tab>{t('modelManager.metadata')}</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels h="100%">
|
||||
<TabPanel>{selectedModelMode === 'view' ? <ModelView /> : <ModelEdit />}</TabPanel>
|
||||
<TabPanel h="full">
|
||||
<ModelMetadata />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Box, Button, Flex, Heading, Text } from '@invoke-ai/ui-library';
|
||||
import { Box, Button, Flex, Text } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
|
||||
import { setSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IoPencil } from 'react-icons/io5';
|
||||
import { useGetModelConfigQuery, useGetModelMetadataQuery } from 'services/api/endpoints/models';
|
||||
import { useGetModelConfigQuery } from 'services/api/endpoints/models';
|
||||
import type {
|
||||
CheckpointModelConfig,
|
||||
ControlNetModelConfig,
|
||||
@@ -18,6 +17,7 @@ import type {
|
||||
VAEModelConfig,
|
||||
} from 'services/api/types';
|
||||
|
||||
import { DefaultSettings } from './DefaultSettings';
|
||||
import { ModelAttrView } from './ModelAttrView';
|
||||
import { ModelConvert } from './ModelConvert';
|
||||
|
||||
@@ -26,7 +26,6 @@ export const ModelView = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||
const { data, isLoading } = useGetModelConfigQuery(selectedModelKey ?? skipToken);
|
||||
const { data: metadata } = useGetModelMetadataQuery(selectedModelKey ?? skipToken);
|
||||
|
||||
const modelData = useMemo(() => {
|
||||
if (!data) {
|
||||
@@ -73,85 +72,56 @@ export const ModelView = () => {
|
||||
return <Text>{t('common.somethingWentWrong')}</Text>;
|
||||
}
|
||||
return (
|
||||
<Flex flexDir="column" h="full">
|
||||
<Flex w="full" justifyContent="space-between">
|
||||
<Flex flexDir="column" gap={1} p={2}>
|
||||
<Heading as="h2" fontSize="lg">
|
||||
{modelData.name}
|
||||
</Heading>
|
||||
|
||||
{modelData.source && (
|
||||
<Text variant="subtext">
|
||||
{t('modelManager.source')}: {modelData.source}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<Flex flexDir="column" h="full" gap="2">
|
||||
<Box layerStyle="second" borderRadius="base" p={3}>
|
||||
<Flex gap="2" justifyContent="flex-end" w="full">
|
||||
<Button size="sm" leftIcon={<IoPencil />} colorScheme="invokeYellow" onClick={handleEditModel}>
|
||||
{t('modelManager.edit')}
|
||||
</Button>
|
||||
|
||||
{modelData.type === 'main' && modelData.format === 'checkpoint' && <ModelConvert model={modelData} />}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex flexDir="column" p={2} gap={3}>
|
||||
<Flex>
|
||||
<ModelAttrView label="Description" value={modelData.description} />
|
||||
</Flex>
|
||||
<Heading as="h3" fontSize="md" mt="4">
|
||||
{t('modelManager.modelSettings')}
|
||||
</Heading>
|
||||
<Box layerStyle="second" borderRadius="base" p={3}>
|
||||
<Flex flexDir="column" gap={3}>
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('modelManager.baseModel')} value={modelData.base} />
|
||||
<ModelAttrView label={t('modelManager.modelType')} value={modelData.type} />
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('common.format')} value={modelData.format} />
|
||||
<ModelAttrView label={t('modelManager.path')} value={modelData.path} />
|
||||
</Flex>
|
||||
{modelData.type === 'main' && (
|
||||
<>
|
||||
<Flex gap={2}>
|
||||
{modelData.format === 'diffusers' && (
|
||||
<ModelAttrView label={t('modelManager.repoVariant')} value={modelData.repo_variant} />
|
||||
)}
|
||||
{modelData.format === 'checkpoint' && (
|
||||
<ModelAttrView label={t('modelManager.pathToConfig')} value={modelData.config} />
|
||||
)}
|
||||
|
||||
<ModelAttrView label={t('modelManager.variant')} value={modelData.variant} />
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('modelManager.predictionType')} value={modelData.prediction_type} />
|
||||
<ModelAttrView label={t('modelManager.upcastAttention')} value={`${modelData.upcast_attention}`} />
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('modelManager.ztsnrTraining')} value={`${modelData.ztsnr_training}`} />
|
||||
<ModelAttrView label={t('modelManager.vae')} value={modelData.vae} />
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
{modelData.type === 'ip_adapter' && (
|
||||
<Flex flexDir="column" gap={3}>
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('modelManager.baseModel')} value={modelData.base} />
|
||||
<ModelAttrView label={t('modelManager.modelType')} value={modelData.type} />
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('common.format')} value={modelData.format} />
|
||||
<ModelAttrView label={t('modelManager.path')} value={modelData.path} />
|
||||
</Flex>
|
||||
{modelData.type === 'main' && (
|
||||
<>
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('modelManager.imageEncoderModelId')} value={modelData.image_encoder_model_id} />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
</Flex>
|
||||
{modelData.format === 'diffusers' && (
|
||||
<ModelAttrView label={t('modelManager.repoVariant')} value={modelData.repo_variant} />
|
||||
)}
|
||||
{modelData.format === 'checkpoint' && (
|
||||
<ModelAttrView label={t('modelManager.pathToConfig')} value={modelData.config} />
|
||||
)}
|
||||
|
||||
{metadata && (
|
||||
<>
|
||||
<Heading as="h3" fontSize="md" mt="4">
|
||||
{t('modelManager.modelMetadata')}
|
||||
</Heading>
|
||||
<Flex h="full" w="full" p={2}>
|
||||
<DataViewer label="metadata" data={metadata} />
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
<ModelAttrView label={t('modelManager.variant')} value={modelData.variant} />
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('modelManager.predictionType')} value={modelData.prediction_type} />
|
||||
<ModelAttrView label={t('modelManager.upcastAttention')} value={`${modelData.upcast_attention}`} />
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('modelManager.ztsnrTraining')} value={`${modelData.ztsnr_training}`} />
|
||||
<ModelAttrView label={t('modelManager.vae')} value={modelData.vae} />
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
{modelData.type === 'ip_adapter' && (
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('modelManager.imageEncoderModelId')} value={modelData.image_encoder_model_id} />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box layerStyle="second" borderRadius="base" p={3}>
|
||||
<DefaultSettings />
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { setInfillPatchmatchDownscaleSize } from 'features/parameters/store/generationSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -28,9 +27,7 @@ const ParamInfillPatchmatchDownscaleSize = () => {
|
||||
|
||||
return (
|
||||
<FormControl isDisabled={infillMethod !== 'patchmatch'}>
|
||||
<InformationalPopover feature="patchmatchDownScaleSize">
|
||||
<FormLabel>{t('parameters.patchmatchDownScaleSize')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('parameters.patchmatchDownScaleSize')}</FormLabel>
|
||||
<CompositeSlider
|
||||
min={sliderMin}
|
||||
max={sliderMax}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
@@ -28,9 +27,7 @@ export const ParamHeight = memo(() => {
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="paramHeight">
|
||||
<FormLabel>{t('parameters.height')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('parameters.height')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={ctx.height}
|
||||
defaultValue={optimalDimension}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
|
||||
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
|
||||
import { usePrompt } from 'features/embedding/usePrompt';
|
||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||
import { setNegativePrompt } from 'features/parameters/store/generationSlice';
|
||||
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||
import { usePrompt } from 'features/prompt/usePrompt';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -19,19 +19,14 @@ export const ParamNegativePrompt = memo(() => {
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const { onChange, isOpen, onClose, onOpen, onSelectEmbedding, onKeyDown } = usePrompt({
|
||||
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({
|
||||
prompt,
|
||||
textareaRef,
|
||||
onChange: _onChange,
|
||||
});
|
||||
|
||||
return (
|
||||
<EmbeddingPopover
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onSelect={onSelectEmbedding}
|
||||
width={textareaRef.current?.clientWidth}
|
||||
>
|
||||
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
||||
<Box pos="relative">
|
||||
<Textarea
|
||||
id="negativePrompt"
|
||||
@@ -45,10 +40,10 @@ export const ParamNegativePrompt = memo(() => {
|
||||
variant="darkFilled"
|
||||
/>
|
||||
<PromptOverlayButtonWrapper>
|
||||
<AddEmbeddingButton isOpen={isOpen} onOpen={onOpen} />
|
||||
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
||||
</PromptOverlayButtonWrapper>
|
||||
</Box>
|
||||
</EmbeddingPopover>
|
||||
</PromptPopover>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton';
|
||||
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
|
||||
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
|
||||
import { usePrompt } from 'features/embedding/usePrompt';
|
||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||
import { setPositivePrompt } from 'features/parameters/store/generationSlice';
|
||||
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||
import { usePrompt } from 'features/prompt/usePrompt';
|
||||
import { SDXLConcatButton } from 'features/sdxl/components/SDXLPrompts/SDXLConcatButton';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import type { HotkeyCallback } from 'react-hotkeys-hook';
|
||||
@@ -25,7 +25,7 @@ export const ParamPositivePrompt = memo(() => {
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const { onChange, isOpen, onClose, onOpen, onSelectEmbedding, onKeyDown, onFocus } = usePrompt({
|
||||
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown, onFocus } = usePrompt({
|
||||
prompt,
|
||||
textareaRef: textareaRef,
|
||||
onChange: handleChange,
|
||||
@@ -42,12 +42,7 @@ export const ParamPositivePrompt = memo(() => {
|
||||
useHotkeys('alt+a', focus, []);
|
||||
|
||||
return (
|
||||
<EmbeddingPopover
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onSelect={onSelectEmbedding}
|
||||
width={textareaRef.current?.clientWidth}
|
||||
>
|
||||
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
||||
<Box pos="relative">
|
||||
<Textarea
|
||||
id="prompt"
|
||||
@@ -61,12 +56,12 @@ export const ParamPositivePrompt = memo(() => {
|
||||
variant="darkFilled"
|
||||
/>
|
||||
<PromptOverlayButtonWrapper>
|
||||
<AddEmbeddingButton isOpen={isOpen} onOpen={onOpen} />
|
||||
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
||||
{baseModel === 'sdxl' && <SDXLConcatButton />}
|
||||
<ShowDynamicPromptsPreviewButton />
|
||||
</PromptOverlayButtonWrapper>
|
||||
</Box>
|
||||
</EmbeddingPopover>
|
||||
</PromptPopover>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
@@ -28,9 +27,7 @@ export const ParamWidth = memo(() => {
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="paramWidth">
|
||||
<FormLabel>{t('parameters.width')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('parameters.width')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={ctx.width}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ComboboxOption, SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { SingleValue } from 'chakra-react-select';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { ASPECT_RATIO_OPTIONS } from 'features/parameters/components/ImageSize/constants';
|
||||
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
|
||||
import { isAspectRatioID } from 'features/parameters/components/ImageSize/types';
|
||||
@@ -29,9 +28,7 @@ export const AspectRatioSelect = memo(() => {
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="paramAspect">
|
||||
<FormLabel>{t('parameters.aspect')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('parameters.aspect')}</FormLabel>
|
||||
<Combobox value={value} onChange={onChange} options={ASPECT_RATIO_OPTIONS} sx={selectStyles} />
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { setShouldFitToWidthHeight } from 'features/parameters/store/generationSlice';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
@@ -23,9 +22,7 @@ const ImageToImageFit = () => {
|
||||
|
||||
return (
|
||||
<FormControl w="full">
|
||||
<InformationalPopover feature="imageFit">
|
||||
<FormLabel flexGrow={1}>{t('parameters.imageFit')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel flexGrow={1}>{t('parameters.imageFit')}</FormLabel>
|
||||
<Switch isChecked={shouldFitToWidthHeight} onChange={handleChangeFit} />
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,6 @@ import { CustomSelect, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useModelCustomSelect } from 'common/hooks/useModelCustomSelect';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { modelSelected } from 'features/parameters/store/actions';
|
||||
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
@@ -38,9 +37,7 @@ const ParamMainModelSelect = () => {
|
||||
|
||||
return (
|
||||
<FormControl isDisabled={!items.length} isInvalid={!selectedItem || !items.length}>
|
||||
<InformationalPopover feature="paramModel">
|
||||
<FormLabel>{t('modelManager.model')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('modelManager.model')}</FormLabel>
|
||||
<CustomSelect selectedItem={selectedItem} placeholder={placeholder} items={items} onChange={onChange} />
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { setDefaultSettings } from 'features/parameters/store/actions';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RiSparklingFill } from 'react-icons/ri';
|
||||
|
||||
export const UseDefaultSettingsButton = () => {
|
||||
const model = useAppSelector((s) => s.generation.model);
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleClickDefaultSettings = useCallback(() => {
|
||||
dispatch(setDefaultSettings());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
icon={<RiSparklingFill />}
|
||||
tooltip={t('modelManager.useDefaultSettings')}
|
||||
aria-label={t('modelManager.useDefaultSettings')}
|
||||
isDisabled={!model}
|
||||
onClick={handleClickDefaultSettings}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { setSeamlessXAxis } from 'features/parameters/store/generationSlice';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
@@ -21,9 +20,7 @@ const ParamSeamlessXAxis = () => {
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="seamlessTilingXAxis">
|
||||
<FormLabel>{t('parameters.seamlessXAxis')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('parameters.seamlessXAxis')}</FormLabel>
|
||||
<Switch isChecked={seamlessXAxis} onChange={handleChange} />
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { setSeamlessYAxis } from 'features/parameters/store/generationSlice';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
@@ -19,9 +18,7 @@ const ParamSeamlessYAxis = () => {
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="seamlessTilingYAxis">
|
||||
<FormLabel>{t('parameters.seamlessYAxis')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('parameters.seamlessYAxis')}</FormLabel>
|
||||
<Switch isChecked={seamlessYAxis} onChange={handleChange} />
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -5,3 +5,5 @@ import type { ImageDTO } from 'services/api/types';
|
||||
export const initialImageSelected = createAction<ImageDTO | undefined>('generation/initialImageSelected');
|
||||
|
||||
export const modelSelected = createAction<ParameterModel>('generation/modelSelected');
|
||||
|
||||
export const setDefaultSettings = createAction('generation/setDefaultSettings');
|
||||
@@ -230,6 +230,12 @@ export const generationSlice = createSlice({
|
||||
state.height = optimalDimension;
|
||||
}
|
||||
}
|
||||
if (action.payload.sd?.scheduler) {
|
||||
state.scheduler = action.payload.sd.scheduler;
|
||||
}
|
||||
if (action.payload.sd?.vaePrecision) {
|
||||
state.vaePrecision = action.payload.sd.vaePrecision;
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: This is a temp fix to reduce issues with T2I adapter having a different downscaling
|
||||
|
||||
@@ -8,15 +8,15 @@ type Props = {
|
||||
onOpen: () => void;
|
||||
};
|
||||
|
||||
export const AddEmbeddingButton = memo((props: Props) => {
|
||||
export const AddPromptTriggerButton = memo((props: Props) => {
|
||||
const { onOpen, isOpen } = props;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Tooltip label={t('embedding.addEmbedding')}>
|
||||
<Tooltip label={t('prompt.addPromptTrigger')}>
|
||||
<IconButton
|
||||
variant="promptOverlay"
|
||||
isDisabled={isOpen}
|
||||
aria-label={t('embedding.addEmbedding')}
|
||||
aria-label={t('prompt.addPromptTrigger')}
|
||||
icon={<PiCodeBold />}
|
||||
onClick={onOpen}
|
||||
/>
|
||||
@@ -24,4 +24,4 @@ export const AddEmbeddingButton = memo((props: Props) => {
|
||||
);
|
||||
});
|
||||
|
||||
AddEmbeddingButton.displayName = 'AddEmbeddingButton';
|
||||
AddPromptTriggerButton.displayName = 'AddPromptTriggerButton';
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Popover, PopoverAnchor, PopoverBody, PopoverContent } from '@invoke-ai/ui-library';
|
||||
import { EmbeddingSelect } from 'features/embedding/EmbeddingSelect';
|
||||
import type { EmbeddingPopoverProps } from 'features/embedding/types';
|
||||
import { PromptTriggerSelect } from 'features/prompt/PromptTriggerSelect';
|
||||
import type { PromptPopoverProps } from 'features/prompt/types';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const EmbeddingPopover = memo((props: EmbeddingPopoverProps) => {
|
||||
export const PromptPopover = memo((props: PromptPopoverProps) => {
|
||||
const { onSelect, isOpen, onClose, width, children } = props;
|
||||
|
||||
return (
|
||||
@@ -14,7 +14,7 @@ export const EmbeddingPopover = memo((props: EmbeddingPopoverProps) => {
|
||||
openDelay={0}
|
||||
closeDelay={0}
|
||||
closeOnBlur={true}
|
||||
returnFocusOnClose={true}
|
||||
returnFocusOnClose={false}
|
||||
isLazy
|
||||
>
|
||||
<PopoverAnchor>{children}</PopoverAnchor>
|
||||
@@ -27,11 +27,11 @@ export const EmbeddingPopover = memo((props: EmbeddingPopoverProps) => {
|
||||
borderStyle="solid"
|
||||
>
|
||||
<PopoverBody p={0} width={`calc(${width}px - 0.25rem)`}>
|
||||
<EmbeddingSelect onClose={onClose} onSelect={onSelect} />
|
||||
<PromptTriggerSelect onClose={onClose} onSelect={onSelect} />
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
});
|
||||
|
||||
EmbeddingPopover.displayName = 'EmbeddingPopover';
|
||||
PromptPopover.displayName = 'PromptPopover';
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { PromptTriggerSelect } from './PromptTriggerSelect';
|
||||
import type { PromptTriggerSelectProps } from './types';
|
||||
|
||||
const meta: Meta<typeof PromptTriggerSelect> = {
|
||||
title: 'Feature/Prompt/PromptTriggerSelect',
|
||||
tags: ['autodocs'],
|
||||
component: PromptTriggerSelect,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof PromptTriggerSelect>;
|
||||
|
||||
const Component = (props: PromptTriggerSelectProps) => {
|
||||
return <PromptTriggerSelect {...props}>Invoke</PromptTriggerSelect>;
|
||||
};
|
||||
|
||||
export const Default: Story = {
|
||||
render: Component,
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { ChakraProps, ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import type { PromptTriggerSelectProps } from 'features/prompt/types';
|
||||
import { t } from 'i18next';
|
||||
import { map } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetModelMetadataQuery, useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
|
||||
|
||||
const noOptionsMessage = () => t('prompt.noMatchingTriggers');
|
||||
|
||||
export const PromptTriggerSelect = memo(({ onSelect, onClose }: PromptTriggerSelectProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
|
||||
const currentModelKey = useAppSelector((s) => s.generation.model?.key);
|
||||
|
||||
const { data, isLoading } = useGetTextualInversionModelsQuery();
|
||||
const { data: metadata } = useGetModelMetadataQuery(currentModelKey ?? skipToken);
|
||||
|
||||
const _onChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
if (!v) {
|
||||
onSelect('');
|
||||
return;
|
||||
}
|
||||
|
||||
onSelect(v.value);
|
||||
},
|
||||
[onSelect]
|
||||
);
|
||||
|
||||
const embeddingOptions = useMemo(() => {
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const compatibleEmbeddingsArray = map(data.entities).filter((model) => model.base === currentBaseModel);
|
||||
|
||||
return [
|
||||
{
|
||||
label: t('prompt.compatibleEmbeddings'),
|
||||
options: compatibleEmbeddingsArray.map((model) => ({ label: model.name, value: `<${model.name}>` })),
|
||||
},
|
||||
];
|
||||
}, [data, currentBaseModel, t]);
|
||||
|
||||
const options = useMemo(() => {
|
||||
if (!metadata || !metadata.trigger_phrases) {
|
||||
return [...embeddingOptions];
|
||||
}
|
||||
|
||||
const metadataOptions = [
|
||||
{
|
||||
label: t('modelManager.triggerPhrases'),
|
||||
options: metadata.trigger_phrases.map((phrase) => ({ label: phrase, value: phrase })),
|
||||
},
|
||||
];
|
||||
return [...metadataOptions, ...embeddingOptions];
|
||||
}, [embeddingOptions, metadata, t]);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<Combobox
|
||||
placeholder={isLoading ? t('common.loading') : t('prompt.addPromptTrigger')}
|
||||
defaultMenuIsOpen
|
||||
autoFocus
|
||||
value={null}
|
||||
options={options}
|
||||
noOptionsMessage={noOptionsMessage}
|
||||
onChange={_onChange}
|
||||
onMenuClose={onClose}
|
||||
data-testid="add-prompt-trigger"
|
||||
sx={selectStyles}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
});
|
||||
|
||||
PromptTriggerSelect.displayName = 'PromptTriggerSelect';
|
||||
|
||||
const selectStyles: ChakraProps['sx'] = {
|
||||
w: 'full',
|
||||
};
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
export type EmbeddingSelectProps = {
|
||||
export type PromptTriggerSelectProps = {
|
||||
onSelect: (v: string) => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export type EmbeddingPopoverProps = PropsWithChildren &
|
||||
EmbeddingSelectProps & {
|
||||
export type PromptPopoverProps = PropsWithChildren &
|
||||
PromptTriggerSelectProps & {
|
||||
isOpen: boolean;
|
||||
width?: number | string;
|
||||
};
|
||||
@@ -4,13 +4,13 @@ import type { ChangeEventHandler, KeyboardEventHandler, RefObject } from 'react'
|
||||
import { useCallback } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
|
||||
type UseInsertEmbeddingArg = {
|
||||
type UseInsertTriggerArg = {
|
||||
prompt: string;
|
||||
textareaRef: RefObject<HTMLTextAreaElement>;
|
||||
onChange: (v: string) => void;
|
||||
};
|
||||
|
||||
export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInsertEmbeddingArg) => {
|
||||
export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInsertTriggerArg) => {
|
||||
const { isOpen, onClose, onOpen } = useDisclosure();
|
||||
|
||||
const onChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
|
||||
@@ -20,13 +20,13 @@ export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInser
|
||||
[_onChange]
|
||||
);
|
||||
|
||||
const insertEmbedding = useCallback(
|
||||
const insertTrigger = useCallback(
|
||||
(v: string) => {
|
||||
if (!textareaRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this is where we insert the TI trigger
|
||||
// this is where we insert the trigger
|
||||
const caret = textareaRef.current.selectionStart;
|
||||
|
||||
if (isNil(caret)) {
|
||||
@@ -35,13 +35,9 @@ export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInser
|
||||
|
||||
let newPrompt = prompt.slice(0, caret);
|
||||
|
||||
if (newPrompt[newPrompt.length - 1] !== '<') {
|
||||
newPrompt += '<';
|
||||
}
|
||||
newPrompt += `${v}`;
|
||||
|
||||
newPrompt += `${v}>`;
|
||||
|
||||
// we insert the cursor after the `>`
|
||||
// we insert the cursor after the end of trigger
|
||||
const finalCaretPos = newPrompt.length;
|
||||
|
||||
newPrompt += prompt.slice(caret);
|
||||
@@ -51,7 +47,7 @@ export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInser
|
||||
_onChange(newPrompt);
|
||||
});
|
||||
|
||||
// set the caret position to just after the TI trigger
|
||||
// set the cursor position to just after the trigger
|
||||
textareaRef.current.selectionStart = finalCaretPos;
|
||||
textareaRef.current.selectionEnd = finalCaretPos;
|
||||
},
|
||||
@@ -62,17 +58,17 @@ export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInser
|
||||
textareaRef.current?.focus();
|
||||
}, [textareaRef]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
const handleClosePopover = useCallback(() => {
|
||||
onClose();
|
||||
onFocus();
|
||||
}, [onFocus, onClose]);
|
||||
|
||||
const onSelectEmbedding = useCallback(
|
||||
const onSelect = useCallback(
|
||||
(v: string) => {
|
||||
insertEmbedding(v);
|
||||
handleClose();
|
||||
insertTrigger(v);
|
||||
handleClosePopover();
|
||||
},
|
||||
[handleClose, insertEmbedding]
|
||||
[handleClosePopover, insertTrigger]
|
||||
);
|
||||
|
||||
const onKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = useCallback(
|
||||
@@ -90,7 +86,7 @@ export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInser
|
||||
isOpen,
|
||||
onClose,
|
||||
onOpen,
|
||||
onSelectEmbedding,
|
||||
onSelect,
|
||||
onKeyDown,
|
||||
onFocus,
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
|
||||
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
|
||||
import { usePrompt } from 'features/embedding/usePrompt';
|
||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||
import { usePrompt } from 'features/prompt/usePrompt';
|
||||
import { setNegativeStylePromptSDXL } from 'features/sdxl/store/sdxlSlice';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
@@ -20,7 +20,7 @@ export const ParamSDXLNegativeStylePrompt = memo(() => {
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const { onChange, isOpen, onClose, onOpen, onSelectEmbedding, onKeyDown, onFocus } = usePrompt({
|
||||
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown, onFocus } = usePrompt({
|
||||
prompt,
|
||||
textareaRef: textareaRef,
|
||||
onChange: handleChange,
|
||||
@@ -29,12 +29,7 @@ export const ParamSDXLNegativeStylePrompt = memo(() => {
|
||||
useHotkeys('alt+a', onFocus, []);
|
||||
|
||||
return (
|
||||
<EmbeddingPopover
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onSelect={onSelectEmbedding}
|
||||
width={textareaRef.current?.clientWidth}
|
||||
>
|
||||
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
||||
<Box pos="relative">
|
||||
<Textarea
|
||||
id="prompt"
|
||||
@@ -48,10 +43,10 @@ export const ParamSDXLNegativeStylePrompt = memo(() => {
|
||||
variant="darkFilled"
|
||||
/>
|
||||
<PromptOverlayButtonWrapper>
|
||||
<AddEmbeddingButton isOpen={isOpen} onOpen={onOpen} />
|
||||
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
||||
</PromptOverlayButtonWrapper>
|
||||
</Box>
|
||||
</EmbeddingPopover>
|
||||
</PromptPopover>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
|
||||
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
|
||||
import { usePrompt } from 'features/embedding/usePrompt';
|
||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||
import { usePrompt } from 'features/prompt/usePrompt';
|
||||
import { setPositiveStylePromptSDXL } from 'features/sdxl/store/sdxlSlice';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -19,19 +19,14 @@ export const ParamSDXLPositiveStylePrompt = memo(() => {
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const { onChange, isOpen, onClose, onOpen, onSelectEmbedding, onKeyDown } = usePrompt({
|
||||
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({
|
||||
prompt,
|
||||
textareaRef: textareaRef,
|
||||
onChange: handleChange,
|
||||
});
|
||||
|
||||
return (
|
||||
<EmbeddingPopover
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onSelect={onSelectEmbedding}
|
||||
width={textareaRef.current?.clientWidth}
|
||||
>
|
||||
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
||||
<Box pos="relative">
|
||||
<Textarea
|
||||
id="prompt"
|
||||
@@ -45,10 +40,10 @@ export const ParamSDXLPositiveStylePrompt = memo(() => {
|
||||
variant="darkFilled"
|
||||
/>
|
||||
<PromptOverlayButtonWrapper>
|
||||
<AddEmbeddingButton isOpen={isOpen} onOpen={onOpen} />
|
||||
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
||||
</PromptOverlayButtonWrapper>
|
||||
</Box>
|
||||
</EmbeddingPopover>
|
||||
</PromptPopover>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { setRefinerCFGScale } from 'features/sdxl/store/sdxlSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -22,9 +21,7 @@ const ParamSDXLRefinerCFGScale = () => {
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="refinerCfgScale">
|
||||
<FormLabel>{t('sdxl.cfgScale')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('sdxl.cfgScale')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={refinerCFGScale}
|
||||
defaultValue={initial}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { useModelCombobox } from 'common/hooks/useModelCombobox';
|
||||
import { getModelKeyAndBase } from 'features/metadata/util/modelFetchingHelpers';
|
||||
import { refinerModelChanged, selectSdxlSlice } from 'features/sdxl/store/sdxlSlice';
|
||||
@@ -39,9 +38,7 @@ const ParamSDXLRefinerModelSelect = () => {
|
||||
});
|
||||
return (
|
||||
<FormControl isDisabled={!options.length} isInvalid={!options.length}>
|
||||
<InformationalPopover feature="refinerModel">
|
||||
<FormLabel>{t('sdxl.refinermodel')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('sdxl.refinermodel')}</FormLabel>
|
||||
<Combobox
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { setRefinerNegativeAestheticScore } from 'features/sdxl/store/sdxlSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -15,9 +14,7 @@ const ParamSDXLRefinerNegativeAestheticScore = () => {
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="refinerNegativeAestheticScore">
|
||||
<FormLabel>{t('sdxl.negAestheticScore')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('sdxl.negAestheticScore')}</FormLabel>
|
||||
<CompositeSlider
|
||||
min={1}
|
||||
max={10}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { setRefinerPositiveAestheticScore } from 'features/sdxl/store/sdxlSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -14,9 +13,7 @@ const ParamSDXLRefinerPositiveAestheticScore = () => {
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="refinerPositiveAestheticScore">
|
||||
<FormLabel>{t('sdxl.posAestheticScore')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('sdxl.posAestheticScore')}</FormLabel>
|
||||
<CompositeSlider
|
||||
step={0.5}
|
||||
min={1}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { SCHEDULER_OPTIONS } from 'features/parameters/types/constants';
|
||||
import { isParameterScheduler } from 'features/parameters/types/parameterSchemas';
|
||||
import { setRefinerScheduler } from 'features/sdxl/store/sdxlSlice';
|
||||
@@ -27,9 +26,7 @@ const ParamSDXLRefinerScheduler = () => {
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="refinerScheduler">
|
||||
<FormLabel>{t('sdxl.scheduler')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('sdxl.scheduler')}</FormLabel>
|
||||
<Combobox value={value} options={SCHEDULER_OPTIONS} onChange={onChange} />
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { setRefinerStart } from 'features/sdxl/store/sdxlSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -13,9 +12,7 @@ const ParamSDXLRefinerStart = () => {
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="refinerStart">
|
||||
<FormLabel>{t('sdxl.refinerStart')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('sdxl.refinerStart')}</FormLabel>
|
||||
<CompositeSlider
|
||||
step={0.01}
|
||||
min={0}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { setRefinerSteps } from 'features/sdxl/store/sdxlSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -28,9 +27,7 @@ const ParamSDXLRefinerSteps = () => {
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="refinerSteps">
|
||||
<FormLabel>{t('sdxl.steps')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<FormLabel>{t('sdxl.steps')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={refinerSteps}
|
||||
defaultValue={initial}
|
||||
|
||||
@@ -21,6 +21,7 @@ import ParamCFGScale from 'features/parameters/components/Core/ParamCFGScale';
|
||||
import ParamScheduler from 'features/parameters/components/Core/ParamScheduler';
|
||||
import ParamSteps from 'features/parameters/components/Core/ParamSteps';
|
||||
import ParamMainModelSelect from 'features/parameters/components/MainModel/ParamMainModelSelect';
|
||||
import { UseDefaultSettingsButton } from 'features/parameters/components/MainModel/UseDefaultSettingsButton';
|
||||
import { useExpanderToggle } from 'features/settingsAccordions/hooks/useExpanderToggle';
|
||||
import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle';
|
||||
import { filter } from 'lodash-es';
|
||||
@@ -71,7 +72,10 @@ export const GenerationSettingsAccordion = memo(() => {
|
||||
<TabPanel overflow="visible" px={4} pt={4}>
|
||||
<Flex gap={4} alignItems="center">
|
||||
<ParamMainModelSelect />
|
||||
<SyncModelsIconButton />
|
||||
<Flex>
|
||||
<UseDefaultSettingsButton />
|
||||
<SyncModelsIconButton />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Expander isOpen={isOpenExpander} onToggle={onToggleExpander}>
|
||||
<Flex gap={4} flexDir="column" pb={4}>
|
||||
|
||||
@@ -41,6 +41,8 @@ const initialConfigState: AppConfig = {
|
||||
boundingBoxHeight: { ...baseDimensionConfig },
|
||||
scaledBoundingBoxWidth: { ...baseDimensionConfig },
|
||||
scaledBoundingBoxHeight: { ...baseDimensionConfig },
|
||||
scheduler: "euler",
|
||||
vaePrecision: "fp32",
|
||||
steps: {
|
||||
initial: 30,
|
||||
sliderMin: 1,
|
||||
|
||||
@@ -24,12 +24,21 @@ export type UpdateModelArg = {
|
||||
body: paths['/api/v2/models/i/{key}']['patch']['requestBody']['content']['application/json'];
|
||||
};
|
||||
|
||||
type UpdateModelMetadataArg = {
|
||||
key: paths['/api/v2/models/i/{key}/metadata']['patch']['parameters']['path']['key'];
|
||||
body: paths['/api/v2/models/i/{key}/metadata']['patch']['requestBody']['content']['application/json'];
|
||||
};
|
||||
|
||||
type UpdateModelResponse = paths['/api/v2/models/i/{key}']['patch']['responses']['200']['content']['application/json'];
|
||||
type UpdateModelMetadataResponse =
|
||||
paths['/api/v2/models/i/{key}/metadata']['patch']['responses']['200']['content']['application/json'];
|
||||
|
||||
type GetModelConfigResponse = paths['/api/v2/models/i/{key}']['get']['responses']['200']['content']['application/json'];
|
||||
|
||||
type GetModelMetadataResponse =
|
||||
paths['/api/v2/models/i/{key}/metadata']['get']['responses']['200']['content']['application/json'];
|
||||
|
||||
|
||||
type ListModelsArg = NonNullable<paths['/api/v2/models/']['get']['parameters']['query']>;
|
||||
|
||||
type DeleteMainModelArg = {
|
||||
@@ -108,25 +117,25 @@ const anyModelConfigAdapterSelectors = anyModelConfigAdapter.getSelectors(undefi
|
||||
|
||||
const buildProvidesTags =
|
||||
<TEntity extends AnyModelConfig>(tagType: (typeof tagTypes)[number]) =>
|
||||
(result: EntityState<TEntity, string> | undefined) => {
|
||||
const tags: ApiTagDescription[] = [{ type: tagType, id: LIST_TAG }, 'Model'];
|
||||
if (result) {
|
||||
tags.push(
|
||||
...result.ids.map((id) => ({
|
||||
type: tagType,
|
||||
id,
|
||||
}))
|
||||
);
|
||||
}
|
||||
(result: EntityState<TEntity, string> | undefined) => {
|
||||
const tags: ApiTagDescription[] = [{ type: tagType, id: LIST_TAG }, 'Model'];
|
||||
if (result) {
|
||||
tags.push(
|
||||
...result.ids.map((id) => ({
|
||||
type: tagType,
|
||||
id,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return tags;
|
||||
};
|
||||
return tags;
|
||||
};
|
||||
|
||||
const buildTransformResponse =
|
||||
<T extends AnyModelConfig>(adapter: EntityAdapter<T, string>) =>
|
||||
(response: { models: T[] }) => {
|
||||
return adapter.setAll(adapter.getInitialState(), response.models);
|
||||
};
|
||||
(response: { models: T[] }) => {
|
||||
return adapter.setAll(adapter.getInitialState(), response.models);
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds an endpoint URL for the models router
|
||||
@@ -172,6 +181,16 @@ export const modelsApi = api.injectEndpoints({
|
||||
},
|
||||
invalidatesTags: ['Model'],
|
||||
}),
|
||||
updateModelMetadata: build.mutation<UpdateModelMetadataResponse, UpdateModelMetadataArg>({
|
||||
query: ({ key, body }) => {
|
||||
return {
|
||||
url: buildModelsUrl(`i/${key}/metadata`),
|
||||
method: 'PATCH',
|
||||
body: body,
|
||||
};
|
||||
},
|
||||
invalidatesTags: ['Model'],
|
||||
}),
|
||||
installModel: build.mutation<InstallModelResponse, InstallModelArg>({
|
||||
query: ({ source, config, access_token }) => {
|
||||
return {
|
||||
@@ -351,6 +370,7 @@ export const {
|
||||
useGetModelMetadataQuery,
|
||||
useDeleteModelImportMutation,
|
||||
usePruneModelImportsMutation,
|
||||
useUpdateModelMetadataMutation,
|
||||
} = modelsApi;
|
||||
|
||||
const upsertModelConfigs = (
|
||||
|
||||
@@ -55,22 +55,10 @@ const dynamicBaseQuery: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryE
|
||||
const baseUrl = $baseUrl.get();
|
||||
const authToken = $authToken.get();
|
||||
const projectId = $projectId.get();
|
||||
const isOpenAPIRequest =
|
||||
(args instanceof Object && args.url.includes('openapi.json')) ||
|
||||
(typeof args === 'string' && args.includes('openapi.json'));
|
||||
|
||||
const fetchBaseQueryArgs: FetchBaseQueryArgs = {
|
||||
baseUrl: baseUrl ? `${baseUrl}/api/v1` : `${window.location.href.replace(/\/$/, '')}/api/v1`,
|
||||
};
|
||||
|
||||
// When fetching the openapi.json, we need to remove circular references from the JSON.
|
||||
if (isOpenAPIRequest) {
|
||||
fetchBaseQueryArgs.jsonReplacer = getCircularReplacer();
|
||||
}
|
||||
|
||||
// openapi.json isn't protected by authorization, but all other requests need to include the auth token and project id.
|
||||
if (!isOpenAPIRequest) {
|
||||
fetchBaseQueryArgs.prepareHeaders = (headers) => {
|
||||
baseUrl: baseUrl || window.location.href.replace(/\/$/, ''),
|
||||
prepareHeaders: (headers) => {
|
||||
if (authToken) {
|
||||
headers.set('Authorization', `Bearer ${authToken}`);
|
||||
}
|
||||
@@ -79,7 +67,15 @@ const dynamicBaseQuery: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryE
|
||||
}
|
||||
|
||||
return headers;
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// When fetching the openapi.json, we need to remove circular references from the JSON.
|
||||
if (
|
||||
(args instanceof Object && args.url.includes('openapi.json')) ||
|
||||
(typeof args === 'string' && args.includes('openapi.json'))
|
||||
) {
|
||||
fetchBaseQueryArgs.jsonReplacer = getCircularReplacer();
|
||||
}
|
||||
|
||||
const rawBaseQuery = fetchBaseQuery(fetchBaseQueryArgs);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
__version__ = "3.7.0"
|
||||
__version__ = "3.6.3"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -133,7 +133,7 @@ def test_metadata_civitai_fetch(mm2_session: Session) -> None:
|
||||
assert metadata.id == 215485
|
||||
assert metadata.author == "test_author" # note that this is not the same as the original from Civitai
|
||||
assert metadata.allow_commercial_use # changed to make sure we are reading locally not remotely
|
||||
assert CommercialUsage("RentCivit") in metadata.restrictions.AllowCommercialUse
|
||||
assert metadata.restrictions.AllowCommercialUse == CommercialUsage("RentCivit")
|
||||
assert metadata.version_id == 242807
|
||||
assert metadata.tags == {"tool", "turbo", "sdxl turbo"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user