mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-01-20 04:17:59 -05:00
Compare commits
39 Commits
psyche/per
...
v5.7.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84c9ecc83f | ||
|
|
52aa839b7e | ||
|
|
316ed1d478 | ||
|
|
3519e8ae39 | ||
|
|
82f645c7a1 | ||
|
|
cc36cfb617 | ||
|
|
ded8a84284 | ||
|
|
94771ea626 | ||
|
|
51d661023e | ||
|
|
d215829b91 | ||
|
|
fad6c67f01 | ||
|
|
f366640d46 | ||
|
|
36a3fba8cb | ||
|
|
b2ff83092f | ||
|
|
d2db38a5b9 | ||
|
|
fa988a6273 | ||
|
|
149f60946c | ||
|
|
ee9d620a36 | ||
|
|
4e8ce4abab | ||
|
|
d40f2fa37c | ||
|
|
933f4f6857 | ||
|
|
f499b2db7b | ||
|
|
706aaf7460 | ||
|
|
4a706d00bb | ||
|
|
2a8bff601f | ||
|
|
3f0e3192f6 | ||
|
|
c65147e2ff | ||
|
|
1c14e257a3 | ||
|
|
fe24217082 | ||
|
|
aee847065c | ||
|
|
525da3257c | ||
|
|
559654f0ca | ||
|
|
5d33874d58 | ||
|
|
0063315139 | ||
|
|
1cbd609860 | ||
|
|
047c643295 | ||
|
|
d1e03aa1c5 | ||
|
|
1bb8edf57e | ||
|
|
a3e78f0db6 |
@@ -1,4 +1,4 @@
|
||||
from typing import Literal, Optional, Union
|
||||
from typing import Optional, Union
|
||||
|
||||
from fastapi import Body, HTTPException, Path, Query
|
||||
from fastapi.routing import APIRouter
|
||||
@@ -146,7 +146,7 @@ async def list_boards(
|
||||
response_model=list[str],
|
||||
)
|
||||
async def list_all_board_image_names(
|
||||
board_id: str | Literal["none"] = Path(description="The id of the board"),
|
||||
board_id: str = Path(description="The id of the board"),
|
||||
categories: list[ImageCategory] | None = Query(default=None, description="The categories of image to include."),
|
||||
is_intermediate: bool | None = Query(default=None, description="Whether to list intermediate images."),
|
||||
) -> list[str]:
|
||||
|
||||
@@ -36,6 +36,7 @@ from invokeai.app.api.routers import (
|
||||
workflows,
|
||||
)
|
||||
from invokeai.app.api.sockets import SocketIO
|
||||
from invokeai.app.invocations.load_custom_nodes import load_custom_nodes
|
||||
from invokeai.app.services.config.config_default import get_config
|
||||
from invokeai.app.util.custom_openapi import get_openapi_func
|
||||
from invokeai.backend.util.devices import TorchDevice
|
||||
@@ -63,6 +64,11 @@ loop = asyncio.new_event_loop()
|
||||
# the correct port when the server starts in the lifespan handler.
|
||||
port = app_config.port
|
||||
|
||||
# Load custom nodes. This must be done after importing the Graph class, which itself imports all modules from the
|
||||
# invocations module. The ordering here is implicit, but important - we want to load custom nodes after all the
|
||||
# core nodes have been imported so that we can catch when a custom node clobbers a core node.
|
||||
load_custom_nodes(custom_nodes_path=app_config.custom_nodes_path)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
|
||||
@@ -1,33 +1,5 @@
|
||||
import shutil
|
||||
import sys
|
||||
from importlib.util import module_from_spec, spec_from_file_location
|
||||
from pathlib import Path
|
||||
|
||||
from invokeai.app.services.config.config_default import get_config
|
||||
|
||||
custom_nodes_path = Path(get_config().custom_nodes_path)
|
||||
custom_nodes_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
custom_nodes_init_path = str(custom_nodes_path / "__init__.py")
|
||||
custom_nodes_readme_path = str(custom_nodes_path / "README.md")
|
||||
|
||||
# copy our custom nodes __init__.py to the custom nodes directory
|
||||
shutil.copy(Path(__file__).parent / "custom_nodes/init.py", custom_nodes_init_path)
|
||||
shutil.copy(Path(__file__).parent / "custom_nodes/README.md", custom_nodes_readme_path)
|
||||
|
||||
# set the same permissions as the destination directory, in case our source is read-only,
|
||||
# so that the files are user-writable
|
||||
for p in custom_nodes_path.glob("**/*"):
|
||||
p.chmod(custom_nodes_path.stat().st_mode)
|
||||
|
||||
# Import custom nodes, see https://docs.python.org/3/library/importlib.html#importing-programmatically
|
||||
spec = spec_from_file_location("custom_nodes", custom_nodes_init_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"Could not load custom nodes from {custom_nodes_init_path}")
|
||||
module = module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
# add core nodes to __all__
|
||||
python_files = filter(lambda f: not f.name.startswith("_"), Path(__file__).parent.glob("*.py"))
|
||||
__all__ = [f.stem for f in python_files] # type: ignore
|
||||
|
||||
@@ -44,8 +44,6 @@ if TYPE_CHECKING:
|
||||
|
||||
logger = InvokeAILogger.get_logger()
|
||||
|
||||
CUSTOM_NODE_PACK_SUFFIX = "__invokeai-custom-node"
|
||||
|
||||
|
||||
class InvalidVersionError(ValueError):
|
||||
pass
|
||||
@@ -240,6 +238,11 @@ class BaseInvocation(ABC, BaseModel):
|
||||
"""Gets the invocation's output annotation (i.e. the return annotation of its `invoke()` method)."""
|
||||
return signature(cls.invoke).return_annotation
|
||||
|
||||
@classmethod
|
||||
def get_invocation_for_type(cls, invocation_type: str) -> BaseInvocation | None:
|
||||
"""Gets the invocation class for a given invocation type."""
|
||||
return cls.get_invocations_map().get(invocation_type)
|
||||
|
||||
@staticmethod
|
||||
def json_schema_extra(schema: dict[str, Any], model_class: Type[BaseInvocation]) -> None:
|
||||
"""Adds various UI-facing attributes to the invocation's OpenAPI schema."""
|
||||
@@ -446,8 +449,27 @@ def invocation(
|
||||
if re.compile(r"^\S+$").match(invocation_type) is None:
|
||||
raise ValueError(f'"invocation_type" must consist of non-whitespace characters, got "{invocation_type}"')
|
||||
|
||||
# The node pack is the module name - will be "invokeai" for built-in nodes
|
||||
node_pack = cls.__module__.split(".")[0]
|
||||
|
||||
# Handle the case where an existing node is being clobbered by the one we are registering
|
||||
if invocation_type in BaseInvocation.get_invocation_types():
|
||||
raise ValueError(f'Invocation type "{invocation_type}" already exists')
|
||||
clobbered_invocation = BaseInvocation.get_invocation_for_type(invocation_type)
|
||||
# This should always be true - we just checked if the invocation type was in the set
|
||||
assert clobbered_invocation is not None
|
||||
|
||||
clobbered_node_pack = clobbered_invocation.UIConfig.node_pack
|
||||
|
||||
if clobbered_node_pack == "invokeai":
|
||||
# The node being clobbered is a core node
|
||||
raise ValueError(
|
||||
f'Cannot load node "{invocation_type}" from node pack "{node_pack}" - a core node with the same type already exists'
|
||||
)
|
||||
else:
|
||||
# The node being clobbered is a custom node
|
||||
raise ValueError(
|
||||
f'Cannot load node "{invocation_type}" from node pack "{node_pack}" - a node with the same type already exists in node pack "{clobbered_node_pack}"'
|
||||
)
|
||||
|
||||
validate_fields(cls.model_fields, invocation_type)
|
||||
|
||||
@@ -457,8 +479,7 @@ def invocation(
|
||||
uiconfig["tags"] = tags
|
||||
uiconfig["category"] = category
|
||||
uiconfig["classification"] = classification
|
||||
# The node pack is the module name - will be "invokeai" for built-in nodes
|
||||
uiconfig["node_pack"] = cls.__module__.split(".")[0]
|
||||
uiconfig["node_pack"] = node_pack
|
||||
|
||||
if version is not None:
|
||||
try:
|
||||
|
||||
@@ -10,10 +10,12 @@ from pathlib import Path
|
||||
from invokeai.backend.util.logging import InvokeAILogger
|
||||
|
||||
logger = InvokeAILogger.get_logger()
|
||||
loaded_count = 0
|
||||
loaded_packs: list[str] = []
|
||||
failed_packs: list[str] = []
|
||||
|
||||
custom_nodes_dir = Path(__file__).parent
|
||||
|
||||
for d in Path(__file__).parent.iterdir():
|
||||
for d in custom_nodes_dir.iterdir():
|
||||
# skip files
|
||||
if not d.is_dir():
|
||||
continue
|
||||
@@ -47,12 +49,16 @@ for d in Path(__file__).parent.iterdir():
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
loaded_count += 1
|
||||
loaded_packs.append(module_name)
|
||||
except Exception:
|
||||
failed_packs.append(module_name)
|
||||
full_error = traceback.format_exc()
|
||||
logger.error(f"Failed to load node pack {module_name}:\n{full_error}")
|
||||
logger.error(f"Failed to load node pack {module_name} (may have partially loaded):\n{full_error}")
|
||||
|
||||
del init, module_name
|
||||
|
||||
loaded_count = len(loaded_packs)
|
||||
if loaded_count > 0:
|
||||
logger.info(f"Loaded {loaded_count} node packs from {Path(__file__).parent}")
|
||||
logger.info(
|
||||
f"Loaded {loaded_count} node pack{'s' if loaded_count != 1 else ''} from {custom_nodes_dir}: {', '.join(loaded_packs)}"
|
||||
)
|
||||
|
||||
40
invokeai/app/invocations/load_custom_nodes.py
Normal file
40
invokeai/app/invocations/load_custom_nodes.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import shutil
|
||||
import sys
|
||||
from importlib.util import module_from_spec, spec_from_file_location
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load_custom_nodes(custom_nodes_path: Path):
|
||||
"""
|
||||
Loads all custom nodes from the custom_nodes_path directory.
|
||||
|
||||
This function copies a custom __init__.py file to the custom_nodes_path directory, effectively turning it into a
|
||||
python module.
|
||||
|
||||
The custom __init__.py file itself imports all the custom node packs as python modules from the custom_nodes_path
|
||||
directory.
|
||||
|
||||
Then,the custom __init__.py file is programmatically imported using importlib. As it executes, it imports all the
|
||||
custom node packs as python modules.
|
||||
"""
|
||||
custom_nodes_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
custom_nodes_init_path = str(custom_nodes_path / "__init__.py")
|
||||
custom_nodes_readme_path = str(custom_nodes_path / "README.md")
|
||||
|
||||
# copy our custom nodes __init__.py to the custom nodes directory
|
||||
shutil.copy(Path(__file__).parent / "custom_nodes/init.py", custom_nodes_init_path)
|
||||
shutil.copy(Path(__file__).parent / "custom_nodes/README.md", custom_nodes_readme_path)
|
||||
|
||||
# set the same permissions as the destination directory, in case our source is read-only,
|
||||
# so that the files are user-writable
|
||||
for p in custom_nodes_path.glob("**/*"):
|
||||
p.chmod(custom_nodes_path.stat().st_mode)
|
||||
|
||||
# Import custom nodes, see https://docs.python.org/3/library/importlib.html#importing-programmatically
|
||||
spec = spec_from_file_location("custom_nodes", custom_nodes_init_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"Could not load custom nodes from {custom_nodes_init_path}")
|
||||
module = module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
@@ -284,6 +284,7 @@ class CoreMetadataInvocation(BaseInvocation):
|
||||
tags=["metadata"],
|
||||
category="metadata",
|
||||
version="1.0.0",
|
||||
classification=Classification.Deprecated,
|
||||
)
|
||||
class MetadataFieldExtractorInvocation(BaseInvocation):
|
||||
"""Extracts the text value from an image's metadata given a key.
|
||||
|
||||
1164
invokeai/app/invocations/metadata_linked.py
Normal file
1164
invokeai/app/invocations/metadata_linked.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -265,13 +265,9 @@ class ImageInvocation(BaseInvocation):
|
||||
image: ImageField = InputField(description="The image to load")
|
||||
|
||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||
image = context.images.get_pil(self.image.image_name)
|
||||
image_dto = context.images.get_dto(self.image.image_name)
|
||||
|
||||
return ImageOutput(
|
||||
image=ImageField(image_name=self.image.image_name),
|
||||
width=image.width,
|
||||
height=image.height,
|
||||
)
|
||||
return ImageOutput.build(image_dto=image_dto)
|
||||
|
||||
|
||||
@invocation(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Literal, Optional
|
||||
from typing import Optional
|
||||
|
||||
from invokeai.app.services.image_records.image_records_common import ImageCategory
|
||||
|
||||
@@ -27,7 +27,7 @@ class BoardImageRecordStorageBase(ABC):
|
||||
@abstractmethod
|
||||
def get_all_board_image_names_for_board(
|
||||
self,
|
||||
board_id: str | Literal["none"],
|
||||
board_id: str,
|
||||
categories: list[ImageCategory] | None,
|
||||
is_intermediate: bool | None,
|
||||
) -> list[str]:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import sqlite3
|
||||
import threading
|
||||
from typing import Literal, Optional, cast
|
||||
from typing import Optional, cast
|
||||
|
||||
from invokeai.app.services.board_image_records.board_image_records_base import BoardImageRecordStorageBase
|
||||
from invokeai.app.services.image_records.image_records_common import (
|
||||
@@ -103,7 +103,7 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase):
|
||||
|
||||
def get_all_board_image_names_for_board(
|
||||
self,
|
||||
board_id: str | Literal["none"],
|
||||
board_id: str,
|
||||
categories: list[ImageCategory] | None,
|
||||
is_intermediate: bool | None,
|
||||
) -> list[str]:
|
||||
@@ -118,14 +118,9 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase):
|
||||
FROM images
|
||||
LEFT JOIN board_images ON board_images.image_name = images.image_name
|
||||
WHERE 1=1
|
||||
AND board_images.board_id = ?
|
||||
"""
|
||||
|
||||
# Add the board_id filter
|
||||
if board_id == "none":
|
||||
stmt += "AND board_images.board_id IS NULL"
|
||||
else:
|
||||
stmt += "AND board_images.board_id = ?"
|
||||
params.append(board_id)
|
||||
params.append(board_id)
|
||||
|
||||
# Add the category filter
|
||||
if categories is not None:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Literal, Optional
|
||||
from typing import Optional
|
||||
|
||||
from invokeai.app.services.image_records.image_records_common import ImageCategory
|
||||
|
||||
@@ -27,7 +27,7 @@ class BoardImagesServiceABC(ABC):
|
||||
@abstractmethod
|
||||
def get_all_board_image_names_for_board(
|
||||
self,
|
||||
board_id: str | Literal["none"],
|
||||
board_id: str,
|
||||
categories: list[ImageCategory] | None,
|
||||
is_intermediate: bool | None,
|
||||
) -> list[str]:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Literal, Optional
|
||||
from typing import Optional
|
||||
|
||||
from invokeai.app.services.board_images.board_images_base import BoardImagesServiceABC
|
||||
from invokeai.app.services.image_records.image_records_common import ImageCategory
|
||||
@@ -26,7 +26,7 @@ class BoardImagesService(BoardImagesServiceABC):
|
||||
|
||||
def get_all_board_image_names_for_board(
|
||||
self,
|
||||
board_id: str | Literal["none"],
|
||||
board_id: str,
|
||||
categories: list[ImageCategory] | None,
|
||||
is_intermediate: bool | None,
|
||||
) -> list[str]:
|
||||
|
||||
@@ -63,7 +63,11 @@ class BulkDownloadService(BulkDownloadBase):
|
||||
return [self._invoker.services.images.get_dto(image_name) for image_name in image_names]
|
||||
|
||||
def _board_handler(self, board_id: str) -> list[ImageDTO]:
|
||||
image_names = self._invoker.services.board_image_records.get_all_board_image_names_for_board(board_id)
|
||||
image_names = self._invoker.services.board_image_records.get_all_board_image_names_for_board(
|
||||
board_id,
|
||||
categories=None,
|
||||
is_intermediate=None,
|
||||
)
|
||||
return self._image_handler(image_names)
|
||||
|
||||
def generate_item_id(self, board_id: Optional[str]) -> str:
|
||||
|
||||
@@ -265,7 +265,11 @@ class ImageService(ImageServiceABC):
|
||||
|
||||
def delete_images_on_board(self, board_id: str):
|
||||
try:
|
||||
image_names = self.__invoker.services.board_image_records.get_all_board_image_names_for_board(board_id)
|
||||
image_names = self.__invoker.services.board_image_records.get_all_board_image_names_for_board(
|
||||
board_id,
|
||||
categories=None,
|
||||
is_intermediate=None,
|
||||
)
|
||||
for image_name in image_names:
|
||||
self.__invoker.services.image_files.delete(image_name)
|
||||
self.__invoker.services.image_records.delete_many(image_names)
|
||||
@@ -278,7 +282,7 @@ class ImageService(ImageServiceABC):
|
||||
self.__invoker.services.logger.error("Failed to delete image files")
|
||||
raise
|
||||
except Exception as e:
|
||||
self.__invoker.services.logger.error("Problem deleting image records and files")
|
||||
self.__invoker.services.logger.error(f"Problem deleting image records and files: {str(e)}")
|
||||
raise e
|
||||
|
||||
def delete_intermediates(self) -> int:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import datetime
|
||||
import json
|
||||
from itertools import chain, product
|
||||
from typing import Generator, Iterable, Literal, NamedTuple, Optional, TypeAlias, Union, cast
|
||||
from typing import Generator, Literal, Optional, TypeAlias, Union, cast
|
||||
|
||||
from pydantic import (
|
||||
AliasChoices,
|
||||
@@ -406,61 +406,143 @@ class IsFullResult(BaseModel):
|
||||
# region Util
|
||||
|
||||
|
||||
def populate_graph(graph: Graph, node_field_values: Iterable[NodeFieldValue]) -> Graph:
|
||||
def create_session_nfv_tuples(batch: Batch, maximum: int) -> Generator[tuple[str, str, str], None, None]:
|
||||
"""
|
||||
Populates the given graph with the given batch data items.
|
||||
"""
|
||||
graph_clone = graph.model_copy(deep=True)
|
||||
for item in node_field_values:
|
||||
node = graph_clone.get_node(item.node_path)
|
||||
if node is None:
|
||||
continue
|
||||
setattr(node, item.field_name, item.value)
|
||||
graph_clone.update_node(item.node_path, node)
|
||||
return graph_clone
|
||||
Given a batch and a maximum number of sessions to create, generate a tuple of session_id, session_json, and
|
||||
field_values_json for each session.
|
||||
|
||||
The batch has a "source" graph and a data property. The data property is a list of lists of BatchDatum objects.
|
||||
Each BatchDatum has a field identifier (e.g. a node id and field name), and a list of values to substitute into
|
||||
the field.
|
||||
|
||||
def create_session_nfv_tuples(
|
||||
batch: Batch, maximum: int
|
||||
) -> Generator[tuple[GraphExecutionState, list[NodeFieldValue], Optional[WorkflowWithoutID]], None, None]:
|
||||
"""
|
||||
Create all graph permutations from the given batch data and graph. Yields tuples
|
||||
of the form (graph, batch_data_items) where batch_data_items is the list of BatchDataItems
|
||||
that was applied to the graph.
|
||||
This structure allows us to create a new graph for every possible permutation of BatchDatum objects:
|
||||
- Each BatchDatum can be "expanded" into a dict of node-field-value tuples - one for each item in the BatchDatum.
|
||||
- Zip each inner list of expanded BatchDatum objects together. Call this a "batch_data_list".
|
||||
- Take the cartesian product of all zipped batch_data_lists, resulting in a list of permutations of BatchDatum
|
||||
- Take the cartesian product of all zipped batch_data_lists, resulting in a list of lists of BatchDatum objects.
|
||||
Each inner list now represents the substitution values for a single permutation (session).
|
||||
- For each permutation, substitute the values into the graph
|
||||
|
||||
This function is optimized for performance, as it is used to generate a large number of sessions at once.
|
||||
|
||||
Args:
|
||||
batch: The batch to generate sessions from
|
||||
maximum: The maximum number of sessions to generate
|
||||
|
||||
Returns:
|
||||
A generator that yields tuples of session_id, session_json, and field_values_json for each session. The
|
||||
generator will stop early if the maximum number of sessions is reached.
|
||||
"""
|
||||
|
||||
# TODO: Should this be a class method on Batch?
|
||||
|
||||
data: list[list[tuple[NodeFieldValue]]] = []
|
||||
data: list[list[tuple[dict]]] = []
|
||||
batch_data_collection = batch.data if batch.data is not None else []
|
||||
for batch_datum_list in batch_data_collection:
|
||||
# each batch_datum_list needs to be convered to NodeFieldValues and then zipped
|
||||
|
||||
node_field_values_to_zip: list[list[NodeFieldValue]] = []
|
||||
for batch_datum_list in batch_data_collection:
|
||||
node_field_values_to_zip: list[list[dict]] = []
|
||||
# Expand each BatchDatum into a list of dicts - one for each item in the BatchDatum
|
||||
for batch_datum in batch_datum_list:
|
||||
node_field_values = [
|
||||
NodeFieldValue(node_path=batch_datum.node_path, field_name=batch_datum.field_name, value=item)
|
||||
# Note: A tuple here is slightly faster than a dict, but we need the object in dict form to be inserted
|
||||
# in the session_queue table anyways. So, overall creating NFVs as dicts is faster.
|
||||
{"node_path": batch_datum.node_path, "field_name": batch_datum.field_name, "value": item}
|
||||
for item in batch_datum.items
|
||||
]
|
||||
node_field_values_to_zip.append(node_field_values)
|
||||
# Zip the dicts together to create a list of dicts for each permutation
|
||||
data.append(list(zip(*node_field_values_to_zip, strict=True))) # type: ignore [arg-type]
|
||||
|
||||
# create generator to yield session,nfv tuples
|
||||
# We serialize the graph and session once, then mutate the graph dict in place for each session.
|
||||
#
|
||||
# This sounds scary, but it's actually fine.
|
||||
#
|
||||
# The batch prep logic injects field values into the same fields for each generated session.
|
||||
#
|
||||
# For example, after the product operation, we'll end up with a list of node-field-value tuples like this:
|
||||
# [
|
||||
# (
|
||||
# {"node_path": "1", "field_name": "a", "value": 1},
|
||||
# {"node_path": "2", "field_name": "b", "value": 2},
|
||||
# {"node_path": "3", "field_name": "c", "value": 3},
|
||||
# ),
|
||||
# (
|
||||
# {"node_path": "1", "field_name": "a", "value": 4},
|
||||
# {"node_path": "2", "field_name": "b", "value": 5},
|
||||
# {"node_path": "3", "field_name": "c", "value": 6},
|
||||
# )
|
||||
# ]
|
||||
#
|
||||
# Note that each tuple has the same length, and each tuple substitutes values in for exactly the same node fields.
|
||||
# No matter the complexity of the batch, this property holds true.
|
||||
#
|
||||
# This means each permutation's substitution can be done in-place on the same graph dict, because it overwrites the
|
||||
# previous mutation. We only need to serialize the graph once, and then we can mutate it in place for each session.
|
||||
#
|
||||
# Previously, we had created new Graph objects for each session, but this was very slow for large (1k+ session
|
||||
# batches). We then tried dumping the graph to dict and using deep-copy to create a new dict for each session,
|
||||
# but this was also slow.
|
||||
#
|
||||
# Overall, we achieved a 100x speedup by mutating the graph dict in place for each session over creating new Graph
|
||||
# objects for each session.
|
||||
#
|
||||
# We will also mutate the session dict in place, setting a new ID for each session and setting the mutated graph
|
||||
# dict as the session's graph.
|
||||
|
||||
# Dump the batch's graph to a dict once
|
||||
graph_as_dict = batch.graph.model_dump(warnings=False, exclude_none=True)
|
||||
|
||||
# We must provide a Graph object when creating the "dummy" session dict, but we don't actually use it. It will be
|
||||
# overwritten for each session by the mutated graph_as_dict.
|
||||
session_dict = GraphExecutionState(graph=Graph()).model_dump(warnings=False, exclude_none=True)
|
||||
|
||||
# Now we can create a generator that yields the session_id, session_json, and field_values_json for each session.
|
||||
count = 0
|
||||
|
||||
# Each batch may have multiple runs, so we need to generate the same number of sessions for each run. The total is
|
||||
# still limited by the maximum number of sessions.
|
||||
for _ in range(batch.runs):
|
||||
for d in product(*data):
|
||||
if count >= maximum:
|
||||
# We've reached the maximum number of sessions we may generate
|
||||
return
|
||||
|
||||
# Flatten the list of lists of dicts into a single list of dicts
|
||||
# TODO(psyche): Is the a more efficient way to do this?
|
||||
flat_node_field_values = list(chain.from_iterable(d))
|
||||
graph = populate_graph(batch.graph, flat_node_field_values)
|
||||
yield (GraphExecutionState(graph=graph), flat_node_field_values, batch.workflow)
|
||||
|
||||
# Need a fresh ID for each session
|
||||
session_id = uuid_string()
|
||||
|
||||
# Mutate the session dict in place
|
||||
session_dict["id"] = session_id
|
||||
|
||||
# Substitute the values into the graph
|
||||
for nfv in flat_node_field_values:
|
||||
graph_as_dict["nodes"][nfv["node_path"]][nfv["field_name"]] = nfv["value"]
|
||||
|
||||
# Mutate the session dict in place
|
||||
session_dict["graph"] = graph_as_dict
|
||||
|
||||
# Serialize the session and field values
|
||||
# Note the use of pydantic's to_jsonable_python to handle serialization of any python object, including sets.
|
||||
session_json = json.dumps(session_dict, default=to_jsonable_python)
|
||||
field_values_json = json.dumps(flat_node_field_values, default=to_jsonable_python)
|
||||
|
||||
# Yield the session_id, session_json, and field_values_json
|
||||
yield (session_id, session_json, field_values_json)
|
||||
|
||||
# Increment the count so we know when to stop
|
||||
count += 1
|
||||
|
||||
|
||||
def calc_session_count(batch: Batch) -> int:
|
||||
"""
|
||||
Calculates the number of sessions that would be created by the batch, without incurring
|
||||
the overhead of actually generating them. Adapted from `create_sessions().
|
||||
Calculates the number of sessions that would be created by the batch, without incurring the overhead of actually
|
||||
creating them, as is done in `create_session_nfv_tuples()`.
|
||||
|
||||
The count is used to communicate to the user how many sessions were _requested_ to be created, as opposed to how
|
||||
many were _actually_ created (which may be less due to the maximum number of sessions).
|
||||
"""
|
||||
# TODO: Should this be a class method on Batch?
|
||||
if not batch.data:
|
||||
@@ -476,42 +558,75 @@ def calc_session_count(batch: Batch) -> int:
|
||||
return len(data_product) * batch.runs
|
||||
|
||||
|
||||
class SessionQueueValueToInsert(NamedTuple):
|
||||
"""A tuple of values to insert into the session_queue table"""
|
||||
|
||||
# Careful with the ordering of this - it must match the insert statement
|
||||
queue_id: str # queue_id
|
||||
session: str # session json
|
||||
session_id: str # session_id
|
||||
batch_id: str # batch_id
|
||||
field_values: Optional[str] # field_values json
|
||||
priority: int # priority
|
||||
workflow: Optional[str] # workflow json
|
||||
origin: str | None
|
||||
destination: str | None
|
||||
retried_from_item_id: int | None = None
|
||||
ValueToInsertTuple: TypeAlias = tuple[
|
||||
str, # queue_id
|
||||
str, # session (as stringified JSON)
|
||||
str, # session_id
|
||||
str, # batch_id
|
||||
str | None, # field_values (optional, as stringified JSON)
|
||||
int, # priority
|
||||
str | None, # workflow (optional, as stringified JSON)
|
||||
str | None, # origin (optional)
|
||||
str | None, # destination (optional)
|
||||
int | None, # retried_from_item_id (optional, this is always None for new items)
|
||||
]
|
||||
"""A type alias for the tuple of values to insert into the session queue table."""
|
||||
|
||||
|
||||
ValuesToInsert: TypeAlias = list[SessionQueueValueToInsert]
|
||||
def prepare_values_to_insert(
|
||||
queue_id: str, batch: Batch, priority: int, max_new_queue_items: int
|
||||
) -> list[ValueToInsertTuple]:
|
||||
"""
|
||||
Given a batch, prepare the values to insert into the session queue table. The list of tuples can be used with an
|
||||
`executemany` statement to insert multiple rows at once.
|
||||
|
||||
Args:
|
||||
queue_id: The ID of the queue to insert the items into
|
||||
batch: The batch to prepare the values for
|
||||
priority: The priority of the queue items
|
||||
max_new_queue_items: The maximum number of queue items to insert
|
||||
|
||||
def prepare_values_to_insert(queue_id: str, batch: Batch, priority: int, max_new_queue_items: int) -> ValuesToInsert:
|
||||
values_to_insert: ValuesToInsert = []
|
||||
for session, field_values, workflow in create_session_nfv_tuples(batch, max_new_queue_items):
|
||||
# sessions must have unique id
|
||||
session.id = uuid_string()
|
||||
Returns:
|
||||
A list of tuples to insert into the session queue table. Each tuple contains the following values:
|
||||
- queue_id
|
||||
- session (as stringified JSON)
|
||||
- session_id
|
||||
- batch_id
|
||||
- field_values (optional, as stringified JSON)
|
||||
- priority
|
||||
- workflow (optional, as stringified JSON)
|
||||
- origin (optional)
|
||||
- destination (optional)
|
||||
- retried_from_item_id (optional, this is always None for new items)
|
||||
"""
|
||||
|
||||
# A tuple is a fast and memory-efficient way to store the values to insert. Previously, we used a NamedTuple, but
|
||||
# measured a ~5% performance improvement by using a normal tuple instead. For very large batches (10k+ items), the
|
||||
# this difference becomes noticeable.
|
||||
#
|
||||
# So, despite the inferior DX with normal tuples, we use one here for performance reasons.
|
||||
|
||||
values_to_insert: list[ValueToInsertTuple] = []
|
||||
|
||||
# pydantic's to_jsonable_python handles serialization of any python object, including sets, which json.dumps does
|
||||
# not support by default. Apparently there are sets somewhere in the graph.
|
||||
|
||||
# The same workflow is used for all sessions in the batch - serialize it once
|
||||
workflow_json = json.dumps(batch.workflow, default=to_jsonable_python) if batch.workflow else None
|
||||
|
||||
for session_id, session_json, field_values_json in create_session_nfv_tuples(batch, max_new_queue_items):
|
||||
values_to_insert.append(
|
||||
SessionQueueValueToInsert(
|
||||
queue_id=queue_id,
|
||||
session=session.model_dump_json(warnings=False, exclude_none=True), # as json
|
||||
session_id=session.id,
|
||||
batch_id=batch.batch_id,
|
||||
# must use pydantic_encoder bc field_values is a list of models
|
||||
field_values=json.dumps(field_values, default=to_jsonable_python) if field_values else None, # as json
|
||||
priority=priority,
|
||||
workflow=json.dumps(workflow, default=to_jsonable_python) if workflow else None, # as json
|
||||
origin=batch.origin,
|
||||
destination=batch.destination,
|
||||
(
|
||||
queue_id,
|
||||
session_json,
|
||||
session_id,
|
||||
batch.batch_id,
|
||||
field_values_json,
|
||||
priority,
|
||||
workflow_json,
|
||||
batch.origin,
|
||||
batch.destination,
|
||||
None,
|
||||
)
|
||||
)
|
||||
return values_to_insert
|
||||
|
||||
@@ -27,7 +27,6 @@ from invokeai.app.services.session_queue.session_queue_common import (
|
||||
SessionQueueItemDTO,
|
||||
SessionQueueItemNotFoundError,
|
||||
SessionQueueStatus,
|
||||
SessionQueueValueToInsert,
|
||||
calc_session_count,
|
||||
prepare_values_to_insert,
|
||||
)
|
||||
@@ -772,7 +771,7 @@ class SqliteSessionQueue(SessionQueueBase):
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
|
||||
values_to_insert: list[SessionQueueValueToInsert] = []
|
||||
values_to_insert: list[tuple] = []
|
||||
retried_item_ids: list[int] = []
|
||||
|
||||
for item_id in item_ids:
|
||||
@@ -798,17 +797,17 @@ class SqliteSessionQueue(SessionQueueBase):
|
||||
else queue_item.item_id
|
||||
)
|
||||
|
||||
value_to_insert = SessionQueueValueToInsert(
|
||||
queue_id=queue_item.queue_id,
|
||||
batch_id=queue_item.batch_id,
|
||||
destination=queue_item.destination,
|
||||
field_values=field_values_json,
|
||||
origin=queue_item.origin,
|
||||
priority=queue_item.priority,
|
||||
workflow=workflow_json,
|
||||
session=cloned_session_json,
|
||||
session_id=cloned_session.id,
|
||||
retried_from_item_id=retried_from_item_id,
|
||||
value_to_insert = (
|
||||
queue_item.queue_id,
|
||||
queue_item.batch_id,
|
||||
queue_item.destination,
|
||||
field_values_json,
|
||||
queue_item.origin,
|
||||
queue_item.priority,
|
||||
workflow_json,
|
||||
cloned_session_json,
|
||||
cloned_session.id,
|
||||
retried_from_item_id,
|
||||
)
|
||||
values_to_insert.append(value_to_insert)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from torch import Tensor
|
||||
|
||||
from invokeai.app.invocations.constants import IMAGE_MODES
|
||||
from invokeai.app.invocations.fields import MetadataField, WithBoard, WithMetadata
|
||||
from invokeai.app.services.board_records.board_records_common import BoardRecordOrderBy
|
||||
from invokeai.app.services.boards.boards_common import BoardDTO
|
||||
from invokeai.app.services.config.config_default import InvokeAIAppConfig
|
||||
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
|
||||
@@ -16,6 +17,7 @@ from invokeai.app.services.images.images_common import ImageDTO
|
||||
from invokeai.app.services.invocation_services import InvocationServices
|
||||
from invokeai.app.services.model_records.model_records_base import UnknownModelException
|
||||
from invokeai.app.services.session_processor.session_processor_common import ProgressImage
|
||||
from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection
|
||||
from invokeai.app.util.step_callback import flux_step_callback, stable_diffusion_step_callback
|
||||
from invokeai.backend.model_manager.config import (
|
||||
AnyModel,
|
||||
@@ -102,7 +104,9 @@ class BoardsInterface(InvocationContextInterface):
|
||||
Returns:
|
||||
A list of all boards.
|
||||
"""
|
||||
return self._services.boards.get_all()
|
||||
return self._services.boards.get_all(
|
||||
order_by=BoardRecordOrderBy.CreatedAt, direction=SQLiteDirection.Descending
|
||||
)
|
||||
|
||||
def add_image_to_board(self, board_id: str, image_name: str) -> None:
|
||||
"""Adds an image to a board.
|
||||
@@ -122,7 +126,11 @@ class BoardsInterface(InvocationContextInterface):
|
||||
Returns:
|
||||
A list of all image names for the board.
|
||||
"""
|
||||
return self._services.board_images.get_all_board_image_names_for_board(board_id)
|
||||
return self._services.board_images.get_all_board_image_names_for_board(
|
||||
board_id,
|
||||
categories=None,
|
||||
is_intermediate=None,
|
||||
)
|
||||
|
||||
|
||||
class LoggerInterface(InvocationContextInterface):
|
||||
@@ -283,7 +291,7 @@ class ImagesInterface(InvocationContextInterface):
|
||||
Returns:
|
||||
The local path of the image or thumbnail.
|
||||
"""
|
||||
return self._services.images.get_path(image_name, thumbnail)
|
||||
return Path(self._services.images.get_path(image_name, thumbnail))
|
||||
|
||||
|
||||
class TensorsInterface(InvocationContextInterface):
|
||||
|
||||
@@ -75,6 +75,8 @@
|
||||
"idb-keyval": "^6.2.1",
|
||||
"jsondiffpatch": "^0.6.0",
|
||||
"konva": "^9.3.15",
|
||||
"linkify-react": "^4.2.0",
|
||||
"linkifyjs": "^4.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lru-cache": "^11.0.1",
|
||||
"mtwist": "^1.0.2",
|
||||
|
||||
20
invokeai/frontend/web/pnpm-lock.yaml
generated
20
invokeai/frontend/web/pnpm-lock.yaml
generated
@@ -74,6 +74,12 @@ dependencies:
|
||||
konva:
|
||||
specifier: ^9.3.15
|
||||
version: 9.3.15
|
||||
linkify-react:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0(linkifyjs@4.2.0)(react@18.3.1)
|
||||
linkifyjs:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
lodash-es:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
@@ -6714,6 +6720,20 @@ packages:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
dev: false
|
||||
|
||||
/linkify-react@4.2.0(linkifyjs@4.2.0)(react@18.3.1):
|
||||
resolution: {integrity: sha512-dIcDGo+n4FP2FPIHDcqB7cUE+omkcEgQJpc7sNNP4+XZ9FUhFAkKjGnHMzsZM+B4yF93sK166z9K5cKTe/JpzA==}
|
||||
peerDependencies:
|
||||
linkifyjs: ^4.0.0
|
||||
react: '>= 15.0.0'
|
||||
dependencies:
|
||||
linkifyjs: 4.2.0
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/linkifyjs@4.2.0:
|
||||
resolution: {integrity: sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==}
|
||||
dev: false
|
||||
|
||||
/liqe@3.8.0:
|
||||
resolution: {integrity: sha512-cZ1rDx4XzxONBTskSPBp7/KwJ9qbUdF8EPnY4VjKXwHF1Krz9lgnlMTh1G7kd+KtPYvUte1mhuZeQSnk7KiSBg==}
|
||||
engines: {node: '>=12.0'}
|
||||
|
||||
@@ -2301,12 +2301,8 @@
|
||||
"whatsNew": {
|
||||
"whatsNewInInvoke": "What's New in Invoke",
|
||||
"items": [
|
||||
"Improved VRAM setting defaults",
|
||||
"On-demand model cache clearing",
|
||||
"Expanded FLUX LoRA compatibility",
|
||||
"Canvas Adjust Image filter",
|
||||
"Cancel all but current queue item",
|
||||
"Copy from and paste to Canvas"
|
||||
"Workflow Editor: New drag-and-drop form builder for easier workflow creation.",
|
||||
"Other improvements: Faster batch queuing, better upscaling, improved color picker, and metadata nodes."
|
||||
],
|
||||
"readReleaseNotes": "Read Release Notes",
|
||||
"watchRecentReleaseVideos": "Watch Recent Release Videos",
|
||||
|
||||
@@ -98,7 +98,22 @@
|
||||
"close": "Fermer",
|
||||
"clipboard": "Presse-papier",
|
||||
"loadingModel": "Chargement du modèle",
|
||||
"generating": "En Génération"
|
||||
"generating": "En Génération",
|
||||
"warnings": "Alertes",
|
||||
"layout": "Disposition",
|
||||
"row": "Ligne",
|
||||
"column": "Colonne",
|
||||
"start": "Commencer",
|
||||
"board": "Planche",
|
||||
"count": "Quantité",
|
||||
"step": "Étape",
|
||||
"end": "Fin",
|
||||
"min": "Min",
|
||||
"max": "Max",
|
||||
"values": "Valeurs",
|
||||
"resetToDefaults": "Réinitialiser par défaut",
|
||||
"seed": "Graine",
|
||||
"combinatorial": "Combinatoire"
|
||||
},
|
||||
"gallery": {
|
||||
"galleryImageSize": "Taille de l'image",
|
||||
@@ -165,7 +180,9 @@
|
||||
"imagesSettings": "Paramètres des images de la galerie",
|
||||
"assetsTab": "Fichiers que vous avez importés pour vos projets.",
|
||||
"imagesTab": "Images que vous avez créées et enregistrées dans Invoke.",
|
||||
"boardsSettings": "Paramètres des planches"
|
||||
"boardsSettings": "Paramètres des planches",
|
||||
"assets": "Ressources",
|
||||
"images": "Images"
|
||||
},
|
||||
"modelManager": {
|
||||
"modelManager": "Gestionnaire de modèle",
|
||||
@@ -289,7 +306,7 @@
|
||||
"usingDefaultSettings": "Utilisation des paramètres par défaut du modèle",
|
||||
"defaultSettingsOutOfSync": "Certain paramètres ne correspondent pas aux valeurs par défaut du modèle :",
|
||||
"restoreDefaultSettings": "Cliquez pour utiliser les paramètres par défaut du modèle.",
|
||||
"hfForbiddenErrorMessage": "Nous vous recommandons de visiter la page du modèle sur HuggingFace.com. Le propriétaire peut exiger l'acceptation des conditions pour pouvoir télécharger.",
|
||||
"hfForbiddenErrorMessage": "Nous vous recommandons de visiter la page du modèle. Le propriétaire peut exiger l'acceptation des conditions pour pouvoir télécharger.",
|
||||
"hfTokenRequired": "Vous essayez de télécharger un modèle qui nécessite un token HuggingFace valide.",
|
||||
"clipLEmbed": "CLIP-L Embed",
|
||||
"hfTokenSaved": "Token HF enregistré",
|
||||
@@ -303,7 +320,10 @@
|
||||
"hfForbidden": "Vous n'avez pas accès à ce modèle HF.",
|
||||
"hfTokenInvalidErrorMessage2": "Mettre à jour dans le ",
|
||||
"controlLora": "Controle LoRA",
|
||||
"urlUnauthorizedErrorMessage2": "Découvrir comment ici."
|
||||
"urlUnauthorizedErrorMessage2": "Découvrir comment ici.",
|
||||
"urlUnauthorizedErrorMessage": "Vous devrez peut-être configurer un jeton API pour accéder à ce modèle.",
|
||||
"urlForbidden": "Vous n'avez pas accès à ce modèle",
|
||||
"urlForbiddenErrorMessage": "Vous devrez peut-être demander l'autorisation du site qui distribue le modèle."
|
||||
},
|
||||
"parameters": {
|
||||
"images": "Images",
|
||||
@@ -345,19 +365,31 @@
|
||||
"fluxModelIncompatibleBboxHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), la hauteur de la bounding box est {{height}}",
|
||||
"fluxModelIncompatibleScaledBboxHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), la hauteur de la bounding box est {{height}}",
|
||||
"noFLUXVAEModelSelected": "Aucun modèle VAE sélectionné pour la génération FLUX",
|
||||
"canvasIsTransforming": "La Toile se transforme",
|
||||
"canvasIsRasterizing": "La Toile se rastérise",
|
||||
"canvasIsTransforming": "La Toile est occupée (en transformation)",
|
||||
"canvasIsRasterizing": "La Toile est occupée (en rastérisation)",
|
||||
"noCLIPEmbedModelSelected": "Aucun modèle CLIP Embed sélectionné pour la génération FLUX",
|
||||
"canvasIsFiltering": "La Toile est en train de filtrer",
|
||||
"canvasIsFiltering": "La Toile est occupée (en filtration)",
|
||||
"fluxModelIncompatibleBboxWidth": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), la largeur de la bounding box est {{width}}",
|
||||
"noT5EncoderModelSelected": "Aucun modèle T5 Encoder sélectionné pour la génération FLUX",
|
||||
"fluxModelIncompatibleScaledBboxWidth": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), la largeur de la bounding box mise à l'échelle est {{width}}",
|
||||
"canvasIsCompositing": "La toile est en train de composer",
|
||||
"collectionTooFewItems": "{{nodeLabel}} -> {{fieldLabel}} : trop peu d'éléments, minimum {{minItems}}",
|
||||
"collectionTooManyItems": "{{nodeLabel}} -> {{fieldLabel}} : trop d'éléments, maximum {{maxItems}}",
|
||||
"canvasIsCompositing": "La Toile est occupée (en composition)",
|
||||
"collectionTooFewItems": "trop peu d'éléments, minimum {{minItems}}",
|
||||
"collectionTooManyItems": "trop d'éléments, maximum {{maxItems}}",
|
||||
"canvasIsSelectingObject": "La toile est occupée (sélection d'objet)",
|
||||
"emptyBatches": "lots vides",
|
||||
"batchNodeNotConnected": "Noeud de lots non connecté : {{label}}"
|
||||
"batchNodeNotConnected": "Noeud de lots non connecté : {{label}}",
|
||||
"fluxModelMultipleControlLoRAs": "Vous ne pouvez utiliser qu'un seul Control LoRA à la fois",
|
||||
"collectionNumberLTMin": "{{value}} < {{minimum}} (incl. min)",
|
||||
"collectionNumberGTMax": "{{value}} > {{maximum}} (incl. max)",
|
||||
"collectionNumberGTExclusiveMax": "{{value}} >= {{exclusiveMaximum}} (max exc)",
|
||||
"batchNodeEmptyCollection": "Certains nœuds de lot ont des collections vides",
|
||||
"batchNodeCollectionSizeMismatch": "Non-concordance de taille de collection sur le lot {{batchGroupId}}",
|
||||
"collectionStringTooLong": "trop long, max {{maxLength}}",
|
||||
"collectionNumberNotMultipleOf": "{{value}} n'est pas un multiple de {{multipleOf}}",
|
||||
"collectionEmpty": "collection vide",
|
||||
"collectionStringTooShort": "trop court, min {{minLength}}",
|
||||
"collectionNumberLTExclusiveMin": "{{value}} <= {{exclusiveMinimum}} (min exc)",
|
||||
"batchNodeCollectionSizeMismatchNoGroupId": "Taille de collection de groupe par lot non conforme"
|
||||
},
|
||||
"negativePromptPlaceholder": "Prompt Négatif",
|
||||
"positivePromptPlaceholder": "Prompt Positif",
|
||||
@@ -501,7 +533,13 @@
|
||||
"uploadFailedInvalidUploadDesc_withCount_one": "Doit être au maximum une image PNG ou JPEG.",
|
||||
"uploadFailedInvalidUploadDesc_withCount_many": "Doit être au maximum {{count}} images PNG ou JPEG.",
|
||||
"uploadFailedInvalidUploadDesc_withCount_other": "Doit être au maximum {{count}} images PNG ou JPEG.",
|
||||
"addedToUncategorized": "Ajouté aux ressources de la planche $t(boards.uncategorized)"
|
||||
"addedToUncategorized": "Ajouté aux ressources de la planche $t(boards.uncategorized)",
|
||||
"pasteSuccess": "Collé à {{destination}}",
|
||||
"pasteFailed": "Échec du collage",
|
||||
"outOfMemoryErrorDescLocal": "Suivez notre <LinkComponent>guide Low VRAM</LinkComponent> pour réduire les OOMs.",
|
||||
"unableToCopy": "Incapable de Copier",
|
||||
"unableToCopyDesc": "Votre navigateur ne prend pas en charge l'accès au presse-papiers. Les utilisateurs de Firefox peuvent peut-être résoudre ce problème en suivant ",
|
||||
"unableToCopyDesc_theseSteps": "ces étapes"
|
||||
},
|
||||
"accessibility": {
|
||||
"uploadImage": "Importer une image",
|
||||
@@ -659,7 +697,14 @@
|
||||
"iterations_many": "Itérations",
|
||||
"iterations_other": "Itérations",
|
||||
"back": "fin",
|
||||
"batchSize": "Taille de lot"
|
||||
"batchSize": "Taille de lot",
|
||||
"retryFailed": "Problème de nouvelle tentative de l'élément",
|
||||
"retrySucceeded": "Élément Retenté",
|
||||
"retryItem": "Réessayer l'élement",
|
||||
"cancelAllExceptCurrentQueueItemAlertDialog": "Annuler tous les éléments de la file d'attente, sauf celui en cours, arrêtera les éléments en attente mais permettra à celui en cours de se terminer.",
|
||||
"cancelAllExceptCurrentQueueItemAlertDialog2": "Êtes-vous sûr de vouloir annuler tous les éléments en attente dans la file d'attente ?",
|
||||
"cancelAllExceptCurrentTooltip": "Annuler tout sauf l'élément actuel",
|
||||
"confirm": "Confirmer"
|
||||
},
|
||||
"prompt": {
|
||||
"noMatchingTriggers": "Pas de déclancheurs correspondants",
|
||||
@@ -1031,7 +1076,9 @@
|
||||
"controlNetWeight": {
|
||||
"heading": "Poids",
|
||||
"paragraphs": [
|
||||
"Poids du Control Adapter. Un poids plus élevé aura un impact plus important sur l'image finale."
|
||||
"Poids du Control Adapter. Un poids plus élevé aura un impact plus important sur l'image finale.",
|
||||
"• Poids plus élevé (.75-2) : Crée un impact plus significatif sur le résultat final.",
|
||||
"• Poids inférieur (0-.75) : Crée un impact plus faible sur le résultat final."
|
||||
]
|
||||
},
|
||||
"compositingMaskAdjustments": {
|
||||
@@ -1076,8 +1123,9 @@
|
||||
"controlNetBeginEnd": {
|
||||
"heading": "Pourcentage de début / de fin d'étape",
|
||||
"paragraphs": [
|
||||
"La partie du processus de débruitage à laquelle le Control Adapter sera appliqué.",
|
||||
"En général, les Control Adapter appliqués au début du processus guident la composition, tandis que les Control Adapter appliqués à la fin guident les détails."
|
||||
"Ce paramètre détérmine quelle portion du processus de débruitage (génération) utilisera cette couche comme guide.",
|
||||
"En général, les Control Adapter appliqués au début du processus guident la composition, tandis que les Control Adapter appliqués à la fin guident les détails.",
|
||||
"• Étape de fin (%): Spécifie quand arrêter d'appliquer le guide de cette couche et revenir aux guides généraux du modèle et aux autres paramètres."
|
||||
]
|
||||
},
|
||||
"controlNetControlMode": {
|
||||
@@ -1442,7 +1490,8 @@
|
||||
"showDynamicPrompts": "Afficher les Prompts dynamiques",
|
||||
"dynamicPrompts": "Prompts Dynamiques",
|
||||
"promptsPreview": "Prévisualisation des Prompts",
|
||||
"loading": "Génération des Pompts Dynamiques..."
|
||||
"loading": "Génération des Pompts Dynamiques...",
|
||||
"promptsToGenerate": "Prompts à générer"
|
||||
},
|
||||
"metadata": {
|
||||
"positivePrompt": "Prompt Positif",
|
||||
@@ -1653,7 +1702,22 @@
|
||||
"internalDesc": "Cette invocation est utilisée internalement par Invoke. En fonction des mises à jours il est possible que des changements y soit effectués ou qu'elle soit supprimé sans prévention.",
|
||||
"splitOn": "Diviser sur",
|
||||
"generatorNoValues": "vide",
|
||||
"addItem": "Ajouter un élément"
|
||||
"addItem": "Ajouter un élément",
|
||||
"specialDesc": "Cette invocation nécessite un traitement spécial dans l'application. Par exemple, les nœuds Batch sont utilisés pour mettre en file d'attente plusieurs graphes à partir d'un seul workflow.",
|
||||
"unableToUpdateNode": "La mise à jour du nœud a échoué : nœud {{node}} de type {{type}} (peut nécessiter la suppression et la recréation).",
|
||||
"deletedMissingNodeFieldFormElement": "Champ de formulaire manquant supprimé : nœud {{nodeId}} champ {{fieldName}}",
|
||||
"nodeName": "Nom du nœud",
|
||||
"description": "Description",
|
||||
"loadWorkflowDesc": "Charger le workflow ?",
|
||||
"missingSourceOrTargetNode": "Nœud source ou cible manquant",
|
||||
"generatorImagesCategory": "Catégorie",
|
||||
"generatorImagesFromBoard": "Images de la Planche",
|
||||
"missingSourceOrTargetHandle": "Manque de gestionnaire source ou cible",
|
||||
"loadingTemplates": "Chargement de {{name}}",
|
||||
"loadWorkflowDesc2": "Votre workflow actuel contient des modifications non enregistrées.",
|
||||
"generatorImages_one": "{{count}} image",
|
||||
"generatorImages_many": "{{count}} images",
|
||||
"generatorImages_other": "{{count}} images"
|
||||
},
|
||||
"models": {
|
||||
"noMatchingModels": "Aucun modèle correspondant",
|
||||
@@ -1712,13 +1776,41 @@
|
||||
"deleteWorkflow2": "Êtes-vous sûr de vouloir supprimer ce Workflow ? Cette action ne peut pas être annulé.",
|
||||
"download": "Télécharger",
|
||||
"copyShareLinkForWorkflow": "Copier le lien de partage pour le Workflow",
|
||||
"delete": "Supprimer"
|
||||
"delete": "Supprimer",
|
||||
"builder": {
|
||||
"component": "Composant",
|
||||
"numberInput": "Entrée de nombre",
|
||||
"slider": "Curseur",
|
||||
"both": "Les deux",
|
||||
"singleLine": "Ligne unique",
|
||||
"multiLine": "Multi Ligne",
|
||||
"headingPlaceholder": "En-tête vide",
|
||||
"emptyRootPlaceholderEditMode": "Faites glisser un élément de formulaire ou un champ de nœud ici pour commencer.",
|
||||
"emptyRootPlaceholderViewMode": "Cliquez sur Modifier pour commencer à créer un formulaire pour ce workflow.",
|
||||
"containerPlaceholder": "Conteneur Vide",
|
||||
"row": "Ligne",
|
||||
"column": "Colonne",
|
||||
"layout": "Mise en page",
|
||||
"nodeField": "Champ de nœud",
|
||||
"zoomToNode": "Zoomer sur le nœud",
|
||||
"nodeFieldTooltip": "Pour ajouter un champ de nœud, cliquez sur le petit bouton plus sur le champ dans l'Éditeur de Workflow, ou faites glisser le champ par son nom dans le formulaire.",
|
||||
"addToForm": "Ajouter au formulaire",
|
||||
"label": "Étiquette",
|
||||
"textPlaceholder": "Texte vide",
|
||||
"builder": "Constructeur de Formulaire",
|
||||
"resetAllNodeFields": "Réinitialiser tous les champs de nœud",
|
||||
"deleteAllElements": "Supprimer tous les éléments de formulaire",
|
||||
"workflowBuilderAlphaWarning": "Le constructeur de workflow est actuellement en version alpha. Il peut y avoir des changements majeurs avant la version stable.",
|
||||
"showDescription": "Afficher la description"
|
||||
},
|
||||
"openLibrary": "Ouvrir la Bibliothèque"
|
||||
},
|
||||
"whatsNew": {
|
||||
"whatsNewInInvoke": "Quoi de neuf dans Invoke",
|
||||
"watchRecentReleaseVideos": "Regarder les vidéos des dernières versions",
|
||||
"items": [
|
||||
"<StrongComponent>FLUX Guidage Régional (bêta)</StrongComponent> : Notre version bêta de FLUX Guidage Régional est en ligne pour le contrôle des prompt régionaux."
|
||||
"<StrongComponent>FLUX Guidage Régional (bêta)</StrongComponent> : Notre version bêta de FLUX Guidage Régional est en ligne pour le contrôle des prompt régionaux.",
|
||||
"Autres améliorations : mise en file d'attente par lots plus rapide, meilleur redimensionnement, sélecteur de couleurs amélioré et nœuds de métadonnées."
|
||||
],
|
||||
"readReleaseNotes": "Notes de version",
|
||||
"watchUiUpdatesOverview": "Aperçu des mises à jour de l'interface utilisateur"
|
||||
@@ -1832,7 +1924,49 @@
|
||||
"cancel": "Annuler",
|
||||
"advanced": "Avancé",
|
||||
"processingLayerWith": "Calque de traitement avec le filtre {{type}}.",
|
||||
"forMoreControl": "Pour plus de contrôle, cliquez sur Avancé ci-dessous."
|
||||
"forMoreControl": "Pour plus de contrôle, cliquez sur Avancé ci-dessous.",
|
||||
"adjust_image": {
|
||||
"b": "B (LAB)",
|
||||
"blue": "Bleu (RGBA)",
|
||||
"alpha": "Alpha (RGBA)",
|
||||
"magenta": "Magenta (CMJN)",
|
||||
"yellow": "Jaune (CMJN)",
|
||||
"cb": "Cb (YCbCr)",
|
||||
"cr": "Cr (YCbCr)",
|
||||
"cyan": "Cyan (CMJN)",
|
||||
"label": "Ajuster l'image",
|
||||
"description": "Ajuste le canal sélectionné d'une image.",
|
||||
"channel": "Canal",
|
||||
"value_setting": "Valeur",
|
||||
"scale_values": "Valeurs d'échelle",
|
||||
"red": "Rouge (RGBA)",
|
||||
"green": "Vert (RGBA)",
|
||||
"black": "Noir (CMJN)",
|
||||
"hue": "Teinte (HSV)",
|
||||
"saturation": "Saturation (HSV)",
|
||||
"value": "Valeur (HSV)",
|
||||
"luminosity": "Luminosité (LAB)",
|
||||
"a": "A (LAB)",
|
||||
"y": "Y (YCbCr)"
|
||||
},
|
||||
"img_blur": {
|
||||
"label": "Flou de l'image",
|
||||
"blur_type": "Type de flou",
|
||||
"box_type": "Boîte",
|
||||
"description": "Floute la couche sélectionnée.",
|
||||
"blur_radius": "Rayon",
|
||||
"gaussian_type": "Gaussien"
|
||||
},
|
||||
"img_noise": {
|
||||
"label": "Image de bruit",
|
||||
"description": "Ajoute du bruit à la couche sélectionnée.",
|
||||
"gaussian_type": "Gaussien",
|
||||
"size": "Taille du bruit",
|
||||
"noise_amount": "Quantité",
|
||||
"noise_type": "Type de bruit",
|
||||
"salt_and_pepper_type": "Sel et Poivre",
|
||||
"noise_color": "Bruit coloré"
|
||||
}
|
||||
},
|
||||
"canvasContextMenu": {
|
||||
"saveToGalleryGroup": "Enregistrer dans la galerie",
|
||||
@@ -1846,7 +1980,10 @@
|
||||
"newGlobalReferenceImage": "Nouvelle image de référence globale",
|
||||
"newControlLayer": "Nouveau couche de contrôle",
|
||||
"newInpaintMask": "Nouveau Masque Inpaint",
|
||||
"newRegionalGuidance": "Nouveau Guide Régional"
|
||||
"newRegionalGuidance": "Nouveau Guide Régional",
|
||||
"copyToClipboard": "Copier dans le presse-papiers",
|
||||
"copyBboxToClipboard": "Copier Bbox dans le presse-papiers",
|
||||
"copyCanvasToClipboard": "Copier la Toile dans le presse-papiers"
|
||||
},
|
||||
"bookmark": "Marque-page pour Changement Rapide",
|
||||
"saveLayerToAssets": "Enregistrer la couche dans les ressources",
|
||||
@@ -2012,7 +2149,10 @@
|
||||
"ipAdapterMethod": "Méthode d'IP Adapter",
|
||||
"full": "Complet",
|
||||
"style": "Style uniquement",
|
||||
"composition": "Composition uniquement"
|
||||
"composition": "Composition uniquement",
|
||||
"fullDesc": "Applique le style visuel (couleurs, textures) et la composition (mise en page, structure).",
|
||||
"styleDesc": "Applique un style visuel (couleurs, textures) sans tenir compte de sa mise en page.",
|
||||
"compositionDesc": "Réplique la mise en page et la structure tout en ignorant le style de la référence."
|
||||
},
|
||||
"fitBboxToLayers": "Ajuster la bounding box aux calques",
|
||||
"regionIsEmpty": "La zone sélectionnée est vide",
|
||||
@@ -2095,7 +2235,40 @@
|
||||
"asRasterLayerResize": "En tant que $t(controlLayers.rasterLayer) (Redimensionner)",
|
||||
"asControlLayer": "En tant que $t(controlLayers.controlLayer)",
|
||||
"asControlLayerResize": "En $t(controlLayers.controlLayer) (Redimensionner)",
|
||||
"newSession": "Nouvelle session"
|
||||
"newSession": "Nouvelle session",
|
||||
"warnings": {
|
||||
"controlAdapterIncompatibleBaseModel": "modèle de base de la couche de contrôle incompatible",
|
||||
"controlAdapterNoControl": "aucun contrôle sélectionné/dessiné",
|
||||
"rgNoPromptsOrIPAdapters": "pas de textes d'instructions ni d'images de référence",
|
||||
"rgAutoNegativeNotSupported": "Auto-négatif non pris en charge pour le modèle de base sélectionné",
|
||||
"rgNoRegion": "aucune région dessinée",
|
||||
"ipAdapterNoModelSelected": "aucun modèle d'image de référence sélectionné",
|
||||
"rgReferenceImagesNotSupported": "Les images de référence régionales ne sont pas prises en charge pour le modèle de base sélectionné",
|
||||
"problemsFound": "Problèmes trouvés",
|
||||
"unsupportedModel": "couche non prise en charge pour le modèle de base sélectionné",
|
||||
"rgNegativePromptNotSupported": "Prompt négatif non pris en charge pour le modèle de base sélectionné",
|
||||
"ipAdapterIncompatibleBaseModel": "modèle de base d'image de référence incompatible",
|
||||
"controlAdapterNoModelSelected": "aucun modèle de couche de contrôle sélectionné",
|
||||
"ipAdapterNoImageSelected": "Aucune image de référence sélectionnée."
|
||||
},
|
||||
"pasteTo": "Coller vers",
|
||||
"pasteToAssets": "Ressources",
|
||||
"pasteToAssetsDesc": "Coller dans les ressources",
|
||||
"pasteToBbox": "Bbox",
|
||||
"regionCopiedToClipboard": "{{region}} Copié dans le presse-papiers",
|
||||
"copyRegionError": "Erreur de copie {{region}}",
|
||||
"pasteToCanvas": "Toile",
|
||||
"errors": {
|
||||
"unableToFindImage": "Impossible de trouver l'image",
|
||||
"unableToLoadImage": "Impossible de charger l'image"
|
||||
},
|
||||
"referenceImageRegional": "Image de référence (régionale)",
|
||||
"pasteToBboxDesc": "Nouvelle couche (dans Bbox)",
|
||||
"pasteToCanvasDesc": "Nouvelle couche (dans la Toile)",
|
||||
"useImage": "Utiliser l'image",
|
||||
"pastedTo": "Collé à {{destination}}",
|
||||
"referenceImageEmptyState": "<UploadButton>Séléctionner une image</UploadButton> ou faites glisser une image depuis la <GalleryButton>galerie</GalleryButton> sur cette couche pour commencer.",
|
||||
"referenceImageGlobal": "Image de référence (Globale)"
|
||||
},
|
||||
"upscaling": {
|
||||
"exceedsMaxSizeDetails": "La limite maximale d'agrandissement est de {{maxUpscaleDimension}}x{{maxUpscaleDimension}} pixels. Veuillez essayer une image plus petite ou réduire votre sélection d'échelle.",
|
||||
@@ -2175,7 +2348,8 @@
|
||||
"queue": "File d'attente",
|
||||
"events": "Événements",
|
||||
"metadata": "Métadonnées",
|
||||
"gallery": "Galerie"
|
||||
"gallery": "Galerie",
|
||||
"dnd": "Glisser et déposer"
|
||||
},
|
||||
"logLevel": {
|
||||
"trace": "Trace",
|
||||
@@ -2192,7 +2366,8 @@
|
||||
"toGetStarted": "Pour commencer, saisissez un prompt dans la boîte et cliquez sur <StrongComponent>Invoke</StrongComponent> pour générer votre première image. Sélectionnez un template de prompt pour améliorer les résultats. Vous pouvez choisir de sauvegarder vos images directement dans la <StrongComponent>Galerie</StrongComponent> ou de les modifier sur la <StrongComponent>Toile</StrongComponent>.",
|
||||
"gettingStartedSeries": "Vous souhaitez plus de conseils ? Consultez notre <LinkComponent>Série de démarrage</LinkComponent> pour des astuces sur l'exploitation du plein potentiel de l'Invoke Studio.",
|
||||
"noModelsInstalled": "Il semble qu'aucun modèle ne soit installé",
|
||||
"toGetStartedLocal": "Pour commencer, assurez-vous de télécharger ou d'importer des modèles nécessaires pour exécuter Invoke. Ensuite, saisissez le prompt dans la boîte et cliquez sur <StrongComponent>Invoke</StrongComponent> pour générer votre première image. Sélectionnez un template de prompt pour améliorer les résultats. Vous pouvez choisir de sauvegarder vos images directement sur <StrongComponent>Galerie</StrongComponent> ou les modifier sur la <StrongComponent>Toile</StrongComponent>."
|
||||
"toGetStartedLocal": "Pour commencer, assurez-vous de télécharger ou d'importer des modèles nécessaires pour exécuter Invoke. Ensuite, saisissez le prompt dans la boîte et cliquez sur <StrongComponent>Invoke</StrongComponent> pour générer votre première image. Sélectionnez un template de prompt pour améliorer les résultats. Vous pouvez choisir de sauvegarder vos images directement sur <StrongComponent>Galerie</StrongComponent> ou les modifier sur la <StrongComponent>Toile</StrongComponent>.",
|
||||
"lowVRAMMode": "Pour de meilleures performances, suivez notre <LinkComponent>guide Low VRAM</LinkComponent>."
|
||||
},
|
||||
"upsell": {
|
||||
"shareAccess": "Partager l'accès",
|
||||
@@ -2240,7 +2415,8 @@
|
||||
"description": "Introduction à l'ajout d'images de référence et IP Adapters globaux."
|
||||
},
|
||||
"howDoIUseInpaintMasks": {
|
||||
"title": "Comment utiliser les masques d'inpainting ?"
|
||||
"title": "Comment utiliser les masques d'inpainting ?",
|
||||
"description": "Comment appliquer des masques de retourche pour la correction et la variation d'image."
|
||||
},
|
||||
"creatingYourFirstImage": {
|
||||
"title": "Créer votre première image",
|
||||
@@ -2260,5 +2436,10 @@
|
||||
"studioSessionsDesc2": "Rejoignez notre <DiscordLink /> pour participer aux sessions en direct et poser vos questions. Les sessions sont ajoutée dans la playlist la semaine suivante.",
|
||||
"supportVideos": "Vidéos d'assistance",
|
||||
"controlCanvas": "Contrôler la toile"
|
||||
},
|
||||
"modelCache": {
|
||||
"clear": "Effacer le cache du modèle",
|
||||
"clearSucceeded": "Cache du modèle effacée",
|
||||
"clearFailed": "Problème de nettoyage du cache du modèle"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2277,11 +2277,7 @@
|
||||
"watchUiUpdatesOverview": "Guarda le novità dell'interfaccia",
|
||||
"items": [
|
||||
"Impostazioni predefinite VRAM migliorate",
|
||||
"Cancellazione della cache del modello su richiesta",
|
||||
"Compatibilità estesa FLUX LoRA",
|
||||
"Filtro Regola Immagine su Tela",
|
||||
"Annulla tutto tranne l'elemento della coda corrente",
|
||||
"Copia da e incolla sulla Tela"
|
||||
"Cancellazione della cache del modello su richiesta"
|
||||
]
|
||||
},
|
||||
"system": {
|
||||
|
||||
@@ -32,29 +32,29 @@
|
||||
"learnMore": "もっと学ぶ",
|
||||
"random": "ランダム",
|
||||
"batch": "バッチマネージャー",
|
||||
"advanced": "高度な設定",
|
||||
"advanced": "高度",
|
||||
"created": "作成済",
|
||||
"green": "緑",
|
||||
"blue": "青",
|
||||
"alpha": "アルファ",
|
||||
"outpaint": "アウトペイント",
|
||||
"outpaint": "outpaint",
|
||||
"unknown": "不明",
|
||||
"updated": "更新済",
|
||||
"add": "追加",
|
||||
"ai": "AI",
|
||||
"ai": "ai",
|
||||
"copyError": "$t(gallery.copy) エラー",
|
||||
"data": "データ",
|
||||
"template": "テンプレート",
|
||||
"red": "赤",
|
||||
"or": "または",
|
||||
"checkpoint": "チェックポイント",
|
||||
"checkpoint": "Checkpoint",
|
||||
"direction": "方向",
|
||||
"simple": "シンプル",
|
||||
"save": "保存",
|
||||
"saveAs": "名前をつけて保存",
|
||||
"somethingWentWrong": "何かの問題が発生しました",
|
||||
"details": "詳細",
|
||||
"inpaint": "インペイント",
|
||||
"inpaint": "inpaint",
|
||||
"delete": "削除",
|
||||
"nextPage": "次のページ",
|
||||
"copy": "コピー",
|
||||
@@ -70,12 +70,12 @@
|
||||
"unknownError": "未知のエラー",
|
||||
"orderBy": "並び順:",
|
||||
"enabled": "有効",
|
||||
"notInstalled": "未インストール",
|
||||
"notInstalled": "未 $t(common.installed)",
|
||||
"positivePrompt": "ポジティブプロンプト",
|
||||
"negativePrompt": "ネガティブプロンプト",
|
||||
"selected": "選択済み",
|
||||
"aboutDesc": "Invokeを業務で利用する場合はマークしてください:",
|
||||
"beta": "ベータ",
|
||||
"beta": "Beta",
|
||||
"disabled": "無効",
|
||||
"editor": "エディタ",
|
||||
"safetensors": "Safetensors",
|
||||
@@ -93,7 +93,27 @@
|
||||
"reset": "リセット",
|
||||
"none": "なし",
|
||||
"new": "新規",
|
||||
"close": "閉じる"
|
||||
"close": "閉じる",
|
||||
"warnings": "警告",
|
||||
"dontShowMeThese": "次回から表示しない",
|
||||
"goTo": "移動",
|
||||
"generating": "生成中",
|
||||
"loadingModel": "モデルをロード中",
|
||||
"layout": "レイアウト",
|
||||
"step": "ステップ",
|
||||
"start": "開始",
|
||||
"count": "回数",
|
||||
"end": "終了",
|
||||
"min": "最小",
|
||||
"max": "最大",
|
||||
"values": "値",
|
||||
"resetToDefaults": "デフォルトに戻す",
|
||||
"row": "行",
|
||||
"column": "列",
|
||||
"board": "ボード",
|
||||
"seed": "シード",
|
||||
"combinatorial": "組み合わせ",
|
||||
"aboutHeading": "想像力をこの手に"
|
||||
},
|
||||
"gallery": {
|
||||
"galleryImageSize": "画像のサイズ",
|
||||
@@ -109,7 +129,7 @@
|
||||
"currentlyInUse": "この画像は現在下記の機能を使用しています:",
|
||||
"drop": "ドロップ",
|
||||
"dropOrUpload": "$t(gallery.drop) またはアップロード",
|
||||
"deleteImage_other": "画像を削除",
|
||||
"deleteImage_other": "画像 {{count}} 枚を削除",
|
||||
"deleteImagePermanent": "削除された画像は復元できません。",
|
||||
"download": "ダウンロード",
|
||||
"unableToLoad": "ギャラリーをロードできません",
|
||||
@@ -155,7 +175,12 @@
|
||||
"displayBoardSearch": "ボード検索",
|
||||
"displaySearch": "画像を検索",
|
||||
"boardsSettings": "ボード設定",
|
||||
"imagesSettings": "ギャラリー画像設定"
|
||||
"imagesSettings": "ギャラリー画像設定",
|
||||
"selectAllOnPage": "ページ上のすべてを選択",
|
||||
"images": "画像",
|
||||
"assetsTab": "プロジェクトで使用するためにアップロードされたファイル。",
|
||||
"imagesTab": "Invoke内で作成および保存された画像。",
|
||||
"assets": "アセット"
|
||||
},
|
||||
"hotkeys": {
|
||||
"searchHotkeys": "ホットキーを検索",
|
||||
@@ -180,44 +205,121 @@
|
||||
},
|
||||
"canvas": {
|
||||
"redo": {
|
||||
"title": "やり直し"
|
||||
"title": "やり直し",
|
||||
"desc": "最後のキャンバス操作をやり直します。"
|
||||
},
|
||||
"transformSelected": {
|
||||
"title": "変形"
|
||||
"title": "変形",
|
||||
"desc": "選択したレイヤーを変形します。"
|
||||
},
|
||||
"undo": {
|
||||
"title": "取り消し"
|
||||
"title": "取り消し",
|
||||
"desc": "最後のキャンバス操作を取り消します。"
|
||||
},
|
||||
"selectEraserTool": {
|
||||
"title": "消しゴムツール"
|
||||
"title": "消しゴムツール",
|
||||
"desc": "消しゴムツールを選択します。"
|
||||
},
|
||||
"cancelTransform": {
|
||||
"title": "変形をキャンセル"
|
||||
"title": "変形をキャンセル",
|
||||
"desc": "保留中の変形をキャンセルします。"
|
||||
},
|
||||
"resetSelected": {
|
||||
"title": "レイヤーをリセット"
|
||||
"title": "レイヤーをリセット",
|
||||
"desc": "選択したレイヤーをリセットします。この操作はInpaint MaskおよびRegional Guidanceにのみ適用されます。"
|
||||
},
|
||||
"applyTransform": {
|
||||
"title": "変形を適用"
|
||||
"title": "変形を適用",
|
||||
"desc": "保留中の変形を選択したレイヤーに適用します。"
|
||||
},
|
||||
"selectColorPickerTool": {
|
||||
"title": "スポイトツール"
|
||||
"title": "スポイトツール",
|
||||
"desc": "スポイトツールを選択します。"
|
||||
},
|
||||
"fitBboxToCanvas": {
|
||||
"title": "バウンディングボックスをキャンバスにフィット"
|
||||
"title": "バウンディングボックスをキャンバスにフィット",
|
||||
"desc": "バウンディングボックスがキャンバスに収まるように表示を拡大、位置調整します。"
|
||||
},
|
||||
"selectBrushTool": {
|
||||
"title": "ブラシツール"
|
||||
"title": "ブラシツール",
|
||||
"desc": "ブラシツールを選択します。"
|
||||
},
|
||||
"selectMoveTool": {
|
||||
"title": "移動ツール"
|
||||
"title": "移動ツール",
|
||||
"desc": "移動ツールを選択します。"
|
||||
},
|
||||
"selectBboxTool": {
|
||||
"title": "バウンディングボックスツール"
|
||||
"title": "バウンディングボックスツール",
|
||||
"desc": "バウンディングボックスツールを選択します。"
|
||||
},
|
||||
"title": "キャンバス",
|
||||
"fitLayersToCanvas": {
|
||||
"title": "レイヤーをキャンバスにフィット"
|
||||
"title": "レイヤーをキャンバスにフィット",
|
||||
"desc": "すべての表示レイヤーがキャンバスに収まるように表示を拡大、位置調整します。"
|
||||
},
|
||||
"setZoomTo400Percent": {
|
||||
"desc": "キャンバスのズームを400%に設定します。",
|
||||
"title": "400%にズーム"
|
||||
},
|
||||
"setZoomTo800Percent": {
|
||||
"title": "800%にズーム",
|
||||
"desc": "キャンバスのズームを800%に設定します。"
|
||||
},
|
||||
"quickSwitch": {
|
||||
"title": "レイヤーのクイックスイッチ",
|
||||
"desc": "最後に選択した2つのレイヤー間を切り替えます。レイヤーがブックマークされている場合、常にそのレイヤーと最後に選択したブックマークされていないレイヤーの間を切り替えます。"
|
||||
},
|
||||
"nextEntity": {
|
||||
"title": "次のレイヤー",
|
||||
"desc": "リスト内の次のレイヤーを選択します。"
|
||||
},
|
||||
"filterSelected": {
|
||||
"title": "フィルター",
|
||||
"desc": "選択したレイヤーをフィルターします。RasterおよびControlレイヤーにのみ適用されます。"
|
||||
},
|
||||
"prevEntity": {
|
||||
"desc": "リスト内の前のレイヤーを選択します。",
|
||||
"title": "前のレイヤー"
|
||||
},
|
||||
"setFillToWhite": {
|
||||
"title": "ツール色を白に設定",
|
||||
"desc": "現在のツールの色を白色に設定します。"
|
||||
},
|
||||
"selectViewTool": {
|
||||
"title": "表示ツール",
|
||||
"desc": "表示ツールを選択します。"
|
||||
},
|
||||
"setZoomTo100Percent": {
|
||||
"title": "100%にズーム",
|
||||
"desc": "キャンバスのズームを100%に設定します。"
|
||||
},
|
||||
"deleteSelected": {
|
||||
"desc": "選択したレイヤーを削除します。",
|
||||
"title": "レイヤーを削除"
|
||||
},
|
||||
"cancelFilter": {
|
||||
"desc": "保留中のフィルターをキャンセルします。",
|
||||
"title": "フィルターをキャンセル"
|
||||
},
|
||||
"applyFilter": {
|
||||
"title": "フィルターを適用",
|
||||
"desc": "保留中のフィルターを選択したレイヤーに適用します。"
|
||||
},
|
||||
"setZoomTo200Percent": {
|
||||
"title": "200%にズーム",
|
||||
"desc": "キャンバスのズームを200%に設定します。"
|
||||
},
|
||||
"decrementToolWidth": {
|
||||
"title": "ツール幅を縮小する",
|
||||
"desc": "選択中のブラシまたは消しゴムツールの幅を減少させます。"
|
||||
},
|
||||
"incrementToolWidth": {
|
||||
"desc": "選択中のブラシまたは消しゴムツールの幅を増加させます。",
|
||||
"title": "ツール幅を増加する"
|
||||
},
|
||||
"selectRectTool": {
|
||||
"title": "矩形ツール",
|
||||
"desc": "矩形ツールを選択します。"
|
||||
}
|
||||
},
|
||||
"workflows": {
|
||||
@@ -226,6 +328,13 @@
|
||||
},
|
||||
"redo": {
|
||||
"title": "やり直し"
|
||||
},
|
||||
"title": "ワークフロー",
|
||||
"pasteSelection": {
|
||||
"title": "ペースト"
|
||||
},
|
||||
"copySelection": {
|
||||
"title": "コピー"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
@@ -235,16 +344,62 @@
|
||||
},
|
||||
"title": "アプリケーション",
|
||||
"invoke": {
|
||||
"title": "Invoke"
|
||||
"title": "生成",
|
||||
"desc": "生成をキューに追加し、キューの末尾に加えます。"
|
||||
},
|
||||
"cancelQueueItem": {
|
||||
"title": "キャンセル"
|
||||
"title": "キャンセル",
|
||||
"desc": "現在処理中のキュー項目をキャンセルします。"
|
||||
},
|
||||
"clearQueue": {
|
||||
"title": "キューをクリア"
|
||||
"title": "キューをクリア",
|
||||
"desc": "すべてのキュー項目をキャンセルして消去します。"
|
||||
},
|
||||
"selectCanvasTab": {
|
||||
"desc": "キャンバスタブを選択します。",
|
||||
"title": "キャンバスタブを選択"
|
||||
},
|
||||
"selectUpscalingTab": {
|
||||
"desc": "アップスケーリングタブを選択します。",
|
||||
"title": "アップスケーリングタブを選択"
|
||||
},
|
||||
"toggleRightPanel": {
|
||||
"desc": "右パネルを表示または非表示。",
|
||||
"title": "右パネルをトグル"
|
||||
},
|
||||
"selectModelsTab": {
|
||||
"title": "モデルタブを選択",
|
||||
"desc": "モデルタブを選択します。"
|
||||
},
|
||||
"invokeFront": {
|
||||
"desc": "生成をキューに追加し、キューの先頭に加えます。",
|
||||
"title": "生成(先頭)"
|
||||
},
|
||||
"resetPanelLayout": {
|
||||
"title": "パネルレイアウトをリセット",
|
||||
"desc": "左パネルと右パネルをデフォルトのサイズとレイアウトにリセットします。"
|
||||
},
|
||||
"togglePanels": {
|
||||
"desc": "左パネルと右パネルを合わせて表示または非表示。",
|
||||
"title": "パネルをトグル"
|
||||
},
|
||||
"selectWorkflowsTab": {
|
||||
"desc": "ワークフロータブを選択します。",
|
||||
"title": "ワークフロータブを選択"
|
||||
},
|
||||
"selectQueueTab": {
|
||||
"title": "キュータブを選択",
|
||||
"desc": "キュータブを選択します。"
|
||||
},
|
||||
"focusPrompt": {
|
||||
"title": "プロンプトにフォーカス",
|
||||
"desc": "カーソルをポジティブプロンプト欄に移動します。"
|
||||
}
|
||||
},
|
||||
"hotkeys": "ホットキー"
|
||||
"hotkeys": "ホットキー",
|
||||
"gallery": {
|
||||
"title": "ギャラリー"
|
||||
}
|
||||
},
|
||||
"modelManager": {
|
||||
"modelManager": "モデルマネージャ",
|
||||
@@ -255,13 +410,13 @@
|
||||
"name": "名前",
|
||||
"description": "概要",
|
||||
"config": "コンフィグ",
|
||||
"repo_id": "Repo ID",
|
||||
"repo_id": "リポジトリID",
|
||||
"width": "幅",
|
||||
"height": "高さ",
|
||||
"addModel": "モデルを追加",
|
||||
"availableModels": "モデルを有効化",
|
||||
"search": "検索",
|
||||
"load": "Load",
|
||||
"load": "ロード",
|
||||
"active": "active",
|
||||
"selected": "選択済",
|
||||
"delete": "削除",
|
||||
@@ -281,7 +436,7 @@
|
||||
"modelConverted": "モデル変換が完了しました",
|
||||
"predictionType": "予測タイプ(SD 2.x モデルおよび一部のSD 1.x モデル用)",
|
||||
"selectModel": "モデルを選択",
|
||||
"advanced": "高度な設定",
|
||||
"advanced": "高度",
|
||||
"modelDeleted": "モデルが削除されました",
|
||||
"convertToDiffusersHelpText2": "このプロセスでは、モデルマネージャーのエントリーを同じモデルのディフューザーバージョンに置き換えます。",
|
||||
"modelUpdateFailed": "モデル更新が失敗しました",
|
||||
@@ -294,7 +449,20 @@
|
||||
"convertToDiffusersHelpText4": "これは一回限りのプロセスです。コンピュータの仕様によっては、約30秒から60秒かかる可能性があります。",
|
||||
"cancel": "キャンセル",
|
||||
"uploadImage": "画像をアップロード",
|
||||
"addModels": "モデルを追加"
|
||||
"addModels": "モデルを追加",
|
||||
"modelName": "モデル名",
|
||||
"source": "ソース",
|
||||
"path": "パス",
|
||||
"modelSettings": "モデル設定",
|
||||
"vae": "VAE",
|
||||
"huggingFace": "HuggingFace",
|
||||
"huggingFaceRepoID": "HuggingFace リポジトリID",
|
||||
"metadata": "メタデータ",
|
||||
"loraModels": "LoRA",
|
||||
"edit": "編集",
|
||||
"install": "インストール",
|
||||
"huggingFacePlaceholder": "owner/model-name",
|
||||
"variant": "Variant"
|
||||
},
|
||||
"parameters": {
|
||||
"images": "画像",
|
||||
@@ -305,7 +473,7 @@
|
||||
"shuffle": "シャッフル",
|
||||
"strength": "強度",
|
||||
"upscaling": "アップスケーリング",
|
||||
"scale": "Scale",
|
||||
"scale": "スケール",
|
||||
"scaleBeforeProcessing": "処理前のスケール",
|
||||
"scaledWidth": "幅のスケール",
|
||||
"scaledHeight": "高さのスケール",
|
||||
@@ -314,7 +482,7 @@
|
||||
"useSeed": "シード値を使用",
|
||||
"useAll": "すべてを使用",
|
||||
"info": "情報",
|
||||
"showOptionsPanel": "オプションパネルを表示",
|
||||
"showOptionsPanel": "サイドパネルを表示 (O or T)",
|
||||
"iterations": "生成回数",
|
||||
"general": "基本設定",
|
||||
"setToOptimalSize": "サイズをモデルに最適化",
|
||||
@@ -328,16 +496,29 @@
|
||||
"useSize": "サイズを使用",
|
||||
"postProcessing": "ポストプロセス (Shift + U)",
|
||||
"denoisingStrength": "ノイズ除去強度",
|
||||
"recallMetadata": "メタデータを再使用"
|
||||
"recallMetadata": "メタデータを再使用",
|
||||
"copyImage": "画像をコピー",
|
||||
"positivePromptPlaceholder": "ポジティブプロンプト",
|
||||
"negativePromptPlaceholder": "ネガティブプロンプト",
|
||||
"type": "タイプ",
|
||||
"cancel": {
|
||||
"cancel": "キャンセル"
|
||||
},
|
||||
"cfgScale": "CFGスケール",
|
||||
"tileSize": "タイルサイズ",
|
||||
"coherenceMode": "モード"
|
||||
},
|
||||
"settings": {
|
||||
"models": "モデル",
|
||||
"displayInProgress": "生成中の画像を表示する",
|
||||
"displayInProgress": "生成中の画像を表示",
|
||||
"confirmOnDelete": "削除時に確認",
|
||||
"resetWebUI": "WebUIをリセット",
|
||||
"resetWebUIDesc1": "WebUIのリセットは、画像と保存された設定のキャッシュをリセットするだけです。画像を削除するわけではありません。",
|
||||
"resetWebUIDesc2": "もしギャラリーに画像が表示されないなど、何か問題が発生した場合はGitHubにissueを提出する前にリセットを試してください。",
|
||||
"resetComplete": "WebUIはリセットされました。F5を押して再読み込みしてください。"
|
||||
"resetComplete": "WebUIはリセットされました。",
|
||||
"ui": "ユーザーインターフェイス",
|
||||
"beta": "ベータ",
|
||||
"developer": "開発者"
|
||||
},
|
||||
"toast": {
|
||||
"uploadFailed": "アップロード失敗",
|
||||
@@ -345,7 +526,8 @@
|
||||
"imageUploadFailed": "画像のアップロードに失敗しました",
|
||||
"uploadFailedInvalidUploadDesc": "画像はPNGかJPGである必要があります。",
|
||||
"sentToUpscale": "アップスケーラーに転送しました",
|
||||
"imageUploaded": "画像をアップロードしました"
|
||||
"imageUploaded": "画像をアップロードしました",
|
||||
"serverError": "サーバーエラー"
|
||||
},
|
||||
"accessibility": {
|
||||
"invokeProgressBar": "進捗バー",
|
||||
@@ -356,7 +538,7 @@
|
||||
"menu": "メニュー",
|
||||
"createIssue": "問題を報告",
|
||||
"resetUI": "$t(accessibility.reset) UI",
|
||||
"mode": "モード:",
|
||||
"mode": "モード",
|
||||
"about": "Invoke について",
|
||||
"submitSupportTicket": "サポート依頼を送信する",
|
||||
"uploadImages": "画像をアップロード",
|
||||
@@ -373,7 +555,20 @@
|
||||
"positivePrompt": "ポジティブプロンプト",
|
||||
"strength": "Image to Image 強度",
|
||||
"recallParameters": "パラメータを再使用",
|
||||
"recallParameter": "{{label}} を再使用"
|
||||
"recallParameter": "{{label}} を再使用",
|
||||
"imageDimensions": "画像サイズ",
|
||||
"imageDetails": "画像の詳細",
|
||||
"model": "モデル",
|
||||
"allPrompts": "すべてのプロンプト",
|
||||
"cfgScale": "CFGスケール",
|
||||
"createdBy": "作成:",
|
||||
"metadata": "メタデータ",
|
||||
"height": "高さ",
|
||||
"negativePrompt": "ネガティブプロンプト",
|
||||
"generationMode": "生成モード",
|
||||
"vae": "VAE",
|
||||
"cfgRescaleMultiplier": "$t(parameters.cfgRescaleMultiplier)",
|
||||
"canvasV2Metadata": "キャンバス"
|
||||
},
|
||||
"queue": {
|
||||
"queueEmpty": "キューが空です",
|
||||
@@ -405,7 +600,7 @@
|
||||
"batchQueuedDesc_other": "{{count}} セッションをキューの{{direction}}に追加しました",
|
||||
"graphQueued": "グラフをキューに追加しました",
|
||||
"batch": "バッチ",
|
||||
"clearQueueAlertDialog": "キューをクリアすると、処理中のアイテムは直ちにキャンセルされ、キューは完全にクリアされます。",
|
||||
"clearQueueAlertDialog": "キューをクリアすると、処理中の項目は直ちにキャンセルされ、キューは完全にクリアされます。保留中のフィルターもキャンセルされます。",
|
||||
"pending": "保留中",
|
||||
"resumeFailed": "処理の再開に問題があります",
|
||||
"clear": "クリア",
|
||||
@@ -423,7 +618,7 @@
|
||||
"enqueueing": "バッチをキューに追加",
|
||||
"cancelBatchFailed": "バッチのキャンセルに問題があります",
|
||||
"clearQueueAlertDialog2": "キューをクリアしてもよろしいですか?",
|
||||
"item": "アイテム",
|
||||
"item": "項目",
|
||||
"graphFailedToQueue": "グラフをキューに追加できませんでした",
|
||||
"batchFieldValues": "バッチの詳細",
|
||||
"openQueue": "キューを開く",
|
||||
@@ -439,7 +634,17 @@
|
||||
"upscaling": "アップスケール",
|
||||
"generation": "生成",
|
||||
"other": "その他",
|
||||
"gallery": "ギャラリー"
|
||||
"gallery": "ギャラリー",
|
||||
"cancelAllExceptCurrentQueueItemAlertDialog2": "すべての保留中のキュー項目をキャンセルしてもよいですか?",
|
||||
"cancelAllExceptCurrentTooltip": "現在の項目を除いてすべてキャンセル",
|
||||
"origin": "先頭",
|
||||
"destination": "宛先",
|
||||
"confirm": "確認",
|
||||
"retryItem": "項目をリトライ",
|
||||
"batchSize": "バッチサイズ",
|
||||
"retryFailed": "項目のリトライに問題があります",
|
||||
"cancelAllExceptCurrentQueueItemAlertDialog": "現在の項目を除くすべてのキュー項目をキャンセルすると、保留中の項目は停止しますが、進行中の項目は完了します。",
|
||||
"retrySucceeded": "項目がリトライされました"
|
||||
},
|
||||
"models": {
|
||||
"noMatchingModels": "一致するモデルがありません",
|
||||
@@ -448,13 +653,14 @@
|
||||
"noModelsAvailable": "使用可能なモデルがありません",
|
||||
"selectModel": "モデルを選択してください",
|
||||
"concepts": "コンセプト",
|
||||
"addLora": "LoRAを追加"
|
||||
"addLora": "LoRAを追加",
|
||||
"lora": "LoRA"
|
||||
},
|
||||
"nodes": {
|
||||
"addNode": "ノードを追加",
|
||||
"boolean": "ブーリアン",
|
||||
"addNodeToolTip": "ノードを追加 (Shift+A, Space)",
|
||||
"missingTemplate": "テンプレートが見つかりません",
|
||||
"missingTemplate": "Invalid node: タイプ {{type}} のノード {{node}} にテンプレートがありません(未インストール?)",
|
||||
"loadWorkflow": "ワークフローを読み込み",
|
||||
"hideLegendNodes": "フィールドタイプの凡例を非表示",
|
||||
"float": "浮動小数点",
|
||||
@@ -465,7 +671,7 @@
|
||||
"currentImageDescription": "ノードエディタ内の現在の画像を表示",
|
||||
"downloadWorkflow": "ワークフローのJSONをダウンロード",
|
||||
"fieldTypesMustMatch": "フィールドタイプが一致している必要があります",
|
||||
"edge": "輪郭",
|
||||
"edge": "エッジ",
|
||||
"animatedEdgesHelp": "選択したエッジおよび選択したノードに接続されたエッジをアニメーション化します",
|
||||
"cannotDuplicateConnection": "重複した接続は作れません",
|
||||
"noWorkflow": "ワークフローがありません",
|
||||
@@ -484,7 +690,20 @@
|
||||
"cannotConnectToSelf": "自身のノードには接続できません",
|
||||
"colorCodeEdges": "カラー-Code Edges",
|
||||
"loadingNodes": "ノードを読み込み中...",
|
||||
"scheduler": "スケジューラー"
|
||||
"scheduler": "スケジューラー",
|
||||
"version": "バージョン",
|
||||
"edit": "編集",
|
||||
"nodeVersion": "ノードバージョン",
|
||||
"workflowTags": "タグ",
|
||||
"string": "文字列",
|
||||
"workflowVersion": "バージョン",
|
||||
"workflowAuthor": "作者",
|
||||
"ipAdapter": "IP-Adapter",
|
||||
"notes": "ノート",
|
||||
"workflow": "ワークフロー",
|
||||
"workflowName": "名前",
|
||||
"workflowNotes": "ノート",
|
||||
"enum": "Enum"
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "自動追加するボード",
|
||||
@@ -506,7 +725,7 @@
|
||||
"deleteBoard": "ボードの削除",
|
||||
"deleteBoardAndImages": "ボードと画像の削除",
|
||||
"deleteBoardOnly": "ボードのみ削除",
|
||||
"deletedBoardsCannotbeRestored": "削除されたボードは復元できません",
|
||||
"deletedBoardsCannotbeRestored": "削除されたボードは復元できません。\"ボードのみ削除\"を選択すると画像は未分類に移動されます。",
|
||||
"movingImagesToBoard_other": "{{count}} の画像をボードに移動:",
|
||||
"hideBoards": "ボードを隠す",
|
||||
"assetsWithCount_other": "{{count}} のアセット",
|
||||
@@ -518,7 +737,12 @@
|
||||
"archiveBoard": "ボードをアーカイブ",
|
||||
"archived": "アーカイブ完了",
|
||||
"unarchiveBoard": "アーカイブされていないボード",
|
||||
"imagesWithCount_other": "{{count}} の画像"
|
||||
"imagesWithCount_other": "{{count}} の画像",
|
||||
"updateBoardError": "ボード更新エラー",
|
||||
"selectedForAutoAdd": "自動追加に選択済み",
|
||||
"deletedPrivateBoardsCannotbeRestored": "削除されたボードは復元できません。\"ボードのみ削除\"を選択すると画像はその作成者のプライベートな未分類に移動されます。",
|
||||
"noBoards": "{{boardType}} ボードがありません",
|
||||
"viewBoards": "ボードを表示"
|
||||
},
|
||||
"invocationCache": {
|
||||
"invocationCache": "呼び出しキャッシュ",
|
||||
@@ -570,6 +794,57 @@
|
||||
},
|
||||
"paramAspect": {
|
||||
"heading": "縦横比"
|
||||
},
|
||||
"refinerSteps": {
|
||||
"heading": "ステップ"
|
||||
},
|
||||
"paramVAE": {
|
||||
"heading": "VAE"
|
||||
},
|
||||
"scale": {
|
||||
"heading": "スケール"
|
||||
},
|
||||
"refinerScheduler": {
|
||||
"heading": "スケジューラー"
|
||||
},
|
||||
"compositingCoherenceMode": {
|
||||
"heading": "モード"
|
||||
},
|
||||
"paramModel": {
|
||||
"heading": "モデル"
|
||||
},
|
||||
"paramHeight": {
|
||||
"heading": "高さ"
|
||||
},
|
||||
"paramSteps": {
|
||||
"heading": "ステップ"
|
||||
},
|
||||
"ipAdapterMethod": {
|
||||
"heading": "モード"
|
||||
},
|
||||
"paramSeed": {
|
||||
"heading": "シード"
|
||||
},
|
||||
"paramIterations": {
|
||||
"heading": "生成回数"
|
||||
},
|
||||
"controlNet": {
|
||||
"heading": "ControlNet"
|
||||
},
|
||||
"paramWidth": {
|
||||
"heading": "幅"
|
||||
},
|
||||
"lora": {
|
||||
"heading": "LoRA"
|
||||
},
|
||||
"loraWeight": {
|
||||
"heading": "重み"
|
||||
},
|
||||
"patchmatchDownScaleSize": {
|
||||
"heading": "Downscale"
|
||||
},
|
||||
"controlNetWeight": {
|
||||
"heading": "重み"
|
||||
}
|
||||
},
|
||||
"accordions": {
|
||||
@@ -579,7 +854,8 @@
|
||||
"coherenceTab": "コヒーレンスパス"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "高度な設定"
|
||||
"title": "高度",
|
||||
"options": "$t(accordions.advanced.title) オプション"
|
||||
},
|
||||
"control": {
|
||||
"title": "コントロール"
|
||||
@@ -608,7 +884,11 @@
|
||||
},
|
||||
"ui": {
|
||||
"tabs": {
|
||||
"queue": "キュー"
|
||||
"queue": "キュー",
|
||||
"canvas": "キャンバス",
|
||||
"workflows": "ワークフロー",
|
||||
"models": "モデル",
|
||||
"gallery": "ギャラリー"
|
||||
}
|
||||
},
|
||||
"controlLayers": {
|
||||
@@ -623,7 +903,8 @@
|
||||
"bboxGroup": "バウンディングボックスから作成",
|
||||
"cropCanvasToBbox": "キャンバスをバウンディングボックスでクロップ",
|
||||
"newGlobalReferenceImage": "新規全域参照画像",
|
||||
"newRegionalReferenceImage": "新規領域参照画像"
|
||||
"newRegionalReferenceImage": "新規領域参照画像",
|
||||
"canvasGroup": "キャンバス"
|
||||
},
|
||||
"regionalGuidance": "領域ガイダンス",
|
||||
"globalReferenceImage": "全域参照画像",
|
||||
@@ -644,7 +925,8 @@
|
||||
"brush": "ブラシ",
|
||||
"rectangle": "矩形",
|
||||
"move": "移動",
|
||||
"eraser": "消しゴム"
|
||||
"eraser": "消しゴム",
|
||||
"bbox": "Bbox"
|
||||
},
|
||||
"saveCanvasToGallery": "キャンバスをギャラリーに保存",
|
||||
"saveBboxToGallery": "バウンディングボックスをギャラリーへ保存",
|
||||
@@ -662,7 +944,27 @@
|
||||
"canvas": "キャンバス",
|
||||
"fitBboxToLayers": "バウンディングボックスをレイヤーにフィット",
|
||||
"removeBookmark": "ブックマークを外す",
|
||||
"savedToGalleryOk": "ギャラリーに保存しました"
|
||||
"savedToGalleryOk": "ギャラリーに保存しました",
|
||||
"controlMode": {
|
||||
"prompt": "プロンプト"
|
||||
},
|
||||
"prompt": "プロンプト",
|
||||
"settings": {
|
||||
"snapToGrid": {
|
||||
"off": "オフ",
|
||||
"on": "オン"
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"filter": "フィルター",
|
||||
"spandrel_filter": {
|
||||
"model": "モデル"
|
||||
},
|
||||
"apply": "適用",
|
||||
"reset": "リセット",
|
||||
"cancel": "キャンセル"
|
||||
},
|
||||
"weight": "重み"
|
||||
},
|
||||
"stylePresets": {
|
||||
"clearTemplateSelection": "選択したテンプレートをクリア",
|
||||
@@ -674,15 +976,54 @@
|
||||
"createPromptTemplate": "プロンプトテンプレートを作成",
|
||||
"promptTemplateCleared": "プロンプトテンプレートをクリアしました",
|
||||
"searchByName": "名前で検索",
|
||||
"toggleViewMode": "表示モードを切り替え"
|
||||
"toggleViewMode": "表示モードを切り替え",
|
||||
"negativePromptColumn": "'negative_prompt'",
|
||||
"preview": "プレビュー",
|
||||
"nameColumn": "'name'",
|
||||
"type": "タイプ",
|
||||
"private": "プライベート",
|
||||
"name": "名称"
|
||||
},
|
||||
"upscaling": {
|
||||
"upscaleModel": "アップスケールモデル",
|
||||
"postProcessingModel": "ポストプロセスモデル",
|
||||
"upscale": "アップスケール"
|
||||
"upscale": "アップスケール",
|
||||
"scale": "スケール"
|
||||
},
|
||||
"sdxl": {
|
||||
"denoisingStrength": "ノイズ除去強度",
|
||||
"scheduler": "スケジューラー"
|
||||
"scheduler": "スケジューラー",
|
||||
"loading": "ロード中...",
|
||||
"steps": "ステップ",
|
||||
"refiner": "Refiner"
|
||||
},
|
||||
"modelCache": {
|
||||
"clear": "モデルキャッシュを消去",
|
||||
"clearSucceeded": "モデルキャッシュを消去しました",
|
||||
"clearFailed": "モデルキャッシュの消去中に問題が発生"
|
||||
},
|
||||
"workflows": {
|
||||
"workflows": "ワークフロー",
|
||||
"ascending": "昇順",
|
||||
"name": "名前",
|
||||
"descending": "降順"
|
||||
},
|
||||
"system": {
|
||||
"logNamespaces": {
|
||||
"system": "システム",
|
||||
"gallery": "ギャラリー",
|
||||
"workflows": "ワークフロー",
|
||||
"models": "モデル",
|
||||
"canvas": "キャンバス",
|
||||
"metadata": "メタデータ",
|
||||
"queue": "キュー"
|
||||
},
|
||||
"logLevel": {
|
||||
"debug": "Debug",
|
||||
"info": "Info",
|
||||
"error": "Error",
|
||||
"fatal": "Fatal",
|
||||
"warn": "Warn"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2311,11 +2311,7 @@
|
||||
"watchUiUpdatesOverview": "Xem Tổng Quan Về Những Cập Nhật Cho Giao Diện Người Dùng",
|
||||
"items": [
|
||||
"Cải thiện các thiết lập mặc định của VRAM",
|
||||
"Xoá bộ nhớ đệm của model theo yêu cầu",
|
||||
"Mở rộng khả năng tương thích LoRA trên FLUX",
|
||||
"Bộ lọc điều chỉnh ảnh trên Canvas",
|
||||
"Huỷ tất cả trừ mục đang xếp hàng hiện tại",
|
||||
"Sao chép và dán trên Canvas"
|
||||
"Xoá bộ nhớ đệm của model theo yêu cầu"
|
||||
]
|
||||
},
|
||||
"upsell": {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { enqueueRequested } from 'app/store/actions';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { extractMessageFromAssertionError } from 'common/util/extractMessageFromAssertionError';
|
||||
import { withResult, withResultAsync } from 'common/util/result';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import { $canvasManager } from 'features/controlLayers/store/ephemeral';
|
||||
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
||||
import { buildFLUXGraph } from 'features/nodes/util/graph/generation/buildFLUXGraph';
|
||||
@@ -13,7 +14,6 @@ import { toast } from 'features/toast/toast';
|
||||
import { serializeError } from 'serialize-error';
|
||||
import { enqueueMutationFixedCacheKeyOptions, queueApi } from 'services/api/endpoints/queue';
|
||||
import { assert, AssertionError } from 'tsafe';
|
||||
import type { JsonObject } from 'type-fest';
|
||||
|
||||
const log = logger('generation');
|
||||
|
||||
@@ -80,16 +80,15 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
|
||||
const req = dispatch(
|
||||
queueApi.endpoints.enqueueBatch.initiate(prepareBatchResult.value, enqueueMutationFixedCacheKeyOptions)
|
||||
);
|
||||
req.reset();
|
||||
|
||||
const enqueueResult = await withResultAsync(() => req.unwrap());
|
||||
|
||||
if (enqueueResult.isErr()) {
|
||||
log.error({ error: serializeError(enqueueResult.error) }, 'Failed to enqueue batch');
|
||||
return;
|
||||
try {
|
||||
await req.unwrap();
|
||||
log.debug(parseify({ batchConfig: prepareBatchResult.value }), 'Enqueued batch');
|
||||
} catch (error) {
|
||||
log.error({ error: serializeError(error) }, 'Failed to enqueue batch');
|
||||
} finally {
|
||||
req.reset();
|
||||
}
|
||||
|
||||
log.debug({ batchConfig: prepareBatchResult.value } as JsonObject, 'Enqueued batch');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { enqueueRequested } from 'app/store/actions';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import { $templates } from 'features/nodes/store/nodesSlice';
|
||||
import { selectNodesSlice } from 'features/nodes/store/selectors';
|
||||
import { isBatchNode, isInvocationNode } from 'features/nodes/types/invocation';
|
||||
@@ -7,9 +9,12 @@ import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph';
|
||||
import { resolveBatchValue } from 'features/nodes/util/node/resolveBatchValue';
|
||||
import { buildWorkflowWithValidation } from 'features/nodes/util/workflow/buildWorkflow';
|
||||
import { groupBy } from 'lodash-es';
|
||||
import { serializeError } from 'serialize-error';
|
||||
import { enqueueMutationFixedCacheKeyOptions, queueApi } from 'services/api/endpoints/queue';
|
||||
import type { Batch, BatchConfig } from 'services/api/types';
|
||||
|
||||
const log = logger('generation');
|
||||
|
||||
export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||
@@ -101,6 +106,9 @@ export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) =
|
||||
const req = dispatch(queueApi.endpoints.enqueueBatch.initiate(batchConfig, enqueueMutationFixedCacheKeyOptions));
|
||||
try {
|
||||
await req.unwrap();
|
||||
log.debug(parseify({ batchConfig }), 'Enqueued batch');
|
||||
} catch (error) {
|
||||
log.error({ error: serializeError(error) }, 'Failed to enqueue batch');
|
||||
} finally {
|
||||
req.reset();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { enqueueRequested } from 'app/store/actions';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
||||
import { buildMultidiffusionUpscaleGraph } from 'features/nodes/util/graph/buildMultidiffusionUpscaleGraph';
|
||||
import { serializeError } from 'serialize-error';
|
||||
import { enqueueMutationFixedCacheKeyOptions, queueApi } from 'services/api/endpoints/queue';
|
||||
|
||||
const log = logger('generation');
|
||||
|
||||
export const addEnqueueRequestedUpscale = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||
@@ -19,6 +24,9 @@ export const addEnqueueRequestedUpscale = (startAppListening: AppStartListening)
|
||||
const req = dispatch(queueApi.endpoints.enqueueBatch.initiate(batchConfig, enqueueMutationFixedCacheKeyOptions));
|
||||
try {
|
||||
await req.unwrap();
|
||||
log.debug(parseify({ batchConfig }), 'Enqueued batch');
|
||||
} catch (error) {
|
||||
log.error({ error: serializeError(error) }, 'Failed to enqueue batch');
|
||||
} finally {
|
||||
req.reset();
|
||||
}
|
||||
|
||||
17
invokeai/frontend/web/src/common/components/linkify.ts
Normal file
17
invokeai/frontend/web/src/common/components/linkify.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import type { Opts as LinkifyOpts } from 'linkifyjs';
|
||||
|
||||
export const linkifySx: SystemStyleObject = {
|
||||
a: {
|
||||
fontWeight: 'semibold',
|
||||
},
|
||||
'a:hover': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
};
|
||||
|
||||
export const linkifyOptions: LinkifyOpts = {
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
validate: (value) => /^https?:\/\//.test(value),
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import type { CanvasState } from 'features/controlLayers/store/types';
|
||||
import { selectDeleteImageModalSlice } from 'features/deleteImageModal/store/slice';
|
||||
import { selectNodesSlice } from 'features/nodes/store/selectors';
|
||||
import type { NodesState } from 'features/nodes/store/types';
|
||||
import { isImageFieldInputInstance } from 'features/nodes/types/field';
|
||||
import { isImageFieldCollectionInputInstance, isImageFieldInputInstance } from 'features/nodes/types/field';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import type { UpscaleState } from 'features/parameters/store/upscaleSlice';
|
||||
import { selectUpscaleSlice } from 'features/parameters/store/upscaleSlice';
|
||||
@@ -13,11 +13,23 @@ import { some } from 'lodash-es';
|
||||
import type { ImageUsage } from './types';
|
||||
// TODO(psyche): handle image deletion (canvas staging area?)
|
||||
export const getImageUsage = (nodes: NodesState, canvas: CanvasState, upscale: UpscaleState, image_name: string) => {
|
||||
const isNodesImage = nodes.nodes
|
||||
.filter(isInvocationNode)
|
||||
.some((node) =>
|
||||
some(node.data.inputs, (input) => isImageFieldInputInstance(input) && input.value?.image_name === image_name)
|
||||
);
|
||||
const isNodesImage = nodes.nodes.filter(isInvocationNode).some((node) =>
|
||||
some(node.data.inputs, (input) => {
|
||||
if (isImageFieldInputInstance(input)) {
|
||||
if (input.value?.image_name === image_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isImageFieldCollectionInputInstance(input)) {
|
||||
if (input.value?.some((value) => value?.image_name === image_name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
);
|
||||
|
||||
const isUpscaleImage = upscale.upscaleInitialImage?.image_name === image_name;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
||||
import { Combobox, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { EMPTY_ARRAY } from 'app/store/constants';
|
||||
import { NO_DRAG_CLASS, NO_WHEEL_CLASS } from 'features/nodes/types/constants';
|
||||
import type { ImageGeneratorImagesFromBoard } from 'features/nodes/types/field';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
@@ -49,31 +50,21 @@ const BoardCombobox = ({
|
||||
board_id,
|
||||
onChange: _onChange,
|
||||
}: {
|
||||
board_id: string;
|
||||
board_id: string | undefined;
|
||||
onChange: (board_id: string) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const listAllBoardsQuery = useListAllBoardsQuery(listAllBoardsQueryArg);
|
||||
|
||||
const noneOption = useMemo<ComboboxOption>(() => {
|
||||
return {
|
||||
label: `${t('common.none')} (${t('boards.uncategorized')})`,
|
||||
value: 'none',
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
const options = useMemo<ComboboxOption[]>(() => {
|
||||
const _options: ComboboxOption[] = [noneOption];
|
||||
if (listAllBoardsQuery.data) {
|
||||
for (const board of listAllBoardsQuery.data) {
|
||||
_options.push({
|
||||
label: board.board_name,
|
||||
value: board.board_id,
|
||||
});
|
||||
}
|
||||
if (!listAllBoardsQuery.data) {
|
||||
return EMPTY_ARRAY;
|
||||
}
|
||||
return _options;
|
||||
}, [listAllBoardsQuery.data, noneOption]);
|
||||
return listAllBoardsQuery.data.map((board) => ({
|
||||
label: board.board_name,
|
||||
value: board.board_id,
|
||||
}));
|
||||
}, [listAllBoardsQuery.data]);
|
||||
|
||||
const onChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
@@ -87,13 +78,7 @@ const BoardCombobox = ({
|
||||
[_onChange]
|
||||
);
|
||||
|
||||
const value = useMemo(() => {
|
||||
if (board_id === 'none') {
|
||||
return noneOption;
|
||||
}
|
||||
const boardOption = options.find((o) => o.value === board_id);
|
||||
return boardOption ?? noneOption;
|
||||
}, [board_id, options, noneOption]);
|
||||
const value = useMemo(() => options.find((o) => o.value === board_id) ?? null, [board_id, options]);
|
||||
|
||||
const noOptionsMessage = useCallback(() => t('boards.noMatching'), [t]);
|
||||
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { Flex, Spacer } from '@invoke-ai/ui-library';
|
||||
import { Flex, IconButton, Spacer } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import AddNodeButton from 'features/nodes/components/flow/panels/TopPanel/AddNodeButton';
|
||||
import ClearFlowButton from 'features/nodes/components/flow/panels/TopPanel/ClearFlowButton';
|
||||
import SaveWorkflowButton from 'features/nodes/components/flow/panels/TopPanel/SaveWorkflowButton';
|
||||
import UpdateNodesButton from 'features/nodes/components/flow/panels/TopPanel/UpdateNodesButton';
|
||||
import { useWorkflowEditorSettingsModal } from 'features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings';
|
||||
import { WorkflowName } from 'features/nodes/components/sidePanel/WorkflowName';
|
||||
import { selectWorkflowName } from 'features/nodes/store/workflowSlice';
|
||||
import WorkflowLibraryMenu from 'features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiGearSixFill } from 'react-icons/pi';
|
||||
|
||||
const TopCenterPanel = () => {
|
||||
const name = useAppSelector(selectWorkflowName);
|
||||
const modal = useWorkflowEditorSettingsModal();
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Flex gap={2} top={2} left={2} right={2} position="absolute" alignItems="flex-start" pointerEvents="none">
|
||||
<Flex gap="2">
|
||||
@@ -22,7 +27,12 @@ const TopCenterPanel = () => {
|
||||
<Spacer />
|
||||
<ClearFlowButton />
|
||||
<SaveWorkflowButton />
|
||||
<WorkflowLibraryMenu />
|
||||
<IconButton
|
||||
pointerEvents="auto"
|
||||
aria-label={t('workflows.workflowEditorMenu')}
|
||||
icon={<PiGearSixFill />}
|
||||
onClick={modal.setTrue}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Text } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { linkifyOptions, linkifySx } from 'common/components/linkify';
|
||||
import { selectWorkflowDescription } from 'features/nodes/store/workflowSlice';
|
||||
import Linkify from 'linkify-react';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const ActiveWorkflowDescription = memo(() => {
|
||||
@@ -11,8 +13,8 @@ export const ActiveWorkflowDescription = memo(() => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Text color="base.300" fontStyle="italic" noOfLines={1} pb={2}>
|
||||
{description}
|
||||
<Text color="base.300" fontStyle="italic" pb={2} sx={linkifySx}>
|
||||
<Linkify options={linkifyOptions}>{description}</Linkify>
|
||||
</Text>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Flex, Spacer } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { NewWorkflowButton } from 'features/nodes/components/sidePanel/NewWorkflowButton';
|
||||
import { WorkflowListMenuTrigger } from 'features/nodes/components/sidePanel/WorkflowListMenu/WorkflowListMenuTrigger';
|
||||
import { WorkflowViewEditToggleButton } from 'features/nodes/components/sidePanel/WorkflowViewEditToggleButton';
|
||||
import { selectWorkflowMode } from 'features/nodes/store/workflowSlice';
|
||||
import { WorkflowLibraryMenu } from 'features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu';
|
||||
import { memo } from 'react';
|
||||
|
||||
import SaveWorkflowButton from './SaveWorkflowButton';
|
||||
@@ -17,7 +17,7 @@ export const ActiveWorkflowNameAndActions = memo(() => {
|
||||
<Spacer />
|
||||
{mode === 'edit' && <SaveWorkflowButton />}
|
||||
<WorkflowViewEditToggleButton />
|
||||
<NewWorkflowButton />
|
||||
<WorkflowLibraryMenu />
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import { useWorkflowListMenu } from 'features/nodes/store/workflowListMenu';
|
||||
import { selectWorkflowName } from 'features/nodes/store/workflowSlice';
|
||||
import { NewWorkflowButton } from 'features/workflowLibrary/components/NewWorkflowButton';
|
||||
import UploadWorkflowButton from 'features/workflowLibrary/components/UploadWorkflowButton';
|
||||
import { useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -63,6 +64,7 @@ export const WorkflowListMenuTrigger = () => {
|
||||
<WorkflowSearch searchInputRef={searchInputRef} />
|
||||
<WorkflowSortControl />
|
||||
<UploadWorkflowButton />
|
||||
<NewWorkflowButton />
|
||||
</Flex>
|
||||
<Box position="relative" w="full" h="full">
|
||||
<ScrollableContent>
|
||||
|
||||
@@ -50,7 +50,7 @@ const ContainerElement = memo(({ id }: { id: string }) => {
|
||||
ContainerElement.displayName = 'ContainerElementComponent';
|
||||
|
||||
const containerViewModeSx: SystemStyleObject = {
|
||||
gap: 4,
|
||||
gap: 2,
|
||||
'&[data-self-layout="column"]': {
|
||||
flexDir: 'column',
|
||||
alignItems: 'stretch',
|
||||
@@ -197,7 +197,7 @@ const rootViewModeSx: SystemStyleObject = {
|
||||
borderRadius: 'base',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
gap: 4,
|
||||
gap: 2,
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
maxW: '768px',
|
||||
@@ -232,6 +232,7 @@ RootContainerElementViewMode.displayName = 'RootContainerElementViewMode';
|
||||
|
||||
const rootEditModeSx: SystemStyleObject = {
|
||||
...rootViewModeSx,
|
||||
gap: 4,
|
||||
'&[data-is-dragging-over="true"]': {
|
||||
opacity: 1,
|
||||
bg: 'base.850',
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Portal,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { formElementContainerDataChanged } from 'features/nodes/store/workflowSlice';
|
||||
@@ -36,22 +37,24 @@ export const ContainerElementSettings = memo(({ element }: { element: ContainerE
|
||||
<PopoverTrigger>
|
||||
<IconButton aria-label="settings" icon={<PiWrenchFill />} variant="link" size="sm" alignSelf="stretch" />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverArrow />
|
||||
<PopoverBody>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('workflows.builder.layout')}</FormLabel>
|
||||
<ButtonGroup variant="outline" size="sm">
|
||||
<Button onClick={setLayoutToRow} colorScheme={layout === 'row' ? 'invokeBlue' : 'base'}>
|
||||
{t('workflows.builder.row')}
|
||||
</Button>
|
||||
<Button onClick={setLayoutToColumn} colorScheme={layout === 'column' ? 'invokeBlue' : 'base'}>
|
||||
{t('workflows.builder.column')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</FormControl>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
<Portal>
|
||||
<PopoverContent>
|
||||
<PopoverArrow />
|
||||
<PopoverBody>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('workflows.builder.layout')}</FormLabel>
|
||||
<ButtonGroup variant="outline" size="sm">
|
||||
<Button onClick={setLayoutToRow} colorScheme={layout === 'row' ? 'invokeBlue' : 'base'}>
|
||||
{t('workflows.builder.row')}
|
||||
</Button>
|
||||
<Button onClick={setLayoutToColumn} colorScheme={layout === 'column' ? 'invokeBlue' : 'base'}>
|
||||
{t('workflows.builder.column')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</FormControl>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Portal>
|
||||
</Popover>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { HeadingProps, SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Text } from '@invoke-ai/ui-library';
|
||||
import { linkifyOptions, linkifySx } from 'common/components/linkify';
|
||||
import Linkify from 'linkify-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -9,13 +11,14 @@ const headingSx: SystemStyleObject = {
|
||||
'&[data-is-empty="true"]': {
|
||||
opacity: 0.3,
|
||||
},
|
||||
...linkifySx,
|
||||
};
|
||||
|
||||
export const HeadingElementContent = memo(({ content, ...rest }: { content: string } & HeadingProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Text sx={headingSx} data-is-empty={content === ''} {...rest}>
|
||||
{content || t('workflows.builder.headingPlaceholder')}
|
||||
<Linkify options={linkifyOptions}>{content || t('workflows.builder.headingPlaceholder')}</Linkify>
|
||||
</Text>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { FormHelperText, Textarea } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { linkifyOptions, linkifySx } from 'common/components/linkify';
|
||||
import { useEditable } from 'common/hooks/useEditable';
|
||||
import { useInputFieldDescription } from 'features/nodes/hooks/useInputFieldDescription';
|
||||
import { useInputFieldTemplate } from 'features/nodes/hooks/useInputFieldTemplate';
|
||||
import { fieldDescriptionChanged } from 'features/nodes/store/nodesSlice';
|
||||
import type { NodeFieldElement } from 'features/nodes/types/workflow';
|
||||
import Linkify from 'linkify-react';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
|
||||
export const NodeFieldElementDescriptionEditable = memo(({ el }: { el: NodeFieldElement }) => {
|
||||
@@ -36,7 +38,11 @@ export const NodeFieldElementDescriptionEditable = memo(({ el }: { el: NodeField
|
||||
});
|
||||
|
||||
if (!editable.isEditing) {
|
||||
return <FormHelperText onDoubleClick={editable.startEditing}>{editable.value}</FormHelperText>;
|
||||
return (
|
||||
<FormHelperText onDoubleClick={editable.startEditing} sx={linkifySx}>
|
||||
<Linkify options={linkifyOptions}>{editable.value}</Linkify>
|
||||
</FormHelperText>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Portal,
|
||||
Switch,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
@@ -65,18 +66,20 @@ export const NodeFieldElementSettings = memo(({ element }: { element: NodeFieldE
|
||||
<PopoverTrigger>
|
||||
<IconButton aria-label="settings" icon={<PiWrenchFill />} variant="link" size="sm" alignSelf="stretch" />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverArrow />
|
||||
<PopoverBody minW={48}>
|
||||
<FormControl>
|
||||
<FormLabel flex={1}>{t('workflows.builder.showDescription')}</FormLabel>
|
||||
<Switch size="sm" isChecked={showDescription} onChange={toggleShowDescription} />
|
||||
</FormControl>
|
||||
{settings?.type === 'integer-field-config' && <NodeFieldElementIntegerConfig id={id} config={settings} />}
|
||||
{settings?.type === 'float-field-config' && <NodeFieldElementFloatSettings id={id} config={settings} />}
|
||||
{settings?.type === 'string-field-config' && <NodeFieldElementStringSettings id={id} config={settings} />}
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
<Portal>
|
||||
<PopoverContent>
|
||||
<PopoverArrow />
|
||||
<PopoverBody minW={48}>
|
||||
<FormControl>
|
||||
<FormLabel flex={1}>{t('workflows.builder.showDescription')}</FormLabel>
|
||||
<Switch size="sm" isChecked={showDescription} onChange={toggleShowDescription} />
|
||||
</FormControl>
|
||||
{settings?.type === 'integer-field-config' && <NodeFieldElementIntegerConfig id={id} config={settings} />}
|
||||
{settings?.type === 'float-field-config' && <NodeFieldElementFloatSettings id={id} config={settings} />}
|
||||
{settings?.type === 'string-field-config' && <NodeFieldElementStringSettings id={id} config={settings} />}
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Portal>
|
||||
</Popover>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Flex, FormControl, FormHelperText } from '@invoke-ai/ui-library';
|
||||
import { linkifyOptions, linkifySx } from 'common/components/linkify';
|
||||
import { InputFieldRenderer } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer';
|
||||
import { useContainerContext } from 'features/nodes/components/sidePanel/builder/contexts';
|
||||
import { NodeFieldElementLabel } from 'features/nodes/components/sidePanel/builder/NodeFieldElementLabel';
|
||||
@@ -7,6 +8,7 @@ import { useInputFieldDescription } from 'features/nodes/hooks/useInputFieldDesc
|
||||
import { useInputFieldTemplate } from 'features/nodes/hooks/useInputFieldTemplate';
|
||||
import type { NodeFieldElement } from 'features/nodes/types/workflow';
|
||||
import { NODE_FIELD_CLASS_NAME } from 'features/nodes/types/workflow';
|
||||
import Linkify from 'linkify-react';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
const sx: SystemStyleObject = {
|
||||
@@ -18,6 +20,9 @@ const sx: SystemStyleObject = {
|
||||
flex: '1 1 0',
|
||||
minW: 32,
|
||||
},
|
||||
'&[data-with-description="false"]': {
|
||||
pb: 2,
|
||||
},
|
||||
};
|
||||
|
||||
export const NodeFieldElementViewMode = memo(({ el }: { el: NodeFieldElement }) => {
|
||||
@@ -33,7 +38,13 @@ export const NodeFieldElementViewMode = memo(({ el }: { el: NodeFieldElement })
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex id={id} className={NODE_FIELD_CLASS_NAME} sx={sx} data-parent-layout={containerCtx.layout}>
|
||||
<Flex
|
||||
id={id}
|
||||
className={NODE_FIELD_CLASS_NAME}
|
||||
sx={sx}
|
||||
data-parent-layout={containerCtx.layout}
|
||||
data-with-description={showDescription && !!_description}
|
||||
>
|
||||
<FormControl flex="1 1 0" orientation="vertical">
|
||||
<NodeFieldElementLabel el={el} />
|
||||
<Flex w="full" gap={4}>
|
||||
@@ -43,7 +54,11 @@ export const NodeFieldElementViewMode = memo(({ el }: { el: NodeFieldElement })
|
||||
settings={data.settings}
|
||||
/>
|
||||
</Flex>
|
||||
{showDescription && _description && <FormHelperText>{_description}</FormHelperText>}
|
||||
{showDescription && _description && (
|
||||
<FormHelperText sx={linkifySx}>
|
||||
<Linkify options={linkifyOptions}>{_description}</Linkify>
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { SystemStyleObject, TextProps } from '@invoke-ai/ui-library';
|
||||
import { Text } from '@invoke-ai/ui-library';
|
||||
import { linkifyOptions, linkifySx } from 'common/components/linkify';
|
||||
import Linkify from 'linkify-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -9,13 +11,14 @@ const textSx: SystemStyleObject = {
|
||||
'&[data-is-empty="true"]': {
|
||||
opacity: 0.3,
|
||||
},
|
||||
...linkifySx,
|
||||
};
|
||||
|
||||
export const TextElementContent = memo(({ content, ...rest }: { content: string } & TextProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Text sx={textSx} data-is-empty={content === ''} {...rest}>
|
||||
{content || t('workflows.builder.textPlaceholder')}
|
||||
<Linkify options={linkifyOptions}>{content || t('workflows.builder.textPlaceholder')}</Linkify>
|
||||
</Text>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1534,7 +1534,7 @@ export const getStringGeneratorDefaults = (type: StringGeneratorFieldValue['type
|
||||
export const ImageGeneratorImagesFromBoardType = 'image_generator_images_from_board';
|
||||
const zImageGeneratorImagesFromBoard = z.object({
|
||||
type: z.literal(ImageGeneratorImagesFromBoardType).default(ImageGeneratorImagesFromBoardType),
|
||||
board_id: z.string().trim().min(1).optional().default('none'),
|
||||
board_id: z.string().trim().min(1).optional(),
|
||||
category: z.union([z.literal('images'), z.literal('assets')]).default('images'),
|
||||
});
|
||||
export type ImageGeneratorImagesFromBoard = z.infer<typeof zImageGeneratorImagesFromBoard>;
|
||||
@@ -1544,10 +1544,13 @@ const getImageGeneratorImagesFromBoardValues = async (
|
||||
dispatch: AppDispatch
|
||||
) => {
|
||||
const { board_id, category } = generator;
|
||||
if (!board_id) {
|
||||
return EMPTY_ARRAY;
|
||||
}
|
||||
const req = dispatch(
|
||||
boardsApi.endpoints.listAllImageNamesForBoard.initiate(
|
||||
{
|
||||
board_id: board_id ?? 'none',
|
||||
board_id,
|
||||
categories: category === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES,
|
||||
is_intermediate: false,
|
||||
},
|
||||
|
||||
@@ -23,7 +23,6 @@ export const NewWorkflowButton = memo(() => {
|
||||
<IconButton
|
||||
onClick={onClickNewWorkflow}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label={t('nodes.newWorkflow')}
|
||||
tooltip={t('nodes.newWorkflow')}
|
||||
icon={<PiFilePlusBold />}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { useWorkflowEditorSettingsModal } from 'features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiGearSixFill } from 'react-icons/pi';
|
||||
|
||||
const DownloadWorkflowMenuItem = () => {
|
||||
const { t } = useTranslation();
|
||||
const modal = useWorkflowEditorSettingsModal();
|
||||
|
||||
return (
|
||||
<MenuItem as="button" icon={<PiGearSixFill />} onClick={modal.setTrue}>
|
||||
{t('nodes.workflowSettings')}
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(DownloadWorkflowMenuItem);
|
||||
@@ -13,13 +13,12 @@ import LoadWorkflowFromGraphMenuItem from 'features/workflowLibrary/components/W
|
||||
import { NewWorkflowMenuItem } from 'features/workflowLibrary/components/WorkflowLibraryMenu/NewWorkflowMenuItem';
|
||||
import SaveWorkflowAsMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/SaveWorkflowAsMenuItem';
|
||||
import SaveWorkflowMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/SaveWorkflowMenuItem';
|
||||
import SettingsMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/SettingsMenuItem';
|
||||
import UploadWorkflowMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/UploadWorkflowMenuItem';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiDotsThreeOutlineFill } from 'react-icons/pi';
|
||||
|
||||
const WorkflowLibraryMenu = () => {
|
||||
export const WorkflowLibraryMenu = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const shift = useShiftModifier();
|
||||
@@ -31,6 +30,8 @@ const WorkflowLibraryMenu = () => {
|
||||
aria-label={t('workflows.workflowEditorMenu')}
|
||||
icon={<PiDotsThreeOutlineFill />}
|
||||
pointerEvents="auto"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
/>
|
||||
<MenuList pointerEvents="auto">
|
||||
<NewWorkflowMenuItem />
|
||||
@@ -39,13 +40,10 @@ const WorkflowLibraryMenu = () => {
|
||||
<SaveWorkflowMenuItem />
|
||||
<SaveWorkflowAsMenuItem />
|
||||
<DownloadWorkflowMenuItem />
|
||||
<MenuDivider />
|
||||
<SettingsMenuItem />
|
||||
{shift && <MenuDivider />}
|
||||
{shift && <LoadWorkflowFromGraphMenuItem />}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(WorkflowLibraryMenu);
|
||||
});
|
||||
WorkflowLibraryMenu.displayName = 'WorkflowLibraryMenu';
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
__version__ = "5.7.0rc1"
|
||||
__version__ = "5.7.1"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from pydantic import TypeAdapter, ValidationError
|
||||
|
||||
@@ -44,46 +46,46 @@ def test_create_sessions_from_batch_with_runs(batch_data_collection, batch_graph
|
||||
# 2 list[BatchDatum] * length 2 * 2 runs = 8
|
||||
assert len(t) == 8
|
||||
|
||||
assert t[0][0].graph.get_node("1").prompt == "Banana sushi"
|
||||
assert t[0][0].graph.get_node("2").prompt == "Strawberry sushi"
|
||||
assert t[0][0].graph.get_node("3").prompt == "Orange sushi"
|
||||
assert t[0][0].graph.get_node("4").prompt == "Nissan"
|
||||
assert json.loads(t[0][1])["graph"]["nodes"]["1"]["prompt"] == "Banana sushi"
|
||||
assert json.loads(t[0][1])["graph"]["nodes"]["2"]["prompt"] == "Strawberry sushi"
|
||||
assert json.loads(t[0][1])["graph"]["nodes"]["3"]["prompt"] == "Orange sushi"
|
||||
assert json.loads(t[0][1])["graph"]["nodes"]["4"]["prompt"] == "Nissan"
|
||||
|
||||
assert t[1][0].graph.get_node("1").prompt == "Banana sushi"
|
||||
assert t[1][0].graph.get_node("2").prompt == "Strawberry sushi"
|
||||
assert t[1][0].graph.get_node("3").prompt == "Apple sushi"
|
||||
assert t[1][0].graph.get_node("4").prompt == "Nissan"
|
||||
assert json.loads(t[1][1])["graph"]["nodes"]["1"]["prompt"] == "Banana sushi"
|
||||
assert json.loads(t[1][1])["graph"]["nodes"]["2"]["prompt"] == "Strawberry sushi"
|
||||
assert json.loads(t[1][1])["graph"]["nodes"]["3"]["prompt"] == "Apple sushi"
|
||||
assert json.loads(t[1][1])["graph"]["nodes"]["4"]["prompt"] == "Nissan"
|
||||
|
||||
assert t[2][0].graph.get_node("1").prompt == "Grape sushi"
|
||||
assert t[2][0].graph.get_node("2").prompt == "Blueberry sushi"
|
||||
assert t[2][0].graph.get_node("3").prompt == "Orange sushi"
|
||||
assert t[2][0].graph.get_node("4").prompt == "Nissan"
|
||||
assert json.loads(t[2][1])["graph"]["nodes"]["1"]["prompt"] == "Grape sushi"
|
||||
assert json.loads(t[2][1])["graph"]["nodes"]["2"]["prompt"] == "Blueberry sushi"
|
||||
assert json.loads(t[2][1])["graph"]["nodes"]["3"]["prompt"] == "Orange sushi"
|
||||
assert json.loads(t[2][1])["graph"]["nodes"]["4"]["prompt"] == "Nissan"
|
||||
|
||||
assert t[3][0].graph.get_node("1").prompt == "Grape sushi"
|
||||
assert t[3][0].graph.get_node("2").prompt == "Blueberry sushi"
|
||||
assert t[3][0].graph.get_node("3").prompt == "Apple sushi"
|
||||
assert t[3][0].graph.get_node("4").prompt == "Nissan"
|
||||
assert json.loads(t[3][1])["graph"]["nodes"]["1"]["prompt"] == "Grape sushi"
|
||||
assert json.loads(t[3][1])["graph"]["nodes"]["2"]["prompt"] == "Blueberry sushi"
|
||||
assert json.loads(t[3][1])["graph"]["nodes"]["3"]["prompt"] == "Apple sushi"
|
||||
assert json.loads(t[3][1])["graph"]["nodes"]["4"]["prompt"] == "Nissan"
|
||||
|
||||
# repeat for second run
|
||||
assert t[4][0].graph.get_node("1").prompt == "Banana sushi"
|
||||
assert t[4][0].graph.get_node("2").prompt == "Strawberry sushi"
|
||||
assert t[4][0].graph.get_node("3").prompt == "Orange sushi"
|
||||
assert t[4][0].graph.get_node("4").prompt == "Nissan"
|
||||
assert json.loads(t[4][1])["graph"]["nodes"]["1"]["prompt"] == "Banana sushi"
|
||||
assert json.loads(t[4][1])["graph"]["nodes"]["2"]["prompt"] == "Strawberry sushi"
|
||||
assert json.loads(t[4][1])["graph"]["nodes"]["3"]["prompt"] == "Orange sushi"
|
||||
assert json.loads(t[4][1])["graph"]["nodes"]["4"]["prompt"] == "Nissan"
|
||||
|
||||
assert t[5][0].graph.get_node("1").prompt == "Banana sushi"
|
||||
assert t[5][0].graph.get_node("2").prompt == "Strawberry sushi"
|
||||
assert t[5][0].graph.get_node("3").prompt == "Apple sushi"
|
||||
assert t[5][0].graph.get_node("4").prompt == "Nissan"
|
||||
assert json.loads(t[5][1])["graph"]["nodes"]["1"]["prompt"] == "Banana sushi"
|
||||
assert json.loads(t[5][1])["graph"]["nodes"]["2"]["prompt"] == "Strawberry sushi"
|
||||
assert json.loads(t[5][1])["graph"]["nodes"]["3"]["prompt"] == "Apple sushi"
|
||||
assert json.loads(t[5][1])["graph"]["nodes"]["4"]["prompt"] == "Nissan"
|
||||
|
||||
assert t[6][0].graph.get_node("1").prompt == "Grape sushi"
|
||||
assert t[6][0].graph.get_node("2").prompt == "Blueberry sushi"
|
||||
assert t[6][0].graph.get_node("3").prompt == "Orange sushi"
|
||||
assert t[6][0].graph.get_node("4").prompt == "Nissan"
|
||||
assert json.loads(t[6][1])["graph"]["nodes"]["1"]["prompt"] == "Grape sushi"
|
||||
assert json.loads(t[6][1])["graph"]["nodes"]["2"]["prompt"] == "Blueberry sushi"
|
||||
assert json.loads(t[6][1])["graph"]["nodes"]["3"]["prompt"] == "Orange sushi"
|
||||
assert json.loads(t[6][1])["graph"]["nodes"]["4"]["prompt"] == "Nissan"
|
||||
|
||||
assert t[7][0].graph.get_node("1").prompt == "Grape sushi"
|
||||
assert t[7][0].graph.get_node("2").prompt == "Blueberry sushi"
|
||||
assert t[7][0].graph.get_node("3").prompt == "Apple sushi"
|
||||
assert t[7][0].graph.get_node("4").prompt == "Nissan"
|
||||
assert json.loads(t[7][1])["graph"]["nodes"]["1"]["prompt"] == "Grape sushi"
|
||||
assert json.loads(t[7][1])["graph"]["nodes"]["2"]["prompt"] == "Blueberry sushi"
|
||||
assert json.loads(t[7][1])["graph"]["nodes"]["3"]["prompt"] == "Apple sushi"
|
||||
assert json.loads(t[7][1])["graph"]["nodes"]["4"]["prompt"] == "Nissan"
|
||||
|
||||
|
||||
def test_create_sessions_from_batch_without_runs(batch_data_collection, batch_graph):
|
||||
@@ -127,7 +129,7 @@ def test_prepare_values_to_insert(batch_data_collection, batch_graph):
|
||||
|
||||
GraphExecutionStateValidator = TypeAdapter(GraphExecutionState)
|
||||
# graph should be serialized
|
||||
ges = GraphExecutionStateValidator.validate_json(values[0].session)
|
||||
ges = GraphExecutionStateValidator.validate_json(values[0][1])
|
||||
|
||||
# graph values should be populated
|
||||
assert ges.graph.get_node("1").prompt == "Banana sushi"
|
||||
@@ -136,26 +138,26 @@ def test_prepare_values_to_insert(batch_data_collection, batch_graph):
|
||||
assert ges.graph.get_node("4").prompt == "Nissan"
|
||||
|
||||
# session ids should match deserialized graph
|
||||
assert [v.session_id for v in values] == [GraphExecutionStateValidator.validate_json(v.session).id for v in values]
|
||||
assert [v[2] for v in values] == [GraphExecutionStateValidator.validate_json(v[1]).id for v in values]
|
||||
|
||||
# should unique session ids
|
||||
sids = [v.session_id for v in values]
|
||||
sids = [v[2] for v in values]
|
||||
assert len(sids) == len(set(sids))
|
||||
|
||||
NodeFieldValueValidator = TypeAdapter(list[NodeFieldValue])
|
||||
# should have 3 node field values
|
||||
assert isinstance(values[0].field_values, str)
|
||||
assert len(NodeFieldValueValidator.validate_json(values[0].field_values)) == 3
|
||||
assert isinstance(values[0][4], str)
|
||||
assert len(NodeFieldValueValidator.validate_json(values[0][4])) == 3
|
||||
|
||||
# should have batch id and priority
|
||||
assert all(v.batch_id == b.batch_id for v in values)
|
||||
assert all(v.priority == 0 for v in values)
|
||||
assert all(v[3] == b.batch_id for v in values)
|
||||
assert all(v[5] == 0 for v in values)
|
||||
|
||||
|
||||
def test_prepare_values_to_insert_with_priority(batch_data_collection, batch_graph):
|
||||
b = Batch(graph=batch_graph, data=batch_data_collection, runs=2)
|
||||
values = prepare_values_to_insert(queue_id="default", batch=b, priority=1, max_new_queue_items=1000)
|
||||
assert all(v.priority == 1 for v in values)
|
||||
assert all(v[5] == 1 for v in values)
|
||||
|
||||
|
||||
def test_prepare_values_to_insert_with_max(batch_data_collection, batch_graph):
|
||||
|
||||
Reference in New Issue
Block a user