mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into fix-inpainting
This commit is contained in:
commit
5a546e66f1
@ -13,7 +13,6 @@ from invokeai.app.services.board_record_storage import SqliteBoardRecordStorage
|
||||
from invokeai.app.services.boards import BoardService, BoardServiceDependencies
|
||||
from invokeai.app.services.image_record_storage import SqliteImageRecordStorage
|
||||
from invokeai.app.services.images import ImageService, ImageServiceDependencies
|
||||
from invokeai.app.services.metadata import CoreMetadataService
|
||||
from invokeai.app.services.resource_name import SimpleNameService
|
||||
from invokeai.app.services.urls import LocalUrlService
|
||||
from invokeai.backend.util.logging import InvokeAILogger
|
||||
@ -75,7 +74,6 @@ class ApiDependencies:
|
||||
)
|
||||
|
||||
urls = LocalUrlService()
|
||||
metadata = CoreMetadataService()
|
||||
image_record_storage = SqliteImageRecordStorage(db_location)
|
||||
image_file_storage = DiskImageFileStorage(f"{output_folder}/images")
|
||||
names = SimpleNameService()
|
||||
@ -111,7 +109,6 @@ class ApiDependencies:
|
||||
board_image_record_storage=board_image_record_storage,
|
||||
image_record_storage=image_record_storage,
|
||||
image_file_storage=image_file_storage,
|
||||
metadata=metadata,
|
||||
url=urls,
|
||||
logger=logger,
|
||||
names=names,
|
||||
|
@ -1,20 +1,19 @@
|
||||
import io
|
||||
from typing import Optional
|
||||
from fastapi import Body, HTTPException, Path, Query, Request, Response, UploadFile
|
||||
from fastapi.routing import APIRouter
|
||||
|
||||
from fastapi import (Body, HTTPException, Path, Query, Request, Response,
|
||||
UploadFile)
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.routing import APIRouter
|
||||
from PIL import Image
|
||||
from invokeai.app.models.image import (
|
||||
ImageCategory,
|
||||
ResourceOrigin,
|
||||
)
|
||||
|
||||
from invokeai.app.invocations.metadata import ImageMetadata
|
||||
from invokeai.app.models.image import ImageCategory, ResourceOrigin
|
||||
from invokeai.app.services.image_record_storage import OffsetPaginatedResults
|
||||
from invokeai.app.services.models.image_record import (
|
||||
ImageDTO,
|
||||
ImageRecordChanges,
|
||||
ImageUrlsDTO,
|
||||
)
|
||||
from invokeai.app.services.item_storage import PaginatedResults
|
||||
from invokeai.app.services.models.image_record import (ImageDTO,
|
||||
ImageRecordChanges,
|
||||
ImageUrlsDTO)
|
||||
|
||||
from ..dependencies import ApiDependencies
|
||||
|
||||
@ -103,23 +102,38 @@ async def update_image(
|
||||
|
||||
|
||||
@images_router.get(
|
||||
"/{image_name}/metadata",
|
||||
operation_id="get_image_metadata",
|
||||
"/{image_name}",
|
||||
operation_id="get_image_dto",
|
||||
response_model=ImageDTO,
|
||||
)
|
||||
async def get_image_metadata(
|
||||
async def get_image_dto(
|
||||
image_name: str = Path(description="The name of image to get"),
|
||||
) -> ImageDTO:
|
||||
"""Gets an image's metadata"""
|
||||
"""Gets an image's DTO"""
|
||||
|
||||
try:
|
||||
return ApiDependencies.invoker.services.images.get_dto(image_name)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
@images_router.get(
|
||||
"/{image_name}/metadata",
|
||||
operation_id="get_image_metadata",
|
||||
response_model=ImageMetadata,
|
||||
)
|
||||
async def get_image_metadata(
|
||||
image_name: str = Path(description="The name of image to get"),
|
||||
) -> ImageMetadata:
|
||||
"""Gets an image's metadata"""
|
||||
|
||||
try:
|
||||
return ApiDependencies.invoker.services.images.get_metadata(image_name)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
|
||||
@images_router.get(
|
||||
"/{image_name}",
|
||||
"/{image_name}/full",
|
||||
operation_id="get_image_full",
|
||||
response_class=Response,
|
||||
responses={
|
||||
@ -208,10 +222,10 @@ async def get_image_urls(
|
||||
|
||||
@images_router.get(
|
||||
"/",
|
||||
operation_id="list_images_with_metadata",
|
||||
operation_id="list_image_dtos",
|
||||
response_model=OffsetPaginatedResults[ImageDTO],
|
||||
)
|
||||
async def list_images_with_metadata(
|
||||
async def list_image_dtos(
|
||||
image_origin: Optional[ResourceOrigin] = Query(
|
||||
default=None, description="The origin of images to list"
|
||||
),
|
||||
@ -227,7 +241,7 @@ async def list_images_with_metadata(
|
||||
offset: int = Query(default=0, description="The page offset"),
|
||||
limit: int = Query(default=10, description="The number of images per page"),
|
||||
) -> OffsetPaginatedResults[ImageDTO]:
|
||||
"""Gets a list of images"""
|
||||
"""Gets a list of image DTOs"""
|
||||
|
||||
image_dtos = ApiDependencies.invoker.services.images.get_many(
|
||||
offset,
|
||||
|
@ -34,7 +34,6 @@ from invokeai.app.services.board_record_storage import SqliteBoardRecordStorage
|
||||
from invokeai.app.services.boards import BoardService, BoardServiceDependencies
|
||||
from invokeai.app.services.image_record_storage import SqliteImageRecordStorage
|
||||
from invokeai.app.services.images import ImageService, ImageServiceDependencies
|
||||
from invokeai.app.services.metadata import CoreMetadataService
|
||||
from invokeai.app.services.resource_name import SimpleNameService
|
||||
from invokeai.app.services.urls import LocalUrlService
|
||||
from .services.default_graphs import (default_text_to_image_graph_id,
|
||||
@ -244,7 +243,6 @@ def invoke_cli():
|
||||
)
|
||||
|
||||
urls = LocalUrlService()
|
||||
metadata = CoreMetadataService()
|
||||
image_record_storage = SqliteImageRecordStorage(db_location)
|
||||
image_file_storage = DiskImageFileStorage(f"{output_folder}/images")
|
||||
names = SimpleNameService()
|
||||
@ -277,7 +275,6 @@ def invoke_cli():
|
||||
board_image_record_storage=board_image_record_storage,
|
||||
image_record_storage=image_record_storage,
|
||||
image_file_storage=image_file_storage,
|
||||
metadata=metadata,
|
||||
url=urls,
|
||||
logger=logger,
|
||||
names=names,
|
||||
|
@ -9,9 +9,9 @@ from diffusers.image_processor import VaeImageProcessor
|
||||
from diffusers.schedulers import SchedulerMixin as Scheduler
|
||||
from pydantic import BaseModel, Field, validator
|
||||
|
||||
from invokeai.app.invocations.metadata import CoreMetadata
|
||||
from invokeai.app.util.step_callback import stable_diffusion_step_callback
|
||||
|
||||
from ..models.image import ImageCategory, ImageField, ResourceOrigin
|
||||
from ...backend.model_management.lora import ModelPatcher
|
||||
from ...backend.stable_diffusion import PipelineIntermediateState
|
||||
from ...backend.stable_diffusion.diffusers_pipeline import (
|
||||
@ -21,6 +21,7 @@ from ...backend.stable_diffusion.diffusion.shared_invokeai_diffusion import \
|
||||
PostprocessingSettings
|
||||
from ...backend.stable_diffusion.schedulers import SCHEDULER_MAP
|
||||
from ...backend.util.devices import torch_dtype
|
||||
from ..models.image import ImageCategory, ImageField, ResourceOrigin
|
||||
from .baseinvocation import (BaseInvocation, BaseInvocationOutput,
|
||||
InvocationConfig, InvocationContext)
|
||||
from .compel import ConditioningField
|
||||
@ -449,6 +450,8 @@ class LatentsToImageInvocation(BaseInvocation):
|
||||
tiled: bool = Field(
|
||||
default=False,
|
||||
description="Decode latents by overlaping tiles(less memory consumption)")
|
||||
metadata: Optional[CoreMetadata] = Field(default=None, description="Optional core metadata to be written to the image")
|
||||
|
||||
|
||||
# Schema customisation
|
||||
class Config(InvocationConfig):
|
||||
@ -493,7 +496,8 @@ class LatentsToImageInvocation(BaseInvocation):
|
||||
image_category=ImageCategory.GENERAL,
|
||||
node_id=self.id,
|
||||
session_id=context.graph_execution_state_id,
|
||||
is_intermediate=self.is_intermediate
|
||||
is_intermediate=self.is_intermediate,
|
||||
metadata=self.metadata.dict() if self.metadata else None,
|
||||
)
|
||||
|
||||
return ImageOutput(
|
||||
|
124
invokeai/app/invocations/metadata.py
Normal file
124
invokeai/app/invocations/metadata.py
Normal file
@ -0,0 +1,124 @@
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from invokeai.app.invocations.baseinvocation import (BaseInvocation,
|
||||
BaseInvocationOutput,
|
||||
InvocationContext)
|
||||
from invokeai.app.invocations.controlnet_image_processors import ControlField
|
||||
from invokeai.app.invocations.model import (LoRAModelField, MainModelField,
|
||||
VAEModelField)
|
||||
|
||||
|
||||
class LoRAMetadataField(BaseModel):
|
||||
"""LoRA metadata for an image generated in InvokeAI."""
|
||||
lora: LoRAModelField = Field(description="The LoRA model")
|
||||
weight: float = Field(description="The weight of the LoRA model")
|
||||
|
||||
|
||||
class CoreMetadata(BaseModel):
|
||||
"""Core generation metadata for an image generated in InvokeAI."""
|
||||
|
||||
generation_mode: str = Field(description="The generation mode that output this image",)
|
||||
positive_prompt: str = Field(description="The positive prompt parameter")
|
||||
negative_prompt: str = Field(description="The negative prompt parameter")
|
||||
width: int = Field(description="The width parameter")
|
||||
height: int = Field(description="The height parameter")
|
||||
seed: int = Field(description="The seed used for noise generation")
|
||||
rand_device: str = Field(description="The device used for random number generation")
|
||||
cfg_scale: float = Field(description="The classifier-free guidance scale parameter")
|
||||
steps: int = Field(description="The number of steps used for inference")
|
||||
scheduler: str = Field(description="The scheduler used for inference")
|
||||
clip_skip: int = Field(description="The number of skipped CLIP layers",)
|
||||
model: MainModelField = Field(description="The main model used for inference")
|
||||
controlnets: list[ControlField]= Field(description="The ControlNets used for inference")
|
||||
loras: list[LoRAMetadataField] = Field(description="The LoRAs used for inference")
|
||||
strength: Union[float, None] = Field(
|
||||
default=None,
|
||||
description="The strength used for latents-to-latents",
|
||||
)
|
||||
init_image: Union[str, None] = Field(
|
||||
default=None, description="The name of the initial image"
|
||||
)
|
||||
vae: Union[VAEModelField, None] = Field(
|
||||
default=None,
|
||||
description="The VAE used for decoding, if the main model's default was not used",
|
||||
)
|
||||
|
||||
|
||||
class ImageMetadata(BaseModel):
|
||||
"""An image's generation metadata"""
|
||||
|
||||
metadata: Optional[dict] = Field(
|
||||
default=None,
|
||||
description="The image's core metadata, if it was created in the Linear or Canvas UI",
|
||||
)
|
||||
graph: Optional[dict] = Field(
|
||||
default=None, description="The graph that created the image"
|
||||
)
|
||||
|
||||
|
||||
class MetadataAccumulatorOutput(BaseInvocationOutput):
|
||||
"""The output of the MetadataAccumulator node"""
|
||||
|
||||
type: Literal["metadata_accumulator_output"] = "metadata_accumulator_output"
|
||||
|
||||
metadata: CoreMetadata = Field(description="The core metadata for the image")
|
||||
|
||||
|
||||
class MetadataAccumulatorInvocation(BaseInvocation):
|
||||
"""Outputs a Core Metadata Object"""
|
||||
|
||||
type: Literal["metadata_accumulator"] = "metadata_accumulator"
|
||||
|
||||
generation_mode: str = Field(description="The generation mode that output this image",)
|
||||
positive_prompt: str = Field(description="The positive prompt parameter")
|
||||
negative_prompt: str = Field(description="The negative prompt parameter")
|
||||
width: int = Field(description="The width parameter")
|
||||
height: int = Field(description="The height parameter")
|
||||
seed: int = Field(description="The seed used for noise generation")
|
||||
rand_device: str = Field(description="The device used for random number generation")
|
||||
cfg_scale: float = Field(description="The classifier-free guidance scale parameter")
|
||||
steps: int = Field(description="The number of steps used for inference")
|
||||
scheduler: str = Field(description="The scheduler used for inference")
|
||||
clip_skip: int = Field(description="The number of skipped CLIP layers",)
|
||||
model: MainModelField = Field(description="The main model used for inference")
|
||||
controlnets: list[ControlField]= Field(description="The ControlNets used for inference")
|
||||
loras: list[LoRAMetadataField] = Field(description="The LoRAs used for inference")
|
||||
strength: Union[float, None] = Field(
|
||||
default=None,
|
||||
description="The strength used for latents-to-latents",
|
||||
)
|
||||
init_image: Union[str, None] = Field(
|
||||
default=None, description="The name of the initial image"
|
||||
)
|
||||
vae: Union[VAEModelField, None] = Field(
|
||||
default=None,
|
||||
description="The VAE used for decoding, if the main model's default was not used",
|
||||
)
|
||||
|
||||
|
||||
def invoke(self, context: InvocationContext) -> MetadataAccumulatorOutput:
|
||||
"""Collects and outputs a CoreMetadata object"""
|
||||
|
||||
return MetadataAccumulatorOutput(
|
||||
metadata=CoreMetadata(
|
||||
generation_mode=self.generation_mode,
|
||||
positive_prompt=self.positive_prompt,
|
||||
negative_prompt=self.negative_prompt,
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
seed=self.seed,
|
||||
rand_device=self.rand_device,
|
||||
cfg_scale=self.cfg_scale,
|
||||
steps=self.steps,
|
||||
scheduler=self.scheduler,
|
||||
model=self.model,
|
||||
strength=self.strength,
|
||||
init_image=self.init_image,
|
||||
vae=self.vae,
|
||||
controlnets=self.controlnets,
|
||||
loras=self.loras,
|
||||
clip_skip=self.clip_skip,
|
||||
)
|
||||
)
|
@ -1,93 +0,0 @@
|
||||
from typing import Optional, Union, List
|
||||
from pydantic import BaseModel, Extra, Field, StrictFloat, StrictInt, StrictStr
|
||||
|
||||
|
||||
class ImageMetadata(BaseModel):
|
||||
"""
|
||||
Core generation metadata for an image/tensor generated in InvokeAI.
|
||||
|
||||
Also includes any metadata from the image's PNG tEXt chunks.
|
||||
|
||||
Generated by traversing the execution graph, collecting the parameters of the nearest ancestors
|
||||
of a given node.
|
||||
|
||||
Full metadata may be accessed by querying for the session in the `graph_executions` table.
|
||||
"""
|
||||
|
||||
class Config:
|
||||
extra = Extra.allow
|
||||
"""
|
||||
This lets the ImageMetadata class accept arbitrary additional fields. The CoreMetadataService
|
||||
won't add any fields that are not already defined, but other a different metadata service
|
||||
implementation might.
|
||||
"""
|
||||
|
||||
type: Optional[StrictStr] = Field(
|
||||
default=None,
|
||||
description="The type of the ancestor node of the image output node.",
|
||||
)
|
||||
"""The type of the ancestor node of the image output node."""
|
||||
positive_conditioning: Optional[StrictStr] = Field(
|
||||
default=None, description="The positive conditioning."
|
||||
)
|
||||
"""The positive conditioning"""
|
||||
negative_conditioning: Optional[StrictStr] = Field(
|
||||
default=None, description="The negative conditioning."
|
||||
)
|
||||
"""The negative conditioning"""
|
||||
width: Optional[StrictInt] = Field(
|
||||
default=None, description="Width of the image/latents in pixels."
|
||||
)
|
||||
"""Width of the image/latents in pixels"""
|
||||
height: Optional[StrictInt] = Field(
|
||||
default=None, description="Height of the image/latents in pixels."
|
||||
)
|
||||
"""Height of the image/latents in pixels"""
|
||||
seed: Optional[StrictInt] = Field(
|
||||
default=None, description="The seed used for noise generation."
|
||||
)
|
||||
"""The seed used for noise generation"""
|
||||
# cfg_scale: Optional[StrictFloat] = Field(
|
||||
# cfg_scale: Union[float, list[float]] = Field(
|
||||
cfg_scale: Union[StrictFloat, List[StrictFloat]] = Field(
|
||||
default=None, description="The classifier-free guidance scale."
|
||||
)
|
||||
"""The classifier-free guidance scale"""
|
||||
steps: Optional[StrictInt] = Field(
|
||||
default=None, description="The number of steps used for inference."
|
||||
)
|
||||
"""The number of steps used for inference"""
|
||||
scheduler: Optional[StrictStr] = Field(
|
||||
default=None, description="The scheduler used for inference."
|
||||
)
|
||||
"""The scheduler used for inference"""
|
||||
model: Optional[StrictStr] = Field(
|
||||
default=None, description="The model used for inference."
|
||||
)
|
||||
"""The model used for inference"""
|
||||
strength: Optional[StrictFloat] = Field(
|
||||
default=None,
|
||||
description="The strength used for image-to-image/latents-to-latents.",
|
||||
)
|
||||
"""The strength used for image-to-image/latents-to-latents."""
|
||||
latents: Optional[StrictStr] = Field(
|
||||
default=None, description="The ID of the initial latents."
|
||||
)
|
||||
"""The ID of the initial latents"""
|
||||
vae: Optional[StrictStr] = Field(
|
||||
default=None, description="The VAE used for decoding."
|
||||
)
|
||||
"""The VAE used for decoding"""
|
||||
unet: Optional[StrictStr] = Field(
|
||||
default=None, description="The UNet used dor inference."
|
||||
)
|
||||
"""The UNet used dor inference"""
|
||||
clip: Optional[StrictStr] = Field(
|
||||
default=None, description="The CLIP Encoder used for conditioning."
|
||||
)
|
||||
"""The CLIP Encoder used for conditioning"""
|
||||
extra: Optional[StrictStr] = Field(
|
||||
default=None,
|
||||
description="Uploaded image metadata, extracted from the PNG tEXt chunk.",
|
||||
)
|
||||
"""Uploaded image metadata, extracted from the PNG tEXt chunk."""
|
@ -1,14 +1,14 @@
|
||||
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) and the InvokeAI Team
|
||||
import json
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
from typing import Dict, Optional, Union
|
||||
|
||||
from PIL.Image import Image as PILImageType
|
||||
from PIL import Image, PngImagePlugin
|
||||
from PIL.Image import Image as PILImageType
|
||||
from send2trash import send2trash
|
||||
|
||||
from invokeai.app.models.metadata import ImageMetadata
|
||||
from invokeai.app.util.thumbnails import get_thumbnail_name, make_thumbnail
|
||||
|
||||
|
||||
@ -59,7 +59,8 @@ class ImageFileStorageBase(ABC):
|
||||
self,
|
||||
image: PILImageType,
|
||||
image_name: str,
|
||||
metadata: Optional[ImageMetadata] = None,
|
||||
metadata: Optional[dict] = None,
|
||||
graph: Optional[dict] = None,
|
||||
thumbnail_size: int = 256,
|
||||
) -> None:
|
||||
"""Saves an image and a 256x256 WEBP thumbnail. Returns a tuple of the image name, thumbnail name, and created timestamp."""
|
||||
@ -110,20 +111,22 @@ class DiskImageFileStorage(ImageFileStorageBase):
|
||||
self,
|
||||
image: PILImageType,
|
||||
image_name: str,
|
||||
metadata: Optional[ImageMetadata] = None,
|
||||
metadata: Optional[dict] = None,
|
||||
graph: Optional[dict] = None,
|
||||
thumbnail_size: int = 256,
|
||||
) -> None:
|
||||
try:
|
||||
self.__validate_storage_folders()
|
||||
image_path = self.get_path(image_name)
|
||||
|
||||
if metadata is not None:
|
||||
pnginfo = PngImagePlugin.PngInfo()
|
||||
pnginfo.add_text("invokeai", metadata.json())
|
||||
image.save(image_path, "PNG", pnginfo=pnginfo)
|
||||
else:
|
||||
image.save(image_path, "PNG")
|
||||
pnginfo = PngImagePlugin.PngInfo()
|
||||
|
||||
if metadata is not None:
|
||||
pnginfo.add_text("invokeai_metadata", json.dumps(metadata))
|
||||
if graph is not None:
|
||||
pnginfo.add_text("invokeai_graph", json.dumps(graph))
|
||||
|
||||
image.save(image_path, "PNG", pnginfo=pnginfo)
|
||||
thumbnail_name = get_thumbnail_name(image_name)
|
||||
thumbnail_path = self.get_path(thumbnail_name, thumbnail=True)
|
||||
thumbnail_image = make_thumbnail(image, thumbnail_size)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import json
|
||||
import sqlite3
|
||||
import threading
|
||||
from abc import ABC, abstractmethod
|
||||
@ -8,7 +9,6 @@ from pydantic import BaseModel, Field
|
||||
from pydantic.generics import GenericModel
|
||||
|
||||
from invokeai.app.models.image import ImageCategory, ResourceOrigin
|
||||
from invokeai.app.models.metadata import ImageMetadata
|
||||
from invokeai.app.services.models.image_record import (
|
||||
ImageRecord, ImageRecordChanges, deserialize_image_record)
|
||||
|
||||
@ -48,6 +48,28 @@ class ImageRecordDeleteException(Exception):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
IMAGE_DTO_COLS = ", ".join(
|
||||
list(
|
||||
map(
|
||||
lambda c: "images." + c,
|
||||
[
|
||||
"image_name",
|
||||
"image_origin",
|
||||
"image_category",
|
||||
"width",
|
||||
"height",
|
||||
"session_id",
|
||||
"node_id",
|
||||
"is_intermediate",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ImageRecordStorageBase(ABC):
|
||||
"""Low-level service responsible for interfacing with the image record store."""
|
||||
|
||||
@ -58,6 +80,11 @@ class ImageRecordStorageBase(ABC):
|
||||
"""Gets an image record."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_metadata(self, image_name: str) -> Optional[dict]:
|
||||
"""Gets an image's metadata'."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update(
|
||||
self,
|
||||
@ -102,7 +129,7 @@ class ImageRecordStorageBase(ABC):
|
||||
height: int,
|
||||
session_id: Optional[str],
|
||||
node_id: Optional[str],
|
||||
metadata: Optional[ImageMetadata],
|
||||
metadata: Optional[dict],
|
||||
is_intermediate: bool = False,
|
||||
) -> datetime:
|
||||
"""Saves an image record."""
|
||||
@ -206,7 +233,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
||||
|
||||
self._cursor.execute(
|
||||
f"""--sql
|
||||
SELECT * FROM images
|
||||
SELECT {IMAGE_DTO_COLS} FROM images
|
||||
WHERE image_name = ?;
|
||||
""",
|
||||
(image_name,),
|
||||
@ -224,6 +251,28 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
||||
|
||||
return deserialize_image_record(dict(result))
|
||||
|
||||
def get_metadata(self, image_name: str) -> Optional[dict]:
|
||||
try:
|
||||
self._lock.acquire()
|
||||
|
||||
self._cursor.execute(
|
||||
f"""--sql
|
||||
SELECT images.metadata FROM images
|
||||
WHERE image_name = ?;
|
||||
""",
|
||||
(image_name,),
|
||||
)
|
||||
|
||||
result = cast(Optional[sqlite3.Row], self._cursor.fetchone())
|
||||
if not result or not result[0]:
|
||||
return None
|
||||
return json.loads(result[0])
|
||||
except sqlite3.Error as e:
|
||||
self._conn.rollback()
|
||||
raise ImageRecordNotFoundException from e
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def update(
|
||||
self,
|
||||
image_name: str,
|
||||
@ -291,8 +340,8 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
||||
WHERE 1=1
|
||||
"""
|
||||
|
||||
images_query = """--sql
|
||||
SELECT images.*
|
||||
images_query = f"""--sql
|
||||
SELECT {IMAGE_DTO_COLS}
|
||||
FROM images
|
||||
LEFT JOIN board_images ON board_images.image_name = images.image_name
|
||||
WHERE 1=1
|
||||
@ -410,12 +459,12 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
||||
width: int,
|
||||
height: int,
|
||||
node_id: Optional[str],
|
||||
metadata: Optional[ImageMetadata],
|
||||
metadata: Optional[dict],
|
||||
is_intermediate: bool = False,
|
||||
) -> datetime:
|
||||
try:
|
||||
metadata_json = (
|
||||
None if metadata is None else metadata.json(exclude_none=True)
|
||||
None if metadata is None else json.dumps(metadata)
|
||||
)
|
||||
self._lock.acquire()
|
||||
self._cursor.execute(
|
||||
@ -465,9 +514,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def get_most_recent_image_for_board(
|
||||
self, board_id: str
|
||||
) -> Optional[ImageRecord]:
|
||||
def get_most_recent_image_for_board(self, board_id: str) -> Optional[ImageRecord]:
|
||||
try:
|
||||
self._lock.acquire()
|
||||
self._cursor.execute(
|
||||
|
@ -1,39 +1,30 @@
|
||||
import json
|
||||
from abc import ABC, abstractmethod
|
||||
from logging import Logger
|
||||
from typing import Optional, TYPE_CHECKING, Union
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from PIL.Image import Image as PILImageType
|
||||
|
||||
from invokeai.app.models.image import (
|
||||
ImageCategory,
|
||||
ResourceOrigin,
|
||||
InvalidImageCategoryException,
|
||||
InvalidOriginException,
|
||||
)
|
||||
from invokeai.app.models.metadata import ImageMetadata
|
||||
from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase
|
||||
from invokeai.app.services.image_record_storage import (
|
||||
ImageRecordDeleteException,
|
||||
ImageRecordNotFoundException,
|
||||
ImageRecordSaveException,
|
||||
ImageRecordStorageBase,
|
||||
OffsetPaginatedResults,
|
||||
)
|
||||
from invokeai.app.services.models.image_record import (
|
||||
ImageRecord,
|
||||
ImageDTO,
|
||||
ImageRecordChanges,
|
||||
image_record_to_dto,
|
||||
)
|
||||
from invokeai.app.invocations.metadata import ImageMetadata
|
||||
from invokeai.app.models.image import (ImageCategory,
|
||||
InvalidImageCategoryException,
|
||||
InvalidOriginException, ResourceOrigin)
|
||||
from invokeai.app.services.board_image_record_storage import \
|
||||
BoardImageRecordStorageBase
|
||||
from invokeai.app.services.graph import Graph
|
||||
from invokeai.app.services.image_file_storage import (
|
||||
ImageFileDeleteException,
|
||||
ImageFileNotFoundException,
|
||||
ImageFileSaveException,
|
||||
ImageFileStorageBase,
|
||||
)
|
||||
from invokeai.app.services.item_storage import ItemStorageABC, PaginatedResults
|
||||
from invokeai.app.services.metadata import MetadataServiceBase
|
||||
ImageFileDeleteException, ImageFileNotFoundException,
|
||||
ImageFileSaveException, ImageFileStorageBase)
|
||||
from invokeai.app.services.image_record_storage import (
|
||||
ImageRecordDeleteException, ImageRecordNotFoundException,
|
||||
ImageRecordSaveException, ImageRecordStorageBase, OffsetPaginatedResults)
|
||||
from invokeai.app.services.item_storage import ItemStorageABC
|
||||
from invokeai.app.services.models.image_record import (ImageDTO, ImageRecord,
|
||||
ImageRecordChanges,
|
||||
image_record_to_dto)
|
||||
from invokeai.app.services.resource_name import NameServiceBase
|
||||
from invokeai.app.services.urls import UrlServiceBase
|
||||
from invokeai.app.util.metadata import get_metadata_graph_from_raw_session
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from invokeai.app.services.graph import GraphExecutionState
|
||||
@ -51,6 +42,7 @@ class ImageServiceABC(ABC):
|
||||
node_id: Optional[str] = None,
|
||||
session_id: Optional[str] = None,
|
||||
is_intermediate: bool = False,
|
||||
metadata: Optional[dict] = None,
|
||||
) -> ImageDTO:
|
||||
"""Creates an image, storing the file and its metadata."""
|
||||
pass
|
||||
@ -79,6 +71,11 @@ class ImageServiceABC(ABC):
|
||||
"""Gets an image DTO."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_metadata(self, image_name: str) -> ImageMetadata:
|
||||
"""Gets an image's metadata."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_path(self, image_name: str, thumbnail: bool = False) -> str:
|
||||
"""Gets an image's path."""
|
||||
@ -124,7 +121,6 @@ class ImageServiceDependencies:
|
||||
image_records: ImageRecordStorageBase
|
||||
image_files: ImageFileStorageBase
|
||||
board_image_records: BoardImageRecordStorageBase
|
||||
metadata: MetadataServiceBase
|
||||
urls: UrlServiceBase
|
||||
logger: Logger
|
||||
names: NameServiceBase
|
||||
@ -135,7 +131,6 @@ class ImageServiceDependencies:
|
||||
image_record_storage: ImageRecordStorageBase,
|
||||
image_file_storage: ImageFileStorageBase,
|
||||
board_image_record_storage: BoardImageRecordStorageBase,
|
||||
metadata: MetadataServiceBase,
|
||||
url: UrlServiceBase,
|
||||
logger: Logger,
|
||||
names: NameServiceBase,
|
||||
@ -144,7 +139,6 @@ class ImageServiceDependencies:
|
||||
self.image_records = image_record_storage
|
||||
self.image_files = image_file_storage
|
||||
self.board_image_records = board_image_record_storage
|
||||
self.metadata = metadata
|
||||
self.urls = url
|
||||
self.logger = logger
|
||||
self.names = names
|
||||
@ -165,6 +159,7 @@ class ImageService(ImageServiceABC):
|
||||
node_id: Optional[str] = None,
|
||||
session_id: Optional[str] = None,
|
||||
is_intermediate: bool = False,
|
||||
metadata: Optional[dict] = None,
|
||||
) -> ImageDTO:
|
||||
if image_origin not in ResourceOrigin:
|
||||
raise InvalidOriginException
|
||||
@ -174,7 +169,16 @@ class ImageService(ImageServiceABC):
|
||||
|
||||
image_name = self._services.names.create_image_name()
|
||||
|
||||
metadata = self._get_metadata(session_id, node_id)
|
||||
graph = None
|
||||
|
||||
if session_id is not None:
|
||||
session_raw = self._services.graph_execution_manager.get_raw(session_id)
|
||||
if session_raw is not None:
|
||||
try:
|
||||
graph = get_metadata_graph_from_raw_session(session_raw)
|
||||
except Exception as e:
|
||||
self._services.logger.warn(f"Failed to parse session graph: {e}")
|
||||
graph = None
|
||||
|
||||
(width, height) = image.size
|
||||
|
||||
@ -191,14 +195,12 @@ class ImageService(ImageServiceABC):
|
||||
is_intermediate=is_intermediate,
|
||||
# Nullable fields
|
||||
node_id=node_id,
|
||||
session_id=session_id,
|
||||
metadata=metadata,
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
self._services.image_files.save(
|
||||
image_name=image_name,
|
||||
image=image,
|
||||
metadata=metadata,
|
||||
image_name=image_name, image=image, metadata=metadata, graph=graph
|
||||
)
|
||||
|
||||
image_dto = self.get_dto(image_name)
|
||||
@ -268,6 +270,34 @@ class ImageService(ImageServiceABC):
|
||||
self._services.logger.error("Problem getting image DTO")
|
||||
raise e
|
||||
|
||||
def get_metadata(self, image_name: str) -> Optional[ImageMetadata]:
|
||||
try:
|
||||
image_record = self._services.image_records.get(image_name)
|
||||
|
||||
if not image_record.session_id:
|
||||
return ImageMetadata()
|
||||
|
||||
session_raw = self._services.graph_execution_manager.get_raw(
|
||||
image_record.session_id
|
||||
)
|
||||
graph = None
|
||||
|
||||
if session_raw:
|
||||
try:
|
||||
graph = get_metadata_graph_from_raw_session(session_raw)
|
||||
except Exception as e:
|
||||
self._services.logger.warn(f"Failed to parse session graph: {e}")
|
||||
graph = None
|
||||
|
||||
metadata = self._services.image_records.get_metadata(image_name)
|
||||
return ImageMetadata(graph=graph, metadata=metadata)
|
||||
except ImageRecordNotFoundException:
|
||||
self._services.logger.error("Image record not found")
|
||||
raise
|
||||
except Exception as e:
|
||||
self._services.logger.error("Problem getting image DTO")
|
||||
raise e
|
||||
|
||||
def get_path(self, image_name: str, thumbnail: bool = False) -> str:
|
||||
try:
|
||||
return self._services.image_files.get_path(image_name, thumbnail)
|
||||
@ -367,15 +397,3 @@ class ImageService(ImageServiceABC):
|
||||
except Exception as e:
|
||||
self._services.logger.error("Problem deleting image records and files")
|
||||
raise e
|
||||
|
||||
def _get_metadata(
|
||||
self, session_id: Optional[str] = None, node_id: Optional[str] = None
|
||||
) -> Optional[ImageMetadata]:
|
||||
"""Get the metadata for a node."""
|
||||
metadata = None
|
||||
|
||||
if node_id is not None and session_id is not None:
|
||||
session = self._services.graph_execution_manager.get(session_id)
|
||||
metadata = self._services.metadata.create_image_metadata(session, node_id)
|
||||
|
||||
return metadata
|
||||
|
@ -1,5 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Callable, Generic, TypeVar
|
||||
from typing import Callable, Generic, Optional, TypeVar
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic.generics import GenericModel
|
||||
@ -29,14 +29,22 @@ class ItemStorageABC(ABC, Generic[T]):
|
||||
|
||||
@abstractmethod
|
||||
def get(self, item_id: str) -> T:
|
||||
"""Gets the item, parsing it into a Pydantic model"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_raw(self, item_id: str) -> Optional[str]:
|
||||
"""Gets the raw item as a string, skipping Pydantic parsing"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set(self, item: T) -> None:
|
||||
"""Sets the item"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list(self, page: int = 0, per_page: int = 10) -> PaginatedResults[T]:
|
||||
"""Gets a paginated list of items"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
@ -1,142 +0,0 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Optional
|
||||
import networkx as nx
|
||||
|
||||
from invokeai.app.models.metadata import ImageMetadata
|
||||
from invokeai.app.services.graph import Graph, GraphExecutionState
|
||||
|
||||
|
||||
class MetadataServiceBase(ABC):
|
||||
"""Handles building metadata for nodes, images, and outputs."""
|
||||
|
||||
@abstractmethod
|
||||
def create_image_metadata(
|
||||
self, session: GraphExecutionState, node_id: str
|
||||
) -> ImageMetadata:
|
||||
"""Builds an ImageMetadata object for a node."""
|
||||
pass
|
||||
|
||||
|
||||
class CoreMetadataService(MetadataServiceBase):
|
||||
_ANCESTOR_TYPES = ["t2l", "l2l"]
|
||||
"""The ancestor types that contain the core metadata"""
|
||||
|
||||
_ANCESTOR_PARAMS = ["type", "steps", "model", "cfg_scale", "scheduler", "strength"]
|
||||
"""The core metadata parameters in the ancestor types"""
|
||||
|
||||
_NOISE_FIELDS = ["seed", "width", "height"]
|
||||
"""The core metadata parameters in the noise node"""
|
||||
|
||||
def create_image_metadata(
|
||||
self, session: GraphExecutionState, node_id: str
|
||||
) -> ImageMetadata:
|
||||
metadata = self._build_metadata_from_graph(session, node_id)
|
||||
|
||||
return metadata
|
||||
|
||||
def _find_nearest_ancestor(self, G: nx.DiGraph, node_id: str) -> Optional[str]:
|
||||
"""
|
||||
Finds the id of the nearest ancestor (of a valid type) of a given node.
|
||||
|
||||
Parameters:
|
||||
G (nx.DiGraph): The execution graph, converted in to a networkx DiGraph. Its nodes must
|
||||
have the same data as the execution graph.
|
||||
node_id (str): The ID of the node.
|
||||
|
||||
Returns:
|
||||
str | None: The ID of the nearest ancestor, or None if there are no valid ancestors.
|
||||
"""
|
||||
|
||||
# Retrieve the node from the graph
|
||||
node = G.nodes[node_id]
|
||||
|
||||
# If the node type is one of the core metadata node types, return its id
|
||||
if node.get("type") in self._ANCESTOR_TYPES:
|
||||
return node.get("id")
|
||||
|
||||
# Else, look for the ancestor in the predecessor nodes
|
||||
for predecessor in G.predecessors(node_id):
|
||||
result = self._find_nearest_ancestor(G, predecessor)
|
||||
if result:
|
||||
return result
|
||||
|
||||
# If there are no valid ancestors, return None
|
||||
return None
|
||||
|
||||
def _get_additional_metadata(
|
||||
self, graph: Graph, node_id: str
|
||||
) -> Optional[dict[str, Any]]:
|
||||
"""
|
||||
Returns additional metadata for a given node.
|
||||
|
||||
Parameters:
|
||||
graph (Graph): The execution graph.
|
||||
node_id (str): The ID of the node.
|
||||
|
||||
Returns:
|
||||
dict[str, Any] | None: A dictionary of additional metadata.
|
||||
"""
|
||||
|
||||
metadata = {}
|
||||
|
||||
# Iterate over all edges in the graph
|
||||
for edge in graph.edges:
|
||||
dest_node_id = edge.destination.node_id
|
||||
dest_field = edge.destination.field
|
||||
source_node_dict = graph.nodes[edge.source.node_id].dict()
|
||||
|
||||
# If the destination node ID matches the given node ID, gather necessary metadata
|
||||
if dest_node_id == node_id:
|
||||
# Prompt
|
||||
if dest_field == "positive_conditioning":
|
||||
metadata["positive_conditioning"] = source_node_dict.get("prompt")
|
||||
# Negative prompt
|
||||
if dest_field == "negative_conditioning":
|
||||
metadata["negative_conditioning"] = source_node_dict.get("prompt")
|
||||
# Seed, width and height
|
||||
if dest_field == "noise":
|
||||
for field in self._NOISE_FIELDS:
|
||||
metadata[field] = source_node_dict.get(field)
|
||||
return metadata
|
||||
|
||||
def _build_metadata_from_graph(
|
||||
self, session: GraphExecutionState, node_id: str
|
||||
) -> ImageMetadata:
|
||||
"""
|
||||
Builds an ImageMetadata object for a node.
|
||||
|
||||
Parameters:
|
||||
session (GraphExecutionState): The session.
|
||||
node_id (str): The ID of the node.
|
||||
|
||||
Returns:
|
||||
ImageMetadata: The metadata for the node.
|
||||
"""
|
||||
|
||||
# We need to do all the traversal on the execution graph
|
||||
graph = session.execution_graph
|
||||
|
||||
# Find the nearest `t2l`/`l2l` ancestor of the given node
|
||||
ancestor_id = self._find_nearest_ancestor(graph.nx_graph_with_data(), node_id)
|
||||
|
||||
# If no ancestor was found, return an empty ImageMetadata object
|
||||
if ancestor_id is None:
|
||||
return ImageMetadata()
|
||||
|
||||
ancestor_node = graph.get_node(ancestor_id)
|
||||
|
||||
# Grab all the core metadata from the ancestor node
|
||||
ancestor_metadata = {
|
||||
param: val
|
||||
for param, val in ancestor_node.dict().items()
|
||||
if param in self._ANCESTOR_PARAMS
|
||||
}
|
||||
|
||||
# Get this image's prompts and noise parameters
|
||||
addl_metadata = self._get_additional_metadata(graph, ancestor_id)
|
||||
|
||||
# If additional metadata was found, add it to the main metadata
|
||||
if addl_metadata is not None:
|
||||
ancestor_metadata.update(addl_metadata)
|
||||
|
||||
return ImageMetadata(**ancestor_metadata)
|
@ -1,13 +1,14 @@
|
||||
import datetime
|
||||
from typing import Optional, Union
|
||||
|
||||
from pydantic import BaseModel, Extra, Field, StrictBool, StrictStr
|
||||
|
||||
from invokeai.app.models.image import ImageCategory, ResourceOrigin
|
||||
from invokeai.app.models.metadata import ImageMetadata
|
||||
from invokeai.app.util.misc import get_iso_timestamp
|
||||
|
||||
|
||||
class ImageRecord(BaseModel):
|
||||
"""Deserialized image record."""
|
||||
"""Deserialized image record without metadata."""
|
||||
|
||||
image_name: str = Field(description="The unique name of the image.")
|
||||
"""The unique name of the image."""
|
||||
@ -43,11 +44,6 @@ class ImageRecord(BaseModel):
|
||||
description="The node ID that generated this image, if it is a generated image.",
|
||||
)
|
||||
"""The node ID that generated this image, if it is a generated image."""
|
||||
metadata: Optional[ImageMetadata] = Field(
|
||||
default=None,
|
||||
description="A limited subset of the image's generation metadata. Retrieve the image's session for full metadata.",
|
||||
)
|
||||
"""A limited subset of the image's generation metadata. Retrieve the image's session for full metadata."""
|
||||
|
||||
|
||||
class ImageRecordChanges(BaseModel, extra=Extra.forbid):
|
||||
@ -112,6 +108,7 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord:
|
||||
|
||||
# Retrieve all the values, setting "reasonable" defaults if they are not present.
|
||||
|
||||
# TODO: do we really need to handle default values here? ideally the data is the correct shape...
|
||||
image_name = image_dict.get("image_name", "unknown")
|
||||
image_origin = ResourceOrigin(
|
||||
image_dict.get("image_origin", ResourceOrigin.INTERNAL.value)
|
||||
@ -128,13 +125,6 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord:
|
||||
deleted_at = image_dict.get("deleted_at", get_iso_timestamp())
|
||||
is_intermediate = image_dict.get("is_intermediate", False)
|
||||
|
||||
raw_metadata = image_dict.get("metadata")
|
||||
|
||||
if raw_metadata is not None:
|
||||
metadata = ImageMetadata.parse_raw(raw_metadata)
|
||||
else:
|
||||
metadata = None
|
||||
|
||||
return ImageRecord(
|
||||
image_name=image_name,
|
||||
image_origin=image_origin,
|
||||
@ -143,7 +133,6 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord:
|
||||
height=height,
|
||||
session_id=session_id,
|
||||
node_id=node_id,
|
||||
metadata=metadata,
|
||||
created_at=created_at,
|
||||
updated_at=updated_at,
|
||||
deleted_at=deleted_at,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import sqlite3
|
||||
from threading import Lock
|
||||
from typing import Generic, TypeVar, Optional, Union, get_args
|
||||
from typing import Generic, Optional, TypeVar, get_args
|
||||
|
||||
from pydantic import BaseModel, parse_raw_as
|
||||
|
||||
@ -78,6 +78,21 @@ class SqliteItemStorage(ItemStorageABC, Generic[T]):
|
||||
|
||||
return self._parse_item(result[0])
|
||||
|
||||
def get_raw(self, id: str) -> Optional[str]:
|
||||
try:
|
||||
self._lock.acquire()
|
||||
self._cursor.execute(
|
||||
f"""SELECT item FROM {self._table_name} WHERE id = ?;""", (str(id),)
|
||||
)
|
||||
result = self._cursor.fetchone()
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
if not result:
|
||||
return None
|
||||
|
||||
return result[0]
|
||||
|
||||
def delete(self, id: str):
|
||||
try:
|
||||
self._lock.acquire()
|
||||
|
@ -22,4 +22,4 @@ class LocalUrlService(UrlServiceBase):
|
||||
if thumbnail:
|
||||
return f"{self._base_url}/images/{image_basename}/thumbnail"
|
||||
|
||||
return f"{self._base_url}/images/{image_basename}"
|
||||
return f"{self._base_url}/images/{image_basename}/full"
|
||||
|
55
invokeai/app/util/metadata.py
Normal file
55
invokeai/app/util/metadata.py
Normal file
@ -0,0 +1,55 @@
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from invokeai.app.services.graph import Edge
|
||||
|
||||
|
||||
def get_metadata_graph_from_raw_session(session_raw: str) -> Optional[dict]:
|
||||
"""
|
||||
Parses raw session string, returning a dict of the graph.
|
||||
|
||||
Only the general graph shape is validated; none of the fields are validated.
|
||||
|
||||
Any `metadata_accumulator` nodes and edges are removed.
|
||||
|
||||
Any validation failure will return None.
|
||||
"""
|
||||
|
||||
graph = json.loads(session_raw).get("graph", None)
|
||||
|
||||
# sanity check make sure the graph is at least reasonably shaped
|
||||
if (
|
||||
type(graph) is not dict
|
||||
or "nodes" not in graph
|
||||
or type(graph["nodes"]) is not dict
|
||||
or "edges" not in graph
|
||||
or type(graph["edges"]) is not list
|
||||
):
|
||||
# something has gone terribly awry, return an empty dict
|
||||
return None
|
||||
|
||||
try:
|
||||
# delete the `metadata_accumulator` node
|
||||
del graph["nodes"]["metadata_accumulator"]
|
||||
except KeyError:
|
||||
# no accumulator node, all good
|
||||
pass
|
||||
|
||||
# delete any edges to or from it
|
||||
for i, edge in enumerate(graph["edges"]):
|
||||
try:
|
||||
# try to parse the edge
|
||||
Edge(**edge)
|
||||
except ValidationError:
|
||||
# something has gone terribly awry, return an empty dict
|
||||
return None
|
||||
|
||||
if (
|
||||
edge["source"]["node_id"] == "metadata_accumulator"
|
||||
or edge["destination"]["node_id"] == "metadata_accumulator"
|
||||
):
|
||||
del graph["edges"][i]
|
||||
|
||||
return graph
|
@ -121,8 +121,8 @@ class ModelInstall(object):
|
||||
installed_models = self.mgr.list_models()
|
||||
for md in installed_models:
|
||||
base = md['base_model']
|
||||
model_type = md['type']
|
||||
name = md['name']
|
||||
model_type = md['model_type']
|
||||
name = md['model_name']
|
||||
key = ModelManager.create_key(name, base, model_type)
|
||||
if key in model_dict:
|
||||
model_dict[key].installed = True
|
||||
|
@ -538,9 +538,9 @@ class ModelManager(object):
|
||||
model_dict = dict(
|
||||
**model_config.dict(exclude_defaults=True),
|
||||
# OpenAPIModelInfoBase
|
||||
name=cur_model_name,
|
||||
model_name=cur_model_name,
|
||||
base_model=cur_base_model,
|
||||
type=cur_model_type,
|
||||
model_type=cur_model_type,
|
||||
)
|
||||
|
||||
models.append(model_dict)
|
||||
|
@ -37,9 +37,9 @@ MODEL_CONFIGS = list()
|
||||
OPENAPI_MODEL_CONFIGS = list()
|
||||
|
||||
class OpenAPIModelInfoBase(BaseModel):
|
||||
name: str
|
||||
model_name: str
|
||||
base_model: BaseModelType
|
||||
type: ModelType
|
||||
model_type: ModelType
|
||||
|
||||
|
||||
for base_model, models in MODEL_CLASSES.items():
|
||||
@ -56,7 +56,7 @@ for base_model, models in MODEL_CLASSES.items():
|
||||
|
||||
api_wrapper = type(openapi_cfg_name, (cfg, OpenAPIModelInfoBase), dict(
|
||||
__annotations__ = dict(
|
||||
type=Literal[model_type.value],
|
||||
model_type=Literal[model_type.value],
|
||||
),
|
||||
))
|
||||
|
||||
|
@ -108,6 +108,7 @@
|
||||
"roarr": "^7.15.0",
|
||||
"serialize-error": "^11.0.0",
|
||||
"socket.io-client": "^4.7.0",
|
||||
"use-debounce": "^9.0.4",
|
||||
"use-image": "^1.1.1",
|
||||
"uuid": "^9.0.0",
|
||||
"zod": "^3.21.4"
|
||||
|
@ -102,7 +102,8 @@
|
||||
"openInNewTab": "Open in New Tab",
|
||||
"dontAskMeAgain": "Don't ask me again",
|
||||
"areYouSure": "Are you sure?",
|
||||
"imagePrompt": "Image Prompt"
|
||||
"imagePrompt": "Image Prompt",
|
||||
"clearNodes": "Are you sure you want to clear all nodes?"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Generations",
|
||||
@ -596,7 +597,9 @@
|
||||
"initialImageNotSetDesc": "Could not load initial image",
|
||||
"nodesSaved": "Nodes Saved",
|
||||
"nodesLoaded": "Nodes Loaded",
|
||||
"nodesLoadedFailed": "Failed To Load Nodes"
|
||||
"nodesLoadedFailed": "Failed To Load Nodes",
|
||||
"nodesCleared": "Nodes Cleared"
|
||||
|
||||
},
|
||||
"tooltip": {
|
||||
"feature": {
|
||||
@ -681,6 +684,7 @@
|
||||
"nodes": {
|
||||
"reloadSchema": "Reload Schema",
|
||||
"saveNodes": "Save Nodes",
|
||||
"loadNodes": "Load Nodes"
|
||||
"loadNodes": "Load Nodes",
|
||||
"clearNodes": "Clear Nodes"
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ import {
|
||||
} from './listeners/imageUrlsReceived';
|
||||
import { addInitialImageSelectedListener } from './listeners/initialImageSelected';
|
||||
import { addModelSelectedListener } from './listeners/modelSelected';
|
||||
import { addModelsLoadedListener } from './listeners/modelsLoaded';
|
||||
import { addReceivedOpenAPISchemaListener } from './listeners/receivedOpenAPISchema';
|
||||
import {
|
||||
addReceivedPageOfImagesFulfilledListener,
|
||||
@ -224,3 +225,4 @@ addModelSelectedListener();
|
||||
|
||||
// app startup
|
||||
addAppStartedListener();
|
||||
addModelsLoadedListener();
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { startAppListening } from '..';
|
||||
import { imageMetadataReceived } from 'services/api/thunks/image';
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { controlNetImageProcessed } from 'features/controlNet/store/actions';
|
||||
import { Graph } from 'services/api/types';
|
||||
import { sessionCreated } from 'services/api/thunks/session';
|
||||
import { sessionReadyToInvoke } from 'features/system/store/actions';
|
||||
import { socketInvocationComplete } from 'services/events/actions';
|
||||
import { isImageOutput } from 'services/api/guards';
|
||||
import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice';
|
||||
import { sessionReadyToInvoke } from 'features/system/store/actions';
|
||||
import { isImageOutput } from 'services/api/guards';
|
||||
import { imageDTOReceived } from 'services/api/thunks/image';
|
||||
import { sessionCreated } from 'services/api/thunks/session';
|
||||
import { Graph } from 'services/api/types';
|
||||
import { socketInvocationComplete } from 'services/events/actions';
|
||||
import { startAppListening } from '..';
|
||||
|
||||
const moduleLog = log.child({ namespace: 'controlNet' });
|
||||
|
||||
@ -63,10 +63,8 @@ export const addControlNetImageProcessedListener = () => {
|
||||
|
||||
// Wait for the ImageDTO to be received
|
||||
const [imageMetadataReceivedAction] = await take(
|
||||
(
|
||||
action
|
||||
): action is ReturnType<typeof imageMetadataReceived.fulfilled> =>
|
||||
imageMetadataReceived.fulfilled.match(action) &&
|
||||
(action): action is ReturnType<typeof imageDTOReceived.fulfilled> =>
|
||||
imageDTOReceived.fulfilled.match(action) &&
|
||||
action.payload.image_name === image_name
|
||||
);
|
||||
const processedControlImage = imageMetadataReceivedAction.payload;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { startAppListening } from '..';
|
||||
import { imageMetadataReceived } from 'services/api/thunks/image';
|
||||
import { boardImagesApi } from 'services/api/endpoints/boardImages';
|
||||
import { imageDTOReceived } from 'services/api/thunks/image';
|
||||
import { startAppListening } from '..';
|
||||
|
||||
const moduleLog = log.child({ namespace: 'boards' });
|
||||
|
||||
@ -17,7 +17,7 @@ export const addImageAddedToBoardFulfilledListener = () => {
|
||||
);
|
||||
|
||||
dispatch(
|
||||
imageMetadataReceived({
|
||||
imageDTOReceived({
|
||||
image_name,
|
||||
})
|
||||
);
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { startAppListening } from '..';
|
||||
import { imageMetadataReceived, imageUpdated } from 'services/api/thunks/image';
|
||||
import { imageUpserted } from 'features/gallery/store/gallerySlice';
|
||||
import { imageDTOReceived, imageUpdated } from 'services/api/thunks/image';
|
||||
import { startAppListening } from '..';
|
||||
|
||||
const moduleLog = log.child({ namespace: 'image' });
|
||||
|
||||
export const addImageMetadataReceivedFulfilledListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: imageMetadataReceived.fulfilled,
|
||||
actionCreator: imageDTOReceived.fulfilled,
|
||||
effect: (action, { getState, dispatch }) => {
|
||||
const image = action.payload;
|
||||
|
||||
@ -40,7 +40,7 @@ export const addImageMetadataReceivedFulfilledListener = () => {
|
||||
|
||||
export const addImageMetadataReceivedRejectedListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: imageMetadataReceived.rejected,
|
||||
actionCreator: imageDTOReceived.rejected,
|
||||
effect: (action, { getState, dispatch }) => {
|
||||
moduleLog.debug(
|
||||
{ data: { image: action.meta.arg } },
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { startAppListening } from '..';
|
||||
import { imageMetadataReceived } from 'services/api/thunks/image';
|
||||
import { boardImagesApi } from 'services/api/endpoints/boardImages';
|
||||
import { imageDTOReceived } from 'services/api/thunks/image';
|
||||
import { startAppListening } from '..';
|
||||
|
||||
const moduleLog = log.child({ namespace: 'boards' });
|
||||
|
||||
@ -17,7 +17,7 @@ export const addImageRemovedFromBoardFulfilledListener = () => {
|
||||
);
|
||||
|
||||
dispatch(
|
||||
imageMetadataReceived({
|
||||
imageDTOReceived({
|
||||
image_name,
|
||||
})
|
||||
);
|
||||
|
@ -14,7 +14,7 @@ export const addModelSelectedListener = () => {
|
||||
actionCreator: modelSelected,
|
||||
effect: (action, { getState, dispatch }) => {
|
||||
const state = getState();
|
||||
const [base_model, type, name] = action.payload.split('/');
|
||||
const { base_model, model_name } = action.payload;
|
||||
|
||||
if (state.generation.model?.base_model !== base_model) {
|
||||
dispatch(
|
||||
@ -30,11 +30,7 @@ export const addModelSelectedListener = () => {
|
||||
// TODO: controlnet cleared
|
||||
}
|
||||
|
||||
const newModel = zMainModel.parse({
|
||||
id: action.payload,
|
||||
base_model,
|
||||
name,
|
||||
});
|
||||
const newModel = zMainModel.parse(action.payload);
|
||||
|
||||
dispatch(modelChanged(newModel));
|
||||
},
|
||||
|
@ -0,0 +1,42 @@
|
||||
import { modelChanged } from 'features/parameters/store/generationSlice';
|
||||
import { some } from 'lodash-es';
|
||||
import { modelsApi } from 'services/api/endpoints/models';
|
||||
import { startAppListening } from '..';
|
||||
|
||||
export const addModelsLoadedListener = () => {
|
||||
startAppListening({
|
||||
matcher: modelsApi.endpoints.getMainModels.matchFulfilled,
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
// models loaded, we need to ensure the selected model is available and if not, select the first one
|
||||
|
||||
const currentModel = getState().generation.model;
|
||||
|
||||
const isCurrentModelAvailable = some(
|
||||
action.payload.entities,
|
||||
(m) =>
|
||||
m?.model_name === currentModel?.model_name &&
|
||||
m?.base_model === currentModel?.base_model
|
||||
);
|
||||
|
||||
if (isCurrentModelAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstModelId = action.payload.ids[0];
|
||||
const firstModel = action.payload.entities[firstModelId];
|
||||
|
||||
if (!firstModel) {
|
||||
// No models loaded at all
|
||||
dispatch(modelChanged(null));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
modelChanged({
|
||||
base_model: firstModel.base_model,
|
||||
model_name: firstModel.model_name,
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
@ -30,6 +30,7 @@ export const addSessionCreatedRejectedListener = () => {
|
||||
effect: (action, { getState, dispatch }) => {
|
||||
if (action.payload) {
|
||||
const { arg, error } = action.payload;
|
||||
const stringifiedError = JSON.stringify(error);
|
||||
moduleLog.error(
|
||||
{
|
||||
data: {
|
||||
@ -37,7 +38,7 @@ export const addSessionCreatedRejectedListener = () => {
|
||||
error: serializeError(error),
|
||||
},
|
||||
},
|
||||
`Problem creating session`
|
||||
`Problem creating session: ${stringifiedError}`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -33,6 +33,7 @@ export const addSessionInvokedRejectedListener = () => {
|
||||
effect: (action, { getState, dispatch }) => {
|
||||
if (action.payload) {
|
||||
const { arg, error } = action.payload;
|
||||
const stringifiedError = JSON.stringify(error);
|
||||
moduleLog.error(
|
||||
{
|
||||
data: {
|
||||
@ -40,7 +41,7 @@ export const addSessionInvokedRejectedListener = () => {
|
||||
error: serializeError(error),
|
||||
},
|
||||
},
|
||||
`Problem invoking session`
|
||||
`Problem invoking session: ${stringifiedError}`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
|
||||
import { startAppListening } from '../..';
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
|
||||
import { progressImageSet } from 'features/system/store/systemSlice';
|
||||
import { boardImagesApi } from 'services/api/endpoints/boardImages';
|
||||
import { isImageOutput } from 'services/api/guards';
|
||||
import { imageDTOReceived } from 'services/api/thunks/image';
|
||||
import { sessionCanceled } from 'services/api/thunks/session';
|
||||
import {
|
||||
appSocketInvocationComplete,
|
||||
socketInvocationComplete,
|
||||
} from 'services/events/actions';
|
||||
import { imageMetadataReceived } from 'services/api/thunks/image';
|
||||
import { sessionCanceled } from 'services/api/thunks/session';
|
||||
import { isImageOutput } from 'services/api/guards';
|
||||
import { progressImageSet } from 'features/system/store/systemSlice';
|
||||
import { boardImagesApi } from 'services/api/endpoints/boardImages';
|
||||
import { startAppListening } from '../..';
|
||||
|
||||
const moduleLog = log.child({ namespace: 'socketio' });
|
||||
const nodeDenylist = ['dataURL_image'];
|
||||
@ -42,13 +42,13 @@ export const addInvocationCompleteEventListener = () => {
|
||||
|
||||
// Get its metadata
|
||||
dispatch(
|
||||
imageMetadataReceived({
|
||||
imageDTOReceived({
|
||||
image_name,
|
||||
})
|
||||
);
|
||||
|
||||
const [{ payload: imageDTO }] = await take(
|
||||
imageMetadataReceived.fulfilled.match
|
||||
imageDTOReceived.fulfilled.match
|
||||
);
|
||||
|
||||
// Handle canvas image
|
||||
|
@ -13,7 +13,7 @@ export const addInvocationErrorEventListener = () => {
|
||||
effect: (action, { dispatch, getState }) => {
|
||||
moduleLog.error(
|
||||
action.payload,
|
||||
`Invocation error (${action.payload.data.node.type})`
|
||||
`Invocation error (${action.payload.data.node.type}): ${action.payload.data.error}`
|
||||
);
|
||||
dispatch(appSocketInvocationError(action.payload));
|
||||
},
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
AnyAction,
|
||||
ThunkDispatch,
|
||||
autoBatchEnhancer,
|
||||
combineReducers,
|
||||
configureStore,
|
||||
} from '@reduxjs/toolkit';
|
||||
@ -79,14 +80,18 @@ const rememberedKeys: (keyof typeof allReducers)[] = [
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: rememberedRootReducer,
|
||||
enhancers: [
|
||||
rememberEnhancer(window.localStorage, rememberedKeys, {
|
||||
persistDebounce: 300,
|
||||
serialize,
|
||||
unserialize,
|
||||
prefix: LOCALSTORAGE_PREFIX,
|
||||
}),
|
||||
],
|
||||
enhancers: (existingEnhancers) => {
|
||||
return existingEnhancers
|
||||
.concat(
|
||||
rememberEnhancer(window.localStorage, rememberedKeys, {
|
||||
persistDebounce: 300,
|
||||
serialize,
|
||||
unserialize,
|
||||
prefix: LOCALSTORAGE_PREFIX,
|
||||
})
|
||||
)
|
||||
.concat(autoBatchEnhancer());
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
immutableCheck: false,
|
||||
|
@ -47,8 +47,8 @@ const ParamEmbeddingPopover = (props: Props) => {
|
||||
const disabled = currentMainModel?.base_model !== embedding.base_model;
|
||||
|
||||
data.push({
|
||||
value: embedding.name,
|
||||
label: embedding.name,
|
||||
value: embedding.model_name,
|
||||
label: embedding.model_name,
|
||||
group: MODEL_TYPE_MAP[embedding.base_model],
|
||||
disabled,
|
||||
tooltip: disabled
|
||||
|
@ -45,7 +45,11 @@ import {
|
||||
FaShare,
|
||||
FaShareAlt,
|
||||
} from 'react-icons/fa';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import {
|
||||
useGetImageDTOQuery,
|
||||
useGetImageMetadataQuery,
|
||||
} from 'services/api/endpoints/images';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
|
||||
|
||||
const currentImageButtonsSelector = createSelector(
|
||||
@ -128,10 +132,23 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
||||
useRecallParameters();
|
||||
|
||||
const { currentData: image } = useGetImageDTOQuery(
|
||||
const [debouncedMetadataQueryArg, debounceState] = useDebounce(
|
||||
lastSelectedImage,
|
||||
500
|
||||
);
|
||||
|
||||
const { currentData: image, isFetching } = useGetImageDTOQuery(
|
||||
lastSelectedImage ?? skipToken
|
||||
);
|
||||
|
||||
const { currentData: metadataData } = useGetImageMetadataQuery(
|
||||
debounceState.isPending()
|
||||
? skipToken
|
||||
: debouncedMetadataQueryArg ?? skipToken
|
||||
);
|
||||
|
||||
const metadata = metadataData?.metadata;
|
||||
|
||||
// const handleCopyImage = useCallback(async () => {
|
||||
// if (!image?.url) {
|
||||
// return;
|
||||
@ -193,29 +210,26 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
}, [toaster, t, image]);
|
||||
|
||||
const handleClickUseAllParameters = useCallback(() => {
|
||||
recallAllParameters(image);
|
||||
}, [image, recallAllParameters]);
|
||||
recallAllParameters(metadata);
|
||||
}, [metadata, recallAllParameters]);
|
||||
|
||||
useHotkeys(
|
||||
'a',
|
||||
() => {
|
||||
handleClickUseAllParameters;
|
||||
},
|
||||
[image, recallAllParameters]
|
||||
[metadata, recallAllParameters]
|
||||
);
|
||||
|
||||
const handleUseSeed = useCallback(() => {
|
||||
recallSeed(image?.metadata?.seed);
|
||||
}, [image, recallSeed]);
|
||||
recallSeed(metadata?.seed);
|
||||
}, [metadata?.seed, recallSeed]);
|
||||
|
||||
useHotkeys('s', handleUseSeed, [image]);
|
||||
|
||||
const handleUsePrompt = useCallback(() => {
|
||||
recallBothPrompts(
|
||||
image?.metadata?.positive_conditioning,
|
||||
image?.metadata?.negative_conditioning
|
||||
);
|
||||
}, [image, recallBothPrompts]);
|
||||
recallBothPrompts(metadata?.positive_prompt, metadata?.negative_prompt);
|
||||
}, [metadata?.negative_prompt, metadata?.positive_prompt, recallBothPrompts]);
|
||||
|
||||
useHotkeys('p', handleUsePrompt, [image]);
|
||||
|
||||
@ -440,7 +454,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
icon={<FaQuoteRight />}
|
||||
tooltip={`${t('parameters.usePrompt')} (P)`}
|
||||
aria-label={`${t('parameters.usePrompt')} (P)`}
|
||||
isDisabled={!image?.metadata?.positive_conditioning}
|
||||
isDisabled={!metadata?.positive_prompt}
|
||||
onClick={handleUsePrompt}
|
||||
/>
|
||||
|
||||
@ -448,7 +462,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
icon={<FaSeedling />}
|
||||
tooltip={`${t('parameters.useSeed')} (S)`}
|
||||
aria-label={`${t('parameters.useSeed')} (S)`}
|
||||
isDisabled={!image?.metadata?.seed}
|
||||
isDisabled={!metadata?.seed}
|
||||
onClick={handleUseSeed}
|
||||
/>
|
||||
|
||||
@ -456,10 +470,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
icon={<FaAsterisk />}
|
||||
tooltip={`${t('parameters.useAll')} (A)`}
|
||||
aria-label={`${t('parameters.useAll')} (A)`}
|
||||
isDisabled={
|
||||
// not sure what this list should be
|
||||
!['t2l', 'l2l', 'inpaint'].includes(String(image?.metadata?.type))
|
||||
}
|
||||
isDisabled={!metadata}
|
||||
onClick={handleClickUseAllParameters}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
|
@ -11,7 +11,9 @@ import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import { selectLastSelectedImage } from 'features/gallery/store/gallerySlice';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import { useNextPrevImage } from '../hooks/useNextPrevImage';
|
||||
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
|
||||
import NextPrevImageButtons from './NextPrevImageButtons';
|
||||
|
||||
@ -49,6 +51,45 @@ const CurrentImagePreview = () => {
|
||||
shouldAntialiasProgressImage,
|
||||
} = useAppSelector(imagesSelector);
|
||||
|
||||
const {
|
||||
handlePrevImage,
|
||||
handleNextImage,
|
||||
prevImageId,
|
||||
nextImageId,
|
||||
isOnLastImage,
|
||||
handleLoadMoreImages,
|
||||
areMoreImagesAvailable,
|
||||
isFetching,
|
||||
} = useNextPrevImage();
|
||||
|
||||
useHotkeys(
|
||||
'left',
|
||||
() => {
|
||||
handlePrevImage();
|
||||
},
|
||||
[prevImageId]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'right',
|
||||
() => {
|
||||
if (isOnLastImage && areMoreImagesAvailable && !isFetching) {
|
||||
handleLoadMoreImages();
|
||||
return;
|
||||
}
|
||||
if (!isOnLastImage) {
|
||||
handleNextImage();
|
||||
}
|
||||
},
|
||||
[
|
||||
nextImageId,
|
||||
isOnLastImage,
|
||||
areMoreImagesAvailable,
|
||||
handleLoadMoreImages,
|
||||
isFetching,
|
||||
]
|
||||
);
|
||||
|
||||
const {
|
||||
currentData: imageDTO,
|
||||
isLoading,
|
||||
@ -118,7 +159,6 @@ const CurrentImagePreview = () => {
|
||||
width: 'full',
|
||||
height: 'full',
|
||||
borderRadius: 'base',
|
||||
overflow: 'scroll',
|
||||
}}
|
||||
>
|
||||
<ImageMetadataViewer image={imageDTO} />
|
||||
|
@ -6,10 +6,7 @@ import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu';
|
||||
import {
|
||||
imagesAddedToBatch,
|
||||
selectionAddedToBatch,
|
||||
} from 'features/batch/store/batchSlice';
|
||||
import { imagesAddedToBatch } from 'features/batch/store/batchSlice';
|
||||
import {
|
||||
resizeAndScaleCanvas,
|
||||
setInitialCanvasImage,
|
||||
@ -24,6 +21,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { FaExpand, FaFolder, FaShare, FaTrash } from 'react-icons/fa';
|
||||
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
||||
import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages';
|
||||
import { useGetImageMetadataQuery } from 'services/api/endpoints/images';
|
||||
import { ImageDTO } from 'services/api/types';
|
||||
import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext';
|
||||
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
|
||||
@ -38,24 +36,17 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
||||
() =>
|
||||
createSelector(
|
||||
[stateSelector],
|
||||
({ gallery, batch }) => {
|
||||
({ gallery }) => {
|
||||
const selectionCount = gallery.selection.length;
|
||||
const isInBatch = batch.imageNames.includes(image.image_name);
|
||||
|
||||
return { selectionCount, isInBatch };
|
||||
return { selectionCount };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
),
|
||||
[image.image_name]
|
||||
[]
|
||||
);
|
||||
const { selectionCount, isInBatch } = useAppSelector(selector);
|
||||
const { selectionCount } = useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const toaster = useAppToaster();
|
||||
|
||||
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
|
||||
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
||||
|
||||
const { onClickAddToBoard } = useContext(AddImageToBoardContext);
|
||||
|
||||
@ -66,178 +57,17 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
||||
dispatch(imageToDeleteSelected(image));
|
||||
}, [dispatch, image]);
|
||||
|
||||
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
||||
useRecallParameters();
|
||||
|
||||
const [removeFromBoard] = useRemoveImageFromBoardMutation();
|
||||
|
||||
// Recall parameters handlers
|
||||
const handleRecallPrompt = useCallback(() => {
|
||||
recallBothPrompts(
|
||||
image.metadata?.positive_conditioning,
|
||||
image.metadata?.negative_conditioning
|
||||
);
|
||||
}, [
|
||||
image.metadata?.negative_conditioning,
|
||||
image.metadata?.positive_conditioning,
|
||||
recallBothPrompts,
|
||||
]);
|
||||
|
||||
const handleRecallSeed = useCallback(() => {
|
||||
recallSeed(image.metadata?.seed);
|
||||
}, [image, recallSeed]);
|
||||
|
||||
const handleSendToImageToImage = useCallback(() => {
|
||||
dispatch(sentImageToImg2Img());
|
||||
dispatch(initialImageSelected(image));
|
||||
}, [dispatch, image]);
|
||||
|
||||
// const handleRecallInitialImage = useCallback(() => {
|
||||
// recallInitialImage(image.metadata.invokeai?.node?.image);
|
||||
// }, [image, recallInitialImage]);
|
||||
|
||||
const handleSendToCanvas = () => {
|
||||
dispatch(sentImageToCanvas());
|
||||
dispatch(setInitialCanvasImage(image));
|
||||
dispatch(resizeAndScaleCanvas());
|
||||
dispatch(setActiveTab('unifiedCanvas'));
|
||||
|
||||
toaster({
|
||||
title: t('toast.sentToUnifiedCanvas'),
|
||||
status: 'success',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUseAllParameters = useCallback(() => {
|
||||
recallAllParameters(image);
|
||||
}, [image, recallAllParameters]);
|
||||
|
||||
const handleLightBox = () => {
|
||||
// dispatch(setCurrentImage(image));
|
||||
// dispatch(setIsLightboxOpen(true));
|
||||
};
|
||||
|
||||
const handleAddToBoard = useCallback(() => {
|
||||
onClickAddToBoard(image);
|
||||
}, [image, onClickAddToBoard]);
|
||||
|
||||
const handleRemoveFromBoard = useCallback(() => {
|
||||
if (!image.board_id) {
|
||||
return;
|
||||
}
|
||||
removeFromBoard({ board_id: image.board_id, image_name: image.image_name });
|
||||
}, [image.board_id, image.image_name, removeFromBoard]);
|
||||
|
||||
const handleOpenInNewTab = () => {
|
||||
window.open(image.image_url, '_blank');
|
||||
};
|
||||
|
||||
const handleAddSelectionToBatch = useCallback(() => {
|
||||
dispatch(selectionAddedToBatch());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleAddToBatch = useCallback(() => {
|
||||
dispatch(imagesAddedToBatch([image.image_name]));
|
||||
}, [dispatch, image.image_name]);
|
||||
|
||||
return (
|
||||
<ContextMenu<HTMLDivElement>
|
||||
menuProps={{ size: 'sm', isLazy: true }}
|
||||
renderMenu={() => (
|
||||
<MenuList sx={{ visibility: 'visible !important' }}>
|
||||
{selectionCount === 1 ? (
|
||||
<>
|
||||
<MenuItem
|
||||
icon={<ExternalLinkIcon />}
|
||||
onClickCapture={handleOpenInNewTab}
|
||||
>
|
||||
{t('common.openInNewTab')}
|
||||
</MenuItem>
|
||||
{isLightboxEnabled && (
|
||||
<MenuItem icon={<FaExpand />} onClickCapture={handleLightBox}>
|
||||
{t('parameters.openInViewer')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
onClickCapture={handleRecallPrompt}
|
||||
isDisabled={
|
||||
image?.metadata?.positive_conditioning === undefined
|
||||
}
|
||||
>
|
||||
{t('parameters.usePrompt')}
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
onClickCapture={handleRecallSeed}
|
||||
isDisabled={image?.metadata?.seed === undefined}
|
||||
>
|
||||
{t('parameters.useSeed')}
|
||||
</MenuItem>
|
||||
{/* <MenuItem
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
onClickCapture={handleRecallInitialImage}
|
||||
isDisabled={image?.metadata?.type !== 'img2img'}
|
||||
>
|
||||
{t('parameters.useInitImg')}
|
||||
</MenuItem> */}
|
||||
<MenuItem
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
onClickCapture={handleUseAllParameters}
|
||||
isDisabled={
|
||||
// what should these be
|
||||
!['t2l', 'l2l', 'inpaint'].includes(
|
||||
String(image?.metadata?.type)
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('parameters.useAll')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<FaShare />}
|
||||
onClickCapture={handleSendToImageToImage}
|
||||
id="send-to-img2img"
|
||||
>
|
||||
{t('parameters.sendToImg2Img')}
|
||||
</MenuItem>
|
||||
{isCanvasEnabled && (
|
||||
<MenuItem
|
||||
icon={<FaShare />}
|
||||
onClickCapture={handleSendToCanvas}
|
||||
id="send-to-canvas"
|
||||
>
|
||||
{t('parameters.sendToUnifiedCanvas')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{/* <MenuItem
|
||||
icon={<FaFolder />}
|
||||
isDisabled={isInBatch}
|
||||
onClickCapture={handleAddToBatch}
|
||||
>
|
||||
Add to Batch
|
||||
</MenuItem> */}
|
||||
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToBoard}>
|
||||
{image.board_id ? 'Change Board' : 'Add to Board'}
|
||||
</MenuItem>
|
||||
{image.board_id && (
|
||||
<MenuItem
|
||||
icon={<FaFolder />}
|
||||
onClickCapture={handleRemoveFromBoard}
|
||||
>
|
||||
Remove from Board
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
sx={{ color: 'error.600', _dark: { color: 'error.300' } }}
|
||||
icon={<FaTrash />}
|
||||
onClickCapture={handleDelete}
|
||||
>
|
||||
{t('gallery.deleteImage')}
|
||||
</MenuItem>
|
||||
</>
|
||||
<SingleSelectionMenuItems image={image} />
|
||||
) : (
|
||||
<>
|
||||
<MenuItem
|
||||
@ -271,3 +101,185 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
||||
};
|
||||
|
||||
export default memo(ImageContextMenu);
|
||||
|
||||
type SingleSelectionMenuItemsProps = {
|
||||
image: ImageDTO;
|
||||
};
|
||||
|
||||
const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
||||
const { image } = props;
|
||||
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createSelector(
|
||||
[stateSelector],
|
||||
({ batch }) => {
|
||||
const isInBatch = batch.imageNames.includes(image.image_name);
|
||||
|
||||
return { isInBatch };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
),
|
||||
[image.image_name]
|
||||
);
|
||||
|
||||
const { isInBatch } = useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const toaster = useAppToaster();
|
||||
|
||||
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
|
||||
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
||||
|
||||
const { onClickAddToBoard } = useContext(AddImageToBoardContext);
|
||||
|
||||
const { currentData } = useGetImageMetadataQuery(image.image_name);
|
||||
|
||||
const metadata = currentData?.metadata;
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
dispatch(imageToDeleteSelected(image));
|
||||
}, [dispatch, image]);
|
||||
|
||||
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
||||
useRecallParameters();
|
||||
|
||||
const [removeFromBoard] = useRemoveImageFromBoardMutation();
|
||||
|
||||
// Recall parameters handlers
|
||||
const handleRecallPrompt = useCallback(() => {
|
||||
recallBothPrompts(metadata?.positive_prompt, metadata?.negative_prompt);
|
||||
}, [metadata?.negative_prompt, metadata?.positive_prompt, recallBothPrompts]);
|
||||
|
||||
const handleRecallSeed = useCallback(() => {
|
||||
recallSeed(metadata?.seed);
|
||||
}, [metadata?.seed, recallSeed]);
|
||||
|
||||
const handleSendToImageToImage = useCallback(() => {
|
||||
dispatch(sentImageToImg2Img());
|
||||
dispatch(initialImageSelected(image));
|
||||
}, [dispatch, image]);
|
||||
|
||||
const handleSendToCanvas = () => {
|
||||
dispatch(sentImageToCanvas());
|
||||
dispatch(setInitialCanvasImage(image));
|
||||
dispatch(resizeAndScaleCanvas());
|
||||
dispatch(setActiveTab('unifiedCanvas'));
|
||||
|
||||
toaster({
|
||||
title: t('toast.sentToUnifiedCanvas'),
|
||||
status: 'success',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUseAllParameters = useCallback(() => {
|
||||
console.log(metadata);
|
||||
recallAllParameters(metadata);
|
||||
}, [metadata, recallAllParameters]);
|
||||
|
||||
const handleLightBox = () => {
|
||||
// dispatch(setCurrentImage(image));
|
||||
// dispatch(setIsLightboxOpen(true));
|
||||
};
|
||||
|
||||
const handleAddToBoard = useCallback(() => {
|
||||
onClickAddToBoard(image);
|
||||
}, [image, onClickAddToBoard]);
|
||||
|
||||
const handleRemoveFromBoard = useCallback(() => {
|
||||
if (!image.board_id) {
|
||||
return;
|
||||
}
|
||||
removeFromBoard({ board_id: image.board_id, image_name: image.image_name });
|
||||
}, [image.board_id, image.image_name, removeFromBoard]);
|
||||
|
||||
const handleOpenInNewTab = () => {
|
||||
window.open(image.image_url, '_blank');
|
||||
};
|
||||
|
||||
const handleAddToBatch = useCallback(() => {
|
||||
dispatch(imagesAddedToBatch([image.image_name]));
|
||||
}, [dispatch, image.image_name]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem icon={<ExternalLinkIcon />} onClickCapture={handleOpenInNewTab}>
|
||||
{t('common.openInNewTab')}
|
||||
</MenuItem>
|
||||
{isLightboxEnabled && (
|
||||
<MenuItem icon={<FaExpand />} onClickCapture={handleLightBox}>
|
||||
{t('parameters.openInViewer')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
onClickCapture={handleRecallPrompt}
|
||||
isDisabled={
|
||||
metadata?.positive_prompt === undefined &&
|
||||
metadata?.negative_prompt === undefined
|
||||
}
|
||||
>
|
||||
{t('parameters.usePrompt')}
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
onClickCapture={handleRecallSeed}
|
||||
isDisabled={metadata?.seed === undefined}
|
||||
>
|
||||
{t('parameters.useSeed')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
onClickCapture={handleUseAllParameters}
|
||||
isDisabled={!metadata}
|
||||
>
|
||||
{t('parameters.useAll')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<FaShare />}
|
||||
onClickCapture={handleSendToImageToImage}
|
||||
id="send-to-img2img"
|
||||
>
|
||||
{t('parameters.sendToImg2Img')}
|
||||
</MenuItem>
|
||||
{isCanvasEnabled && (
|
||||
<MenuItem
|
||||
icon={<FaShare />}
|
||||
onClickCapture={handleSendToCanvas}
|
||||
id="send-to-canvas"
|
||||
>
|
||||
{t('parameters.sendToUnifiedCanvas')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
icon={<FaFolder />}
|
||||
isDisabled={isInBatch}
|
||||
onClickCapture={handleAddToBatch}
|
||||
>
|
||||
Add to Batch
|
||||
</MenuItem>
|
||||
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToBoard}>
|
||||
{image.board_id ? 'Change Board' : 'Add to Board'}
|
||||
</MenuItem>
|
||||
{image.board_id && (
|
||||
<MenuItem icon={<FaFolder />} onClickCapture={handleRemoveFromBoard}>
|
||||
Remove from Board
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
sx={{ color: 'error.600', _dark: { color: 'error.300' } }}
|
||||
icon={<FaTrash />}
|
||||
onClickCapture={handleDelete}
|
||||
>
|
||||
{t('gallery.deleteImage')}
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,212 @@
|
||||
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
||||
import { useCallback } from 'react';
|
||||
import { UnsafeImageMetadata } from 'services/api/endpoints/images';
|
||||
import MetadataItem from './MetadataItem';
|
||||
|
||||
type Props = {
|
||||
metadata?: UnsafeImageMetadata['metadata'];
|
||||
};
|
||||
|
||||
const ImageMetadataActions = (props: Props) => {
|
||||
const { metadata } = props;
|
||||
|
||||
const {
|
||||
recallBothPrompts,
|
||||
recallPositivePrompt,
|
||||
recallNegativePrompt,
|
||||
recallSeed,
|
||||
recallInitialImage,
|
||||
recallCfgScale,
|
||||
recallModel,
|
||||
recallScheduler,
|
||||
recallSteps,
|
||||
recallWidth,
|
||||
recallHeight,
|
||||
recallStrength,
|
||||
recallAllParameters,
|
||||
} = useRecallParameters();
|
||||
|
||||
const handleRecallPositivePrompt = useCallback(() => {
|
||||
recallPositivePrompt(metadata?.positive_prompt);
|
||||
}, [metadata?.positive_prompt, recallPositivePrompt]);
|
||||
|
||||
const handleRecallNegativePrompt = useCallback(() => {
|
||||
recallNegativePrompt(metadata?.negative_prompt);
|
||||
}, [metadata?.negative_prompt, recallNegativePrompt]);
|
||||
|
||||
const handleRecallSeed = useCallback(() => {
|
||||
recallSeed(metadata?.seed);
|
||||
}, [metadata?.seed, recallSeed]);
|
||||
|
||||
const handleRecallModel = useCallback(() => {
|
||||
recallModel(metadata?.model);
|
||||
}, [metadata?.model, recallModel]);
|
||||
|
||||
const handleRecallWidth = useCallback(() => {
|
||||
recallWidth(metadata?.width);
|
||||
}, [metadata?.width, recallWidth]);
|
||||
|
||||
const handleRecallHeight = useCallback(() => {
|
||||
recallHeight(metadata?.height);
|
||||
}, [metadata?.height, recallHeight]);
|
||||
|
||||
const handleRecallScheduler = useCallback(() => {
|
||||
recallScheduler(metadata?.scheduler);
|
||||
}, [metadata?.scheduler, recallScheduler]);
|
||||
|
||||
const handleRecallSteps = useCallback(() => {
|
||||
recallSteps(metadata?.steps);
|
||||
}, [metadata?.steps, recallSteps]);
|
||||
|
||||
const handleRecallCfgScale = useCallback(() => {
|
||||
recallCfgScale(metadata?.cfg_scale);
|
||||
}, [metadata?.cfg_scale, recallCfgScale]);
|
||||
|
||||
const handleRecallStrength = useCallback(() => {
|
||||
recallStrength(metadata?.strength);
|
||||
}, [metadata?.strength, recallStrength]);
|
||||
|
||||
if (!metadata || Object.keys(metadata).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{metadata.generation_mode && (
|
||||
<MetadataItem
|
||||
label="Generation Mode"
|
||||
value={metadata.generation_mode}
|
||||
/>
|
||||
)}
|
||||
{metadata.positive_prompt && (
|
||||
<MetadataItem
|
||||
label="Positive Prompt"
|
||||
labelPosition="top"
|
||||
value={metadata.positive_prompt}
|
||||
onClick={handleRecallPositivePrompt}
|
||||
/>
|
||||
)}
|
||||
{metadata.negative_prompt && (
|
||||
<MetadataItem
|
||||
label="Negative Prompt"
|
||||
labelPosition="top"
|
||||
value={metadata.negative_prompt}
|
||||
onClick={handleRecallNegativePrompt}
|
||||
/>
|
||||
)}
|
||||
{metadata.seed !== undefined && (
|
||||
<MetadataItem
|
||||
label="Seed"
|
||||
value={metadata.seed}
|
||||
onClick={handleRecallSeed}
|
||||
/>
|
||||
)}
|
||||
{metadata.model !== undefined && (
|
||||
<MetadataItem
|
||||
label="Model"
|
||||
value={metadata.model.model_name}
|
||||
onClick={handleRecallModel}
|
||||
/>
|
||||
)}
|
||||
{metadata.width && (
|
||||
<MetadataItem
|
||||
label="Width"
|
||||
value={metadata.width}
|
||||
onClick={handleRecallWidth}
|
||||
/>
|
||||
)}
|
||||
{metadata.height && (
|
||||
<MetadataItem
|
||||
label="Height"
|
||||
value={metadata.height}
|
||||
onClick={handleRecallHeight}
|
||||
/>
|
||||
)}
|
||||
{/* {metadata.threshold !== undefined && (
|
||||
<MetadataItem
|
||||
label="Noise Threshold"
|
||||
value={metadata.threshold}
|
||||
onClick={() => dispatch(setThreshold(Number(metadata.threshold)))}
|
||||
/>
|
||||
)}
|
||||
{metadata.perlin !== undefined && (
|
||||
<MetadataItem
|
||||
label="Perlin Noise"
|
||||
value={metadata.perlin}
|
||||
onClick={() => dispatch(setPerlin(Number(metadata.perlin)))}
|
||||
/>
|
||||
)} */}
|
||||
{metadata.scheduler && (
|
||||
<MetadataItem
|
||||
label="Scheduler"
|
||||
value={metadata.scheduler}
|
||||
onClick={handleRecallScheduler}
|
||||
/>
|
||||
)}
|
||||
{metadata.steps && (
|
||||
<MetadataItem
|
||||
label="Steps"
|
||||
value={metadata.steps}
|
||||
onClick={handleRecallSteps}
|
||||
/>
|
||||
)}
|
||||
{metadata.cfg_scale !== undefined && (
|
||||
<MetadataItem
|
||||
label="CFG scale"
|
||||
value={metadata.cfg_scale}
|
||||
onClick={handleRecallCfgScale}
|
||||
/>
|
||||
)}
|
||||
{/* {metadata.variations && metadata.variations.length > 0 && (
|
||||
<MetadataItem
|
||||
label="Seed-weight pairs"
|
||||
value={seedWeightsToString(metadata.variations)}
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
setSeedWeights(seedWeightsToString(metadata.variations))
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{metadata.seamless && (
|
||||
<MetadataItem
|
||||
label="Seamless"
|
||||
value={metadata.seamless}
|
||||
onClick={() => dispatch(setSeamless(metadata.seamless))}
|
||||
/>
|
||||
)}
|
||||
{metadata.hires_fix && (
|
||||
<MetadataItem
|
||||
label="High Resolution Optimization"
|
||||
value={metadata.hires_fix}
|
||||
onClick={() => dispatch(setHiresFix(metadata.hires_fix))}
|
||||
/>
|
||||
)} */}
|
||||
|
||||
{/* {init_image_path && (
|
||||
<MetadataItem
|
||||
label="Initial image"
|
||||
value={init_image_path}
|
||||
isLink
|
||||
onClick={() => dispatch(setInitialImage(init_image_path))}
|
||||
/>
|
||||
)} */}
|
||||
{metadata.strength && (
|
||||
<MetadataItem
|
||||
label="Image to image strength"
|
||||
value={metadata.strength}
|
||||
onClick={handleRecallStrength}
|
||||
/>
|
||||
)}
|
||||
{/* {metadata.fit && (
|
||||
<MetadataItem
|
||||
label="Image to image fit"
|
||||
value={metadata.fit}
|
||||
onClick={() => dispatch(setShouldFitToWidthHeight(metadata.fit))}
|
||||
/>
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageMetadataActions;
|
@ -1,131 +1,74 @@
|
||||
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
Flex,
|
||||
IconButton,
|
||||
Link,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanel,
|
||||
TabPanels,
|
||||
Tabs,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
||||
import { setShouldShowImageDetails } from 'features/ui/store/uiSlice';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import { memo } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaCopy } from 'react-icons/fa';
|
||||
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
||||
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useGetImageMetadataQuery } from 'services/api/endpoints/images';
|
||||
import { ImageDTO } from 'services/api/types';
|
||||
|
||||
type MetadataItemProps = {
|
||||
isLink?: boolean;
|
||||
label: string;
|
||||
onClick?: () => void;
|
||||
value: number | string | boolean;
|
||||
labelPosition?: string;
|
||||
withCopy?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to display an individual metadata item or parameter.
|
||||
*/
|
||||
const MetadataItem = ({
|
||||
label,
|
||||
value,
|
||||
onClick,
|
||||
isLink,
|
||||
labelPosition,
|
||||
withCopy = false,
|
||||
}: MetadataItemProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex gap={2}>
|
||||
{onClick && (
|
||||
<Tooltip label={`Recall ${label}`}>
|
||||
<IconButton
|
||||
aria-label={t('accessibility.useThisParameter')}
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
fontSize={20}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{withCopy && (
|
||||
<Tooltip label={`Copy ${label}`}>
|
||||
<IconButton
|
||||
aria-label={`Copy ${label}`}
|
||||
icon={<FaCopy />}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
fontSize={14}
|
||||
onClick={() => navigator.clipboard.writeText(value.toString())}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Flex direction={labelPosition ? 'column' : 'row'}>
|
||||
<Text fontWeight="semibold" whiteSpace="pre-wrap" pr={2}>
|
||||
{label}:
|
||||
</Text>
|
||||
{isLink ? (
|
||||
<Link href={value.toString()} isExternal wordBreak="break-all">
|
||||
{value.toString()} <ExternalLinkIcon mx="2px" />
|
||||
</Link>
|
||||
) : (
|
||||
<Text overflowY="scroll" wordBreak="break-all">
|
||||
{value.toString()}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
import { useDebounce } from 'use-debounce';
|
||||
import ImageMetadataActions from './ImageMetadataActions';
|
||||
import MetadataJSONViewer from './MetadataJSONViewer';
|
||||
|
||||
type ImageMetadataViewerProps = {
|
||||
image: ImageDTO;
|
||||
};
|
||||
|
||||
/**
|
||||
* Image metadata viewer overlays currently selected image and provides
|
||||
* access to any of its metadata for use in processing.
|
||||
*/
|
||||
const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const {
|
||||
recallBothPrompts,
|
||||
recallPositivePrompt,
|
||||
recallNegativePrompt,
|
||||
recallSeed,
|
||||
recallInitialImage,
|
||||
recallCfgScale,
|
||||
recallModel,
|
||||
recallScheduler,
|
||||
recallSteps,
|
||||
recallWidth,
|
||||
recallHeight,
|
||||
recallStrength,
|
||||
recallAllParameters,
|
||||
} = useRecallParameters();
|
||||
// TODO: fix hotkeys
|
||||
// const dispatch = useAppDispatch();
|
||||
// useHotkeys('esc', () => {
|
||||
// dispatch(setShouldShowImageDetails(false));
|
||||
// });
|
||||
|
||||
useHotkeys('esc', () => {
|
||||
dispatch(setShouldShowImageDetails(false));
|
||||
});
|
||||
const [debouncedMetadataQueryArg, debounceState] = useDebounce(
|
||||
image.image_name,
|
||||
500
|
||||
);
|
||||
|
||||
const sessionId = image?.session_id;
|
||||
const { currentData } = useGetImageMetadataQuery(
|
||||
debounceState.isPending()
|
||||
? skipToken
|
||||
: debouncedMetadataQueryArg ?? skipToken
|
||||
);
|
||||
const metadata = currentData?.metadata;
|
||||
const graph = currentData?.graph;
|
||||
|
||||
const metadata = image?.metadata;
|
||||
const tabData = useMemo(() => {
|
||||
const _tabData: { label: string; data: object; copyTooltip: string }[] = [];
|
||||
|
||||
const { t } = useTranslation();
|
||||
if (metadata) {
|
||||
_tabData.push({
|
||||
label: 'Core Metadata',
|
||||
data: metadata,
|
||||
copyTooltip: 'Copy Core Metadata JSON',
|
||||
});
|
||||
}
|
||||
|
||||
const metadataJSON = JSON.stringify(image, null, 2);
|
||||
if (image) {
|
||||
_tabData.push({
|
||||
label: 'Image Details',
|
||||
data: image,
|
||||
copyTooltip: 'Copy Image Details JSON',
|
||||
});
|
||||
}
|
||||
|
||||
if (graph) {
|
||||
_tabData.push({
|
||||
label: 'Graph',
|
||||
data: graph,
|
||||
copyTooltip: 'Copy Graph JSON',
|
||||
});
|
||||
}
|
||||
return _tabData;
|
||||
}, [metadata, graph, image]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@ -136,11 +79,13 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
|
||||
width: 'full',
|
||||
height: 'full',
|
||||
backdropFilter: 'blur(20px)',
|
||||
bg: 'whiteAlpha.600',
|
||||
bg: 'baseAlpha.200',
|
||||
_dark: {
|
||||
bg: 'blackAlpha.600',
|
||||
},
|
||||
overflow: 'scroll',
|
||||
borderRadius: 'base',
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Flex gap={2}>
|
||||
@ -150,179 +95,42 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
|
||||
<ExternalLinkIcon mx="2px" />
|
||||
</Link>
|
||||
</Flex>
|
||||
{metadata && Object.keys(metadata).length > 0 ? (
|
||||
<>
|
||||
{metadata.type && (
|
||||
<MetadataItem label="Invocation type" value={metadata.type} />
|
||||
)}
|
||||
{sessionId && <MetadataItem label="Session ID" value={sessionId} />}
|
||||
{metadata.positive_conditioning && (
|
||||
<MetadataItem
|
||||
label="Positive Prompt"
|
||||
labelPosition="top"
|
||||
value={metadata.positive_conditioning}
|
||||
onClick={() =>
|
||||
recallPositivePrompt(metadata.positive_conditioning)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{metadata.negative_conditioning && (
|
||||
<MetadataItem
|
||||
label="Negative Prompt"
|
||||
labelPosition="top"
|
||||
value={metadata.negative_conditioning}
|
||||
onClick={() =>
|
||||
recallNegativePrompt(metadata.negative_conditioning)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{metadata.seed !== undefined && (
|
||||
<MetadataItem
|
||||
label="Seed"
|
||||
value={metadata.seed}
|
||||
onClick={() => recallSeed(metadata.seed)}
|
||||
/>
|
||||
)}
|
||||
{metadata.model !== undefined && (
|
||||
<MetadataItem
|
||||
label="Model"
|
||||
value={metadata.model}
|
||||
onClick={() => recallModel(metadata.model)}
|
||||
/>
|
||||
)}
|
||||
{metadata.width && (
|
||||
<MetadataItem
|
||||
label="Width"
|
||||
value={metadata.width}
|
||||
onClick={() => recallWidth(metadata.width)}
|
||||
/>
|
||||
)}
|
||||
{metadata.height && (
|
||||
<MetadataItem
|
||||
label="Height"
|
||||
value={metadata.height}
|
||||
onClick={() => recallHeight(metadata.height)}
|
||||
/>
|
||||
)}
|
||||
{/* {metadata.threshold !== undefined && (
|
||||
<MetadataItem
|
||||
label="Noise Threshold"
|
||||
value={metadata.threshold}
|
||||
onClick={() => dispatch(setThreshold(Number(metadata.threshold)))}
|
||||
/>
|
||||
)}
|
||||
{metadata.perlin !== undefined && (
|
||||
<MetadataItem
|
||||
label="Perlin Noise"
|
||||
value={metadata.perlin}
|
||||
onClick={() => dispatch(setPerlin(Number(metadata.perlin)))}
|
||||
/>
|
||||
)} */}
|
||||
{metadata.scheduler && (
|
||||
<MetadataItem
|
||||
label="Scheduler"
|
||||
value={metadata.scheduler}
|
||||
onClick={() => recallScheduler(metadata.scheduler)}
|
||||
/>
|
||||
)}
|
||||
{metadata.steps && (
|
||||
<MetadataItem
|
||||
label="Steps"
|
||||
value={metadata.steps}
|
||||
onClick={() => recallSteps(metadata.steps)}
|
||||
/>
|
||||
)}
|
||||
{metadata.cfg_scale !== undefined && (
|
||||
<MetadataItem
|
||||
label="CFG scale"
|
||||
value={metadata.cfg_scale}
|
||||
onClick={() => recallCfgScale(metadata.cfg_scale)}
|
||||
/>
|
||||
)}
|
||||
{/* {metadata.variations && metadata.variations.length > 0 && (
|
||||
<MetadataItem
|
||||
label="Seed-weight pairs"
|
||||
value={seedWeightsToString(metadata.variations)}
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
setSeedWeights(seedWeightsToString(metadata.variations))
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{metadata.seamless && (
|
||||
<MetadataItem
|
||||
label="Seamless"
|
||||
value={metadata.seamless}
|
||||
onClick={() => dispatch(setSeamless(metadata.seamless))}
|
||||
/>
|
||||
)}
|
||||
{metadata.hires_fix && (
|
||||
<MetadataItem
|
||||
label="High Resolution Optimization"
|
||||
value={metadata.hires_fix}
|
||||
onClick={() => dispatch(setHiresFix(metadata.hires_fix))}
|
||||
/>
|
||||
)} */}
|
||||
|
||||
{/* {init_image_path && (
|
||||
<MetadataItem
|
||||
label="Initial image"
|
||||
value={init_image_path}
|
||||
isLink
|
||||
onClick={() => dispatch(setInitialImage(init_image_path))}
|
||||
/>
|
||||
)} */}
|
||||
{metadata.strength && (
|
||||
<MetadataItem
|
||||
label="Image to image strength"
|
||||
value={metadata.strength}
|
||||
onClick={() => recallStrength(metadata.strength)}
|
||||
/>
|
||||
)}
|
||||
{/* {metadata.fit && (
|
||||
<MetadataItem
|
||||
label="Image to image fit"
|
||||
value={metadata.fit}
|
||||
onClick={() => dispatch(setShouldFitToWidthHeight(metadata.fit))}
|
||||
/>
|
||||
)} */}
|
||||
</>
|
||||
) : (
|
||||
<Center width="100%" pt={10}>
|
||||
<Text fontSize="lg" fontWeight="semibold">
|
||||
No metadata available
|
||||
</Text>
|
||||
</Center>
|
||||
)}
|
||||
<Flex gap={2} direction="column" overflow="auto">
|
||||
<Flex gap={2}>
|
||||
<Tooltip label="Copy metadata JSON">
|
||||
<IconButton
|
||||
aria-label={t('accessibility.copyMetadataJson')}
|
||||
icon={<FaCopy />}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
fontSize={14}
|
||||
onClick={() => navigator.clipboard.writeText(metadataJSON)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Text fontWeight="semibold">Metadata JSON:</Text>
|
||||
</Flex>
|
||||
<OverlayScrollbarsComponent defer>
|
||||
<Box
|
||||
sx={{
|
||||
padding: 4,
|
||||
borderRadius: 'base',
|
||||
bg: 'whiteAlpha.500',
|
||||
_dark: { bg: 'blackAlpha.500' },
|
||||
w: 'full',
|
||||
}}
|
||||
>
|
||||
<pre>{metadataJSON}</pre>
|
||||
</Box>
|
||||
</OverlayScrollbarsComponent>
|
||||
</Flex>
|
||||
<ImageMetadataActions metadata={metadata} />
|
||||
|
||||
<Tabs
|
||||
variant="line"
|
||||
sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }}
|
||||
>
|
||||
<TabList>
|
||||
{tabData.map((tab) => (
|
||||
<Tab
|
||||
key={tab.label}
|
||||
sx={{
|
||||
borderTopRadius: 'base',
|
||||
}}
|
||||
>
|
||||
<Text sx={{ color: 'base.700', _dark: { color: 'base.300' } }}>
|
||||
{tab.label}
|
||||
</Text>
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
|
||||
<TabPanels sx={{ w: 'full', h: 'full' }}>
|
||||
{tabData.map((tab) => (
|
||||
<TabPanel
|
||||
key={tab.label}
|
||||
sx={{ w: 'full', h: 'full', p: 0, pt: 4 }}
|
||||
>
|
||||
<MetadataJSONViewer
|
||||
jsonObject={tab.data}
|
||||
copyTooltip={tab.copyTooltip}
|
||||
/>
|
||||
</TabPanel>
|
||||
))}
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,77 @@
|
||||
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
||||
import { Flex, IconButton, Link, Text, Tooltip } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaCopy } from 'react-icons/fa';
|
||||
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
||||
|
||||
type MetadataItemProps = {
|
||||
isLink?: boolean;
|
||||
label: string;
|
||||
onClick?: () => void;
|
||||
value: number | string | boolean;
|
||||
labelPosition?: string;
|
||||
withCopy?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to display an individual metadata item or parameter.
|
||||
*/
|
||||
const MetadataItem = ({
|
||||
label,
|
||||
value,
|
||||
onClick,
|
||||
isLink,
|
||||
labelPosition,
|
||||
withCopy = false,
|
||||
}: MetadataItemProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex gap={2}>
|
||||
{onClick && (
|
||||
<Tooltip label={`Recall ${label}`}>
|
||||
<IconButton
|
||||
aria-label={t('accessibility.useThisParameter')}
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
fontSize={20}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{withCopy && (
|
||||
<Tooltip label={`Copy ${label}`}>
|
||||
<IconButton
|
||||
aria-label={`Copy ${label}`}
|
||||
icon={<FaCopy />}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
fontSize={14}
|
||||
onClick={() => navigator.clipboard.writeText(value.toString())}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Flex direction={labelPosition ? 'column' : 'row'}>
|
||||
<Text fontWeight="semibold" whiteSpace="pre-wrap" pr={2}>
|
||||
{label}:
|
||||
</Text>
|
||||
{isLink ? (
|
||||
<Link href={value.toString()} isExternal wordBreak="break-all">
|
||||
{value.toString()} <ExternalLinkIcon mx="2px" />
|
||||
</Link>
|
||||
) : (
|
||||
<Text overflowY="scroll" wordBreak="break-all">
|
||||
{value.toString()}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetadataItem;
|
@ -0,0 +1,70 @@
|
||||
import { Box, Flex, IconButton, Tooltip } from '@chakra-ui/react';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import { useMemo } from 'react';
|
||||
import { FaCopy } from 'react-icons/fa';
|
||||
|
||||
type Props = {
|
||||
copyTooltip: string;
|
||||
jsonObject: object;
|
||||
};
|
||||
|
||||
const MetadataJSONViewer = (props: Props) => {
|
||||
const { copyTooltip, jsonObject } = props;
|
||||
const jsonString = useMemo(
|
||||
() => JSON.stringify(jsonObject, null, 2),
|
||||
[jsonObject]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
borderRadius: 'base',
|
||||
bg: 'whiteAlpha.500',
|
||||
_dark: { bg: 'blackAlpha.500' },
|
||||
flexGrow: 1,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
overflow: 'auto',
|
||||
p: 4,
|
||||
}}
|
||||
>
|
||||
<OverlayScrollbarsComponent
|
||||
defer
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
options={{
|
||||
scrollbars: {
|
||||
visibility: 'auto',
|
||||
autoHide: 'move',
|
||||
autoHideDelay: 1300,
|
||||
theme: 'os-theme-dark',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<pre>{jsonString}</pre>
|
||||
</OverlayScrollbarsComponent>
|
||||
</Box>
|
||||
<Flex sx={{ position: 'absolute', top: 0, insetInlineEnd: 0, p: 2 }}>
|
||||
<Tooltip label={copyTooltip}>
|
||||
<IconButton
|
||||
aria-label={copyTooltip}
|
||||
icon={<FaCopy />}
|
||||
variant="ghost"
|
||||
onClick={() => navigator.clipboard.writeText(jsonString)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetadataJSONViewer;
|
@ -1,18 +1,8 @@
|
||||
import { ChakraProps, Flex, Grid, IconButton, Spinner } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
imageSelected,
|
||||
selectFilteredImages,
|
||||
selectImagesById,
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import { clamp, isEqual } from 'lodash-es';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaAngleDoubleRight, FaAngleLeft, FaAngleRight } from 'react-icons/fa';
|
||||
import { receivedPageOfImages } from 'services/api/thunks/image';
|
||||
import { useNextPrevImage } from '../hooks/useNextPrevImage';
|
||||
|
||||
const nextPrevButtonTriggerAreaStyles: ChakraProps['sx'] = {
|
||||
height: '100%',
|
||||
@ -24,74 +14,18 @@ const nextPrevButtonStyles: ChakraProps['sx'] = {
|
||||
color: 'base.100',
|
||||
};
|
||||
|
||||
export const nextPrevImageButtonsSelector = createSelector(
|
||||
[stateSelector, selectFilteredImages],
|
||||
(state, filteredImages) => {
|
||||
const { total, isFetching } = state.gallery;
|
||||
const lastSelectedImage =
|
||||
state.gallery.selection[state.gallery.selection.length - 1];
|
||||
|
||||
if (!lastSelectedImage || filteredImages.length === 0) {
|
||||
return {
|
||||
isOnFirstImage: true,
|
||||
isOnLastImage: true,
|
||||
};
|
||||
}
|
||||
|
||||
const currentImageIndex = filteredImages.findIndex(
|
||||
(i) => i.image_name === lastSelectedImage
|
||||
);
|
||||
const nextImageIndex = clamp(
|
||||
currentImageIndex + 1,
|
||||
0,
|
||||
filteredImages.length - 1
|
||||
);
|
||||
|
||||
const prevImageIndex = clamp(
|
||||
currentImageIndex - 1,
|
||||
0,
|
||||
filteredImages.length - 1
|
||||
);
|
||||
|
||||
const nextImageId = filteredImages[nextImageIndex].image_name;
|
||||
const prevImageId = filteredImages[prevImageIndex].image_name;
|
||||
|
||||
const nextImage = selectImagesById(state, nextImageId);
|
||||
const prevImage = selectImagesById(state, prevImageId);
|
||||
|
||||
const imagesLength = filteredImages.length;
|
||||
|
||||
return {
|
||||
isOnFirstImage: currentImageIndex === 0,
|
||||
isOnLastImage:
|
||||
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
|
||||
areMoreImagesAvailable: total > imagesLength,
|
||||
isFetching,
|
||||
nextImage,
|
||||
prevImage,
|
||||
nextImageId,
|
||||
prevImageId,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const NextPrevImageButtons = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
handlePrevImage,
|
||||
handleNextImage,
|
||||
isOnFirstImage,
|
||||
isOnLastImage,
|
||||
nextImageId,
|
||||
prevImageId,
|
||||
handleLoadMoreImages,
|
||||
areMoreImagesAvailable,
|
||||
isFetching,
|
||||
} = useAppSelector(nextPrevImageButtonsSelector);
|
||||
} = useNextPrevImage();
|
||||
|
||||
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
|
||||
useState<boolean>(false);
|
||||
@ -104,50 +38,6 @@ const NextPrevImageButtons = () => {
|
||||
setShouldShowNextPrevButtons(false);
|
||||
}, []);
|
||||
|
||||
const handlePrevImage = useCallback(() => {
|
||||
prevImageId && dispatch(imageSelected(prevImageId));
|
||||
}, [dispatch, prevImageId]);
|
||||
|
||||
const handleNextImage = useCallback(() => {
|
||||
nextImageId && dispatch(imageSelected(nextImageId));
|
||||
}, [dispatch, nextImageId]);
|
||||
|
||||
const handleLoadMoreImages = useCallback(() => {
|
||||
dispatch(
|
||||
receivedPageOfImages({
|
||||
is_intermediate: false,
|
||||
})
|
||||
);
|
||||
}, [dispatch]);
|
||||
|
||||
useHotkeys(
|
||||
'left',
|
||||
() => {
|
||||
handlePrevImage();
|
||||
},
|
||||
[prevImageId]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'right',
|
||||
() => {
|
||||
if (isOnLastImage && areMoreImagesAvailable && !isFetching) {
|
||||
handleLoadMoreImages();
|
||||
return;
|
||||
}
|
||||
if (!isOnLastImage) {
|
||||
handleNextImage();
|
||||
}
|
||||
},
|
||||
[
|
||||
nextImageId,
|
||||
isOnLastImage,
|
||||
areMoreImagesAvailable,
|
||||
handleLoadMoreImages,
|
||||
isFetching,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
|
@ -0,0 +1,108 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
imageSelected,
|
||||
selectFilteredImages,
|
||||
selectImagesById,
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import { clamp, isEqual } from 'lodash-es';
|
||||
import { useCallback } from 'react';
|
||||
import { receivedPageOfImages } from 'services/api/thunks/image';
|
||||
|
||||
export const nextPrevImageButtonsSelector = createSelector(
|
||||
[stateSelector, selectFilteredImages],
|
||||
(state, filteredImages) => {
|
||||
const { total, isFetching } = state.gallery;
|
||||
const lastSelectedImage =
|
||||
state.gallery.selection[state.gallery.selection.length - 1];
|
||||
|
||||
if (!lastSelectedImage || filteredImages.length === 0) {
|
||||
return {
|
||||
isOnFirstImage: true,
|
||||
isOnLastImage: true,
|
||||
};
|
||||
}
|
||||
|
||||
const currentImageIndex = filteredImages.findIndex(
|
||||
(i) => i.image_name === lastSelectedImage
|
||||
);
|
||||
const nextImageIndex = clamp(
|
||||
currentImageIndex + 1,
|
||||
0,
|
||||
filteredImages.length - 1
|
||||
);
|
||||
|
||||
const prevImageIndex = clamp(
|
||||
currentImageIndex - 1,
|
||||
0,
|
||||
filteredImages.length - 1
|
||||
);
|
||||
|
||||
const nextImageId = filteredImages[nextImageIndex].image_name;
|
||||
const prevImageId = filteredImages[prevImageIndex].image_name;
|
||||
|
||||
const nextImage = selectImagesById(state, nextImageId);
|
||||
const prevImage = selectImagesById(state, prevImageId);
|
||||
|
||||
const imagesLength = filteredImages.length;
|
||||
|
||||
return {
|
||||
isOnFirstImage: currentImageIndex === 0,
|
||||
isOnLastImage:
|
||||
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
|
||||
areMoreImagesAvailable: total > imagesLength,
|
||||
isFetching,
|
||||
nextImage,
|
||||
prevImage,
|
||||
nextImageId,
|
||||
prevImageId,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const useNextPrevImage = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const {
|
||||
isOnFirstImage,
|
||||
isOnLastImage,
|
||||
nextImageId,
|
||||
prevImageId,
|
||||
areMoreImagesAvailable,
|
||||
isFetching,
|
||||
} = useAppSelector(nextPrevImageButtonsSelector);
|
||||
|
||||
const handlePrevImage = useCallback(() => {
|
||||
prevImageId && dispatch(imageSelected(prevImageId));
|
||||
}, [dispatch, prevImageId]);
|
||||
|
||||
const handleNextImage = useCallback(() => {
|
||||
nextImageId && dispatch(imageSelected(nextImageId));
|
||||
}, [dispatch, nextImageId]);
|
||||
|
||||
const handleLoadMoreImages = useCallback(() => {
|
||||
dispatch(
|
||||
receivedPageOfImages({
|
||||
is_intermediate: false,
|
||||
})
|
||||
);
|
||||
}, [dispatch]);
|
||||
|
||||
return {
|
||||
handlePrevImage,
|
||||
handleNextImage,
|
||||
isOnFirstImage,
|
||||
isOnLastImage,
|
||||
nextImageId,
|
||||
prevImageId,
|
||||
areMoreImagesAvailable,
|
||||
handleLoadMoreImages,
|
||||
isFetching,
|
||||
};
|
||||
};
|
@ -45,7 +45,7 @@ const ParamLoraSelect = () => {
|
||||
|
||||
data.push({
|
||||
value: id,
|
||||
label: lora.name,
|
||||
label: lora.model_name,
|
||||
disabled,
|
||||
group: MODEL_TYPE_MAP[lora.base_model],
|
||||
tooltip: disabled
|
||||
|
@ -6,6 +6,7 @@ import LoadNodesButton from '../ui/LoadNodesButton';
|
||||
import NodeInvokeButton from '../ui/NodeInvokeButton';
|
||||
import ReloadSchemaButton from '../ui/ReloadSchemaButton';
|
||||
import SaveNodesButton from '../ui/SaveNodesButton';
|
||||
import ClearNodesButton from '../ui/ClearNodesButton';
|
||||
|
||||
const TopCenterPanel = () => {
|
||||
return (
|
||||
@ -16,6 +17,7 @@ const TopCenterPanel = () => {
|
||||
<ReloadSchemaButton />
|
||||
<SaveNodesButton />
|
||||
<LoadNodesButton />
|
||||
<ClearNodesButton />
|
||||
</HStack>
|
||||
</Panel>
|
||||
);
|
||||
|
@ -0,0 +1,86 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogBody,
|
||||
AlertDialogContent,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogOverlay,
|
||||
Button,
|
||||
Text,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import { makeToast } from 'app/components/Toaster';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { clearNodes } from 'features/nodes/store/nodesSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { memo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaTrash } from 'react-icons/fa';
|
||||
|
||||
const ClearNodesButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const cancelRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
const nodes = useAppSelector((state: RootState) => state.nodes.nodes);
|
||||
|
||||
const handleConfirmClear = () => {
|
||||
dispatch(clearNodes());
|
||||
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.nodesCleared'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<IAIIconButton
|
||||
icon={<FaTrash />}
|
||||
tooltip={t('nodes.clearNodes')}
|
||||
aria-label={t('nodes.clearNodes')}
|
||||
onClick={onOpen}
|
||||
isDisabled={nodes.length === 0}
|
||||
/>
|
||||
|
||||
<AlertDialog
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
leastDestructiveRef={cancelRef}
|
||||
isCentered
|
||||
>
|
||||
<AlertDialogOverlay />
|
||||
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||
{t('nodes.clearNodes')}
|
||||
</AlertDialogHeader>
|
||||
|
||||
<AlertDialogBody>
|
||||
<Text>{t('common.clearNodes')}</Text>
|
||||
</AlertDialogBody>
|
||||
|
||||
<AlertDialogFooter>
|
||||
<Button ref={cancelRef} onClick={onClose}>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button colorScheme="red" ml={3} onClick={handleConfirmClear}>
|
||||
{t('common.accept')}
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ClearNodesButton);
|
@ -12,6 +12,8 @@ const SaveNodesButton = () => {
|
||||
(state: RootState) => state.nodes.editorInstance
|
||||
);
|
||||
|
||||
const nodes = useAppSelector((state: RootState) => state.nodes.nodes);
|
||||
|
||||
const saveEditorToJSON = useCallback(() => {
|
||||
if (editorInstance) {
|
||||
const editorState = editorInstance.toObject();
|
||||
@ -38,6 +40,7 @@ const SaveNodesButton = () => {
|
||||
tooltip={t('nodes.saveNodes')}
|
||||
aria-label={t('nodes.saveNodes')}
|
||||
onClick={saveEditorToJSON}
|
||||
isDisabled={nodes.length === 0}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -133,6 +133,10 @@ const nodesSlice = createSlice({
|
||||
loadFileEdges: (state, action: PayloadAction<Edge[]>) => {
|
||||
state.edges = action.payload;
|
||||
},
|
||||
clearNodes: (state) => {
|
||||
state.nodes = [];
|
||||
state.edges = [];
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => {
|
||||
@ -156,6 +160,7 @@ export const {
|
||||
setEditorInstance,
|
||||
loadFileNodes,
|
||||
loadFileEdges,
|
||||
clearNodes,
|
||||
} = nodesSlice.actions;
|
||||
|
||||
export default nodesSlice.reducer;
|
||||
|
@ -1,94 +0,0 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { getValidControlNets } from 'features/controlNet/util/getValidControlNets';
|
||||
import { CollectInvocation, ControlNetInvocation } from 'services/api/types';
|
||||
import { NonNullableGraph } from '../types/types';
|
||||
import { CONTROL_NET_COLLECT } from './graphBuilders/constants';
|
||||
|
||||
export const addControlNetToLinearGraph = (
|
||||
graph: NonNullableGraph,
|
||||
baseNodeId: string,
|
||||
state: RootState
|
||||
): void => {
|
||||
const { isEnabled: isControlNetEnabled, controlNets } = state.controlNet;
|
||||
|
||||
const validControlNets = getValidControlNets(controlNets);
|
||||
|
||||
if (isControlNetEnabled && Boolean(validControlNets.length)) {
|
||||
if (validControlNets.length > 1) {
|
||||
// We have multiple controlnets, add ControlNet collector
|
||||
const controlNetIterateNode: CollectInvocation = {
|
||||
id: CONTROL_NET_COLLECT,
|
||||
type: 'collect',
|
||||
};
|
||||
graph.nodes[controlNetIterateNode.id] = controlNetIterateNode;
|
||||
graph.edges.push({
|
||||
source: { node_id: controlNetIterateNode.id, field: 'collection' },
|
||||
destination: {
|
||||
node_id: baseNodeId,
|
||||
field: 'control',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
validControlNets.forEach((controlNet) => {
|
||||
const {
|
||||
controlNetId,
|
||||
controlImage,
|
||||
processedControlImage,
|
||||
beginStepPct,
|
||||
endStepPct,
|
||||
controlMode,
|
||||
model,
|
||||
processorType,
|
||||
weight,
|
||||
} = controlNet;
|
||||
|
||||
const controlNetNode: ControlNetInvocation = {
|
||||
id: `control_net_${controlNetId}`,
|
||||
type: 'controlnet',
|
||||
begin_step_percent: beginStepPct,
|
||||
end_step_percent: endStepPct,
|
||||
control_mode: controlMode,
|
||||
control_model: model as ControlNetInvocation['control_model'],
|
||||
control_weight: weight,
|
||||
};
|
||||
|
||||
if (processedControlImage && processorType !== 'none') {
|
||||
// We've already processed the image in the app, so we can just use the processed image
|
||||
controlNetNode.image = {
|
||||
image_name: processedControlImage,
|
||||
};
|
||||
} else if (controlImage) {
|
||||
// The control image is preprocessed
|
||||
controlNetNode.image = {
|
||||
image_name: controlImage,
|
||||
};
|
||||
} else {
|
||||
// Skip ControlNets without an unprocessed image - should never happen if everything is working correctly
|
||||
return;
|
||||
}
|
||||
|
||||
graph.nodes[controlNetNode.id] = controlNetNode;
|
||||
|
||||
if (validControlNets.length > 1) {
|
||||
// if we have multiple controlnets, link to the collector
|
||||
graph.edges.push({
|
||||
source: { node_id: controlNetNode.id, field: 'control' },
|
||||
destination: {
|
||||
node_id: CONTROL_NET_COLLECT,
|
||||
field: 'item',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// otherwise, link directly to the base node
|
||||
graph.edges.push({
|
||||
source: { node_id: controlNetNode.id, field: 'control' },
|
||||
destination: {
|
||||
node_id: baseNodeId,
|
||||
field: 'control',
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
@ -1,40 +0,0 @@
|
||||
import {
|
||||
Edge,
|
||||
ImageToImageInvocation,
|
||||
InpaintInvocation,
|
||||
IterateInvocation,
|
||||
RandomRangeInvocation,
|
||||
RangeInvocation,
|
||||
TextToImageInvocation,
|
||||
} from 'services/api/types';
|
||||
|
||||
export const buildEdges = (
|
||||
baseNode: TextToImageInvocation | ImageToImageInvocation | InpaintInvocation,
|
||||
rangeNode: RangeInvocation | RandomRangeInvocation,
|
||||
iterateNode: IterateInvocation
|
||||
): Edge[] => {
|
||||
const edges: Edge[] = [
|
||||
{
|
||||
source: {
|
||||
node_id: rangeNode.id,
|
||||
field: 'collection',
|
||||
},
|
||||
destination: {
|
||||
node_id: iterateNode.id,
|
||||
field: 'collection',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: iterateNode.id,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: baseNode.id,
|
||||
field: 'seed',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return edges;
|
||||
};
|
@ -0,0 +1,100 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { getValidControlNets } from 'features/controlNet/util/getValidControlNets';
|
||||
import { omit } from 'lodash-es';
|
||||
import {
|
||||
CollectInvocation,
|
||||
ControlField,
|
||||
ControlNetInvocation,
|
||||
MetadataAccumulatorInvocation,
|
||||
} from 'services/api/types';
|
||||
import { NonNullableGraph } from '../../types/types';
|
||||
import { CONTROL_NET_COLLECT, METADATA_ACCUMULATOR } from './constants';
|
||||
|
||||
export const addControlNetToLinearGraph = (
|
||||
state: RootState,
|
||||
graph: NonNullableGraph,
|
||||
baseNodeId: string
|
||||
): void => {
|
||||
const { isEnabled: isControlNetEnabled, controlNets } = state.controlNet;
|
||||
|
||||
const validControlNets = getValidControlNets(controlNets);
|
||||
|
||||
const metadataAccumulator = graph.nodes[
|
||||
METADATA_ACCUMULATOR
|
||||
] as MetadataAccumulatorInvocation;
|
||||
|
||||
if (isControlNetEnabled && Boolean(validControlNets.length)) {
|
||||
if (validControlNets.length) {
|
||||
// We have multiple controlnets, add ControlNet collector
|
||||
const controlNetIterateNode: CollectInvocation = {
|
||||
id: CONTROL_NET_COLLECT,
|
||||
type: 'collect',
|
||||
};
|
||||
graph.nodes[CONTROL_NET_COLLECT] = controlNetIterateNode;
|
||||
graph.edges.push({
|
||||
source: { node_id: CONTROL_NET_COLLECT, field: 'collection' },
|
||||
destination: {
|
||||
node_id: baseNodeId,
|
||||
field: 'control',
|
||||
},
|
||||
});
|
||||
|
||||
validControlNets.forEach((controlNet) => {
|
||||
const {
|
||||
controlNetId,
|
||||
controlImage,
|
||||
processedControlImage,
|
||||
beginStepPct,
|
||||
endStepPct,
|
||||
controlMode,
|
||||
model,
|
||||
processorType,
|
||||
weight,
|
||||
} = controlNet;
|
||||
|
||||
const controlNetNode: ControlNetInvocation = {
|
||||
id: `control_net_${controlNetId}`,
|
||||
type: 'controlnet',
|
||||
begin_step_percent: beginStepPct,
|
||||
end_step_percent: endStepPct,
|
||||
control_mode: controlMode,
|
||||
control_model: model as ControlNetInvocation['control_model'],
|
||||
control_weight: weight,
|
||||
};
|
||||
|
||||
if (processedControlImage && processorType !== 'none') {
|
||||
// We've already processed the image in the app, so we can just use the processed image
|
||||
controlNetNode.image = {
|
||||
image_name: processedControlImage,
|
||||
};
|
||||
} else if (controlImage) {
|
||||
// The control image is preprocessed
|
||||
controlNetNode.image = {
|
||||
image_name: controlImage,
|
||||
};
|
||||
} else {
|
||||
// Skip ControlNets without an unprocessed image - should never happen if everything is working correctly
|
||||
return;
|
||||
}
|
||||
|
||||
graph.nodes[controlNetNode.id] = controlNetNode;
|
||||
|
||||
// metadata accumulator only needs a control field - not the whole node
|
||||
// extract what we need and add to the accumulator
|
||||
const controlField = omit(controlNetNode, [
|
||||
'id',
|
||||
'type',
|
||||
]) as ControlField;
|
||||
metadataAccumulator.controlnets.push(controlField);
|
||||
|
||||
graph.edges.push({
|
||||
source: { node_id: controlNetNode.id, field: 'control' },
|
||||
destination: {
|
||||
node_id: CONTROL_NET_COLLECT,
|
||||
field: 'item',
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
@ -1,8 +1,10 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { unset } from 'lodash-es';
|
||||
import {
|
||||
DynamicPromptInvocation,
|
||||
IterateInvocation,
|
||||
MetadataAccumulatorInvocation,
|
||||
NoiseInvocation,
|
||||
RandomIntInvocation,
|
||||
RangeOfSizeInvocation,
|
||||
@ -10,16 +12,16 @@ import {
|
||||
import {
|
||||
DYNAMIC_PROMPT,
|
||||
ITERATE,
|
||||
METADATA_ACCUMULATOR,
|
||||
NOISE,
|
||||
POSITIVE_CONDITIONING,
|
||||
RANDOM_INT,
|
||||
RANGE_OF_SIZE,
|
||||
} from './constants';
|
||||
import { unset } from 'lodash-es';
|
||||
|
||||
export const addDynamicPromptsToGraph = (
|
||||
graph: NonNullableGraph,
|
||||
state: RootState
|
||||
state: RootState,
|
||||
graph: NonNullableGraph
|
||||
): void => {
|
||||
const { positivePrompt, iterations, seed, shouldRandomizeSeed } =
|
||||
state.generation;
|
||||
@ -30,6 +32,10 @@ export const addDynamicPromptsToGraph = (
|
||||
maxPrompts,
|
||||
} = state.dynamicPrompts;
|
||||
|
||||
const metadataAccumulator = graph.nodes[
|
||||
METADATA_ACCUMULATOR
|
||||
] as MetadataAccumulatorInvocation;
|
||||
|
||||
if (isDynamicPromptsEnabled) {
|
||||
// iteration is handled via dynamic prompts
|
||||
unset(graph.nodes[POSITIVE_CONDITIONING], 'prompt');
|
||||
@ -74,6 +80,18 @@ export const addDynamicPromptsToGraph = (
|
||||
}
|
||||
);
|
||||
|
||||
// hook up positive prompt to metadata
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: METADATA_ACCUMULATOR,
|
||||
field: 'positive_prompt',
|
||||
},
|
||||
});
|
||||
|
||||
if (shouldRandomizeSeed) {
|
||||
// Random int node to generate the starting seed
|
||||
const randomIntNode: RandomIntInvocation = {
|
||||
@ -88,11 +106,22 @@ export const addDynamicPromptsToGraph = (
|
||||
source: { node_id: RANDOM_INT, field: 'a' },
|
||||
destination: { node_id: NOISE, field: 'seed' },
|
||||
});
|
||||
|
||||
graph.edges.push({
|
||||
source: { node_id: RANDOM_INT, field: 'a' },
|
||||
destination: { node_id: METADATA_ACCUMULATOR, field: 'seed' },
|
||||
});
|
||||
} else {
|
||||
// User specified seed, so set the start of the range of size to the seed
|
||||
(graph.nodes[NOISE] as NoiseInvocation).seed = seed;
|
||||
|
||||
// hook up seed to metadata
|
||||
metadataAccumulator.seed = seed;
|
||||
}
|
||||
} else {
|
||||
// no dynamic prompt - hook up positive prompt
|
||||
metadataAccumulator.positive_prompt = positivePrompt;
|
||||
|
||||
const rangeOfSizeNode: RangeOfSizeInvocation = {
|
||||
id: RANGE_OF_SIZE,
|
||||
type: 'range_of_size',
|
||||
@ -130,6 +159,18 @@ export const addDynamicPromptsToGraph = (
|
||||
},
|
||||
});
|
||||
|
||||
// hook up seed to metadata
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: METADATA_ACCUMULATOR,
|
||||
field: 'seed',
|
||||
},
|
||||
});
|
||||
|
||||
// handle seed
|
||||
if (shouldRandomizeSeed) {
|
||||
// Random int node to generate the starting seed
|
||||
|
@ -1,19 +1,23 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { forEach, size } from 'lodash-es';
|
||||
import { LoraLoaderInvocation } from 'services/api/types';
|
||||
import {
|
||||
LoraLoaderInvocation,
|
||||
MetadataAccumulatorInvocation,
|
||||
} from 'services/api/types';
|
||||
import { modelIdToLoRAModelField } from '../modelIdToLoRAName';
|
||||
import {
|
||||
CLIP_SKIP,
|
||||
LORA_LOADER,
|
||||
MAIN_MODEL_LOADER,
|
||||
METADATA_ACCUMULATOR,
|
||||
NEGATIVE_CONDITIONING,
|
||||
POSITIVE_CONDITIONING,
|
||||
} from './constants';
|
||||
|
||||
export const addLoRAsToGraph = (
|
||||
graph: NonNullableGraph,
|
||||
state: RootState,
|
||||
graph: NonNullableGraph,
|
||||
baseNodeId: string
|
||||
): void => {
|
||||
/**
|
||||
@ -26,6 +30,9 @@ export const addLoRAsToGraph = (
|
||||
|
||||
const { loras } = state.lora;
|
||||
const loraCount = size(loras);
|
||||
const metadataAccumulator = graph.nodes[
|
||||
METADATA_ACCUMULATOR
|
||||
] as MetadataAccumulatorInvocation;
|
||||
|
||||
if (loraCount > 0) {
|
||||
// Remove MAIN_MODEL_LOADER unet connection to feed it to LoRAs
|
||||
@ -62,6 +69,10 @@ export const addLoRAsToGraph = (
|
||||
weight,
|
||||
};
|
||||
|
||||
// add the lora to the metadata accumulator
|
||||
metadataAccumulator.loras.push({ lora: loraField, weight });
|
||||
|
||||
// add to graph
|
||||
graph.nodes[currentLoraNodeId] = loraLoaderNode;
|
||||
|
||||
if (currentLoraIndex === 0) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { MetadataAccumulatorInvocation } from 'services/api/types';
|
||||
import { modelIdToVAEModelField } from '../modelIdToVAEModelField';
|
||||
import {
|
||||
IMAGE_TO_IMAGE_GRAPH,
|
||||
@ -8,18 +9,22 @@ import {
|
||||
INPAINT_GRAPH,
|
||||
LATENTS_TO_IMAGE,
|
||||
MAIN_MODEL_LOADER,
|
||||
METADATA_ACCUMULATOR,
|
||||
TEXT_TO_IMAGE_GRAPH,
|
||||
VAE_LOADER,
|
||||
} from './constants';
|
||||
|
||||
export const addVAEToGraph = (
|
||||
graph: NonNullableGraph,
|
||||
state: RootState
|
||||
state: RootState,
|
||||
graph: NonNullableGraph
|
||||
): void => {
|
||||
const { vae } = state.generation;
|
||||
const vae_model = modelIdToVAEModelField(vae?.id || '');
|
||||
|
||||
const isAutoVae = !vae;
|
||||
const metadataAccumulator = graph.nodes[
|
||||
METADATA_ACCUMULATOR
|
||||
] as MetadataAccumulatorInvocation;
|
||||
|
||||
if (!isAutoVae) {
|
||||
graph.nodes[VAE_LOADER] = {
|
||||
@ -67,4 +72,8 @@ export const addVAEToGraph = (
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (vae) {
|
||||
metadataAccumulator.vae = vae_model;
|
||||
}
|
||||
};
|
||||
|
@ -7,8 +7,7 @@ import {
|
||||
ImageResizeInvocation,
|
||||
ImageToLatentsInvocation,
|
||||
} from 'services/api/types';
|
||||
import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph';
|
||||
import { modelIdToMainModelField } from '../modelIdToMainModelField';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
|
||||
import { addLoRAsToGraph } from './addLoRAsToGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
@ -19,6 +18,7 @@ import {
|
||||
LATENTS_TO_IMAGE,
|
||||
LATENTS_TO_LATENTS,
|
||||
MAIN_MODEL_LOADER,
|
||||
METADATA_ACCUMULATOR,
|
||||
NEGATIVE_CONDITIONING,
|
||||
NOISE,
|
||||
POSITIVE_CONDITIONING,
|
||||
@ -37,7 +37,7 @@ export const buildCanvasImageToImageGraph = (
|
||||
const {
|
||||
positivePrompt,
|
||||
negativePrompt,
|
||||
model: currentModel,
|
||||
model,
|
||||
cfgScale: cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
@ -50,7 +50,10 @@ export const buildCanvasImageToImageGraph = (
|
||||
// The bounding box determines width and height, not the width and height params
|
||||
const { width, height } = state.canvas.boundingBoxDimensions;
|
||||
|
||||
const model = modelIdToMainModelField(currentModel?.id || '');
|
||||
if (!model) {
|
||||
moduleLog.error('No model found in state');
|
||||
throw new Error('No model found in state');
|
||||
}
|
||||
|
||||
const use_cpu = shouldUseNoiseSettings
|
||||
? shouldUseCpuNoise
|
||||
@ -275,16 +278,51 @@ export const buildCanvasImageToImageGraph = (
|
||||
});
|
||||
}
|
||||
|
||||
addLoRAsToGraph(graph, state, LATENTS_TO_LATENTS);
|
||||
// add metadata accumulator, which is only mostly populated - some fields are added later
|
||||
graph.nodes[METADATA_ACCUMULATOR] = {
|
||||
id: METADATA_ACCUMULATOR,
|
||||
type: 'metadata_accumulator',
|
||||
generation_mode: 'img2img',
|
||||
cfg_scale,
|
||||
height,
|
||||
width,
|
||||
positive_prompt: '', // set in addDynamicPromptsToGraph
|
||||
negative_prompt: negativePrompt,
|
||||
model,
|
||||
seed: 0, // set in addDynamicPromptsToGraph
|
||||
steps,
|
||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||
scheduler,
|
||||
vae: undefined, // option; set in addVAEToGraph
|
||||
controlnets: [], // populated in addControlNetToLinearGraph
|
||||
loras: [], // populated in addLoRAsToGraph
|
||||
clip_skip: clipSkip,
|
||||
strength,
|
||||
init_image: initialImage.image_name,
|
||||
};
|
||||
|
||||
// Add VAE
|
||||
addVAEToGraph(graph, state);
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: METADATA_ACCUMULATOR,
|
||||
field: 'metadata',
|
||||
},
|
||||
destination: {
|
||||
node_id: LATENTS_TO_IMAGE,
|
||||
field: 'metadata',
|
||||
},
|
||||
});
|
||||
|
||||
// add dynamic prompts, mutating `graph`
|
||||
addDynamicPromptsToGraph(graph, state);
|
||||
// add LoRA support
|
||||
addLoRAsToGraph(state, graph, LATENTS_TO_LATENTS);
|
||||
|
||||
// optionally add custom VAE
|
||||
addVAEToGraph(state, graph);
|
||||
|
||||
// add dynamic prompts - also sets up core iteration and seed
|
||||
addDynamicPromptsToGraph(state, graph);
|
||||
|
||||
// add controlnet, mutating `graph`
|
||||
addControlNetToLinearGraph(graph, LATENTS_TO_LATENTS, state);
|
||||
addControlNetToLinearGraph(state, graph, LATENTS_TO_LATENTS);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -212,10 +212,10 @@ export const buildCanvasInpaintGraph = (
|
||||
],
|
||||
};
|
||||
|
||||
addLoRAsToGraph(graph, state, INPAINT);
|
||||
addLoRAsToGraph(state, graph, INPAINT);
|
||||
|
||||
// Add VAE
|
||||
addVAEToGraph(graph, state);
|
||||
addVAEToGraph(state, graph);
|
||||
|
||||
// handle seed
|
||||
if (shouldRandomizeSeed) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { initialGenerationState } from 'features/parameters/store/generationSlice';
|
||||
import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph';
|
||||
import { modelIdToMainModelField } from '../modelIdToMainModelField';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
|
||||
import { addLoRAsToGraph } from './addLoRAsToGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
@ -10,6 +10,7 @@ import {
|
||||
CLIP_SKIP,
|
||||
LATENTS_TO_IMAGE,
|
||||
MAIN_MODEL_LOADER,
|
||||
METADATA_ACCUMULATOR,
|
||||
NEGATIVE_CONDITIONING,
|
||||
NOISE,
|
||||
POSITIVE_CONDITIONING,
|
||||
@ -17,6 +18,8 @@ import {
|
||||
TEXT_TO_LATENTS,
|
||||
} from './constants';
|
||||
|
||||
const moduleLog = log.child({ namespace: 'nodes' });
|
||||
|
||||
/**
|
||||
* Builds the Canvas tab's Text to Image graph.
|
||||
*/
|
||||
@ -26,7 +29,7 @@ export const buildCanvasTextToImageGraph = (
|
||||
const {
|
||||
positivePrompt,
|
||||
negativePrompt,
|
||||
model: currentModel,
|
||||
model,
|
||||
cfgScale: cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
@ -38,7 +41,10 @@ export const buildCanvasTextToImageGraph = (
|
||||
// The bounding box determines width and height, not the width and height params
|
||||
const { width, height } = state.canvas.boundingBoxDimensions;
|
||||
|
||||
const model = modelIdToMainModelField(currentModel?.id || '');
|
||||
if (!model) {
|
||||
moduleLog.error('No model found in state');
|
||||
throw new Error('No model found in state');
|
||||
}
|
||||
|
||||
const use_cpu = shouldUseNoiseSettings
|
||||
? shouldUseCpuNoise
|
||||
@ -180,16 +186,49 @@ export const buildCanvasTextToImageGraph = (
|
||||
],
|
||||
};
|
||||
|
||||
addLoRAsToGraph(graph, state, TEXT_TO_LATENTS);
|
||||
// add metadata accumulator, which is only mostly populated - some fields are added later
|
||||
graph.nodes[METADATA_ACCUMULATOR] = {
|
||||
id: METADATA_ACCUMULATOR,
|
||||
type: 'metadata_accumulator',
|
||||
generation_mode: 'txt2img',
|
||||
cfg_scale,
|
||||
height,
|
||||
width,
|
||||
positive_prompt: '', // set in addDynamicPromptsToGraph
|
||||
negative_prompt: negativePrompt,
|
||||
model,
|
||||
seed: 0, // set in addDynamicPromptsToGraph
|
||||
steps,
|
||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||
scheduler,
|
||||
vae: undefined, // option; set in addVAEToGraph
|
||||
controlnets: [], // populated in addControlNetToLinearGraph
|
||||
loras: [], // populated in addLoRAsToGraph
|
||||
clip_skip: clipSkip,
|
||||
};
|
||||
|
||||
// Add VAE
|
||||
addVAEToGraph(graph, state);
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: METADATA_ACCUMULATOR,
|
||||
field: 'metadata',
|
||||
},
|
||||
destination: {
|
||||
node_id: LATENTS_TO_IMAGE,
|
||||
field: 'metadata',
|
||||
},
|
||||
});
|
||||
|
||||
// add dynamic prompts, mutating `graph`
|
||||
addDynamicPromptsToGraph(graph, state);
|
||||
// add LoRA support
|
||||
addLoRAsToGraph(state, graph, TEXT_TO_LATENTS);
|
||||
|
||||
// optionally add custom VAE
|
||||
addVAEToGraph(state, graph);
|
||||
|
||||
// add dynamic prompts - also sets up core iteration and seed
|
||||
addDynamicPromptsToGraph(state, graph);
|
||||
|
||||
// add controlnet, mutating `graph`
|
||||
addControlNetToLinearGraph(graph, TEXT_TO_LATENTS, state);
|
||||
addControlNetToLinearGraph(state, graph, TEXT_TO_LATENTS);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -3,25 +3,21 @@ import { RootState } from 'app/store/store';
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { initialGenerationState } from 'features/parameters/store/generationSlice';
|
||||
import {
|
||||
ImageCollectionInvocation,
|
||||
ImageResizeInvocation,
|
||||
ImageToLatentsInvocation,
|
||||
IterateInvocation,
|
||||
} from 'services/api/types';
|
||||
import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph';
|
||||
import { modelIdToMainModelField } from '../modelIdToMainModelField';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
|
||||
import { addLoRAsToGraph } from './addLoRAsToGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
import {
|
||||
CLIP_SKIP,
|
||||
IMAGE_COLLECTION,
|
||||
IMAGE_COLLECTION_ITERATE,
|
||||
IMAGE_TO_IMAGE_GRAPH,
|
||||
IMAGE_TO_LATENTS,
|
||||
LATENTS_TO_IMAGE,
|
||||
LATENTS_TO_LATENTS,
|
||||
MAIN_MODEL_LOADER,
|
||||
METADATA_ACCUMULATOR,
|
||||
NEGATIVE_CONDITIONING,
|
||||
NOISE,
|
||||
POSITIVE_CONDITIONING,
|
||||
@ -39,7 +35,7 @@ export const buildLinearImageToImageGraph = (
|
||||
const {
|
||||
positivePrompt,
|
||||
negativePrompt,
|
||||
model: currentModel,
|
||||
model,
|
||||
cfgScale: cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
@ -53,14 +49,15 @@ export const buildLinearImageToImageGraph = (
|
||||
shouldUseNoiseSettings,
|
||||
} = state.generation;
|
||||
|
||||
const {
|
||||
isEnabled: isBatchEnabled,
|
||||
imageNames: batchImageNames,
|
||||
asInitialImage,
|
||||
} = state.batch;
|
||||
// TODO: add batch functionality
|
||||
// const {
|
||||
// isEnabled: isBatchEnabled,
|
||||
// imageNames: batchImageNames,
|
||||
// asInitialImage,
|
||||
// } = state.batch;
|
||||
|
||||
const shouldBatch =
|
||||
isBatchEnabled && batchImageNames.length > 0 && asInitialImage;
|
||||
// const shouldBatch =
|
||||
// isBatchEnabled && batchImageNames.length > 0 && asInitialImage;
|
||||
|
||||
/**
|
||||
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the
|
||||
@ -71,12 +68,15 @@ export const buildLinearImageToImageGraph = (
|
||||
* the `fit` param. These are added to the graph at the end.
|
||||
*/
|
||||
|
||||
if (!initialImage && !shouldBatch) {
|
||||
if (!initialImage) {
|
||||
moduleLog.error('No initial image found in state');
|
||||
throw new Error('No initial image found in state');
|
||||
}
|
||||
|
||||
const model = modelIdToMainModelField(currentModel?.id || '');
|
||||
if (!model) {
|
||||
moduleLog.error('No model found in state');
|
||||
throw new Error('No model found in state');
|
||||
}
|
||||
|
||||
const use_cpu = shouldUseNoiseSettings
|
||||
? shouldUseCpuNoise
|
||||
@ -295,51 +295,87 @@ export const buildLinearImageToImageGraph = (
|
||||
});
|
||||
}
|
||||
|
||||
if (isBatchEnabled && asInitialImage && batchImageNames.length > 0) {
|
||||
// we are going to connect an iterate up to the init image
|
||||
delete (graph.nodes[IMAGE_TO_LATENTS] as ImageToLatentsInvocation).image;
|
||||
// TODO: add batch functionality
|
||||
// if (isBatchEnabled && asInitialImage && batchImageNames.length > 0) {
|
||||
// // we are going to connect an iterate up to the init image
|
||||
// delete (graph.nodes[IMAGE_TO_LATENTS] as ImageToLatentsInvocation).image;
|
||||
|
||||
const imageCollection: ImageCollectionInvocation = {
|
||||
id: IMAGE_COLLECTION,
|
||||
type: 'image_collection',
|
||||
images: batchImageNames.map((image_name) => ({ image_name })),
|
||||
};
|
||||
// const imageCollection: ImageCollectionInvocation = {
|
||||
// id: IMAGE_COLLECTION,
|
||||
// type: 'image_collection',
|
||||
// images: batchImageNames.map((image_name) => ({ image_name })),
|
||||
// };
|
||||
|
||||
const imageCollectionIterate: IterateInvocation = {
|
||||
id: IMAGE_COLLECTION_ITERATE,
|
||||
type: 'iterate',
|
||||
};
|
||||
// const imageCollectionIterate: IterateInvocation = {
|
||||
// id: IMAGE_COLLECTION_ITERATE,
|
||||
// type: 'iterate',
|
||||
// };
|
||||
|
||||
graph.nodes[IMAGE_COLLECTION] = imageCollection;
|
||||
graph.nodes[IMAGE_COLLECTION_ITERATE] = imageCollectionIterate;
|
||||
// graph.nodes[IMAGE_COLLECTION] = imageCollection;
|
||||
// graph.nodes[IMAGE_COLLECTION_ITERATE] = imageCollectionIterate;
|
||||
|
||||
graph.edges.push({
|
||||
source: { node_id: IMAGE_COLLECTION, field: 'collection' },
|
||||
destination: {
|
||||
node_id: IMAGE_COLLECTION_ITERATE,
|
||||
field: 'collection',
|
||||
},
|
||||
});
|
||||
// graph.edges.push({
|
||||
// source: { node_id: IMAGE_COLLECTION, field: 'collection' },
|
||||
// destination: {
|
||||
// node_id: IMAGE_COLLECTION_ITERATE,
|
||||
// field: 'collection',
|
||||
// },
|
||||
// });
|
||||
|
||||
graph.edges.push({
|
||||
source: { node_id: IMAGE_COLLECTION_ITERATE, field: 'item' },
|
||||
destination: {
|
||||
node_id: IMAGE_TO_LATENTS,
|
||||
field: 'image',
|
||||
},
|
||||
});
|
||||
}
|
||||
// graph.edges.push({
|
||||
// source: { node_id: IMAGE_COLLECTION_ITERATE, field: 'item' },
|
||||
// destination: {
|
||||
// node_id: IMAGE_TO_LATENTS,
|
||||
// field: 'image',
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
addLoRAsToGraph(graph, state, LATENTS_TO_LATENTS);
|
||||
// add metadata accumulator, which is only mostly populated - some fields are added later
|
||||
graph.nodes[METADATA_ACCUMULATOR] = {
|
||||
id: METADATA_ACCUMULATOR,
|
||||
type: 'metadata_accumulator',
|
||||
generation_mode: 'img2img',
|
||||
cfg_scale,
|
||||
height,
|
||||
width,
|
||||
positive_prompt: '', // set in addDynamicPromptsToGraph
|
||||
negative_prompt: negativePrompt,
|
||||
model,
|
||||
seed: 0, // set in addDynamicPromptsToGraph
|
||||
steps,
|
||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||
scheduler,
|
||||
vae: undefined, // option; set in addVAEToGraph
|
||||
controlnets: [], // populated in addControlNetToLinearGraph
|
||||
loras: [], // populated in addLoRAsToGraph
|
||||
clip_skip: clipSkip,
|
||||
strength,
|
||||
init_image: initialImage.imageName,
|
||||
};
|
||||
|
||||
// Add VAE
|
||||
addVAEToGraph(graph, state);
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: METADATA_ACCUMULATOR,
|
||||
field: 'metadata',
|
||||
},
|
||||
destination: {
|
||||
node_id: LATENTS_TO_IMAGE,
|
||||
field: 'metadata',
|
||||
},
|
||||
});
|
||||
|
||||
// add dynamic prompts, mutating `graph`
|
||||
addDynamicPromptsToGraph(graph, state);
|
||||
// add LoRA support
|
||||
addLoRAsToGraph(state, graph, LATENTS_TO_LATENTS);
|
||||
|
||||
// optionally add custom VAE
|
||||
addVAEToGraph(state, graph);
|
||||
|
||||
// add dynamic prompts - also sets up core iteration and seed
|
||||
addDynamicPromptsToGraph(state, graph);
|
||||
|
||||
// add controlnet, mutating `graph`
|
||||
addControlNetToLinearGraph(graph, LATENTS_TO_LATENTS, state);
|
||||
addControlNetToLinearGraph(state, graph, LATENTS_TO_LATENTS);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { initialGenerationState } from 'features/parameters/store/generationSlice';
|
||||
import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph';
|
||||
import { modelIdToMainModelField } from '../modelIdToMainModelField';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
|
||||
import { addLoRAsToGraph } from './addLoRAsToGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
@ -10,6 +10,7 @@ import {
|
||||
CLIP_SKIP,
|
||||
LATENTS_TO_IMAGE,
|
||||
MAIN_MODEL_LOADER,
|
||||
METADATA_ACCUMULATOR,
|
||||
NEGATIVE_CONDITIONING,
|
||||
NOISE,
|
||||
POSITIVE_CONDITIONING,
|
||||
@ -17,13 +18,15 @@ import {
|
||||
TEXT_TO_LATENTS,
|
||||
} from './constants';
|
||||
|
||||
const moduleLog = log.child({ namespace: 'nodes' });
|
||||
|
||||
export const buildLinearTextToImageGraph = (
|
||||
state: RootState
|
||||
): NonNullableGraph => {
|
||||
const {
|
||||
positivePrompt,
|
||||
negativePrompt,
|
||||
model: currentModel,
|
||||
model,
|
||||
cfgScale: cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
@ -34,12 +37,15 @@ export const buildLinearTextToImageGraph = (
|
||||
shouldUseNoiseSettings,
|
||||
} = state.generation;
|
||||
|
||||
const model = modelIdToMainModelField(currentModel?.id || '');
|
||||
|
||||
const use_cpu = shouldUseNoiseSettings
|
||||
? shouldUseCpuNoise
|
||||
: initialGenerationState.shouldUseCpuNoise;
|
||||
|
||||
if (!model) {
|
||||
moduleLog.error('No model found in state');
|
||||
throw new Error('No model found in state');
|
||||
}
|
||||
|
||||
/**
|
||||
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the
|
||||
* full graph here as a template. Then use the parameters from app state and set friendlier node
|
||||
@ -176,16 +182,49 @@ export const buildLinearTextToImageGraph = (
|
||||
],
|
||||
};
|
||||
|
||||
addLoRAsToGraph(graph, state, TEXT_TO_LATENTS);
|
||||
// add metadata accumulator, which is only mostly populated - some fields are added later
|
||||
graph.nodes[METADATA_ACCUMULATOR] = {
|
||||
id: METADATA_ACCUMULATOR,
|
||||
type: 'metadata_accumulator',
|
||||
generation_mode: 'txt2img',
|
||||
cfg_scale,
|
||||
height,
|
||||
width,
|
||||
positive_prompt: '', // set in addDynamicPromptsToGraph
|
||||
negative_prompt: negativePrompt,
|
||||
model,
|
||||
seed: 0, // set in addDynamicPromptsToGraph
|
||||
steps,
|
||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||
scheduler,
|
||||
vae: undefined, // option; set in addVAEToGraph
|
||||
controlnets: [], // populated in addControlNetToLinearGraph
|
||||
loras: [], // populated in addLoRAsToGraph
|
||||
clip_skip: clipSkip,
|
||||
};
|
||||
|
||||
// Add Custom VAE Support
|
||||
addVAEToGraph(graph, state);
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: METADATA_ACCUMULATOR,
|
||||
field: 'metadata',
|
||||
},
|
||||
destination: {
|
||||
node_id: LATENTS_TO_IMAGE,
|
||||
field: 'metadata',
|
||||
},
|
||||
});
|
||||
|
||||
// add dynamic prompts, mutating `graph`
|
||||
addDynamicPromptsToGraph(graph, state);
|
||||
// add LoRA support
|
||||
addLoRAsToGraph(state, graph, TEXT_TO_LATENTS);
|
||||
|
||||
// optionally add custom VAE
|
||||
addVAEToGraph(state, graph);
|
||||
|
||||
// add dynamic prompts - also sets up core iteration and seed
|
||||
addDynamicPromptsToGraph(state, graph);
|
||||
|
||||
// add controlnet, mutating `graph`
|
||||
addControlNetToLinearGraph(graph, TEXT_TO_LATENTS, state);
|
||||
addControlNetToLinearGraph(state, graph, TEXT_TO_LATENTS);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -19,6 +19,7 @@ export const CONTROL_NET_COLLECT = 'control_net_collect';
|
||||
export const DYNAMIC_PROMPT = 'dynamic_prompt';
|
||||
export const IMAGE_COLLECTION = 'image_collection';
|
||||
export const IMAGE_COLLECTION_ITERATE = 'image_collection_iterate';
|
||||
export const METADATA_ACCUMULATOR = 'metadata_accumulator';
|
||||
|
||||
// friendly graph ids
|
||||
export const TEXT_TO_IMAGE_GRAPH = 'text_to_image_graph';
|
||||
|
@ -5,17 +5,21 @@ import {
|
||||
InputFieldTemplate,
|
||||
InvocationSchemaObject,
|
||||
InvocationTemplate,
|
||||
isInvocationSchemaObject,
|
||||
OutputFieldTemplate,
|
||||
isInvocationSchemaObject,
|
||||
} from '../types/types';
|
||||
import {
|
||||
buildInputFieldTemplate,
|
||||
buildOutputFieldTemplates,
|
||||
} from './fieldTemplateBuilders';
|
||||
|
||||
const RESERVED_FIELD_NAMES = ['id', 'type', 'is_intermediate'];
|
||||
const RESERVED_FIELD_NAMES = ['id', 'type', 'is_intermediate', 'core_metadata'];
|
||||
|
||||
const invocationDenylist = ['Graph', 'InvocationMeta'];
|
||||
const invocationDenylist = [
|
||||
'Graph',
|
||||
'InvocationMeta',
|
||||
'MetadataAccumulatorInvocation',
|
||||
];
|
||||
|
||||
export const parseSchema = (openAPI: OpenAPIV3.Document) => {
|
||||
// filter out non-invocation schemas, plus some tricky invocations for now
|
||||
|
@ -2,6 +2,7 @@ import { useAppToaster } from 'app/components/Toaster';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { UnsafeImageMetadata } from 'services/api/endpoints/images';
|
||||
import { isImageField } from 'services/api/guards';
|
||||
import { ImageDTO } from 'services/api/types';
|
||||
import { initialImageSelected, modelSelected } from '../store/actions';
|
||||
@ -162,7 +163,7 @@ export const useRecallParameters = () => {
|
||||
parameterNotSetToast();
|
||||
return;
|
||||
}
|
||||
dispatch(modelSelected(model?.id || ''));
|
||||
dispatch(modelSelected(model));
|
||||
parameterSetToast();
|
||||
},
|
||||
[dispatch, parameterSetToast, parameterNotSetToast]
|
||||
@ -269,28 +270,24 @@ export const useRecallParameters = () => {
|
||||
);
|
||||
|
||||
const recallAllParameters = useCallback(
|
||||
(image: ImageDTO | undefined) => {
|
||||
if (!image || !image.metadata) {
|
||||
(metadata: UnsafeImageMetadata['metadata'] | undefined) => {
|
||||
if (!metadata) {
|
||||
allParameterNotSetToast();
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
cfg_scale,
|
||||
height,
|
||||
model,
|
||||
positive_conditioning,
|
||||
negative_conditioning,
|
||||
positive_prompt,
|
||||
negative_prompt,
|
||||
scheduler,
|
||||
seed,
|
||||
steps,
|
||||
width,
|
||||
strength,
|
||||
clip,
|
||||
extra,
|
||||
latents,
|
||||
unet,
|
||||
vae,
|
||||
} = image.metadata;
|
||||
} = metadata;
|
||||
|
||||
if (isValidCfgScale(cfg_scale)) {
|
||||
dispatch(setCfgScale(cfg_scale));
|
||||
@ -298,11 +295,11 @@ export const useRecallParameters = () => {
|
||||
if (isValidMainModel(model)) {
|
||||
dispatch(modelSelected(model));
|
||||
}
|
||||
if (isValidPositivePrompt(positive_conditioning)) {
|
||||
dispatch(setPositivePrompt(positive_conditioning));
|
||||
if (isValidPositivePrompt(positive_prompt)) {
|
||||
dispatch(setPositivePrompt(positive_prompt));
|
||||
}
|
||||
if (isValidNegativePrompt(negative_conditioning)) {
|
||||
dispatch(setNegativePrompt(negative_conditioning));
|
||||
if (isValidNegativePrompt(negative_prompt)) {
|
||||
dispatch(setNegativePrompt(negative_prompt));
|
||||
}
|
||||
if (isValidScheduler(scheduler)) {
|
||||
dispatch(setScheduler(scheduler));
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { ImageDTO } from 'services/api/types';
|
||||
import { ImageDTO, MainModelField } from 'services/api/types';
|
||||
|
||||
export const initialImageSelected = createAction<ImageDTO | string | undefined>(
|
||||
'generation/initialImageSelected'
|
||||
);
|
||||
|
||||
export const modelSelected = createAction<string>('generation/modelSelected');
|
||||
export const modelSelected = createAction<MainModelField>(
|
||||
'generation/modelSelected'
|
||||
);
|
||||
|
@ -8,12 +8,11 @@ import {
|
||||
setShouldShowAdvancedOptions,
|
||||
} from 'features/ui/store/uiSlice';
|
||||
import { clamp } from 'lodash-es';
|
||||
import { ImageDTO } from 'services/api/types';
|
||||
import { ImageDTO, MainModelField } from 'services/api/types';
|
||||
import { clipSkipMap } from '../components/Parameters/Advanced/ParamClipSkip';
|
||||
import {
|
||||
CfgScaleParam,
|
||||
HeightParam,
|
||||
MainModelParam,
|
||||
NegativePromptParam,
|
||||
PositivePromptParam,
|
||||
SchedulerParam,
|
||||
@ -54,7 +53,7 @@ export interface GenerationState {
|
||||
shouldUseSymmetry: boolean;
|
||||
horizontalSymmetrySteps: number;
|
||||
verticalSymmetrySteps: number;
|
||||
model: MainModelParam | null;
|
||||
model: MainModelField | null;
|
||||
vae: VaeModelParam | null;
|
||||
seamlessXAxis: boolean;
|
||||
seamlessYAxis: boolean;
|
||||
@ -227,23 +226,17 @@ export const generationSlice = createSlice({
|
||||
const { image_name, width, height } = action.payload;
|
||||
state.initialImage = { imageName: image_name, width, height };
|
||||
},
|
||||
modelSelected: (state, action: PayloadAction<string>) => {
|
||||
const [base_model, type, name] = action.payload.split('/');
|
||||
modelChanged: (state, action: PayloadAction<MainModelField | null>) => {
|
||||
if (!action.payload) {
|
||||
state.model = null;
|
||||
}
|
||||
|
||||
state.model = zMainModel.parse({
|
||||
id: action.payload,
|
||||
base_model,
|
||||
name,
|
||||
type,
|
||||
});
|
||||
state.model = zMainModel.parse(action.payload);
|
||||
|
||||
// Clamp ClipSkip Based On Selected Model
|
||||
const { maxClip } = clipSkipMap[state.model.base_model];
|
||||
state.clipSkip = clamp(state.clipSkip, 0, maxClip);
|
||||
},
|
||||
modelChanged: (state, action: PayloadAction<MainModelParam>) => {
|
||||
state.model = action.payload;
|
||||
},
|
||||
vaeSelected: (state, action: PayloadAction<VaeModelParam | null>) => {
|
||||
state.vae = action.payload;
|
||||
},
|
||||
|
@ -135,8 +135,7 @@ export type BaseModelParam = z.infer<typeof zBaseModel>;
|
||||
* TODO: Make this a dynamically generated enum?
|
||||
*/
|
||||
export const zMainModel = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
model_name: z.string(),
|
||||
base_model: zBaseModel,
|
||||
});
|
||||
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { modelIdToMainModelField } from 'features/nodes/util/modelIdToMainModelField';
|
||||
import { modelSelected } from 'features/parameters/store/actions';
|
||||
import { forEach, isString } from 'lodash-es';
|
||||
import { forEach } from 'lodash-es';
|
||||
import { useGetMainModelsQuery } from 'services/api/endpoints/models';
|
||||
|
||||
export const MODEL_TYPE_MAP = {
|
||||
@ -15,13 +18,17 @@ export const MODEL_TYPE_MAP = {
|
||||
'sd-2': 'Stable Diffusion 2.x',
|
||||
};
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
(state) => ({ currentModel: state.generation.model }),
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const ModelSelect = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const currentModel = useAppSelector(
|
||||
(state: RootState) => state.generation.model
|
||||
);
|
||||
const { currentModel } = useAppSelector(selector);
|
||||
|
||||
const { data: mainModels, isLoading } = useGetMainModelsQuery();
|
||||
|
||||
@ -39,7 +46,7 @@ const ModelSelect = () => {
|
||||
|
||||
data.push({
|
||||
value: id,
|
||||
label: model.name,
|
||||
label: model.model_name,
|
||||
group: MODEL_TYPE_MAP[model.base_model],
|
||||
});
|
||||
});
|
||||
@ -48,7 +55,10 @@ const ModelSelect = () => {
|
||||
}, [mainModels]);
|
||||
|
||||
const selectedModel = useMemo(
|
||||
() => mainModels?.entities[currentModel?.id || ''],
|
||||
() =>
|
||||
mainModels?.entities[
|
||||
`${currentModel?.base_model}/main/${currentModel?.model_name}`
|
||||
],
|
||||
[mainModels?.entities, currentModel]
|
||||
);
|
||||
|
||||
@ -57,31 +67,13 @@ const ModelSelect = () => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
dispatch(modelSelected(v));
|
||||
|
||||
const modelField = modelIdToMainModelField(v);
|
||||
dispatch(modelSelected(modelField));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
// return early here to avoid resetting model selection before we've loaded the available models
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedModel && mainModels?.ids.includes(selectedModel?.id)) {
|
||||
// the selected model is an available model, no need to change it
|
||||
return;
|
||||
}
|
||||
|
||||
const firstModel = mainModels?.ids[0];
|
||||
|
||||
if (!isString(firstModel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleChangeModel(firstModel);
|
||||
}, [handleChangeModel, isLoading, mainModels?.ids, selectedModel]);
|
||||
|
||||
return isLoading ? (
|
||||
<IAIMantineSelect
|
||||
label={t('modelManager.model')}
|
||||
@ -94,9 +86,10 @@ const ModelSelect = () => {
|
||||
tooltip={selectedModel?.description}
|
||||
label={t('modelManager.model')}
|
||||
value={selectedModel?.id}
|
||||
placeholder={data.length > 0 ? 'Select a model' : 'No models detected!'}
|
||||
placeholder={data.length > 0 ? 'Select a model' : 'No models available'}
|
||||
data={data}
|
||||
error={data.length === 0}
|
||||
disabled={data.length === 0}
|
||||
onChange={handleChangeModel}
|
||||
/>
|
||||
);
|
||||
|
@ -50,7 +50,7 @@ const VAESelect = () => {
|
||||
|
||||
data.push({
|
||||
value: id,
|
||||
label: model.name,
|
||||
label: model.model_name,
|
||||
group: MODEL_TYPE_MAP[model.base_model],
|
||||
disabled,
|
||||
tooltip: disabled
|
||||
|
@ -1,13 +1,22 @@
|
||||
import { ApiFullTagDescription, api } from '..';
|
||||
import { components } from '../schema';
|
||||
import { ImageDTO } from '../types';
|
||||
|
||||
/**
|
||||
* This is an unsafe type; the object inside is not guaranteed to be valid.
|
||||
*/
|
||||
export type UnsafeImageMetadata = {
|
||||
metadata: components['schemas']['CoreMetadata'];
|
||||
graph: NonNullable<components['schemas']['Graph']>;
|
||||
};
|
||||
|
||||
export const imagesApi = api.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
/**
|
||||
* Image Queries
|
||||
*/
|
||||
getImageDTO: build.query<ImageDTO, string>({
|
||||
query: (image_name) => ({ url: `images/${image_name}/metadata` }),
|
||||
query: (image_name) => ({ url: `images/${image_name}` }),
|
||||
providesTags: (result, error, arg) => {
|
||||
const tags: ApiFullTagDescription[] = [{ type: 'Image', id: arg }];
|
||||
if (result?.board_id) {
|
||||
@ -17,7 +26,17 @@ export const imagesApi = api.injectEndpoints({
|
||||
},
|
||||
keepUnusedDataFor: 86400, // 24 hours
|
||||
}),
|
||||
getImageMetadata: build.query<UnsafeImageMetadata, string>({
|
||||
query: (image_name) => ({ url: `images/${image_name}/metadata` }),
|
||||
providesTags: (result, error, arg) => {
|
||||
const tags: ApiFullTagDescription[] = [
|
||||
{ type: 'ImageMetadata', id: arg },
|
||||
];
|
||||
return tags;
|
||||
},
|
||||
keepUnusedDataFor: 86400, // 24 hours
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetImageDTOQuery } = imagesApi;
|
||||
export const { useGetImageDTOQuery, useGetImageMetadataQuery } = imagesApi;
|
||||
|
@ -33,25 +33,28 @@ type AnyModelConfigEntity =
|
||||
| VaeModelConfigEntity;
|
||||
|
||||
const mainModelsAdapter = createEntityAdapter<MainModelConfigEntity>({
|
||||
sortComparer: (a, b) => a.name.localeCompare(b.name),
|
||||
sortComparer: (a, b) => a.model_name.localeCompare(b.model_name),
|
||||
});
|
||||
const loraModelsAdapter = createEntityAdapter<LoRAModelConfigEntity>({
|
||||
sortComparer: (a, b) => a.name.localeCompare(b.name),
|
||||
sortComparer: (a, b) => a.model_name.localeCompare(b.model_name),
|
||||
});
|
||||
const controlNetModelsAdapter =
|
||||
createEntityAdapter<ControlNetModelConfigEntity>({
|
||||
sortComparer: (a, b) => a.name.localeCompare(b.name),
|
||||
sortComparer: (a, b) => a.model_name.localeCompare(b.model_name),
|
||||
});
|
||||
const textualInversionModelsAdapter =
|
||||
createEntityAdapter<TextualInversionModelConfigEntity>({
|
||||
sortComparer: (a, b) => a.name.localeCompare(b.name),
|
||||
sortComparer: (a, b) => a.model_name.localeCompare(b.model_name),
|
||||
});
|
||||
const vaeModelsAdapter = createEntityAdapter<VaeModelConfigEntity>({
|
||||
sortComparer: (a, b) => a.name.localeCompare(b.name),
|
||||
sortComparer: (a, b) => a.model_name.localeCompare(b.model_name),
|
||||
});
|
||||
|
||||
export const getModelId = ({ base_model, type, name }: AnyModelConfig) =>
|
||||
`${base_model}/${type}/${name}`;
|
||||
export const getModelId = ({
|
||||
base_model,
|
||||
model_type,
|
||||
model_name,
|
||||
}: AnyModelConfig) => `${base_model}/${model_type}/${model_name}`;
|
||||
|
||||
const createModelEntities = <T extends AnyModelConfigEntity>(
|
||||
models: AnyModelConfig[]
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { FullTagDescription } from '@reduxjs/toolkit/dist/query/endpointDefinitions';
|
||||
import {
|
||||
BaseQueryFn,
|
||||
FetchArgs,
|
||||
@ -5,10 +6,9 @@ import {
|
||||
createApi,
|
||||
fetchBaseQuery,
|
||||
} from '@reduxjs/toolkit/query/react';
|
||||
import { FullTagDescription } from '@reduxjs/toolkit/dist/query/endpointDefinitions';
|
||||
import { $authToken, $baseUrl } from 'services/api/client';
|
||||
|
||||
export const tagTypes = ['Board', 'Image', 'Model'];
|
||||
export const tagTypes = ['Board', 'Image', 'ImageMetadata', 'Model'];
|
||||
export type ApiFullTagDescription = FullTagDescription<
|
||||
(typeof tagTypes)[number]
|
||||
>;
|
||||
|
498
invokeai/frontend/web/src/services/api/schema.d.ts
vendored
498
invokeai/frontend/web/src/services/api/schema.d.ts
vendored
@ -109,10 +109,10 @@ export type paths = {
|
||||
};
|
||||
"/api/v1/images/": {
|
||||
/**
|
||||
* List Images With Metadata
|
||||
* @description Gets a list of images
|
||||
* List Image Dtos
|
||||
* @description Gets a list of image DTOs
|
||||
*/
|
||||
get: operations["list_images_with_metadata"];
|
||||
get: operations["list_image_dtos"];
|
||||
/**
|
||||
* Upload Image
|
||||
* @description Uploads an image
|
||||
@ -121,10 +121,10 @@ export type paths = {
|
||||
};
|
||||
"/api/v1/images/{image_name}": {
|
||||
/**
|
||||
* Get Image Full
|
||||
* @description Gets a full-resolution image file
|
||||
* Get Image Dto
|
||||
* @description Gets an image's DTO
|
||||
*/
|
||||
get: operations["get_image_full"];
|
||||
get: operations["get_image_dto"];
|
||||
/**
|
||||
* Delete Image
|
||||
* @description Deletes an image
|
||||
@ -143,6 +143,13 @@ export type paths = {
|
||||
*/
|
||||
get: operations["get_image_metadata"];
|
||||
};
|
||||
"/api/v1/images/{image_name}/full": {
|
||||
/**
|
||||
* Get Image Full
|
||||
* @description Gets a full-resolution image file
|
||||
*/
|
||||
get: operations["get_image_full"];
|
||||
};
|
||||
"/api/v1/images/{image_name}/thumbnail": {
|
||||
/**
|
||||
* Get Image Thumbnail
|
||||
@ -798,14 +805,14 @@ export type components = {
|
||||
};
|
||||
/** ControlNetModelConfig */
|
||||
ControlNetModelConfig: {
|
||||
/** Name */
|
||||
name: string;
|
||||
/** Model Name */
|
||||
model_name: string;
|
||||
base_model: components["schemas"]["BaseModelType"];
|
||||
/**
|
||||
* Type
|
||||
* Model Type
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "controlnet";
|
||||
model_type: "controlnet";
|
||||
/** Path */
|
||||
path: string;
|
||||
/** Description */
|
||||
@ -836,6 +843,97 @@ export type components = {
|
||||
*/
|
||||
control?: components["schemas"]["ControlField"];
|
||||
};
|
||||
/**
|
||||
* CoreMetadata
|
||||
* @description Core generation metadata for an image generated in InvokeAI.
|
||||
*/
|
||||
CoreMetadata: {
|
||||
/**
|
||||
* Generation Mode
|
||||
* @description The generation mode that output this image
|
||||
*/
|
||||
generation_mode: string;
|
||||
/**
|
||||
* Positive Prompt
|
||||
* @description The positive prompt parameter
|
||||
*/
|
||||
positive_prompt: string;
|
||||
/**
|
||||
* Negative Prompt
|
||||
* @description The negative prompt parameter
|
||||
*/
|
||||
negative_prompt: string;
|
||||
/**
|
||||
* Width
|
||||
* @description The width parameter
|
||||
*/
|
||||
width: number;
|
||||
/**
|
||||
* Height
|
||||
* @description The height parameter
|
||||
*/
|
||||
height: number;
|
||||
/**
|
||||
* Seed
|
||||
* @description The seed used for noise generation
|
||||
*/
|
||||
seed: number;
|
||||
/**
|
||||
* Rand Device
|
||||
* @description The device used for random number generation
|
||||
*/
|
||||
rand_device: string;
|
||||
/**
|
||||
* Cfg Scale
|
||||
* @description The classifier-free guidance scale parameter
|
||||
*/
|
||||
cfg_scale: number;
|
||||
/**
|
||||
* Steps
|
||||
* @description The number of steps used for inference
|
||||
*/
|
||||
steps: number;
|
||||
/**
|
||||
* Scheduler
|
||||
* @description The scheduler used for inference
|
||||
*/
|
||||
scheduler: string;
|
||||
/**
|
||||
* Clip Skip
|
||||
* @description The number of skipped CLIP layers
|
||||
*/
|
||||
clip_skip: number;
|
||||
/**
|
||||
* Model
|
||||
* @description The main model used for inference
|
||||
*/
|
||||
model: components["schemas"]["MainModelField"];
|
||||
/**
|
||||
* Controlnets
|
||||
* @description The ControlNets used for inference
|
||||
*/
|
||||
controlnets: (components["schemas"]["ControlField"])[];
|
||||
/**
|
||||
* Loras
|
||||
* @description The LoRAs used for inference
|
||||
*/
|
||||
loras: (components["schemas"]["LoRAMetadataField"])[];
|
||||
/**
|
||||
* Strength
|
||||
* @description The strength used for latents-to-latents
|
||||
*/
|
||||
strength?: number;
|
||||
/**
|
||||
* Init Image
|
||||
* @description The name of the initial image
|
||||
*/
|
||||
init_image?: string;
|
||||
/**
|
||||
* Vae
|
||||
* @description The VAE used for decoding, if the main model's default was not used
|
||||
*/
|
||||
vae?: components["schemas"]["VAEModelField"];
|
||||
};
|
||||
/**
|
||||
* CvInpaintInvocation
|
||||
* @description Simple inpaint using opencv.
|
||||
@ -1058,7 +1156,7 @@ export type components = {
|
||||
* @description The nodes in this graph
|
||||
*/
|
||||
nodes?: {
|
||||
[key: string]: (components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]) | undefined;
|
||||
[key: string]: (components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]) | undefined;
|
||||
};
|
||||
/**
|
||||
* Edges
|
||||
@ -1101,7 +1199,7 @@ export type components = {
|
||||
* @description The results of node executions
|
||||
*/
|
||||
results: {
|
||||
[key: string]: (components["schemas"]["IntCollectionOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["CompelOutput"] | components["schemas"]["ClipSkipInvocationOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["IntOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["PromptOutput"] | components["schemas"]["PromptCollectionOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]) | undefined;
|
||||
[key: string]: (components["schemas"]["ImageOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["MetadataAccumulatorOutput"] | components["schemas"]["PromptOutput"] | components["schemas"]["PromptCollectionOutput"] | components["schemas"]["CompelOutput"] | components["schemas"]["ClipSkipInvocationOutput"] | components["schemas"]["IntOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["IntCollectionOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]) | undefined;
|
||||
};
|
||||
/**
|
||||
* Errors
|
||||
@ -1502,11 +1600,6 @@ export type components = {
|
||||
* @description The node ID that generated this image, if it is a generated image.
|
||||
*/
|
||||
node_id?: string;
|
||||
/**
|
||||
* Metadata
|
||||
* @description A limited subset of the image's generation metadata. Retrieve the image's session for full metadata.
|
||||
*/
|
||||
metadata?: components["schemas"]["ImageMetadata"];
|
||||
/**
|
||||
* Board Id
|
||||
* @description The id of the board the image belongs to, if one exists.
|
||||
@ -1606,96 +1699,19 @@ export type components = {
|
||||
};
|
||||
/**
|
||||
* ImageMetadata
|
||||
* @description Core generation metadata for an image/tensor generated in InvokeAI.
|
||||
*
|
||||
* Also includes any metadata from the image's PNG tEXt chunks.
|
||||
*
|
||||
* Generated by traversing the execution graph, collecting the parameters of the nearest ancestors
|
||||
* of a given node.
|
||||
*
|
||||
* Full metadata may be accessed by querying for the session in the `graph_executions` table.
|
||||
* @description An image's generation metadata
|
||||
*/
|
||||
ImageMetadata: {
|
||||
/**
|
||||
* Type
|
||||
* @description The type of the ancestor node of the image output node.
|
||||
* Metadata
|
||||
* @description The image's core metadata, if it was created in the Linear or Canvas UI
|
||||
*/
|
||||
type?: string;
|
||||
metadata?: Record<string, never>;
|
||||
/**
|
||||
* Positive Conditioning
|
||||
* @description The positive conditioning.
|
||||
* Graph
|
||||
* @description The graph that created the image
|
||||
*/
|
||||
positive_conditioning?: string;
|
||||
/**
|
||||
* Negative Conditioning
|
||||
* @description The negative conditioning.
|
||||
*/
|
||||
negative_conditioning?: string;
|
||||
/**
|
||||
* Width
|
||||
* @description Width of the image/latents in pixels.
|
||||
*/
|
||||
width?: number;
|
||||
/**
|
||||
* Height
|
||||
* @description Height of the image/latents in pixels.
|
||||
*/
|
||||
height?: number;
|
||||
/**
|
||||
* Seed
|
||||
* @description The seed used for noise generation.
|
||||
*/
|
||||
seed?: number;
|
||||
/**
|
||||
* Cfg Scale
|
||||
* @description The classifier-free guidance scale.
|
||||
*/
|
||||
cfg_scale?: number | (number)[];
|
||||
/**
|
||||
* Steps
|
||||
* @description The number of steps used for inference.
|
||||
*/
|
||||
steps?: number;
|
||||
/**
|
||||
* Scheduler
|
||||
* @description The scheduler used for inference.
|
||||
*/
|
||||
scheduler?: string;
|
||||
/**
|
||||
* Model
|
||||
* @description The model used for inference.
|
||||
*/
|
||||
model?: string;
|
||||
/**
|
||||
* Strength
|
||||
* @description The strength used for image-to-image/latents-to-latents.
|
||||
*/
|
||||
strength?: number;
|
||||
/**
|
||||
* Latents
|
||||
* @description The ID of the initial latents.
|
||||
*/
|
||||
latents?: string;
|
||||
/**
|
||||
* Vae
|
||||
* @description The VAE used for decoding.
|
||||
*/
|
||||
vae?: string;
|
||||
/**
|
||||
* Unet
|
||||
* @description The UNet used dor inference.
|
||||
*/
|
||||
unet?: string;
|
||||
/**
|
||||
* Clip
|
||||
* @description The CLIP Encoder used for conditioning.
|
||||
*/
|
||||
clip?: string;
|
||||
/**
|
||||
* Extra
|
||||
* @description Uploaded image metadata, extracted from the PNG tEXt chunk.
|
||||
*/
|
||||
extra?: string;
|
||||
graph?: Record<string, never>;
|
||||
};
|
||||
/**
|
||||
* ImageMultiplyInvocation
|
||||
@ -2436,6 +2452,11 @@ export type components = {
|
||||
* @default false
|
||||
*/
|
||||
tiled?: boolean;
|
||||
/**
|
||||
* Metadata
|
||||
* @description Optional core metadata to be written to the image
|
||||
*/
|
||||
metadata?: components["schemas"]["CoreMetadata"];
|
||||
};
|
||||
/**
|
||||
* LatentsToLatentsInvocation
|
||||
@ -2659,16 +2680,32 @@ export type components = {
|
||||
*/
|
||||
coarse?: boolean;
|
||||
};
|
||||
/**
|
||||
* LoRAMetadataField
|
||||
* @description LoRA metadata for an image generated in InvokeAI.
|
||||
*/
|
||||
LoRAMetadataField: {
|
||||
/**
|
||||
* Lora
|
||||
* @description The LoRA model
|
||||
*/
|
||||
lora: components["schemas"]["LoRAModelField"];
|
||||
/**
|
||||
* Weight
|
||||
* @description The weight of the LoRA model
|
||||
*/
|
||||
weight: number;
|
||||
};
|
||||
/** LoRAModelConfig */
|
||||
LoRAModelConfig: {
|
||||
/** Name */
|
||||
name: string;
|
||||
/** Model Name */
|
||||
model_name: string;
|
||||
base_model: components["schemas"]["BaseModelType"];
|
||||
/**
|
||||
* Type
|
||||
* Model Type
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "lora";
|
||||
model_type: "lora";
|
||||
/** Path */
|
||||
path: string;
|
||||
/** Description */
|
||||
@ -2956,6 +2993,131 @@ export type components = {
|
||||
* @enum {string}
|
||||
*/
|
||||
MergeInterpolationMethod: "weighted_sum" | "sigmoid" | "inv_sigmoid" | "add_difference";
|
||||
/**
|
||||
* MetadataAccumulatorInvocation
|
||||
* @description Outputs a Core Metadata Object
|
||||
*/
|
||||
MetadataAccumulatorInvocation: {
|
||||
/**
|
||||
* Id
|
||||
* @description The id of this node. Must be unique among all nodes.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Is Intermediate
|
||||
* @description Whether or not this node is an intermediate node.
|
||||
* @default false
|
||||
*/
|
||||
is_intermediate?: boolean;
|
||||
/**
|
||||
* Type
|
||||
* @default metadata_accumulator
|
||||
* @enum {string}
|
||||
*/
|
||||
type?: "metadata_accumulator";
|
||||
/**
|
||||
* Generation Mode
|
||||
* @description The generation mode that output this image
|
||||
*/
|
||||
generation_mode: string;
|
||||
/**
|
||||
* Positive Prompt
|
||||
* @description The positive prompt parameter
|
||||
*/
|
||||
positive_prompt: string;
|
||||
/**
|
||||
* Negative Prompt
|
||||
* @description The negative prompt parameter
|
||||
*/
|
||||
negative_prompt: string;
|
||||
/**
|
||||
* Width
|
||||
* @description The width parameter
|
||||
*/
|
||||
width: number;
|
||||
/**
|
||||
* Height
|
||||
* @description The height parameter
|
||||
*/
|
||||
height: number;
|
||||
/**
|
||||
* Seed
|
||||
* @description The seed used for noise generation
|
||||
*/
|
||||
seed: number;
|
||||
/**
|
||||
* Rand Device
|
||||
* @description The device used for random number generation
|
||||
*/
|
||||
rand_device: string;
|
||||
/**
|
||||
* Cfg Scale
|
||||
* @description The classifier-free guidance scale parameter
|
||||
*/
|
||||
cfg_scale: number;
|
||||
/**
|
||||
* Steps
|
||||
* @description The number of steps used for inference
|
||||
*/
|
||||
steps: number;
|
||||
/**
|
||||
* Scheduler
|
||||
* @description The scheduler used for inference
|
||||
*/
|
||||
scheduler: string;
|
||||
/**
|
||||
* Clip Skip
|
||||
* @description The number of skipped CLIP layers
|
||||
*/
|
||||
clip_skip: number;
|
||||
/**
|
||||
* Model
|
||||
* @description The main model used for inference
|
||||
*/
|
||||
model: components["schemas"]["MainModelField"];
|
||||
/**
|
||||
* Controlnets
|
||||
* @description The ControlNets used for inference
|
||||
*/
|
||||
controlnets: (components["schemas"]["ControlField"])[];
|
||||
/**
|
||||
* Loras
|
||||
* @description The LoRAs used for inference
|
||||
*/
|
||||
loras: (components["schemas"]["LoRAMetadataField"])[];
|
||||
/**
|
||||
* Strength
|
||||
* @description The strength used for latents-to-latents
|
||||
*/
|
||||
strength?: number;
|
||||
/**
|
||||
* Init Image
|
||||
* @description The name of the initial image
|
||||
*/
|
||||
init_image?: string;
|
||||
/**
|
||||
* Vae
|
||||
* @description The VAE used for decoding, if the main model's default was not used
|
||||
*/
|
||||
vae?: components["schemas"]["VAEModelField"];
|
||||
};
|
||||
/**
|
||||
* MetadataAccumulatorOutput
|
||||
* @description The output of the MetadataAccumulator node
|
||||
*/
|
||||
MetadataAccumulatorOutput: {
|
||||
/**
|
||||
* Type
|
||||
* @default metadata_accumulator_output
|
||||
* @enum {string}
|
||||
*/
|
||||
type?: "metadata_accumulator_output";
|
||||
/**
|
||||
* Metadata
|
||||
* @description The core metadata for the image
|
||||
*/
|
||||
metadata: components["schemas"]["CoreMetadata"];
|
||||
};
|
||||
/**
|
||||
* MidasDepthImageProcessorInvocation
|
||||
* @description Applies Midas depth processing to image
|
||||
@ -3110,7 +3272,7 @@ export type components = {
|
||||
/** ModelsList */
|
||||
ModelsList: {
|
||||
/** Models */
|
||||
models: (components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"])[];
|
||||
models: (components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"])[];
|
||||
};
|
||||
/**
|
||||
* MultiplyInvocation
|
||||
@ -3900,14 +4062,14 @@ export type components = {
|
||||
};
|
||||
/** StableDiffusion1ModelCheckpointConfig */
|
||||
StableDiffusion1ModelCheckpointConfig: {
|
||||
/** Name */
|
||||
name: string;
|
||||
/** Model Name */
|
||||
model_name: string;
|
||||
base_model: components["schemas"]["BaseModelType"];
|
||||
/**
|
||||
* Type
|
||||
* Model Type
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "main";
|
||||
model_type: "main";
|
||||
/** Path */
|
||||
path: string;
|
||||
/** Description */
|
||||
@ -3926,14 +4088,14 @@ export type components = {
|
||||
};
|
||||
/** StableDiffusion1ModelDiffusersConfig */
|
||||
StableDiffusion1ModelDiffusersConfig: {
|
||||
/** Name */
|
||||
name: string;
|
||||
/** Model Name */
|
||||
model_name: string;
|
||||
base_model: components["schemas"]["BaseModelType"];
|
||||
/**
|
||||
* Type
|
||||
* Model Type
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "main";
|
||||
model_type: "main";
|
||||
/** Path */
|
||||
path: string;
|
||||
/** Description */
|
||||
@ -3950,14 +4112,14 @@ export type components = {
|
||||
};
|
||||
/** StableDiffusion2ModelCheckpointConfig */
|
||||
StableDiffusion2ModelCheckpointConfig: {
|
||||
/** Name */
|
||||
name: string;
|
||||
/** Model Name */
|
||||
model_name: string;
|
||||
base_model: components["schemas"]["BaseModelType"];
|
||||
/**
|
||||
* Type
|
||||
* Model Type
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "main";
|
||||
model_type: "main";
|
||||
/** Path */
|
||||
path: string;
|
||||
/** Description */
|
||||
@ -3976,14 +4138,14 @@ export type components = {
|
||||
};
|
||||
/** StableDiffusion2ModelDiffusersConfig */
|
||||
StableDiffusion2ModelDiffusersConfig: {
|
||||
/** Name */
|
||||
name: string;
|
||||
/** Model Name */
|
||||
model_name: string;
|
||||
base_model: components["schemas"]["BaseModelType"];
|
||||
/**
|
||||
* Type
|
||||
* Model Type
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "main";
|
||||
model_type: "main";
|
||||
/** Path */
|
||||
path: string;
|
||||
/** Description */
|
||||
@ -4190,14 +4352,14 @@ export type components = {
|
||||
};
|
||||
/** TextualInversionModelConfig */
|
||||
TextualInversionModelConfig: {
|
||||
/** Name */
|
||||
name: string;
|
||||
/** Model Name */
|
||||
model_name: string;
|
||||
base_model: components["schemas"]["BaseModelType"];
|
||||
/**
|
||||
* Type
|
||||
* Model Type
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "embedding";
|
||||
model_type: "embedding";
|
||||
/** Path */
|
||||
path: string;
|
||||
/** Description */
|
||||
@ -4367,14 +4529,14 @@ export type components = {
|
||||
};
|
||||
/** VaeModelConfig */
|
||||
VaeModelConfig: {
|
||||
/** Name */
|
||||
name: string;
|
||||
/** Model Name */
|
||||
model_name: string;
|
||||
base_model: components["schemas"]["BaseModelType"];
|
||||
/**
|
||||
* Type
|
||||
* Model Type
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "vae";
|
||||
model_type: "vae";
|
||||
/** Path */
|
||||
path: string;
|
||||
/** Description */
|
||||
@ -4425,18 +4587,18 @@ export type components = {
|
||||
*/
|
||||
image?: components["schemas"]["ImageField"];
|
||||
};
|
||||
/**
|
||||
* StableDiffusion2ModelFormat
|
||||
* @description An enumeration.
|
||||
* @enum {string}
|
||||
*/
|
||||
StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
|
||||
/**
|
||||
* StableDiffusion1ModelFormat
|
||||
* @description An enumeration.
|
||||
* @enum {string}
|
||||
*/
|
||||
StableDiffusion1ModelFormat: "checkpoint" | "diffusers";
|
||||
/**
|
||||
* StableDiffusion2ModelFormat
|
||||
* @description An enumeration.
|
||||
* @enum {string}
|
||||
*/
|
||||
StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
|
||||
};
|
||||
responses: never;
|
||||
parameters: never;
|
||||
@ -4547,7 +4709,7 @@ export type operations = {
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"];
|
||||
"application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
@ -4584,7 +4746,7 @@ export type operations = {
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"];
|
||||
"application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
@ -4817,7 +4979,7 @@ export type operations = {
|
||||
/** @description The model imported successfully */
|
||||
201: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"];
|
||||
"application/json": components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"];
|
||||
};
|
||||
};
|
||||
/** @description The model could not be found */
|
||||
@ -4885,14 +5047,14 @@ export type operations = {
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"];
|
||||
"application/json": components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description The model was updated successfully */
|
||||
200: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"];
|
||||
"application/json": components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"];
|
||||
};
|
||||
};
|
||||
/** @description Bad request */
|
||||
@ -4926,7 +5088,7 @@ export type operations = {
|
||||
/** @description Model converted successfully */
|
||||
200: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"];
|
||||
"application/json": components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"];
|
||||
};
|
||||
};
|
||||
/** @description Bad request */
|
||||
@ -4961,7 +5123,7 @@ export type operations = {
|
||||
/** @description Model converted successfully */
|
||||
200: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"];
|
||||
"application/json": components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"];
|
||||
};
|
||||
};
|
||||
/** @description Incompatible models */
|
||||
@ -4977,10 +5139,10 @@ export type operations = {
|
||||
};
|
||||
};
|
||||
/**
|
||||
* List Images With Metadata
|
||||
* @description Gets a list of images
|
||||
* List Image Dtos
|
||||
* @description Gets a list of image DTOs
|
||||
*/
|
||||
list_images_with_metadata: {
|
||||
list_image_dtos: {
|
||||
parameters: {
|
||||
query?: {
|
||||
/** @description The origin of images to list */
|
||||
@ -5050,25 +5212,23 @@ export type operations = {
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Get Image Full
|
||||
* @description Gets a full-resolution image file
|
||||
* Get Image Dto
|
||||
* @description Gets an image's DTO
|
||||
*/
|
||||
get_image_full: {
|
||||
get_image_dto: {
|
||||
parameters: {
|
||||
path: {
|
||||
/** @description The name of full-resolution image file to get */
|
||||
/** @description The name of image to get */
|
||||
image_name: string;
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Return the full-resolution image */
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
content: {
|
||||
"image/png": unknown;
|
||||
"application/json": components["schemas"]["ImageDTO"];
|
||||
};
|
||||
};
|
||||
/** @description Image not found */
|
||||
404: never;
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
content: {
|
||||
@ -5149,7 +5309,7 @@ export type operations = {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["ImageDTO"];
|
||||
"application/json": components["schemas"]["ImageMetadata"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
@ -5160,6 +5320,34 @@ export type operations = {
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Get Image Full
|
||||
* @description Gets a full-resolution image file
|
||||
*/
|
||||
get_image_full: {
|
||||
parameters: {
|
||||
path: {
|
||||
/** @description The name of full-resolution image file to get */
|
||||
image_name: string;
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Return the full-resolution image */
|
||||
200: {
|
||||
content: {
|
||||
"image/png": unknown;
|
||||
};
|
||||
};
|
||||
/** @description Image not found */
|
||||
404: never;
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Get Image Thumbnail
|
||||
* @description Gets a thumbnail image file
|
||||
|
@ -1,9 +1,9 @@
|
||||
import queryString from 'query-string';
|
||||
import { createAppAsyncThunk } from 'app/store/storeUtils';
|
||||
import { selectImagesAll } from 'features/gallery/store/gallerySlice';
|
||||
import { size } from 'lodash-es';
|
||||
import { paths } from 'services/api/schema';
|
||||
import queryString from 'query-string';
|
||||
import { $client } from 'services/api/client';
|
||||
import { paths } from 'services/api/schema';
|
||||
|
||||
type GetImageUrlsArg =
|
||||
paths['/api/v1/images/{image_name}/urls']['get']['parameters']['path'];
|
||||
@ -24,7 +24,7 @@ export const imageUrlsReceived = createAppAsyncThunk<
|
||||
GetImageUrlsResponse,
|
||||
GetImageUrlsArg,
|
||||
GetImageUrlsThunkConfig
|
||||
>('api/imageUrlsReceived', async (arg, { rejectWithValue }) => {
|
||||
>('thunkApi/imageUrlsReceived', async (arg, { rejectWithValue }) => {
|
||||
const { image_name } = arg;
|
||||
const { get } = $client.get();
|
||||
const { data, error, response } = await get(
|
||||
@ -46,10 +46,10 @@ export const imageUrlsReceived = createAppAsyncThunk<
|
||||
});
|
||||
|
||||
type GetImageMetadataArg =
|
||||
paths['/api/v1/images/{image_name}/metadata']['get']['parameters']['path'];
|
||||
paths['/api/v1/images/{image_name}']['get']['parameters']['path'];
|
||||
|
||||
type GetImageMetadataResponse =
|
||||
paths['/api/v1/images/{image_name}/metadata']['get']['responses']['200']['content']['application/json'];
|
||||
paths['/api/v1/images/{image_name}']['get']['responses']['200']['content']['application/json'];
|
||||
|
||||
type GetImageMetadataThunkConfig = {
|
||||
rejectValue: {
|
||||
@ -58,21 +58,18 @@ type GetImageMetadataThunkConfig = {
|
||||
};
|
||||
};
|
||||
|
||||
export const imageMetadataReceived = createAppAsyncThunk<
|
||||
export const imageDTOReceived = createAppAsyncThunk<
|
||||
GetImageMetadataResponse,
|
||||
GetImageMetadataArg,
|
||||
GetImageMetadataThunkConfig
|
||||
>('api/imageMetadataReceived', async (arg, { rejectWithValue }) => {
|
||||
>('thunkApi/imageMetadataReceived', async (arg, { rejectWithValue }) => {
|
||||
const { image_name } = arg;
|
||||
const { get } = $client.get();
|
||||
const { data, error, response } = await get(
|
||||
'/api/v1/images/{image_name}/metadata',
|
||||
{
|
||||
params: {
|
||||
path: { image_name },
|
||||
},
|
||||
}
|
||||
);
|
||||
const { data, error, response } = await get('/api/v1/images/{image_name}', {
|
||||
params: {
|
||||
path: { image_name },
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return rejectWithValue({ arg, error });
|
||||
@ -148,7 +145,7 @@ export const imageUploaded = createAppAsyncThunk<
|
||||
UploadImageResponse,
|
||||
UploadImageArg,
|
||||
UploadImageThunkConfig
|
||||
>('api/imageUploaded', async (arg, { rejectWithValue }) => {
|
||||
>('thunkApi/imageUploaded', async (arg, { rejectWithValue }) => {
|
||||
const {
|
||||
postUploadAction,
|
||||
file,
|
||||
@ -199,7 +196,7 @@ export const imageDeleted = createAppAsyncThunk<
|
||||
DeleteImageResponse,
|
||||
DeleteImageArg,
|
||||
DeleteImageThunkConfig
|
||||
>('api/imageDeleted', async (arg, { rejectWithValue }) => {
|
||||
>('thunkApi/imageDeleted', async (arg, { rejectWithValue }) => {
|
||||
const { image_name } = arg;
|
||||
const { del } = $client.get();
|
||||
const { data, error, response } = await del('/api/v1/images/{image_name}', {
|
||||
@ -235,7 +232,7 @@ export const imageUpdated = createAppAsyncThunk<
|
||||
UpdateImageResponse,
|
||||
UpdateImageArg,
|
||||
UpdateImageThunkConfig
|
||||
>('api/imageUpdated', async (arg, { rejectWithValue }) => {
|
||||
>('thunkApi/imageUpdated', async (arg, { rejectWithValue }) => {
|
||||
const { image_name, image_category, is_intermediate, session_id } = arg;
|
||||
const { patch } = $client.get();
|
||||
const { data, error, response } = await patch('/api/v1/images/{image_name}', {
|
||||
@ -284,46 +281,49 @@ export const receivedPageOfImages = createAppAsyncThunk<
|
||||
ListImagesResponse,
|
||||
ListImagesArg,
|
||||
ListImagesThunkConfig
|
||||
>('api/receivedPageOfImages', async (arg, { getState, rejectWithValue }) => {
|
||||
const { get } = $client.get();
|
||||
>(
|
||||
'thunkApi/receivedPageOfImages',
|
||||
async (arg, { getState, rejectWithValue }) => {
|
||||
const { get } = $client.get();
|
||||
|
||||
const state = getState();
|
||||
const { categories, selectedBoardId } = state.gallery;
|
||||
const state = getState();
|
||||
const { categories, selectedBoardId } = state.gallery;
|
||||
|
||||
const images = selectImagesAll(state).filter((i) => {
|
||||
const isInCategory = categories.includes(i.image_category);
|
||||
const isInSelectedBoard = selectedBoardId
|
||||
? i.board_id === selectedBoardId
|
||||
: true;
|
||||
return isInCategory && isInSelectedBoard;
|
||||
});
|
||||
const images = selectImagesAll(state).filter((i) => {
|
||||
const isInCategory = categories.includes(i.image_category);
|
||||
const isInSelectedBoard = selectedBoardId
|
||||
? i.board_id === selectedBoardId
|
||||
: true;
|
||||
return isInCategory && isInSelectedBoard;
|
||||
});
|
||||
|
||||
let query: ListImagesArg = {};
|
||||
let query: ListImagesArg = {};
|
||||
|
||||
if (size(arg)) {
|
||||
query = {
|
||||
...DEFAULT_IMAGES_LISTED_ARG,
|
||||
offset: images.length,
|
||||
...arg,
|
||||
};
|
||||
} else {
|
||||
query = {
|
||||
...DEFAULT_IMAGES_LISTED_ARG,
|
||||
categories,
|
||||
offset: images.length,
|
||||
};
|
||||
if (size(arg)) {
|
||||
query = {
|
||||
...DEFAULT_IMAGES_LISTED_ARG,
|
||||
offset: images.length,
|
||||
...arg,
|
||||
};
|
||||
} else {
|
||||
query = {
|
||||
...DEFAULT_IMAGES_LISTED_ARG,
|
||||
categories,
|
||||
offset: images.length,
|
||||
};
|
||||
}
|
||||
|
||||
const { data, error, response } = await get('/api/v1/images/', {
|
||||
params: {
|
||||
query,
|
||||
},
|
||||
querySerializer: (q) => queryString.stringify(q, { arrayFormat: 'none' }),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return rejectWithValue({ arg, error });
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const { data, error, response } = await get('/api/v1/images/', {
|
||||
params: {
|
||||
query,
|
||||
},
|
||||
querySerializer: (q) => queryString.stringify(q, { arrayFormat: 'none' }),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return rejectWithValue({ arg, error });
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
);
|
||||
|
@ -19,6 +19,7 @@ export type ImageChanges = components['schemas']['ImageRecordChanges'];
|
||||
export type ImageCategory = components['schemas']['ImageCategory'];
|
||||
export type ResourceOrigin = components['schemas']['ResourceOrigin'];
|
||||
export type ImageField = components['schemas']['ImageField'];
|
||||
export type ImageMetadata = components['schemas']['ImageMetadata'];
|
||||
export type OffsetPaginatedResults_BoardDTO_ =
|
||||
components['schemas']['OffsetPaginatedResults_BoardDTO_'];
|
||||
export type OffsetPaginatedResults_ImageDTO_ =
|
||||
@ -31,6 +32,7 @@ export type MainModelField = components['schemas']['MainModelField'];
|
||||
export type VAEModelField = components['schemas']['VAEModelField'];
|
||||
export type LoRAModelField = components['schemas']['LoRAModelField'];
|
||||
export type ModelsList = components['schemas']['ModelsList'];
|
||||
export type ControlField = components['schemas']['ControlField'];
|
||||
|
||||
// Model Configs
|
||||
export type LoRAModelConfig = components['schemas']['LoRAModelConfig'];
|
||||
@ -107,6 +109,9 @@ export type MainModelLoaderInvocation = TypeReq<
|
||||
export type LoraLoaderInvocation = TypeReq<
|
||||
components['schemas']['LoraLoaderInvocation']
|
||||
>;
|
||||
export type MetadataAccumulatorInvocation = TypeReq<
|
||||
components['schemas']['MetadataAccumulatorInvocation']
|
||||
>;
|
||||
|
||||
// ControlNet Nodes
|
||||
export type ControlNetInvocation = TypeReq<
|
||||
|
@ -6409,6 +6409,11 @@ use-composed-ref@^1.3.0:
|
||||
resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda"
|
||||
integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==
|
||||
|
||||
use-debounce@^9.0.4:
|
||||
version "9.0.4"
|
||||
resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-9.0.4.tgz#51d25d856fbdfeb537553972ce3943b897f1ac85"
|
||||
integrity sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==
|
||||
|
||||
use-image@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/use-image/-/use-image-1.1.1.tgz#bdd3f2e1718393ffc0e56136f993467103d9d2df"
|
||||
|
Loading…
Reference in New Issue
Block a user