From 257e9725999c8ecfad851e9458b63144c38db862 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Tue, 20 Jun 2023 13:22:39 -0400 Subject: [PATCH 01/99] fix failing pytest for config module --- tests/test_config.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 9317a794c5..cea4991d12 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -8,8 +8,6 @@ from pathlib import Path os.environ['INVOKEAI_ROOT']='/tmp' from invokeai.app.services.config import InvokeAIAppConfig -from invokeai.app.invocations.generate import TextToImageInvocation - init1 = OmegaConf.create( ''' @@ -37,13 +35,13 @@ def test_use_init(): # sys.argv respectively. conf1 = InvokeAIAppConfig.get_config() assert conf1 - conf1.parse_args(conf=init1) + conf1.parse_args(conf=init1,argv=[]) assert conf1.max_loaded_models==5 assert not conf1.nsfw_checker conf2 = InvokeAIAppConfig.get_config() assert conf2 - conf2.parse_args(conf=init2) + conf2.parse_args(conf=init2,argv=[]) assert conf2.nsfw_checker assert conf2.max_loaded_models==2 assert not hasattr(conf2,'invalid_attribute') @@ -67,7 +65,7 @@ def test_env_override(): # environment variables should be case insensitive os.environ['InvokeAI_Max_Loaded_Models'] = '15' conf = InvokeAIAppConfig() - conf.parse_args(conf=init1) + conf.parse_args(conf=init1,argv=[]) assert conf.max_loaded_models == 15 conf = InvokeAIAppConfig() From a1671519d5576a2e28237b52c560146acb1c4883 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 11:51:20 -0700 Subject: [PATCH 02/99] board CRUD --- invokeai/app/api/dependencies.py | 4 + invokeai/app/api/routers/boards.py | 77 ++++++++ invokeai/app/api_app.py | 4 +- invokeai/app/services/boards.py | 172 ++++++++++++++++++ invokeai/app/services/image_record_storage.py | 3 +- invokeai/app/services/invocation_services.py | 4 + 6 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 invokeai/app/api/routers/boards.py create mode 100644 invokeai/app/services/boards.py diff --git a/invokeai/app/api/dependencies.py b/invokeai/app/api/dependencies.py index 5599d569d5..8aa61d08aa 100644 --- a/invokeai/app/api/dependencies.py +++ b/invokeai/app/api/dependencies.py @@ -2,6 +2,7 @@ from logging import Logger import os +from invokeai.app.services import boards from invokeai.app.services.image_record_storage import SqliteImageRecordStorage from invokeai.app.services.images import ImageService from invokeai.app.services.metadata import CoreMetadataService @@ -20,6 +21,7 @@ from ..services.invoker import Invoker from ..services.processor import DefaultInvocationProcessor from ..services.sqlite import SqliteItemStorage from ..services.model_manager_service import ModelManagerService +from ..services.boards import SqliteBoardStorage from .events import FastAPIEventService @@ -71,6 +73,7 @@ class ApiDependencies: latents = ForwardCacheLatentsStorage( DiskLatentsStorage(f"{output_folder}/latents") ) + boards = SqliteBoardStorage(db_location) images = ImageService( image_record_storage=image_record_storage, @@ -96,6 +99,7 @@ class ApiDependencies: restoration=RestorationServices(config, logger), configuration=config, logger=logger, + boards=boards ) create_system_graphs(services.graph_library) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py new file mode 100644 index 0000000000..47e4f74b0a --- /dev/null +++ b/invokeai/app/api/routers/boards.py @@ -0,0 +1,77 @@ +from fastapi import Body, HTTPException, Path, Query +from fastapi.routing import APIRouter +from invokeai.app.services.boards import BoardRecord, BoardRecordChanges +from invokeai.app.services.image_record_storage import OffsetPaginatedResults + +from ..dependencies import ApiDependencies + +boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) + + +@boards_router.post( + "/", + operation_id="create_board", + responses={ + 201: {"description": "The board was created successfully"}, + }, + status_code=201, +) +async def create_board( + board_name: str = Body(description="The name of the board to create"), +): + """Creates a board""" + try: + ApiDependencies.invoker.services.boards.save(board_name=board_name) + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to create board") + + +@boards_router.delete("/{board_id}", operation_id="delete_board") +async def delete_board( + board_id: str = Path(description="The id of board to delete"), +) -> None: + """Deletes a board""" + + try: + ApiDependencies.invoker.services.boards.delete(board_id=board_id) + except Exception as e: + # TODO: Does this need any exception handling at all? + pass + + +@boards_router.patch( + "/{board_id}", + operation_id="update_baord" +) +async def update_baord( + id: str = Path(description="The id of the board to update"), + board_changes: BoardRecordChanges = Body( + description="The changes to apply to the board" + ), +): + """Updates a board""" + + try: + return ApiDependencies.invoker.services.boards.update( + id, board_changes + ) + except Exception as e: + raise HTTPException(status_code=400, detail="Failed to update board") + +@boards_router.get( + "/", + operation_id="list_boards", + response_model=OffsetPaginatedResults[BoardRecord], +) +async def list_boards( + offset: int = Query(default=0, description="The page offset"), + limit: int = Query(default=10, description="The number of boards per page"), +) -> OffsetPaginatedResults[BoardRecord]: + """Gets a list of boards""" + + boards = ApiDependencies.invoker.services.boards.get_many( + offset, + limit, + ) + + return boards diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index fa46762d56..d00d92f763 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -24,7 +24,7 @@ logger = InvokeAILogger.getLogger(config=app_config) import invokeai.frontend.web as web_dir from .api.dependencies import ApiDependencies -from .api.routers import sessions, models, images +from .api.routers import sessions, models, images, boards from .api.sockets import SocketIO from .invocations.baseinvocation import BaseInvocation @@ -78,6 +78,8 @@ app.include_router(models.models_router, prefix="/api") app.include_router(images.images_router, prefix="/api") +app.include_router(boards.boards_router, prefix="/api") + # Build a custom OpenAPI to include all outputs # TODO: can outputs be included on metadata of invocation schemas somehow? def custom_openapi(): diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py new file mode 100644 index 0000000000..69b1afa048 --- /dev/null +++ b/invokeai/app/services/boards.py @@ -0,0 +1,172 @@ +from abc import ABC, abstractmethod +from datetime import datetime +from typing import Generic, Optional, TypeVar, cast +import sqlite3 +import threading +from typing import Optional, Union +import uuid +from invokeai.app.services.image_record_storage import OffsetPaginatedResults + +from pydantic import BaseModel, Field, Extra +from pydantic.generics import GenericModel + +T = TypeVar("T", bound=BaseModel) + +class BoardRecord(BaseModel): + """Deserialized board record.""" + + id: str = Field(description="The unique ID of the board.") + name: str = Field(description="The name of the board.") + """The name of the board.""" + created_at: Union[datetime, str] = Field( + description="The created timestamp of the board." + ) + """The created timestamp of the image.""" + updated_at: Union[datetime, str] = Field( + description="The updated timestamp of the board." + ) + +class BoardRecordChanges(BaseModel, extra=Extra.forbid): + name: Optional[str] = Field( + description="The board's new name." + ) + +class BoardRecordNotFoundException(Exception): + """Raised when an board record is not found.""" + + def __init__(self, message="Board record not found"): + super().__init__(message) + + +class BoardRecordSaveException(Exception): + """Raised when an board record cannot be saved.""" + + def __init__(self, message="Board record not saved"): + super().__init__(message) + + +class BoardRecordDeleteException(Exception): + """Raised when an board record cannot be deleted.""" + + def __init__(self, message="Board record not deleted"): + super().__init__(message) + +class BoardStorageBase(ABC): + """Low-level service responsible for interfacing with the board record store.""" + + @abstractmethod + def get(self, board_id: str) -> BoardRecord: + """Gets an board record.""" + pass + + @abstractmethod + def delete(self, board_id: str) -> None: + """Deletes a board record.""" + pass + + @abstractmethod + def save( + self, + board_name: str, + ): + """Saves a board record.""" + pass + + +class SqliteBoardStorage(BoardStorageBase): + _filename: str + _conn: sqlite3.Connection + _cursor: sqlite3.Cursor + _lock: threading.Lock + + def __init__(self, filename: str) -> None: + super().__init__() + self._filename = filename + self._conn = sqlite3.connect(filename, check_same_thread=False) + # Enable row factory to get rows as dictionaries (must be done before making the cursor!) + self._conn.row_factory = sqlite3.Row + self._cursor = self._conn.cursor() + self._lock = threading.Lock() + + try: + self._lock.acquire() + # Enable foreign keys + self._conn.execute("PRAGMA foreign_keys = ON;") + self._create_tables() + self._conn.commit() + finally: + self._lock.release() + + def _create_tables(self) -> None: + """Creates the `board` table.""" + + # Create the `images` table. + self._cursor.execute( + """--sql + CREATE TABLE IF NOT EXISTS boards ( + id TEXT NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + -- Updated via trigger + updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) + ); + """ + ) + + self._cursor.execute( + """--sql + CREATE INDEX IF NOT EXISTS idx_boards_created_at ON boards(created_at); + """ + ) + + # Add trigger for `updated_at`. + self._cursor.execute( + """--sql + CREATE TRIGGER IF NOT EXISTS tg_boards_updated_at + AFTER UPDATE + ON boards FOR EACH ROW + BEGIN + UPDATE boards SET updated_at = current_timestamp + WHERE board_name = old.board_name; + END; + """ + ) + + + def delete(self, board_id: str) -> None: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + DELETE FROM boards + WHERE id = ?; + """, + (board_id), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordDeleteException from e + finally: + self._lock.release() + + def save( + self, + board_name: str, + ): + try: + board_id = str(uuid.uuid4()) + self._lock.acquire() + self._cursor.execute( + """--sql + INSERT OR IGNORE INTO boards (id, name) + VALUES (?, ?); + """, + (board_id, board_name), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordSaveException from e + finally: + self._lock.release() diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 30b379ed8b..88d9b54926 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -135,7 +135,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): self._lock.release() def _create_tables(self) -> None: - """Creates the tables for the `images` database.""" + """Creates the `images` table.""" # Create the `images` table. self._cursor.execute( @@ -152,6 +152,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): node_id TEXT, metadata TEXT, is_intermediate BOOLEAN DEFAULT FALSE, + board_id TEXT, created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- Updated via trigger updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), diff --git a/invokeai/app/services/invocation_services.py b/invokeai/app/services/invocation_services.py index 1f910253e5..d69e0b294f 100644 --- a/invokeai/app/services/invocation_services.py +++ b/invokeai/app/services/invocation_services.py @@ -14,6 +14,7 @@ if TYPE_CHECKING: from invokeai.app.services.config import InvokeAISettings from invokeai.app.services.graph import GraphExecutionState, LibraryGraph from invokeai.app.services.invoker import InvocationProcessorABC + from invokeai.app.services.boards import BoardStorageBase class InvocationServices: @@ -27,6 +28,7 @@ class InvocationServices: restoration: "RestorationServices" configuration: "InvokeAISettings" images: "ImageService" + boards: "BoardStorageBase" # NOTE: we must forward-declare any types that include invocations, since invocations can use services graph_library: "ItemStorageABC"["LibraryGraph"] @@ -46,6 +48,7 @@ class InvocationServices: processor: "InvocationProcessorABC", restoration: "RestorationServices", configuration: "InvokeAISettings", + boards: "BoardStorageBase", ): self.model_manager = model_manager self.events = events @@ -58,3 +61,4 @@ class InvocationServices: self.processor = processor self.restoration = restoration self.configuration = configuration + self.boards = boards From 207602f42551287f296019bf26e7d3db3933b18d Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 11:53:31 -0700 Subject: [PATCH 03/99] remove unused --- invokeai/app/api/routers/boards.py | 38 ------------------------------ 1 file changed, 38 deletions(-) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index 47e4f74b0a..9ef416e396 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -1,7 +1,5 @@ from fastapi import Body, HTTPException, Path, Query from fastapi.routing import APIRouter -from invokeai.app.services.boards import BoardRecord, BoardRecordChanges -from invokeai.app.services.image_record_storage import OffsetPaginatedResults from ..dependencies import ApiDependencies @@ -39,39 +37,3 @@ async def delete_board( pass -@boards_router.patch( - "/{board_id}", - operation_id="update_baord" -) -async def update_baord( - id: str = Path(description="The id of the board to update"), - board_changes: BoardRecordChanges = Body( - description="The changes to apply to the board" - ), -): - """Updates a board""" - - try: - return ApiDependencies.invoker.services.boards.update( - id, board_changes - ) - except Exception as e: - raise HTTPException(status_code=400, detail="Failed to update board") - -@boards_router.get( - "/", - operation_id="list_boards", - response_model=OffsetPaginatedResults[BoardRecord], -) -async def list_boards( - offset: int = Query(default=0, description="The page offset"), - limit: int = Query(default=10, description="The number of boards per page"), -) -> OffsetPaginatedResults[BoardRecord]: - """Gets a list of boards""" - - boards = ApiDependencies.invoker.services.boards.get_many( - offset, - limit, - ) - - return boards From a121e6b3a05663eb7277f8d232c54cca2ceb9096 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 11:55:38 -0700 Subject: [PATCH 04/99] add board_id association to image --- invokeai/app/services/image_record_storage.py | 12 ++++++++++++ invokeai/app/services/models/image_record.py | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 88d9b54926..0a35763ff3 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -260,6 +260,18 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): """, (changes.is_intermediate, image_name), ) + + # Change the image's `is_intermediate`` flag + if changes.board_id is not None: + self._cursor.execute( + f"""--sql + UPDATE images + SET board_id = ? + WHERE image_name = ?; + """, + (changes.board_id, image_name), + ) + self._conn.commit() except sqlite3.Error as e: self._conn.rollback() diff --git a/invokeai/app/services/models/image_record.py b/invokeai/app/services/models/image_record.py index d971d65916..a4699f74c8 100644 --- a/invokeai/app/services/models/image_record.py +++ b/invokeai/app/services/models/image_record.py @@ -72,6 +72,10 @@ class ImageRecordChanges(BaseModel, extra=Extra.forbid): default=None, description="The image's new `is_intermediate` flag." ) """The image's new `is_intermediate` flag.""" + board_id: Optional[StrictStr] = Field( + default=None, description="The image's new board ID." + ) + """The image's new board ID.""" class ImageUrlsDTO(BaseModel): From 6ca5ad9075b5b5a915f6315ce3ef870781735622 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 12:07:09 -0700 Subject: [PATCH 05/99] filter images by board_id --- invokeai/app/api/routers/images.py | 4 ++++ invokeai/app/services/image_record_storage.py | 5 +++++ invokeai/app/services/images.py | 2 ++ 3 files changed, 11 insertions(+) diff --git a/invokeai/app/api/routers/images.py b/invokeai/app/api/routers/images.py index 11453d97f1..24bb716635 100644 --- a/invokeai/app/api/routers/images.py +++ b/invokeai/app/api/routers/images.py @@ -221,6 +221,9 @@ async def list_images_with_metadata( is_intermediate: Optional[bool] = Query( default=None, description="Whether to list intermediate images" ), + board_id: Optional[str] = Query( + default=None, description="The board of images to include" + ), offset: int = Query(default=0, description="The page offset"), limit: int = Query(default=10, description="The number of images per page"), ) -> OffsetPaginatedResults[ImageDTO]: @@ -232,6 +235,7 @@ async def list_images_with_metadata( image_origin, categories, is_intermediate, + board_id ) return image_dtos diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 0a35763ff3..84abcb6b2e 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -286,6 +286,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageRecord]: try: self._lock.acquire() @@ -317,6 +318,10 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): query_conditions += f"""AND is_intermediate = ?\n""" query_params.append(is_intermediate) + if board_id is not None: + query_conditions += f"""AND board_id = ?\n""" + query_params.append(board_id) + query_pagination = f"""ORDER BY created_at DESC LIMIT ? OFFSET ?\n""" # Final images query with pagination diff --git a/invokeai/app/services/images.py b/invokeai/app/services/images.py index 9f7188f607..173268563a 100644 --- a/invokeai/app/services/images.py +++ b/invokeai/app/services/images.py @@ -322,6 +322,7 @@ class ImageService(ImageServiceABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageDTO]: try: results = self._services.records.get_many( @@ -330,6 +331,7 @@ class ImageService(ImageServiceABC): image_origin, categories, is_intermediate, + board_id ) image_dtos = list( From 499a1748324b399eb0435bf0cf9cee37e72cc82e Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 12:56:09 -0700 Subject: [PATCH 06/99] some more --- invokeai/app/api/routers/boards.py | 3 ++- invokeai/app/services/boards.py | 17 ++++++++++++----- invokeai/app/services/models/image_record.py | 7 +++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index 9ef416e396..eb2f5956ab 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -19,7 +19,8 @@ async def create_board( ): """Creates a board""" try: - ApiDependencies.invoker.services.boards.save(board_name=board_name) + result = ApiDependencies.invoker.services.boards.save(board_name=board_name) + return result except Exception as e: raise HTTPException(status_code=500, detail="Failed to create board") diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index 69b1afa048..00d90637fe 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -54,11 +54,6 @@ class BoardRecordDeleteException(Exception): class BoardStorageBase(ABC): """Low-level service responsible for interfacing with the board record store.""" - @abstractmethod - def get(self, board_id: str) -> BoardRecord: - """Gets an board record.""" - pass - @abstractmethod def delete(self, board_id: str) -> None: """Deletes a board record.""" @@ -165,6 +160,18 @@ class SqliteBoardStorage(BoardStorageBase): (board_id, board_name), ) self._conn.commit() + + self._cursor.execute( + """--sql + SELECT * + FROM boards + WHERE id = ?; + """, + (board_id,), + ) + + result = self._cursor.fetchone() + return result except sqlite3.Error as e: self._conn.rollback() raise BoardRecordSaveException from e diff --git a/invokeai/app/services/models/image_record.py b/invokeai/app/services/models/image_record.py index a4699f74c8..98f370f337 100644 --- a/invokeai/app/services/models/image_record.py +++ b/invokeai/app/services/models/image_record.py @@ -48,6 +48,11 @@ class ImageRecord(BaseModel): 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.""" + board_id: Optional[str] = Field( + default=None, + description="The board ID that this image belongs to.", + ) + """The board ID that this image belongs to.""" class ImageRecordChanges(BaseModel, extra=Extra.forbid): @@ -126,6 +131,7 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord: updated_at = image_dict.get("updated_at", get_iso_timestamp()) deleted_at = image_dict.get("deleted_at", get_iso_timestamp()) is_intermediate = image_dict.get("is_intermediate", False) + board_id = image_dict.get("board_id", None) raw_metadata = image_dict.get("metadata") @@ -147,4 +153,5 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord: updated_at=updated_at, deleted_at=deleted_at, is_intermediate=is_intermediate, + board_id=board_id, ) From 4bfaae66179be25faefba4ad8dd969803f5e10bf Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 12:59:31 -0700 Subject: [PATCH 07/99] fix type --- invokeai/app/services/image_record_storage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 84abcb6b2e..3c98ef5f29 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -82,6 +82,7 @@ class ImageRecordStorageBase(ABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageRecord]: """Gets a page of image records.""" pass From 3833304f57cfccf4fc43afcbe63de3b91659c4e4 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 14:08:04 -0700 Subject: [PATCH 08/99] [WIP] board list endpoint w cover photos --- invokeai/app/api/routers/boards.py | 51 +++++++++++++ invokeai/app/services/boards.py | 74 +++++++++++++++++++ invokeai/app/services/image_record_storage.py | 31 ++++++++ 3 files changed, 156 insertions(+) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index eb2f5956ab..c8e877ca59 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -1,5 +1,7 @@ from fastapi import Body, HTTPException, Path, Query from fastapi.routing import APIRouter +from invokeai.app.services.boards import BoardRecord, BoardRecordChanges +from invokeai.app.services.image_record_storage import OffsetPaginatedResults from ..dependencies import ApiDependencies @@ -38,3 +40,52 @@ async def delete_board( pass +@boards_router.get( + "/", + operation_id="list_boards", + response_model=OffsetPaginatedResults[BoardRecord], +) +async def list_boards( + offset: int = Query(default=0, description="The page offset"), + limit: int = Query(default=10, description="The number of boards per page"), +) -> OffsetPaginatedResults[BoardRecord]: + """Gets a list of boards""" + + results = ApiDependencies.invoker.services.boards.get_many( + offset, + limit, + ) + + boards = list( + map( + lambda r: board_record_to_dto( + r, + generate_cover_photo_url(r.id) + ), + results.boards, + ) + ) + + return boards + + +class BoardDTO(BaseModel): + """A DTO for an image""" + id: str + name: str + cover_image_url: str + +def board_record_to_dto( + board_record: BoardRecord, cover_image_url: str +) -> BoardDTO: + """Converts an image record to an image DTO.""" + return BoardDTO( + **board_record.dict(), + cover_image_url=cover_image_url, + ) + +def generate_cover_photo_url(board_id: str) -> str | None: + cover_photo = ApiDependencies.invoker.services.images._services.records.get_board_cover_photo(board_id) + if cover_photo is not None: + url = ApiDependencies.invoker.services.images._services.urls.get_image_url(cover_photo.image_origin, cover_photo.image_name) + return url diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index 00d90637fe..3cdadd6c22 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -26,6 +26,23 @@ class BoardRecord(BaseModel): description="The updated timestamp of the board." ) +class BoardRecordInList(BaseModel): + """Deserialized board record in a list.""" + + id: str = Field(description="The unique ID of the board.") + name: str = Field(description="The name of the board.") + most_recent_image_url: Optional[str] = Field( + description="The URL of the most recent image in the board." + ) + """The name of the board.""" + created_at: Union[datetime, str] = Field( + description="The created timestamp of the board." + ) + """The created timestamp of the image.""" + updated_at: Union[datetime, str] = Field( + description="The updated timestamp of the board." + ) + class BoardRecordChanges(BaseModel, extra=Extra.forbid): name: Optional[str] = Field( description="The board's new name." @@ -67,6 +84,18 @@ class BoardStorageBase(ABC): """Saves a board record.""" pass + def get_cover_photo(self, board_id: str) -> Optional[str]: + """Gets the cover photo for a board.""" + pass + + def get_many( + self, + offset: int, + limit: int, + ): + """Gets many board records.""" + pass + class SqliteBoardStorage(BoardStorageBase): _filename: str @@ -177,3 +206,48 @@ class SqliteBoardStorage(BoardStorageBase): raise BoardRecordSaveException from e finally: self._lock.release() + + + def get_many( + self, + offset: int, + limit: int, + ) -> OffsetPaginatedResults[BoardRecord]: + try: + + self._lock.acquire() + + count_query = f"""SELECT COUNT(*) FROM images WHERE 1=1\n""" + images_query = f"""SELECT * FROM images WHERE 1=1\n""" + + query_conditions = "" + query_params = [] + + query_pagination = f"""ORDER BY created_at DESC LIMIT ? OFFSET ?\n""" + + # Final images query with pagination + images_query += query_conditions + query_pagination + ";" + # Add all the parameters + images_params = query_params.copy() + images_params.append(limit) + images_params.append(offset) + # Build the list of images, deserializing each row + self._cursor.execute(images_query, images_params) + result = cast(list[sqlite3.Row], self._cursor.fetchall()) + boards = [BoardRecord(**dict(row)) for row in result] + + # Set up and execute the count query, without pagination + count_query += query_conditions + ";" + count_params = query_params.copy() + self._cursor.execute(count_query, count_params) + count = self._cursor.fetchone()[0] + + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordSaveException from e + finally: + self._lock.release() + + return OffsetPaginatedResults( + items=boards, offset=offset, limit=limit, total=count + ) \ No newline at end of file diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 3c98ef5f29..96c6beea12 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -94,6 +94,11 @@ class ImageRecordStorageBase(ABC): """Deletes an image record.""" pass + @abstractmethod + def get_board_cover_photo(self, board_id: str) -> Optional[ImageRecord]: + """Gets the cover photo for a board.""" + pass + @abstractmethod def save( self, @@ -280,6 +285,32 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): finally: self._lock.release() + def get_board_cover_photo(self, board_id: str) -> ImageRecord | None: + try: + self._lock.acquire() + self._cursor.execute( + """ + SELECT * + FROM images + WHERE board_id = ? + ORDER BY created_at DESC + LIMIT 1 + """, + (board_id), + ) + self._conn.commit() + result = cast(Union[sqlite3.Row, None], self._cursor.fetchone()) + except sqlite3.Error as e: + self._conn.rollback() + raise ImageRecordNotFoundException from e + finally: + self._lock.release() + + if not result: + raise ImageRecordNotFoundException + + return deserialize_image_record(dict(result)) + def get_many( self, offset: int = 0, From 72e9ced88997bdfd84c06ea4127250284178b9bc Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 00:07:20 +1000 Subject: [PATCH 09/99] feat(nodes): add boards and board_images services --- invokeai/app/api/dependencies.py | 39 +- invokeai/app/api/routers/boards.py | 145 ++++---- invokeai/app/api/routers/images.py | 4 - invokeai/app/api_app.py | 2 +- .../services/board_image_record_storage.py | 253 +++++++++++++ invokeai/app/services/board_images.py | 166 +++++++++ invokeai/app/services/board_record_storage.py | 331 +++++++++++++++++ invokeai/app/services/boards.py | 340 +++++++----------- invokeai/app/services/image_record_storage.py | 45 +-- invokeai/app/services/images.py | 6 +- invokeai/app/services/invocation_services.py | 19 +- invokeai/app/services/models/image_record.py | 11 - 12 files changed, 993 insertions(+), 368 deletions(-) create mode 100644 invokeai/app/services/board_image_record_storage.py create mode 100644 invokeai/app/services/board_images.py create mode 100644 invokeai/app/services/board_record_storage.py diff --git a/invokeai/app/api/dependencies.py b/invokeai/app/api/dependencies.py index 8aa61d08aa..8889c70674 100644 --- a/invokeai/app/api/dependencies.py +++ b/invokeai/app/api/dependencies.py @@ -2,7 +2,15 @@ from logging import Logger import os -from invokeai.app.services import boards +from invokeai.app.services.board_image_record_storage import ( + SqliteBoardImageRecordStorage, +) +from invokeai.app.services.board_images import ( + BoardImagesService, + BoardImagesServiceDependencies, +) +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 from invokeai.app.services.metadata import CoreMetadataService @@ -59,7 +67,7 @@ class ApiDependencies: # TODO: build a file/path manager? db_location = config.db_path - db_location.parent.mkdir(parents=True,exist_ok=True) + db_location.parent.mkdir(parents=True, exist_ok=True) graph_execution_manager = SqliteItemStorage[GraphExecutionState]( filename=db_location, table_name="graph_executions" @@ -73,7 +81,29 @@ class ApiDependencies: latents = ForwardCacheLatentsStorage( DiskLatentsStorage(f"{output_folder}/latents") ) - boards = SqliteBoardStorage(db_location) + + board_record_storage = SqliteBoardRecordStorage(db_location) + board_image_record_storage = SqliteBoardImageRecordStorage(db_location) + + boards = BoardService( + services=BoardServiceDependencies( + board_image_record_storage=board_image_record_storage, + board_record_storage=board_record_storage, + image_record_storage=image_record_storage, + url=urls, + logger=logger, + ) + ) + + board_images = BoardImagesService( + services=BoardImagesServiceDependencies( + board_image_record_storage=board_image_record_storage, + board_record_storage=board_record_storage, + image_record_storage=image_record_storage, + url=urls, + logger=logger, + ) + ) images = ImageService( image_record_storage=image_record_storage, @@ -90,6 +120,8 @@ class ApiDependencies: events=events, latents=latents, images=images, + boards=boards, + board_images=board_images, queue=MemoryInvocationQueue(), graph_library=SqliteItemStorage[LibraryGraph]( filename=db_location, table_name="graphs" @@ -99,7 +131,6 @@ class ApiDependencies: restoration=RestorationServices(config, logger), configuration=config, logger=logger, - boards=boards ) create_system_graphs(services.graph_library) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index c8e877ca59..f3a76e08d3 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -1,91 +1,86 @@ -from fastapi import Body, HTTPException, Path, Query -from fastapi.routing import APIRouter -from invokeai.app.services.boards import BoardRecord, BoardRecordChanges -from invokeai.app.services.image_record_storage import OffsetPaginatedResults +# from fastapi import Body, HTTPException, Path, Query +# from fastapi.routing import APIRouter +# from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges +# from invokeai.app.services.image_record_storage import OffsetPaginatedResults -from ..dependencies import ApiDependencies +# from ..dependencies import ApiDependencies -boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) +# boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) -@boards_router.post( - "/", - operation_id="create_board", - responses={ - 201: {"description": "The board was created successfully"}, - }, - status_code=201, -) -async def create_board( - board_name: str = Body(description="The name of the board to create"), -): - """Creates a board""" - try: - result = ApiDependencies.invoker.services.boards.save(board_name=board_name) - return result - except Exception as e: - raise HTTPException(status_code=500, detail="Failed to create board") +# @boards_router.post( +# "/", +# operation_id="create_board", +# responses={ +# 201: {"description": "The board was created successfully"}, +# }, +# status_code=201, +# ) +# async def create_board( +# board_name: str = Body(description="The name of the board to create"), +# ): +# """Creates a board""" +# try: +# result = ApiDependencies.invoker.services.boards.save(board_name=board_name) +# return result +# except Exception as e: +# raise HTTPException(status_code=500, detail="Failed to create board") -@boards_router.delete("/{board_id}", operation_id="delete_board") -async def delete_board( - board_id: str = Path(description="The id of board to delete"), -) -> None: - """Deletes a board""" +# @boards_router.delete("/{board_id}", operation_id="delete_board") +# async def delete_board( +# board_id: str = Path(description="The id of board to delete"), +# ) -> None: +# """Deletes a board""" - try: - ApiDependencies.invoker.services.boards.delete(board_id=board_id) - except Exception as e: - # TODO: Does this need any exception handling at all? - pass +# try: +# ApiDependencies.invoker.services.boards.delete(board_id=board_id) +# except Exception as e: +# # TODO: Does this need any exception handling at all? +# pass -@boards_router.get( - "/", - operation_id="list_boards", - response_model=OffsetPaginatedResults[BoardRecord], -) -async def list_boards( - offset: int = Query(default=0, description="The page offset"), - limit: int = Query(default=10, description="The number of boards per page"), -) -> OffsetPaginatedResults[BoardRecord]: - """Gets a list of boards""" +# @boards_router.get( +# "/", +# operation_id="list_boards", +# response_model=OffsetPaginatedResults[BoardRecord], +# ) +# async def list_boards( +# offset: int = Query(default=0, description="The page offset"), +# limit: int = Query(default=10, description="The number of boards per page"), +# ) -> OffsetPaginatedResults[BoardRecord]: +# """Gets a list of boards""" - results = ApiDependencies.invoker.services.boards.get_many( - offset, - limit, - ) +# results = ApiDependencies.invoker.services.boards.get_many( +# offset, +# limit, +# ) - boards = list( - map( - lambda r: board_record_to_dto( - r, - generate_cover_photo_url(r.id) - ), - results.boards, - ) - ) +# boards = list( +# map( +# lambda r: board_record_to_dto( +# r, +# generate_cover_photo_url(r.id) +# ), +# results.boards, +# ) +# ) - return boards +# return boards -class BoardDTO(BaseModel): - """A DTO for an image""" - id: str - name: str - cover_image_url: str -def board_record_to_dto( - board_record: BoardRecord, cover_image_url: str -) -> BoardDTO: - """Converts an image record to an image DTO.""" - return BoardDTO( - **board_record.dict(), - cover_image_url=cover_image_url, - ) +# def board_record_to_dto( +# board_record: BoardRecord, cover_image_url: str +# ) -> BoardDTO: +# """Converts an image record to an image DTO.""" +# return BoardDTO( +# **board_record.dict(), +# cover_image_url=cover_image_url, +# ) -def generate_cover_photo_url(board_id: str) -> str | None: - cover_photo = ApiDependencies.invoker.services.images._services.records.get_board_cover_photo(board_id) - if cover_photo is not None: - url = ApiDependencies.invoker.services.images._services.urls.get_image_url(cover_photo.image_origin, cover_photo.image_name) - return url +# def generate_cover_photo_url(board_id: str) -> str | None: +# cover_photo = ApiDependencies.invoker.services.images._services.records.get_board_cover_photo(board_id) +# if cover_photo is not None: +# url = ApiDependencies.invoker.services.images._services.urls.get_image_url(cover_photo.image_origin, cover_photo.image_name) +# return url diff --git a/invokeai/app/api/routers/images.py b/invokeai/app/api/routers/images.py index 24bb716635..11453d97f1 100644 --- a/invokeai/app/api/routers/images.py +++ b/invokeai/app/api/routers/images.py @@ -221,9 +221,6 @@ async def list_images_with_metadata( is_intermediate: Optional[bool] = Query( default=None, description="Whether to list intermediate images" ), - board_id: Optional[str] = Query( - default=None, description="The board of images to include" - ), offset: int = Query(default=0, description="The page offset"), limit: int = Query(default=10, description="The number of images per page"), ) -> OffsetPaginatedResults[ImageDTO]: @@ -235,7 +232,6 @@ async def list_images_with_metadata( image_origin, categories, is_intermediate, - board_id ) return image_dtos diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index d00d92f763..50228edf7e 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -78,7 +78,7 @@ app.include_router(models.models_router, prefix="/api") app.include_router(images.images_router, prefix="/api") -app.include_router(boards.boards_router, prefix="/api") +# app.include_router(boards.boards_router, prefix="/api") # Build a custom OpenAPI to include all outputs # TODO: can outputs be included on metadata of invocation schemas somehow? diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py new file mode 100644 index 0000000000..b805087da8 --- /dev/null +++ b/invokeai/app/services/board_image_record_storage.py @@ -0,0 +1,253 @@ +from abc import ABC, abstractmethod +import sqlite3 +import threading +from typing import cast +from invokeai.app.services.board_record_storage import BoardRecord + +from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.image_record import ( + ImageRecord, + deserialize_image_record, +) + + +class BoardImageRecordStorageBase(ABC): + """Abstract base class for board-image relationship record storage.""" + + @abstractmethod + def add_image_to_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Adds an image to a board.""" + pass + + @abstractmethod + def remove_image_from_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Removes an image from a board.""" + pass + + @abstractmethod + def get_images_for_board( + self, + board_id: str, + ) -> OffsetPaginatedResults[ImageRecord]: + """Gets images for a board.""" + pass + + @abstractmethod + def get_boards_for_image( + self, + board_id: str, + ) -> OffsetPaginatedResults[BoardRecord]: + """Gets images for a board.""" + pass + + @abstractmethod + def get_image_count_for_board( + self, + board_id: str, + ) -> int: + """Gets the number of images for a board.""" + pass + + +class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): + _filename: str + _conn: sqlite3.Connection + _cursor: sqlite3.Cursor + _lock: threading.Lock + + def __init__(self, filename: str) -> None: + super().__init__() + self._filename = filename + self._conn = sqlite3.connect(filename, check_same_thread=False) + # Enable row factory to get rows as dictionaries (must be done before making the cursor!) + self._conn.row_factory = sqlite3.Row + self._cursor = self._conn.cursor() + self._lock = threading.Lock() + + try: + self._lock.acquire() + # Enable foreign keys + self._conn.execute("PRAGMA foreign_keys = ON;") + self._create_tables() + self._conn.commit() + finally: + self._lock.release() + + def _create_tables(self) -> None: + """Creates the `board_images` junction table.""" + + # Create the `board_images` junction table. + self._cursor.execute( + """--sql + CREATE TABLE IF NOT EXISTS board_images ( + board_id TEXT NOT NULL, + image_name TEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + -- updated via trigger + updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + PRIMARY KEY (board_id, image_name), + FOREIGN KEY (board_id) REFERENCES boards (board_id) ON DELETE CASCADE, + FOREIGN KEY (image_name) REFERENCES images (image_name) ON DELETE CASCADE + ); + """ + ) + + # Add trigger for `updated_at`. + self._cursor.execute( + """--sql + CREATE TRIGGER IF NOT EXISTS tg_board_images_updated_at + AFTER UPDATE + ON board_images FOR EACH ROW + BEGIN + UPDATE board_images SET updated_at = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') + WHERE board_id = old.board_id AND image_name = old.image_name; + END; + """ + ) + + def add_image_to_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Adds an image to a board.""" + try: + self._lock.acquire() + self._cursor.execute( + """--sql + INSERT INTO board_images (board_id, image_name) + VALUES (?, ?); + """, + (board_id, image_name), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + + def remove_image_from_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Removes an image from a board.""" + try: + self._lock.acquire() + self._cursor.execute( + """--sql + DELETE FROM board_images + WHERE board_id = ? AND image_name = ?; + """, + (board_id, image_name), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + + def get_images_for_board( + self, + board_id: str, + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[ImageRecord]: + """Gets images for a board.""" + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT images.* + FROM board_images + INNER JOIN images ON board_images.image_name = images.image_name + WHERE board_images.board_id = ? + ORDER BY board_images.updated_at DESC; + """, + (board_id,), + ) + result = cast(list[sqlite3.Row], self._cursor.fetchall()) + images = list(map(lambda r: deserialize_image_record(dict(r)), result)) + + self._cursor.execute( + """--sql + SELECT COUNT(*) FROM images WHERE 1=1; + """ + ) + count = self._cursor.fetchone()[0] + + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + return OffsetPaginatedResults( + items=images, offset=offset, limit=limit, total=count + ) + + def get_boards_for_image( + self, + board_id: str, + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[BoardRecord]: + """Gets boards for an image.""" + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT boards.* + FROM board_images + INNER JOIN boards ON board_images.board_id = boards.board_id + WHERE board_images.image_name = ? + ORDER BY board_images.updated_at DESC; + """, + (board_id,), + ) + result = cast(list[sqlite3.Row], self._cursor.fetchall()) + boards = list(map(lambda r: BoardRecord(**r), result)) + + self._cursor.execute( + """--sql + SELECT COUNT(*) FROM boards WHERE 1=1; + """ + ) + count = self._cursor.fetchone()[0] + + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + return OffsetPaginatedResults( + items=boards, offset=offset, limit=limit, total=count + ) + + def get_image_count_for_board(self, board_id: str) -> int: + """Gets the number of images for a board.""" + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT COUNT(*) FROM board_images WHERE board_id = ?; + """, + (board_id,), + ) + count = self._cursor.fetchone()[0] + + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + return count diff --git a/invokeai/app/services/board_images.py b/invokeai/app/services/board_images.py new file mode 100644 index 0000000000..dd2e104180 --- /dev/null +++ b/invokeai/app/services/board_images.py @@ -0,0 +1,166 @@ +from abc import ABC, abstractmethod +from logging import Logger +from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase +from invokeai.app.services.board_record_storage import ( + BoardDTO, + BoardRecord, + BoardRecordStorageBase, +) + +from invokeai.app.services.image_record_storage import ( + ImageRecordStorageBase, + OffsetPaginatedResults, +) +from invokeai.app.services.models.image_record import ImageDTO, image_record_to_dto +from invokeai.app.services.urls import UrlServiceBase + + +class BoardImagesServiceABC(ABC): + """High-level service for board-image relationship management.""" + + @abstractmethod + def add_image_to_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Adds an image to a board.""" + pass + + @abstractmethod + def remove_image_from_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Removes an image from a board.""" + pass + + @abstractmethod + def get_images_for_board( + self, + board_id: str, + ) -> OffsetPaginatedResults[ImageDTO]: + """Gets images for a board.""" + pass + + @abstractmethod + def get_boards_for_image( + self, + image_name: str, + ) -> OffsetPaginatedResults[BoardDTO]: + """Gets boards for an image.""" + pass + + +class BoardImagesServiceDependencies: + """Service dependencies for the BoardImagesService.""" + + board_image_records: BoardImageRecordStorageBase + board_records: BoardRecordStorageBase + image_records: ImageRecordStorageBase + urls: UrlServiceBase + logger: Logger + + def __init__( + self, + board_image_record_storage: BoardImageRecordStorageBase, + image_record_storage: ImageRecordStorageBase, + board_record_storage: BoardRecordStorageBase, + url: UrlServiceBase, + logger: Logger, + ): + self.board_image_records = board_image_record_storage + self.image_records = image_record_storage + self.board_records = board_record_storage + self.urls = url + self.logger = logger + + +class BoardImagesService(BoardImagesServiceABC): + _services: BoardImagesServiceDependencies + + def __init__(self, services: BoardImagesServiceDependencies): + self._services = services + + def add_image_to_board( + self, + board_id: str, + image_name: str, + ) -> None: + self._services.board_image_records.add_image_to_board(board_id, image_name) + + def remove_image_from_board( + self, + board_id: str, + image_name: str, + ) -> None: + self._services.board_image_records.remove_image_from_board(board_id, image_name) + + def get_images_for_board( + self, + board_id: str, + ) -> OffsetPaginatedResults[ImageDTO]: + image_records = self._services.board_image_records.get_images_for_board( + board_id + ) + image_dtos = list( + map( + lambda r: image_record_to_dto( + r, + self._services.urls.get_image_url(r.image_name), + self._services.urls.get_image_url(r.image_name, True), + ), + image_records.items, + ) + ) + return OffsetPaginatedResults[ImageDTO]( + items=image_dtos, + offset=image_records.offset, + limit=image_records.limit, + total=image_records.total, + ) + + def get_boards_for_image( + self, + image_name: str, + ) -> OffsetPaginatedResults[BoardDTO]: + board_records = self._services.board_image_records.get_boards_for_image( + image_name + ) + board_dtos = [] + + for r in board_records.items: + cover_image_url = ( + self._services.urls.get_image_url(r.cover_image_name, True) + if r.cover_image_name + else None + ) + image_count = self._services.board_image_records.get_image_count_for_board( + r.board_id + ) + board_dtos.append( + board_record_to_dto( + r, + cover_image_url, + image_count, + ) + ) + + return OffsetPaginatedResults[BoardDTO]( + items=board_dtos, + offset=board_records.offset, + limit=board_records.limit, + total=board_records.total, + ) + + +def board_record_to_dto( + board_record: BoardRecord, cover_image_url: str | None, image_count: int +) -> BoardDTO: + """Converts a board record to a board DTO.""" + return BoardDTO( + **board_record.dict(), + cover_image_url=cover_image_url, + image_count=image_count, + ) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py new file mode 100644 index 0000000000..a954fe7ac4 --- /dev/null +++ b/invokeai/app/services/board_record_storage.py @@ -0,0 +1,331 @@ +from abc import ABC, abstractmethod +from datetime import datetime +from typing import Optional, cast +import sqlite3 +import threading +from typing import Optional, Union +import uuid +from invokeai.app.services.image_record_storage import OffsetPaginatedResults + +from pydantic import BaseModel, Field, Extra + + +class BoardRecord(BaseModel): + """Deserialized board record.""" + + board_id: str = Field(description="The unique ID of the board.") + """The unique ID of the board.""" + board_name: str = Field(description="The name of the board.") + """The name of the board.""" + created_at: Union[datetime, str] = Field( + description="The created timestamp of the board." + ) + """The created timestamp of the image.""" + updated_at: Union[datetime, str] = Field( + description="The updated timestamp of the board." + ) + """The updated timestamp of the image.""" + cover_image_name: Optional[str] = Field( + description="The name of the cover image of the board." + ) + """The name of the cover image of the board.""" + + +class BoardDTO(BoardRecord): + """Deserialized board record with cover image URL and image count.""" + + cover_image_url: Optional[str] = Field( + description="The URL of the thumbnail of the board's cover image." + ) + """The URL of the thumbnail of the most recent image in the board.""" + image_count: int = Field(description="The number of images in the board.") + """The number of images in the board.""" + + +class BoardChanges(BaseModel, extra=Extra.forbid): + board_name: Optional[str] = Field(description="The board's new name.") + cover_image_name: Optional[str] = Field( + description="The name of the board's new cover image." + ) + + +class BoardRecordNotFoundException(Exception): + """Raised when an board record is not found.""" + + def __init__(self, message="Board record not found"): + super().__init__(message) + + +class BoardRecordSaveException(Exception): + """Raised when an board record cannot be saved.""" + + def __init__(self, message="Board record not saved"): + super().__init__(message) + + +class BoardRecordDeleteException(Exception): + """Raised when an board record cannot be deleted.""" + + def __init__(self, message="Board record not deleted"): + super().__init__(message) + + +class BoardRecordStorageBase(ABC): + """Low-level service responsible for interfacing with the board record store.""" + + @abstractmethod + def delete(self, board_id: str) -> None: + """Deletes a board record.""" + pass + + @abstractmethod + def save( + self, + board_name: str, + ) -> BoardRecord: + """Saves a board record.""" + pass + + @abstractmethod + def get( + self, + board_id: str, + ) -> BoardRecord: + """Gets a board record.""" + pass + + @abstractmethod + def update( + self, + board_id: str, + changes: BoardChanges, + ) -> BoardRecord: + """Updates a board record.""" + pass + + @abstractmethod + def get_many( + self, + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[BoardRecord]: + """Gets many board records.""" + pass + + +class SqliteBoardRecordStorage(BoardRecordStorageBase): + _filename: str + _conn: sqlite3.Connection + _cursor: sqlite3.Cursor + _lock: threading.Lock + + def __init__(self, filename: str) -> None: + super().__init__() + self._filename = filename + self._conn = sqlite3.connect(filename, check_same_thread=False) + # Enable row factory to get rows as dictionaries (must be done before making the cursor!) + self._conn.row_factory = sqlite3.Row + self._cursor = self._conn.cursor() + self._lock = threading.Lock() + + try: + self._lock.acquire() + # Enable foreign keys + self._conn.execute("PRAGMA foreign_keys = ON;") + self._create_tables() + self._conn.commit() + finally: + self._lock.release() + + def _create_tables(self) -> None: + """Creates the `boards` table and `board_images` junction table.""" + + # Create the `boards` table. + self._cursor.execute( + """--sql + CREATE TABLE IF NOT EXISTS boards ( + board_id TEXT NOT NULL PRIMARY KEY, + board_name TEXT NOT NULL, + cover_image_name TEXT, + created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + -- Updated via trigger + updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + -- Soft delete, currently unused + deleted_at DATETIME + ); + """ + ) + + self._cursor.execute( + """--sql + CREATE INDEX IF NOT EXISTS idx_boards_created_at ON boards(created_at); + """ + ) + + # Add trigger for `updated_at`. + self._cursor.execute( + """--sql + CREATE TRIGGER IF NOT EXISTS tg_boards_updated_at + AFTER UPDATE + ON boards FOR EACH ROW + BEGIN + UPDATE boards SET updated_at = current_timestamp + WHERE board_id = old.board_id; + END; + """ + ) + + def delete(self, board_id: str) -> None: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + DELETE FROM boards + WHERE board_id = ?; + """, + (board_id), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordDeleteException from e + finally: + self._lock.release() + + def save( + self, + board_name: str, + ) -> BoardRecord: + try: + board_id = str(uuid.uuid4()) + self._lock.acquire() + self._cursor.execute( + """--sql + INSERT OR IGNORE INTO boards (board_id, board_name) + VALUES (?, ?); + """, + (board_id, board_name), + ) + self._conn.commit() + + self._cursor.execute( + """--sql + SELECT * + FROM boards + WHERE board_id = ?; + """, + (board_id,), + ) + + result = self._cursor.fetchone() + return BoardRecord(**result) + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordSaveException from e + finally: + self._lock.release() + + def get( + self, + board_id: str, + ) -> BoardRecord: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT * + FROM boards + WHERE board_id = ?; + """, + (board_id,), + ) + + result = cast(Union[sqlite3.Row, None], self._cursor.fetchone()) + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordNotFoundException from e + finally: + self._lock.release() + if result is None: + raise BoardRecordNotFoundException + return BoardRecord(**dict(result)) + + def update( + self, + board_id: str, + changes: BoardChanges, + ) -> None: + try: + self._lock.acquire() + + # Change the name of a board + if changes.board_name is not None: + self._cursor.execute( + f"""--sql + UPDATE boards + SET board_name = ? + WHERE board_id = ?; + """, + (changes.board_name, board_id), + ) + + # Change the cover image of a board + if changes.cover_image_name is not None: + self._cursor.execute( + f"""--sql + UPDATE boards + SET cover_image_name = ? + WHERE board_id = ?; + """, + (changes.cover_image_name, board_id), + ) + + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordSaveException from e + finally: + self._lock.release() + + def get_many( + self, + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[BoardRecord]: + try: + self._lock.acquire() + + # Get all the boards + self._cursor.execute( + """--sql + SELECT * + FROM boards + ORDER BY updated_at DESC + LIMIT ? OFFSET ?; + """, + (limit, offset), + ) + + result = cast(list[sqlite3.Row], self._cursor.fetchall()) + boards = [BoardRecord(**dict(row)) for row in result] + + # Get the total number of boards + self._cursor.execute( + """--sql + SELECT COUNT(*) + FROM boards + WHERE 1=1; + """ + ) + + count = cast(int, self._cursor.fetchone()[0]) + + return OffsetPaginatedResults[BoardRecord]( + items=boards, offset=offset, limit=limit, total=count + ) + + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index 3cdadd6c22..07d64e655a 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -1,253 +1,153 @@ from abc import ABC, abstractmethod -from datetime import datetime -from typing import Generic, Optional, TypeVar, cast -import sqlite3 -import threading -from typing import Optional, Union -import uuid -from invokeai.app.services.image_record_storage import OffsetPaginatedResults -from pydantic import BaseModel, Field, Extra -from pydantic.generics import GenericModel +from logging import Logger +from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase +from invokeai.app.services.board_images import board_record_to_dto -T = TypeVar("T", bound=BaseModel) - -class BoardRecord(BaseModel): - """Deserialized board record.""" - - id: str = Field(description="The unique ID of the board.") - name: str = Field(description="The name of the board.") - """The name of the board.""" - created_at: Union[datetime, str] = Field( - description="The created timestamp of the board." - ) - """The created timestamp of the image.""" - updated_at: Union[datetime, str] = Field( - description="The updated timestamp of the board." - ) - -class BoardRecordInList(BaseModel): - """Deserialized board record in a list.""" - - id: str = Field(description="The unique ID of the board.") - name: str = Field(description="The name of the board.") - most_recent_image_url: Optional[str] = Field( - description="The URL of the most recent image in the board." - ) - """The name of the board.""" - created_at: Union[datetime, str] = Field( - description="The created timestamp of the board." - ) - """The created timestamp of the image.""" - updated_at: Union[datetime, str] = Field( - description="The updated timestamp of the board." - ) - -class BoardRecordChanges(BaseModel, extra=Extra.forbid): - name: Optional[str] = Field( - description="The board's new name." - ) - -class BoardRecordNotFoundException(Exception): - """Raised when an board record is not found.""" - - def __init__(self, message="Board record not found"): - super().__init__(message) +from invokeai.app.services.board_record_storage import ( + BoardDTO, + BoardRecord, + BoardChanges, + BoardRecordStorageBase, +) +from invokeai.app.services.image_record_storage import ( + ImageRecordStorageBase, + OffsetPaginatedResults, +) +from invokeai.app.services.models.image_record import ImageDTO +from invokeai.app.services.urls import UrlServiceBase -class BoardRecordSaveException(Exception): - """Raised when an board record cannot be saved.""" - - def __init__(self, message="Board record not saved"): - super().__init__(message) - - -class BoardRecordDeleteException(Exception): - """Raised when an board record cannot be deleted.""" - - def __init__(self, message="Board record not deleted"): - super().__init__(message) - -class BoardStorageBase(ABC): - """Low-level service responsible for interfacing with the board record store.""" +class BoardServiceABC(ABC): + """High-level service for board management.""" @abstractmethod - def delete(self, board_id: str) -> None: - """Deletes a board record.""" + def create( + self, + board_name: str, + ) -> BoardDTO: + """Creates a board.""" pass @abstractmethod - def save( + def get_dto( self, - board_name: str, - ): - """Saves a board record.""" + board_id: str, + ) -> BoardDTO: + """Gets a board.""" pass - def get_cover_photo(self, board_id: str) -> Optional[str]: - """Gets the cover photo for a board.""" + @abstractmethod + def update( + self, + board_id: str, + changes: BoardChanges, + ) -> BoardDTO: + """Updates a board.""" pass + @abstractmethod + def delete( + self, + board_id: str, + ) -> None: + """Deletes a board.""" + pass + + @abstractmethod def get_many( self, - offset: int, - limit: int, - ): - """Gets many board records.""" + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[BoardDTO]: + """Gets many boards.""" pass -class SqliteBoardStorage(BoardStorageBase): - _filename: str - _conn: sqlite3.Connection - _cursor: sqlite3.Cursor - _lock: threading.Lock +class BoardServiceDependencies: + """Service dependencies for the BoardService.""" - def __init__(self, filename: str) -> None: - super().__init__() - self._filename = filename - self._conn = sqlite3.connect(filename, check_same_thread=False) - # Enable row factory to get rows as dictionaries (must be done before making the cursor!) - self._conn.row_factory = sqlite3.Row - self._cursor = self._conn.cursor() - self._lock = threading.Lock() + board_image_records: BoardImageRecordStorageBase + board_records: BoardRecordStorageBase + image_records: ImageRecordStorageBase + urls: UrlServiceBase + logger: Logger - try: - self._lock.acquire() - # Enable foreign keys - self._conn.execute("PRAGMA foreign_keys = ON;") - self._create_tables() - self._conn.commit() - finally: - self._lock.release() + def __init__( + self, + board_image_record_storage: BoardImageRecordStorageBase, + image_record_storage: ImageRecordStorageBase, + board_record_storage: BoardRecordStorageBase, + url: UrlServiceBase, + logger: Logger, + ): + self.board_image_records = board_image_record_storage + self.image_records = image_record_storage + self.board_records = board_record_storage + self.urls = url + self.logger = logger - def _create_tables(self) -> None: - """Creates the `board` table.""" - # Create the `images` table. - self._cursor.execute( - """--sql - CREATE TABLE IF NOT EXISTS boards ( - id TEXT NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), - -- Updated via trigger - updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) - ); - """ +class BoardService(BoardServiceABC): + _services: BoardServiceDependencies + + def __init__(self, services: BoardServiceDependencies): + self._services = services + + def create( + self, + board_name: str, + ) -> BoardDTO: + board_record = self._services.board_records.save(board_name) + return board_record_to_dto(board_record, None, 0) + + def get_dto(self, board_id: str) -> BoardDTO: + board_record = self._services.board_records.get(board_id) + cover_image_url = ( + self._services.urls.get_image_url(board_record.cover_image_name, True) + if board_record.cover_image_name + else None ) - - self._cursor.execute( - """--sql - CREATE INDEX IF NOT EXISTS idx_boards_created_at ON boards(created_at); - """ + image_count = self._services.board_image_records.get_image_count_for_board( + board_id ) + return board_record_to_dto(board_record, cover_image_url, image_count) - # Add trigger for `updated_at`. - self._cursor.execute( - """--sql - CREATE TRIGGER IF NOT EXISTS tg_boards_updated_at - AFTER UPDATE - ON boards FOR EACH ROW - BEGIN - UPDATE boards SET updated_at = current_timestamp - WHERE board_name = old.board_name; - END; - """ + def update( + self, + board_id: str, + changes: BoardChanges, + ) -> BoardDTO: + board_record = self._services.board_records.update(board_id, changes) + cover_image_url = ( + self._services.urls.get_image_url(board_record.cover_image_name, True) + if board_record.cover_image_name + else None ) - + image_count = self._services.board_image_records.get_image_count_for_board( + board_id + ) + return board_record_to_dto(board_record, cover_image_url, image_count) def delete(self, board_id: str) -> None: - try: - self._lock.acquire() - self._cursor.execute( - """--sql - DELETE FROM boards - WHERE id = ?; - """, - (board_id), - ) - self._conn.commit() - except sqlite3.Error as e: - self._conn.rollback() - raise BoardRecordDeleteException from e - finally: - self._lock.release() - - def save( - self, - board_name: str, - ): - try: - board_id = str(uuid.uuid4()) - self._lock.acquire() - self._cursor.execute( - """--sql - INSERT OR IGNORE INTO boards (id, name) - VALUES (?, ?); - """, - (board_id, board_name), - ) - self._conn.commit() - - self._cursor.execute( - """--sql - SELECT * - FROM boards - WHERE id = ?; - """, - (board_id,), - ) - - result = self._cursor.fetchone() - return result - except sqlite3.Error as e: - self._conn.rollback() - raise BoardRecordSaveException from e - finally: - self._lock.release() - + self._services.board_records.delete(board_id) def get_many( - self, - offset: int, - limit: int, - ) -> OffsetPaginatedResults[BoardRecord]: - try: + self, offset: int = 0, limit: int = 10 + ) -> OffsetPaginatedResults[BoardDTO]: + board_records = self._services.board_records.get_many(offset, limit) + board_dtos = [] + for r in board_records.items: + cover_image_url = ( + self._services.urls.get_image_url(r.cover_image_name, True) + if r.cover_image_name + else None + ) + image_count = self._services.board_image_records.get_image_count_for_board( + r.board_id + ) + board_dtos.append(board_record_to_dto(r, cover_image_url, image_count)) - self._lock.acquire() - - count_query = f"""SELECT COUNT(*) FROM images WHERE 1=1\n""" - images_query = f"""SELECT * FROM images WHERE 1=1\n""" - - query_conditions = "" - query_params = [] - - query_pagination = f"""ORDER BY created_at DESC LIMIT ? OFFSET ?\n""" - - # Final images query with pagination - images_query += query_conditions + query_pagination + ";" - # Add all the parameters - images_params = query_params.copy() - images_params.append(limit) - images_params.append(offset) - # Build the list of images, deserializing each row - self._cursor.execute(images_query, images_params) - result = cast(list[sqlite3.Row], self._cursor.fetchall()) - boards = [BoardRecord(**dict(row)) for row in result] - - # Set up and execute the count query, without pagination - count_query += query_conditions + ";" - count_params = query_params.copy() - self._cursor.execute(count_query, count_params) - count = self._cursor.fetchone()[0] - - except sqlite3.Error as e: - self._conn.rollback() - raise BoardRecordSaveException from e - finally: - self._lock.release() - - return OffsetPaginatedResults( - items=boards, offset=offset, limit=limit, total=count - ) \ No newline at end of file + return OffsetPaginatedResults[BoardDTO]( + items=board_dtos, offset=offset, limit=limit, total=len(board_dtos) + ) diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 96c6beea12..2ca9ad66ca 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -82,7 +82,6 @@ class ImageRecordStorageBase(ABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, - board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageRecord]: """Gets a page of image records.""" pass @@ -94,11 +93,6 @@ class ImageRecordStorageBase(ABC): """Deletes an image record.""" pass - @abstractmethod - def get_board_cover_photo(self, board_id: str) -> Optional[ImageRecord]: - """Gets the cover photo for a board.""" - pass - @abstractmethod def save( self, @@ -197,7 +191,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): AFTER UPDATE ON images FOR EACH ROW BEGIN - UPDATE images SET updated_at = current_timestamp + UPDATE images SET updated_at = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE image_name = old.image_name; END; """ @@ -268,14 +262,14 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): ) # Change the image's `is_intermediate`` flag - if changes.board_id is not None: + if changes.is_intermediate is not None: self._cursor.execute( f"""--sql UPDATE images SET board_id = ? WHERE image_name = ?; """, - (changes.board_id, image_name), + (changes.is_intermediate, image_name), ) self._conn.commit() @@ -284,32 +278,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): raise ImageRecordSaveException from e finally: self._lock.release() - - def get_board_cover_photo(self, board_id: str) -> ImageRecord | None: - try: - self._lock.acquire() - self._cursor.execute( - """ - SELECT * - FROM images - WHERE board_id = ? - ORDER BY created_at DESC - LIMIT 1 - """, - (board_id), - ) - self._conn.commit() - result = cast(Union[sqlite3.Row, None], self._cursor.fetchone()) - except sqlite3.Error as e: - self._conn.rollback() - raise ImageRecordNotFoundException from e - finally: - self._lock.release() - - if not result: - raise ImageRecordNotFoundException - - return deserialize_image_record(dict(result)) def get_many( self, @@ -318,7 +286,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, - board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageRecord]: try: self._lock.acquire() @@ -350,10 +317,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): query_conditions += f"""AND is_intermediate = ?\n""" query_params.append(is_intermediate) - if board_id is not None: - query_conditions += f"""AND board_id = ?\n""" - query_params.append(board_id) - query_pagination = f"""ORDER BY created_at DESC LIMIT ? OFFSET ?\n""" # Final images query with pagination @@ -371,7 +334,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): count_query += query_conditions + ";" count_params = query_params.copy() self._cursor.execute(count_query, count_params) - count = self._cursor.fetchone()[0] + count = cast(int, self._cursor.fetchone()[0]) except sqlite3.Error as e: self._conn.rollback() raise e diff --git a/invokeai/app/services/images.py b/invokeai/app/services/images.py index 173268563a..aa27e38d17 100644 --- a/invokeai/app/services/images.py +++ b/invokeai/app/services/images.py @@ -49,7 +49,7 @@ class ImageServiceABC(ABC): image_category: ImageCategory, node_id: Optional[str] = None, session_id: Optional[str] = None, - intermediate: bool = False, + is_intermediate: bool = False, ) -> ImageDTO: """Creates an image, storing the file and its metadata.""" pass @@ -79,7 +79,7 @@ class ImageServiceABC(ABC): pass @abstractmethod - def get_path(self, image_name: str) -> str: + def get_path(self, image_name: str, thumbnail: bool = False) -> str: """Gets an image's path.""" pass @@ -322,7 +322,6 @@ class ImageService(ImageServiceABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, - board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageDTO]: try: results = self._services.records.get_many( @@ -331,7 +330,6 @@ class ImageService(ImageServiceABC): image_origin, categories, is_intermediate, - board_id ) image_dtos = list( diff --git a/invokeai/app/services/invocation_services.py b/invokeai/app/services/invocation_services.py index d69e0b294f..10d1d91920 100644 --- a/invokeai/app/services/invocation_services.py +++ b/invokeai/app/services/invocation_services.py @@ -4,7 +4,9 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from logging import Logger - from invokeai.app.services.images import ImageService + from invokeai.app.services.board_images import BoardImagesServiceABC + from invokeai.app.services.boards import BoardServiceABC + from invokeai.app.services.images import ImageServiceABC from invokeai.backend import ModelManager from invokeai.app.services.events import EventServiceBase from invokeai.app.services.latent_storage import LatentsStorageBase @@ -14,7 +16,6 @@ if TYPE_CHECKING: from invokeai.app.services.config import InvokeAISettings from invokeai.app.services.graph import GraphExecutionState, LibraryGraph from invokeai.app.services.invoker import InvocationProcessorABC - from invokeai.app.services.boards import BoardStorageBase class InvocationServices: @@ -27,10 +28,9 @@ class InvocationServices: model_manager: "ModelManager" restoration: "RestorationServices" configuration: "InvokeAISettings" - images: "ImageService" - boards: "BoardStorageBase" - - # NOTE: we must forward-declare any types that include invocations, since invocations can use services + images: "ImageServiceABC" + boards: "BoardServiceABC" + board_images: "BoardImagesServiceABC" graph_library: "ItemStorageABC"["LibraryGraph"] graph_execution_manager: "ItemStorageABC"["GraphExecutionState"] processor: "InvocationProcessorABC" @@ -41,20 +41,23 @@ class InvocationServices: events: "EventServiceBase", logger: "Logger", latents: "LatentsStorageBase", - images: "ImageService", + images: "ImageServiceABC", + boards: "BoardServiceABC", + board_images: "BoardImagesServiceABC", queue: "InvocationQueueABC", graph_library: "ItemStorageABC"["LibraryGraph"], graph_execution_manager: "ItemStorageABC"["GraphExecutionState"], processor: "InvocationProcessorABC", restoration: "RestorationServices", configuration: "InvokeAISettings", - boards: "BoardStorageBase", ): self.model_manager = model_manager self.events = events self.logger = logger self.latents = latents self.images = images + self.boards = boards + self.board_images = board_images self.queue = queue self.graph_library = graph_library self.graph_execution_manager = graph_execution_manager diff --git a/invokeai/app/services/models/image_record.py b/invokeai/app/services/models/image_record.py index 98f370f337..d971d65916 100644 --- a/invokeai/app/services/models/image_record.py +++ b/invokeai/app/services/models/image_record.py @@ -48,11 +48,6 @@ class ImageRecord(BaseModel): 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.""" - board_id: Optional[str] = Field( - default=None, - description="The board ID that this image belongs to.", - ) - """The board ID that this image belongs to.""" class ImageRecordChanges(BaseModel, extra=Extra.forbid): @@ -77,10 +72,6 @@ class ImageRecordChanges(BaseModel, extra=Extra.forbid): default=None, description="The image's new `is_intermediate` flag." ) """The image's new `is_intermediate` flag.""" - board_id: Optional[StrictStr] = Field( - default=None, description="The image's new board ID." - ) - """The image's new board ID.""" class ImageUrlsDTO(BaseModel): @@ -131,7 +122,6 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord: updated_at = image_dict.get("updated_at", get_iso_timestamp()) deleted_at = image_dict.get("deleted_at", get_iso_timestamp()) is_intermediate = image_dict.get("is_intermediate", False) - board_id = image_dict.get("board_id", None) raw_metadata = image_dict.get("metadata") @@ -153,5 +143,4 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord: updated_at=updated_at, deleted_at=deleted_at, is_intermediate=is_intermediate, - board_id=board_id, ) From 748016bdab0a3c5e12ced2b92ebc63d53b4e6246 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Wed, 14 Jun 2023 11:20:23 -0700 Subject: [PATCH 10/99] routes working --- invokeai/app/api/routers/board_images.py | 69 +++++++++ invokeai/app/api/routers/boards.py | 143 +++++++++--------- invokeai/app/api_app.py | 6 +- invokeai/app/services/board_images.py | 2 +- invokeai/app/services/board_record_storage.py | 35 +---- invokeai/app/services/boards.py | 4 +- invokeai/app/services/image_record_storage.py | 11 -- invokeai/app/services/models/board_record.py | 57 +++++++ 8 files changed, 203 insertions(+), 124 deletions(-) create mode 100644 invokeai/app/api/routers/board_images.py create mode 100644 invokeai/app/services/models/board_record.py diff --git a/invokeai/app/api/routers/board_images.py b/invokeai/app/api/routers/board_images.py new file mode 100644 index 0000000000..b206ab500d --- /dev/null +++ b/invokeai/app/api/routers/board_images.py @@ -0,0 +1,69 @@ +from fastapi import Body, HTTPException, Path, Query +from fastapi.routing import APIRouter +from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges +from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.board_record import BoardDTO +from invokeai.app.services.models.image_record import ImageDTO + +from ..dependencies import ApiDependencies + +board_images_router = APIRouter(prefix="/v1/board_images", tags=["boards"]) + + +@board_images_router.post( + "/", + operation_id="create_board_image", + responses={ + 201: {"description": "The image was added to a board successfully"}, + }, + status_code=201, +) +async def create_board_image( + board_id: str = Body(description="The id of the board to add to"), + image_name: str = Body(description="The name of the image to add"), +): + """Creates a board_image""" + try: + result = ApiDependencies.invoker.services.board_images.add_image_to_board(board_id=board_id, image_name=image_name) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to add to board") + +@board_images_router.delete( + "/", + operation_id="remove_board_image", + responses={ + 201: {"description": "The image was removed from the board successfully"}, + }, + status_code=201, +) +async def remove_board_image( + board_id: str = Body(description="The id of the board"), + image_name: str = Body(description="The name of the image to remove"), +): + """Deletes a board_image""" + try: + result = ApiDependencies.invoker.services.board_images.remove_image_from_board(board_id=board_id, image_name=image_name) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to update board") + + + +@board_images_router.get( + "/{board_id}", + operation_id="list_board_images", + response_model=OffsetPaginatedResults[ImageDTO], +) +async def list_board_images( + board_id: str = Path(description="The id of the board"), + offset: int = Query(default=0, description="The page offset"), + limit: int = Query(default=10, description="The number of boards per page"), +) -> OffsetPaginatedResults[ImageDTO]: + """Gets a list of images for a board""" + + results = ApiDependencies.invoker.services.board_images.get_images_for_board( + board_id, + ) + return results + diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index f3a76e08d3..618e8f990b 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -1,86 +1,79 @@ -# from fastapi import Body, HTTPException, Path, Query -# from fastapi.routing import APIRouter -# from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges -# from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from fastapi import Body, HTTPException, Path, Query +from fastapi.routing import APIRouter +from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges +from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.board_record import BoardDTO -# from ..dependencies import ApiDependencies +from ..dependencies import ApiDependencies -# boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) +boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) -# @boards_router.post( -# "/", -# operation_id="create_board", -# responses={ -# 201: {"description": "The board was created successfully"}, -# }, -# status_code=201, -# ) -# async def create_board( -# board_name: str = Body(description="The name of the board to create"), -# ): -# """Creates a board""" -# try: -# result = ApiDependencies.invoker.services.boards.save(board_name=board_name) -# return result -# except Exception as e: -# raise HTTPException(status_code=500, detail="Failed to create board") +@boards_router.post( + "/", + operation_id="create_board", + responses={ + 201: {"description": "The board was created successfully"}, + }, + status_code=201, +) +async def create_board( + board_name: str = Body(description="The name of the board to create"), +): + """Creates a board""" + try: + result = ApiDependencies.invoker.services.boards.create(board_name=board_name) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to create board") + +@boards_router.patch( + "/{board_id}", + operation_id="update_board", + responses={ + 201: {"description": "The board was updated successfully"}, + }, + status_code=201, +) +async def update_board( + board_id: str = Path(description="The id of board to update"), + changes: BoardChanges = Body(description="The changes to apply to the board"), +): + """Creates a board""" + try: + result = ApiDependencies.invoker.services.boards.update(board_id=board_id, changes=changes) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to update board") -# @boards_router.delete("/{board_id}", operation_id="delete_board") -# async def delete_board( -# board_id: str = Path(description="The id of board to delete"), -# ) -> None: -# """Deletes a board""" +@boards_router.delete("/{board_id}", operation_id="delete_board") +async def delete_board( + board_id: str = Path(description="The id of board to delete"), +) -> None: + """Deletes a board""" -# try: -# ApiDependencies.invoker.services.boards.delete(board_id=board_id) -# except Exception as e: -# # TODO: Does this need any exception handling at all? -# pass + try: + ApiDependencies.invoker.services.boards.delete(board_id=board_id) + except Exception as e: + # TODO: Does this need any exception handling at all? + pass -# @boards_router.get( -# "/", -# operation_id="list_boards", -# response_model=OffsetPaginatedResults[BoardRecord], -# ) -# async def list_boards( -# offset: int = Query(default=0, description="The page offset"), -# limit: int = Query(default=10, description="The number of boards per page"), -# ) -> OffsetPaginatedResults[BoardRecord]: -# """Gets a list of boards""" +@boards_router.get( + "/", + operation_id="list_boards", + response_model=OffsetPaginatedResults[BoardRecord], +) +async def list_boards( + offset: int = Query(default=0, description="The page offset"), + limit: int = Query(default=10, description="The number of boards per page"), +) -> OffsetPaginatedResults[BoardDTO]: + """Gets a list of boards""" -# results = ApiDependencies.invoker.services.boards.get_many( -# offset, -# limit, -# ) + results = ApiDependencies.invoker.services.boards.get_many( + offset, + limit, + ) + return results -# boards = list( -# map( -# lambda r: board_record_to_dto( -# r, -# generate_cover_photo_url(r.id) -# ), -# results.boards, -# ) -# ) - -# return boards - - - -# def board_record_to_dto( -# board_record: BoardRecord, cover_image_url: str -# ) -> BoardDTO: -# """Converts an image record to an image DTO.""" -# return BoardDTO( -# **board_record.dict(), -# cover_image_url=cover_image_url, -# ) - -# def generate_cover_photo_url(board_id: str) -> str | None: -# cover_photo = ApiDependencies.invoker.services.images._services.records.get_board_cover_photo(board_id) -# if cover_photo is not None: -# url = ApiDependencies.invoker.services.images._services.urls.get_image_url(cover_photo.image_origin, cover_photo.image_name) -# return url diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index 50228edf7e..22b4efec74 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -24,7 +24,7 @@ logger = InvokeAILogger.getLogger(config=app_config) import invokeai.frontend.web as web_dir from .api.dependencies import ApiDependencies -from .api.routers import sessions, models, images, boards +from .api.routers import sessions, models, images, boards, board_images from .api.sockets import SocketIO from .invocations.baseinvocation import BaseInvocation @@ -78,7 +78,9 @@ app.include_router(models.models_router, prefix="/api") app.include_router(images.images_router, prefix="/api") -# app.include_router(boards.boards_router, prefix="/api") +app.include_router(boards.boards_router, prefix="/api") + +app.include_router(board_images.board_images_router, prefix="/api") # Build a custom OpenAPI to include all outputs # TODO: can outputs be included on metadata of invocation schemas somehow? diff --git a/invokeai/app/services/board_images.py b/invokeai/app/services/board_images.py index dd2e104180..df2af4bbcf 100644 --- a/invokeai/app/services/board_images.py +++ b/invokeai/app/services/board_images.py @@ -2,7 +2,6 @@ from abc import ABC, abstractmethod from logging import Logger from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase from invokeai.app.services.board_record_storage import ( - BoardDTO, BoardRecord, BoardRecordStorageBase, ) @@ -11,6 +10,7 @@ from invokeai.app.services.image_record_storage import ( ImageRecordStorageBase, OffsetPaginatedResults, ) +from invokeai.app.services.models.board_record import BoardDTO from invokeai.app.services.models.image_record import ImageDTO, image_record_to_dto from invokeai.app.services.urls import UrlServiceBase diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index a954fe7ac4..8207470e6d 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -6,41 +6,11 @@ import threading from typing import Optional, Union import uuid from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.board_record import BoardRecord, deserialize_board_record from pydantic import BaseModel, Field, Extra -class BoardRecord(BaseModel): - """Deserialized board record.""" - - board_id: str = Field(description="The unique ID of the board.") - """The unique ID of the board.""" - board_name: str = Field(description="The name of the board.") - """The name of the board.""" - created_at: Union[datetime, str] = Field( - description="The created timestamp of the board." - ) - """The created timestamp of the image.""" - updated_at: Union[datetime, str] = Field( - description="The updated timestamp of the board." - ) - """The updated timestamp of the image.""" - cover_image_name: Optional[str] = Field( - description="The name of the cover image of the board." - ) - """The name of the cover image of the board.""" - - -class BoardDTO(BoardRecord): - """Deserialized board record with cover image URL and image count.""" - - cover_image_url: Optional[str] = Field( - description="The URL of the thumbnail of the board's cover image." - ) - """The URL of the thumbnail of the most recent image in the board.""" - image_count: int = Field(description="The number of images in the board.") - """The number of images in the board.""" - class BoardChanges(BaseModel, extra=Extra.forbid): board_name: Optional[str] = Field(description="The board's new name.") @@ -221,6 +191,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): return BoardRecord(**result) except sqlite3.Error as e: self._conn.rollback() + print(e) raise BoardRecordSaveException from e finally: self._lock.release() @@ -307,7 +278,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): ) result = cast(list[sqlite3.Row], self._cursor.fetchall()) - boards = [BoardRecord(**dict(row)) for row in result] + boards = list(map(lambda r: deserialize_board_record(dict(r)), result)) # Get the total number of boards self._cursor.execute( diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index 07d64e655a..ddfaacf79c 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -5,8 +5,6 @@ from invokeai.app.services.board_image_record_storage import BoardImageRecordSto from invokeai.app.services.board_images import board_record_to_dto from invokeai.app.services.board_record_storage import ( - BoardDTO, - BoardRecord, BoardChanges, BoardRecordStorageBase, ) @@ -14,7 +12,7 @@ from invokeai.app.services.image_record_storage import ( ImageRecordStorageBase, OffsetPaginatedResults, ) -from invokeai.app.services.models.image_record import ImageDTO +from invokeai.app.services.models.board_record import BoardDTO from invokeai.app.services.urls import UrlServiceBase diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 2ca9ad66ca..677e1d3445 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -261,17 +261,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): (changes.is_intermediate, image_name), ) - # Change the image's `is_intermediate`` flag - if changes.is_intermediate is not None: - self._cursor.execute( - f"""--sql - UPDATE images - SET board_id = ? - WHERE image_name = ?; - """, - (changes.is_intermediate, image_name), - ) - self._conn.commit() except sqlite3.Error as e: self._conn.rollback() diff --git a/invokeai/app/services/models/board_record.py b/invokeai/app/services/models/board_record.py new file mode 100644 index 0000000000..35c4cea9b0 --- /dev/null +++ b/invokeai/app/services/models/board_record.py @@ -0,0 +1,57 @@ +from typing import Optional, Union +from datetime import datetime +from pydantic import BaseModel, Extra, Field, StrictBool, StrictStr +from invokeai.app.util.misc import get_iso_timestamp + +class BoardRecord(BaseModel): + """Deserialized board record.""" + + board_id: str = Field(description="The unique ID of the board.") + """The unique ID of the board.""" + board_name: str = Field(description="The name of the board.") + """The name of the board.""" + created_at: Union[datetime, str] = Field( + description="The created timestamp of the board." + ) + """The created timestamp of the image.""" + updated_at: Union[datetime, str] = Field( + description="The updated timestamp of the board." + ) + """The updated timestamp of the image.""" + cover_image_name: Optional[str] = Field( + description="The name of the cover image of the board." + ) + """The name of the cover image of the board.""" + + +class BoardDTO(BoardRecord): + """Deserialized board record with cover image URL and image count.""" + + cover_image_url: Optional[str] = Field( + description="The URL of the thumbnail of the board's cover image." + ) + """The URL of the thumbnail of the most recent image in the board.""" + image_count: int = Field(description="The number of images in the board.") + """The number of images in the board.""" + + + + +def deserialize_board_record(board_dict: dict) -> BoardRecord: + """Deserializes a board record.""" + + # Retrieve all the values, setting "reasonable" defaults if they are not present. + + board_id = board_dict.get("board_id", "unknown") + board_name = board_dict.get("board_name", "unknown") + created_at = board_dict.get("created_at", get_iso_timestamp()) + updated_at = board_dict.get("updated_at", get_iso_timestamp()) + deleted_at = board_dict.get("deleted_at", get_iso_timestamp()) + + return BoardRecord( + board_id=board_id, + board_name=board_name, + created_at=created_at, + updated_at=updated_at, + deleted_at=deleted_at, + ) From c009f46b00037218344d8b791d13390eac41cde2 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Wed, 14 Jun 2023 14:24:58 -0400 Subject: [PATCH 11/99] regenerate api schema --- .../frontend/web/src/services/api/index.ts | 2 + .../src/services/api/models/BoardChanges.ts | 15 ++ .../src/services/api/models/BoardRecord.ts | 30 +++ .../api/models/Body_create_board_image.ts | 15 ++ .../api/models/Body_remove_board_image.ts | 15 ++ .../OffsetPaginatedResults_BoardRecord_.ts | 28 +++ .../services/api/services/BoardsService.ts | 210 ++++++++++++++++++ 7 files changed, 315 insertions(+) create mode 100644 invokeai/frontend/web/src/services/api/models/BoardChanges.ts create mode 100644 invokeai/frontend/web/src/services/api/models/BoardRecord.ts create mode 100644 invokeai/frontend/web/src/services/api/models/Body_create_board_image.ts create mode 100644 invokeai/frontend/web/src/services/api/models/Body_remove_board_image.ts create mode 100644 invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts create mode 100644 invokeai/frontend/web/src/services/api/services/BoardsService.ts diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index 7481a5daad..60db5eda3a 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -98,6 +98,7 @@ export type { MultiplyInvocation } from './models/MultiplyInvocation'; export type { NoiseInvocation } from './models/NoiseInvocation'; export type { NoiseOutput } from './models/NoiseOutput'; export type { NormalbaeImageProcessorInvocation } from './models/NormalbaeImageProcessorInvocation'; +export type { OffsetPaginatedResults_BoardRecord_ } from './models/OffsetPaginatedResults_BoardRecord_'; export type { OffsetPaginatedResults_ImageDTO_ } from './models/OffsetPaginatedResults_ImageDTO_'; export type { OpenposeImageProcessorInvocation } from './models/OpenposeImageProcessorInvocation'; export type { PaginatedResults_GraphExecutionState_ } from './models/PaginatedResults_GraphExecutionState_'; @@ -129,6 +130,7 @@ export type { VaeRepo } from './models/VaeRepo'; export type { ValidationError } from './models/ValidationError'; export type { ZoeDepthImageProcessorInvocation } from './models/ZoeDepthImageProcessorInvocation'; +export { BoardsService } from './services/BoardsService'; export { ImagesService } from './services/ImagesService'; export { ModelsService } from './services/ModelsService'; export { SessionsService } from './services/SessionsService'; diff --git a/invokeai/frontend/web/src/services/api/models/BoardChanges.ts b/invokeai/frontend/web/src/services/api/models/BoardChanges.ts new file mode 100644 index 0000000000..fb2bfa0cd9 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/BoardChanges.ts @@ -0,0 +1,15 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type BoardChanges = { + /** + * The board's new name. + */ + board_name?: string; + /** + * The name of the board's new cover image. + */ + cover_image_name?: string; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/BoardRecord.ts b/invokeai/frontend/web/src/services/api/models/BoardRecord.ts new file mode 100644 index 0000000000..50db9b22eb --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/BoardRecord.ts @@ -0,0 +1,30 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Deserialized board record. + */ +export type BoardRecord = { + /** + * The unique ID of the board. + */ + board_id: string; + /** + * The name of the board. + */ + board_name: string; + /** + * The created timestamp of the board. + */ + created_at: string; + /** + * The updated timestamp of the board. + */ + updated_at: string; + /** + * The name of the cover image of the board. + */ + cover_image_name?: string; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/Body_create_board_image.ts b/invokeai/frontend/web/src/services/api/models/Body_create_board_image.ts new file mode 100644 index 0000000000..47f8537eaa --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/Body_create_board_image.ts @@ -0,0 +1,15 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Body_create_board_image = { + /** + * The id of the board to add to + */ + board_id: string; + /** + * The name of the image to add + */ + image_name: string; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/Body_remove_board_image.ts b/invokeai/frontend/web/src/services/api/models/Body_remove_board_image.ts new file mode 100644 index 0000000000..6f5a3652d0 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/Body_remove_board_image.ts @@ -0,0 +1,15 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Body_remove_board_image = { + /** + * The id of the board + */ + board_id: string; + /** + * The name of the image to remove + */ + image_name: string; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts new file mode 100644 index 0000000000..7a6736dc54 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts @@ -0,0 +1,28 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { BoardRecord } from './BoardRecord'; + +/** + * Offset-paginated results + */ +export type OffsetPaginatedResults_BoardRecord_ = { + /** + * Items + */ + items: Array; + /** + * Offset from which to retrieve items + */ + offset: number; + /** + * Limit of items to get + */ + limit: number; + /** + * Total number of items in result + */ + total: number; +}; + diff --git a/invokeai/frontend/web/src/services/api/services/BoardsService.ts b/invokeai/frontend/web/src/services/api/services/BoardsService.ts new file mode 100644 index 0000000000..6533c09f03 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/services/BoardsService.ts @@ -0,0 +1,210 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { BoardChanges } from '../models/BoardChanges'; +import type { Body_create_board_image } from '../models/Body_create_board_image'; +import type { Body_remove_board_image } from '../models/Body_remove_board_image'; +import type { OffsetPaginatedResults_BoardRecord_ } from '../models/OffsetPaginatedResults_BoardRecord_'; +import type { OffsetPaginatedResults_ImageDTO_ } from '../models/OffsetPaginatedResults_ImageDTO_'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class BoardsService { + + /** + * List Boards + * Gets a list of boards + * @returns OffsetPaginatedResults_BoardRecord_ Successful Response + * @throws ApiError + */ + public static listBoards({ + offset, + limit = 10, + }: { + /** + * The page offset + */ + offset?: number, + /** + * The number of boards per page + */ + limit?: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/boards/', + query: { + 'offset': offset, + 'limit': limit, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Create Board + * Creates a board + * @returns any The board was created successfully + * @throws ApiError + */ + public static createBoard({ + requestBody, + }: { + requestBody: string, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/boards/', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Delete Board + * Deletes a board + * @returns any Successful Response + * @throws ApiError + */ + public static deleteBoard({ + boardId, + }: { + /** + * The id of board to delete + */ + boardId: string, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/v1/boards/{board_id}', + path: { + 'board_id': boardId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Update Board + * Creates a board + * @returns any The board was updated successfully + * @throws ApiError + */ + public static updateBoard({ + boardId, + requestBody, + }: { + /** + * The id of board to update + */ + boardId: string, + requestBody: BoardChanges, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'PATCH', + url: '/api/v1/boards/{board_id}', + path: { + 'board_id': boardId, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Create Board Image + * Creates a board_image + * @returns any The image was added to a board successfully + * @throws ApiError + */ + public static createBoardImage({ + requestBody, + }: { + requestBody: Body_create_board_image, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/board_images/', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Remove Board Image + * Deletes a board_image + * @returns any The image was removed from the board successfully + * @throws ApiError + */ + public static removeBoardImage({ + requestBody, + }: { + requestBody: Body_remove_board_image, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/v1/board_images/', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * List Board Images + * Gets a list of images for a board + * @returns OffsetPaginatedResults_ImageDTO_ Successful Response + * @throws ApiError + */ + public static listBoardImages({ + boardId, + offset, + limit = 10, + }: { + /** + * The id of the board + */ + boardId: string, + /** + * The page offset + */ + offset?: number, + /** + * The number of boards per page + */ + limit?: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/board_images/{board_id}', + path: { + 'board_id': boardId, + }, + query: { + 'offset': offset, + 'limit': limit, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + +} From e06c43adc82139e588bf6a82f9cbabf2554b5c7f Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Wed, 14 Jun 2023 16:53:01 -0400 Subject: [PATCH 12/99] lint fix --- .../listeners/socketio/socketConnected.ts | 3 + invokeai/frontend/web/src/app/store/store.ts | 3 + .../gallery/components/HoverableBoard.tsx | 101 ++++++++++ .../gallery/components/HoverableImage.tsx | 16 +- .../components/ImageGalleryContent.tsx | 178 +++++++++++++----- .../features/gallery/store/boardSelectors.ts | 3 + .../src/features/gallery/store/boardSlice.ts | 77 ++++++++ .../features/gallery/store/gallerySlice.ts | 9 + .../src/features/gallery/store/imagesSlice.ts | 13 ++ .../frontend/web/src/services/thunks/board.ts | 23 +++ .../frontend/web/src/services/types/guards.ts | 11 ++ 11 files changed, 392 insertions(+), 45 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts create mode 100644 invokeai/frontend/web/src/features/gallery/store/boardSlice.ts create mode 100644 invokeai/frontend/web/src/services/thunks/board.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index 3049d2c933..5bb02f60fa 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -4,6 +4,7 @@ import { appSocketConnected, socketConnected } from 'services/events/actions'; import { receivedPageOfImages } from 'services/thunks/image'; import { receivedModels } from 'services/thunks/model'; import { receivedOpenAPISchema } from 'services/thunks/schema'; +import { receivedBoards } from '../../../../../../services/thunks/board'; const moduleLog = log.child({ namespace: 'socketio' }); @@ -19,6 +20,8 @@ export const addSocketConnectedEventListener = () => { const { disabledTabs } = config; + dispatch(receivedBoards()); + if (!images.ids.length) { dispatch(receivedPageOfImages()); } diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index f577b73895..6198a414bf 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -22,6 +22,7 @@ import uiReducer from 'features/ui/store/uiSlice'; import hotkeysReducer from 'features/ui/store/hotkeysSlice'; import modelsReducer from 'features/system/store/modelSlice'; import nodesReducer from 'features/nodes/store/nodesSlice'; +import boardsReducer from 'features/gallery/store/boardSlice'; import { listenerMiddleware } from './middleware/listenerMiddleware'; @@ -47,6 +48,7 @@ const allReducers = { hotkeys: hotkeysReducer, images: imagesReducer, controlNet: controlNetReducer, + boards: boardsReducer, // session: sessionReducer, }; @@ -65,6 +67,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [ 'system', 'ui', 'controlNet', + 'boards', // 'hotkeys', // 'config', ]; diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx new file mode 100644 index 0000000000..c6762dbe54 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx @@ -0,0 +1,101 @@ +import { Box, Image, MenuItem, MenuList, Text } from '@chakra-ui/react'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { memo, useCallback, useState } from 'react'; +import { FaImage } from 'react-icons/fa'; +import { ContextMenu } from 'chakra-ui-contextmenu'; +import { useTranslation } from 'react-i18next'; +import { ExternalLinkIcon } from '@chakra-ui/icons'; +import { useAppToaster } from 'app/components/Toaster'; +import { BoardRecord } from 'services/api'; +import { EntityId, createSelector } from '@reduxjs/toolkit'; +import { + selectFilteredImagesIds, + selectImagesById, +} from '../store/imagesSlice'; +import { RootState } from '../../../app/store/store'; +import { defaultSelectorOptions } from '../../../app/store/util/defaultMemoizeOptions'; +import { useSelector } from 'react-redux'; + +interface HoverableBoardProps { + board: BoardRecord; +} + +/** + * Gallery image component with delete/use all/use seed buttons on hover. + */ +const HoverableBoard = memo(({ board }: HoverableBoardProps) => { + const dispatch = useAppDispatch(); + + const { board_name, board_id, cover_image_name } = board; + + const coverImage = useAppSelector((state) => + selectImagesById(state, cover_image_name as EntityId) + ); + + const { t } = useTranslation(); + + const handleSelectBoard = useCallback(() => { + // dispatch(imageSelected(board_id)); + }, []); + + return ( + + + menuProps={{ size: 'sm', isLazy: true }} + renderMenu={() => ( + + } + // onClickCapture={handleOpenInNewTab} + > + Sample Menu Item + + + )} + > + {(ref) => ( + + } + sx={{ + width: '100%', + height: '100%', + maxWidth: '100%', + maxHeight: '100%', + }} + /> + {board_name} + + )} + + + ); +}); + +HoverableBoard.displayName = 'HoverableBoard'; + +export default HoverableBoard; diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index b13d58e322..d52ba89d8f 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -2,7 +2,14 @@ import { Box, Flex, Icon, Image, MenuItem, MenuList } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { imageSelected } from 'features/gallery/store/gallerySlice'; import { memo, useCallback, useContext, useState } from 'react'; -import { FaCheck, FaExpand, FaImage, FaShare, FaTrash } from 'react-icons/fa'; +import { + FaCheck, + FaExpand, + FaFolder, + FaImage, + FaShare, + FaTrash, +} from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; import { resizeAndScaleCanvas, @@ -168,6 +175,10 @@ const HoverableImage = memo((props: HoverableImageProps) => { // dispatch(setIsLightboxOpen(true)); }; + const handleAddToFolder = useCallback(() => { + // dispatch(addImageToFolder(image)); + }, []); + const handleOpenInNewTab = () => { window.open(image.image_url, '_blank'); }; @@ -244,6 +255,9 @@ const HoverableImage = memo((props: HoverableImageProps) => { {t('parameters.sendToUnifiedCanvas')} )} + } onClickCapture={handleAddToFolder}> + Add to Folder + } diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index fe8690e379..a9752d4447 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -20,6 +20,7 @@ import { setGalleryImageObjectFit, setShouldAutoSwitchToNewImages, setShouldUseSingleGalleryColumn, + setGalleryView, } from 'features/gallery/store/gallerySlice'; import { togglePinGalleryPanel } from 'features/ui/store/uiSlice'; import { useOverlayScrollbars } from 'overlayscrollbars-react'; @@ -36,7 +37,7 @@ import { } from 'react'; import { useTranslation } from 'react-i18next'; import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs'; -import { FaImage, FaServer, FaWrench } from 'react-icons/fa'; +import { FaFolder, FaImage, FaPlus, FaServer, FaWrench } from 'react-icons/fa'; import { MdPhotoLibrary } from 'react-icons/md'; import HoverableImage from './HoverableImage'; @@ -53,22 +54,39 @@ import { selectImagesAll, } from '../store/imagesSlice'; import { receivedPageOfImages } from 'services/thunks/image'; +import { boardSelector } from '../store/boardSelectors'; +import { BoardRecord, ImageDTO } from '../../../services/api'; +import { isBoardRecord, isImageDTO } from '../../../services/types/guards'; +import HoverableBoard from './HoverableBoard'; +import IAIInput from '../../../common/components/IAIInput'; +import { boardCreated } from '../../../services/thunks/board'; -const categorySelector = createSelector( +const itemSelector = createSelector( [(state: RootState) => state], (state) => { - const { images } = state; - const { categories } = images; + const { images, boards, gallery } = state; - const allImages = selectImagesAll(state); - const filteredImages = allImages.filter((i) => - categories.includes(i.image_category) - ); + let items: Array = []; + let areMoreAvailable = false; + let isLoading = true; + + if (gallery.galleryView === 'images' || gallery.galleryView === 'assets') { + const { categories } = images; + + const allImages = selectImagesAll(state); + items = allImages.filter((i) => categories.includes(i.image_category)); + areMoreAvailable = items.length < images.total; + isLoading = images.isLoading; + } else if (gallery.galleryView === 'boards') { + items = Object.values(boards.entities) as BoardRecord[]; + areMoreAvailable = items.length < boards.total; + isLoading = boards.isLoading; + } return { - images: filteredImages, - isLoading: images.isLoading, - areMoreImagesAvailable: filteredImages.length < images.total, + items, + isLoading, + areMoreAvailable, categories: images.categories, }; }, @@ -76,18 +94,21 @@ const categorySelector = createSelector( ); const mainSelector = createSelector( - [gallerySelector, uiSelector], - (gallery, ui) => { + [gallerySelector, uiSelector, boardSelector], + (gallery, ui, boardState) => { const { galleryImageMinimumWidth, galleryImageObjectFit, shouldAutoSwitchToNewImages, shouldUseSingleGalleryColumn, selectedImage, + galleryView, } = gallery; const { shouldPinGallery } = ui; + const { entities: boards } = boardState; + return { shouldPinGallery, galleryImageMinimumWidth, @@ -95,6 +116,8 @@ const mainSelector = createSelector( shouldAutoSwitchToNewImages, shouldUseSingleGalleryColumn, selectedImage, + galleryView, + boards, }; }, defaultSelectorOptions @@ -126,21 +149,23 @@ const ImageGalleryContent = () => { shouldAutoSwitchToNewImages, shouldUseSingleGalleryColumn, selectedImage, + galleryView, + boards, } = useAppSelector(mainSelector); - const { images, areMoreImagesAvailable, isLoading, categories } = - useAppSelector(categorySelector); + const { items, areMoreAvailable, isLoading, categories } = + useAppSelector(itemSelector); const handleLoadMoreImages = useCallback(() => { dispatch(receivedPageOfImages()); }, [dispatch]); const handleEndReached = useMemo(() => { - if (areMoreImagesAvailable && !isLoading) { + if (areMoreAvailable && !isLoading) { return handleLoadMoreImages; } return undefined; - }, [areMoreImagesAvailable, handleLoadMoreImages, isLoading]); + }, [areMoreAvailable, handleLoadMoreImages, isLoading]); const handleChangeGalleryImageMinimumWidth = (v: number) => { dispatch(setGalleryImageMinimumWidth(v)); @@ -172,12 +197,24 @@ const ImageGalleryContent = () => { const handleClickImagesCategory = useCallback(() => { dispatch(imageCategoriesChanged(IMAGE_CATEGORIES)); + dispatch(setGalleryView('images')); }, [dispatch]); const handleClickAssetsCategory = useCallback(() => { dispatch(imageCategoriesChanged(ASSETS_CATEGORIES)); + dispatch(setGalleryView('assets')); }, [dispatch]); + const handleClickBoardsView = useCallback(() => { + dispatch(setGalleryView('boards')); + }, [dispatch]); + + const [newBoardName, setNewBoardName] = useState(''); + + const handleCreateNewBoard = () => { + dispatch(boardCreated({ requestBody: newBoardName })); + }; + return ( { tooltip={t('gallery.images')} aria-label={t('gallery.images')} onClick={handleClickImagesCategory} - isChecked={categories === IMAGE_CATEGORIES} + isChecked={galleryView === 'images'} size="sm" icon={} /> @@ -206,12 +243,47 @@ const ImageGalleryContent = () => { tooltip={t('gallery.assets')} aria-label={t('gallery.assets')} onClick={handleClickAssetsCategory} - isChecked={categories === ASSETS_CATEGORIES} + isChecked={galleryView === 'assets'} size="sm" icon={} /> + } + /> + } + /> + } + > + + setNewBoardName(e.target.value)} + /> + + Create + + + { - {images.length || areMoreImagesAvailable ? ( + {items.length || areMoreAvailable ? ( <> {shouldUseSingleGalleryColumn ? ( setScrollerRef(ref)} - itemContent={(index, image) => ( - - - - )} + itemContent={(index, item) => { + if (isImageDTO(item)) { + return ( + + + + ); + } else if (isBoardRecord(item)) { + return ( + + + + ); + } + }} /> ) : ( ( - - )} + itemContent={(index, item) => { + if (isImageDTO(item)) { + return ( + + ); + } else if (isBoardRecord(item)) { + return ( + + ); + } + }} /> )} - {areMoreImagesAvailable + {areMoreAvailable ? t('gallery.loadMore') : t('gallery.allImagesLoaded')} diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts b/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts new file mode 100644 index 0000000000..4bc98553af --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts @@ -0,0 +1,3 @@ +import { RootState } from 'app/store/store'; + +export const boardSelector = (state: RootState) => state.boards; diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts new file mode 100644 index 0000000000..6b4f1e9431 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -0,0 +1,77 @@ +import { + PayloadAction, + Update, + createEntityAdapter, + createSlice, +} from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { BoardRecord } from 'services/api'; +import { dateComparator } from 'common/util/dateComparator'; +import { receivedBoards } from '../../../services/thunks/board'; + +export const boardsAdapter = createEntityAdapter({ + selectId: (board) => board.board_id, + sortComparer: (a, b) => dateComparator(b.updated_at, a.updated_at), +}); + +type AdditionalBoardsState = { + offset: number; + limit: number; + total: number; + isLoading: boolean; +}; + +export const initialBoardsState = + boardsAdapter.getInitialState({ + offset: 0, + limit: 0, + total: 0, + isLoading: false, + }); + +export type BoardsState = typeof initialBoardsState; + +const boardsSlice = createSlice({ + name: 'boards', + initialState: initialBoardsState, + reducers: { + boardUpserted: (state, action: PayloadAction) => { + boardsAdapter.upsertOne(state, action.payload); + }, + boardUpdatedOne: (state, action: PayloadAction>) => { + boardsAdapter.updateOne(state, action.payload); + }, + boardRemoved: (state, action: PayloadAction) => { + boardsAdapter.removeOne(state, action.payload); + }, + }, + extraReducers: (builder) => { + builder.addCase(receivedBoards.pending, (state) => { + state.isLoading = true; + }); + builder.addCase(receivedBoards.rejected, (state) => { + state.isLoading = false; + }); + builder.addCase(receivedBoards.fulfilled, (state, action) => { + state.isLoading = false; + const { items, offset, limit, total } = action.payload; + state.offset = offset; + state.limit = limit; + state.total = total; + boardsAdapter.upsertMany(state, items); + }); + }, +}); + +export const { + selectAll: selectBoardsAll, + selectById: selectBoardsById, + selectEntities: selectBoardsEntities, + selectIds: selectBoardsIds, + selectTotal: selectBoardsTotal, +} = boardsAdapter.getSelectors((state) => state.boards); + +export const { boardUpserted, boardUpdatedOne, boardRemoved } = + boardsSlice.actions; + +export default boardsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index 4f250a7c3a..a8237a711d 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -12,6 +12,7 @@ export interface GalleryState { galleryImageObjectFit: GalleryImageObjectFitType; shouldAutoSwitchToNewImages: boolean; shouldUseSingleGalleryColumn: boolean; + galleryView: 'images' | 'assets' | 'boards'; } export const initialGalleryState: GalleryState = { @@ -19,6 +20,7 @@ export const initialGalleryState: GalleryState = { galleryImageObjectFit: 'cover', shouldAutoSwitchToNewImages: true, shouldUseSingleGalleryColumn: false, + galleryView: 'images', }; export const gallerySlice = createSlice({ @@ -48,6 +50,12 @@ export const gallerySlice = createSlice({ ) => { state.shouldUseSingleGalleryColumn = action.payload; }, + setGalleryView: ( + state, + action: PayloadAction<'images' | 'assets' | 'boards'> + ) => { + state.galleryView = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(imageUpserted, (state, action) => { @@ -75,6 +83,7 @@ export const { setGalleryImageObjectFit, setShouldAutoSwitchToNewImages, setShouldUseSingleGalleryColumn, + setGalleryView, } = gallerySlice.actions; export default gallerySlice.reducer; diff --git a/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts b/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts index 9c18380c54..0b2b9f0f58 100644 --- a/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts @@ -154,3 +154,16 @@ export const selectFilteredImagesIds = createSelector( .map((i) => i.image_name); } ); + +// export const selectImageById = createSelector( +// (state: RootState, imageId) => state, +// (state) => { +// const { +// images: { categories }, +// } = state; + +// return selectImagesAll(state) +// .filter((i) => categories.includes(i.image_category)) +// .map((i) => i.image_name); +// } +// ); diff --git a/invokeai/frontend/web/src/services/thunks/board.ts b/invokeai/frontend/web/src/services/thunks/board.ts new file mode 100644 index 0000000000..4ead87c4d4 --- /dev/null +++ b/invokeai/frontend/web/src/services/thunks/board.ts @@ -0,0 +1,23 @@ +import { createAppAsyncThunk } from '../../app/store/storeUtils'; +import { BoardsService } from '../api'; + +/** + * `BoardsService.listBoards()` thunk + */ +export const receivedBoards = createAppAsyncThunk( + 'api/receivedBoards', + async (_, { getState }) => { + const response = await BoardsService.listBoards({}); + return response; + } +); + +type BoardCreatedArg = Parameters<(typeof BoardsService)['createBoard']>[0]; + +export const boardCreated = createAppAsyncThunk( + 'api/boardCreated', + async (arg: BoardCreatedArg) => { + const response = await BoardsService.createBoard(arg); + return response; + } +); diff --git a/invokeai/frontend/web/src/services/types/guards.ts b/invokeai/frontend/web/src/services/types/guards.ts index 334c04e6ed..469b485cf3 100644 --- a/invokeai/frontend/web/src/services/types/guards.ts +++ b/invokeai/frontend/web/src/services/types/guards.ts @@ -11,6 +11,7 @@ import { LatentsOutput, ResourceOrigin, ImageDTO, + BoardRecord, } from 'services/api'; export const isImageDTO = (obj: unknown): obj is ImageDTO => { @@ -29,6 +30,16 @@ export const isImageDTO = (obj: unknown): obj is ImageDTO => { ); }; +export const isBoardRecord = (obj: unknown): obj is BoardRecord => { + return ( + isObject(obj) && + 'board_id' in obj && + isString(obj?.board_id) && + 'board_name' in obj && + isString(obj?.board_name) + ); +}; + export const isImageOutput = ( output: GraphExecutionState['results'][string] ): output is ImageOutput => output.type === 'image_output'; From 4b32322a58f8927eef3dfa38d555effd846ac207 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:41:36 +1000 Subject: [PATCH 13/99] feat(nodes): make board <> images a one-to-many relationship we can extend this to many-to-many in the future if desired. --- .../app/services/board_image_record_storage.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index b805087da8..abebe8c0a0 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -12,7 +12,7 @@ from invokeai.app.services.models.image_record import ( class BoardImageRecordStorageBase(ABC): - """Abstract base class for board-image relationship record storage.""" + """Abstract base class for the one-to-many board-image relationship record storage.""" @abstractmethod def add_image_to_board( @@ -45,7 +45,7 @@ class BoardImageRecordStorageBase(ABC): self, board_id: str, ) -> OffsetPaginatedResults[BoardRecord]: - """Gets images for a board.""" + """Gets boards for an image.""" pass @abstractmethod @@ -93,7 +93,9 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- updated via trigger updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), - PRIMARY KEY (board_id, image_name), + -- enforce one-to-many relationship between boards and images using PK + -- (we can extend this to many-to-many later) + PRIMARY KEY (image_name), FOREIGN KEY (board_id) REFERENCES boards (board_id) ON DELETE CASCADE, FOREIGN KEY (image_name) REFERENCES images (image_name) ON DELETE CASCADE ); @@ -184,7 +186,7 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): SELECT COUNT(*) FROM images WHERE 1=1; """ ) - count = self._cursor.fetchone()[0] + count = cast(int, self._cursor.fetchone()[0]) except sqlite3.Error as e: self._conn.rollback() @@ -222,7 +224,7 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): SELECT COUNT(*) FROM boards WHERE 1=1; """ ) - count = self._cursor.fetchone()[0] + count = cast(int, self._cursor.fetchone()[0]) except sqlite3.Error as e: self._conn.rollback() @@ -243,11 +245,10 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): """, (board_id,), ) - count = self._cursor.fetchone()[0] - + count = cast(int, self._cursor.fetchone()[0]) + return count except sqlite3.Error as e: self._conn.rollback() raise e finally: self._lock.release() - return count From dd1b3c9f35db137e3ed02a56d6b1e68f68645a29 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:42:18 +1000 Subject: [PATCH 14/99] fix(api): update API models to use BoardDTOs --- invokeai/app/api/routers/boards.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index 618e8f990b..7935db58f2 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -1,6 +1,6 @@ from fastapi import Body, HTTPException, Path, Query from fastapi.routing import APIRouter -from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges +from invokeai.app.services.board_record_storage import BoardChanges from invokeai.app.services.image_record_storage import OffsetPaginatedResults from invokeai.app.services.models.board_record import BoardDTO @@ -16,32 +16,39 @@ boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) 201: {"description": "The board was created successfully"}, }, status_code=201, + response_model=BoardDTO, ) async def create_board( board_name: str = Body(description="The name of the board to create"), -): +) -> BoardDTO: """Creates a board""" try: result = ApiDependencies.invoker.services.boards.create(board_name=board_name) return result except Exception as e: raise HTTPException(status_code=500, detail="Failed to create board") - + + @boards_router.patch( "/{board_id}", operation_id="update_board", responses={ - 201: {"description": "The board was updated successfully"}, + 201: { + "description": "The board was updated successfully", + }, }, status_code=201, + response_model=BoardDTO, ) async def update_board( board_id: str = Path(description="The id of board to update"), changes: BoardChanges = Body(description="The changes to apply to the board"), -): - """Creates a board""" +) -> BoardDTO: + """Updates a board""" try: - result = ApiDependencies.invoker.services.boards.update(board_id=board_id, changes=changes) + result = ApiDependencies.invoker.services.boards.update( + board_id=board_id, changes=changes + ) return result except Exception as e: raise HTTPException(status_code=500, detail="Failed to update board") @@ -63,7 +70,7 @@ async def delete_board( @boards_router.get( "/", operation_id="list_boards", - response_model=OffsetPaginatedResults[BoardRecord], + response_model=OffsetPaginatedResults[BoardDTO], ) async def list_boards( offset: int = Query(default=0, description="The page offset"), @@ -76,4 +83,3 @@ async def list_boards( limit, ) return results - From 48193b7fa7471e98ae1a4228960bd277b4d482c1 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:43:34 +1000 Subject: [PATCH 15/99] chore(ui): regen api client --- invokeai/frontend/web/src/services/api/index.ts | 7 +++++-- .../api/models/{BoardRecord.ts => BoardDTO.ts} | 12 ++++++++++-- .../web/src/services/api/models/Graph.ts | 2 +- .../services/api/models/GraphExecutionState.ts | 2 +- ..._.ts => OffsetPaginatedResults_BoardDTO_.ts} | 6 +++--- .../src/services/api/services/BoardsService.ts | 17 +++++++++-------- .../services/api/services/SessionsService.ts | 4 ++-- .../frontend/web/src/services/types/guards.ts | 4 ++-- 8 files changed, 33 insertions(+), 21 deletions(-) rename invokeai/frontend/web/src/services/api/models/{BoardRecord.ts => BoardDTO.ts} (62%) rename invokeai/frontend/web/src/services/api/models/{OffsetPaginatedResults_BoardRecord_.ts => OffsetPaginatedResults_BoardDTO_.ts} (71%) diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index 60db5eda3a..ef62245553 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -7,7 +7,10 @@ export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; export type { AddInvocation } from './models/AddInvocation'; -export type { BaseModelType } from './models/BaseModelType'; +export type { BoardChanges } from './models/BoardChanges'; +export type { BoardDTO } from './models/BoardDTO'; +export type { Body_create_board_image } from './models/Body_create_board_image'; +export type { Body_remove_board_image } from './models/Body_remove_board_image'; export type { Body_upload_image } from './models/Body_upload_image'; export type { CannyImageProcessorInvocation } from './models/CannyImageProcessorInvocation'; export type { CkptModelInfo } from './models/CkptModelInfo'; @@ -98,7 +101,7 @@ export type { MultiplyInvocation } from './models/MultiplyInvocation'; export type { NoiseInvocation } from './models/NoiseInvocation'; export type { NoiseOutput } from './models/NoiseOutput'; export type { NormalbaeImageProcessorInvocation } from './models/NormalbaeImageProcessorInvocation'; -export type { OffsetPaginatedResults_BoardRecord_ } from './models/OffsetPaginatedResults_BoardRecord_'; +export type { OffsetPaginatedResults_BoardDTO_ } from './models/OffsetPaginatedResults_BoardDTO_'; export type { OffsetPaginatedResults_ImageDTO_ } from './models/OffsetPaginatedResults_ImageDTO_'; export type { OpenposeImageProcessorInvocation } from './models/OpenposeImageProcessorInvocation'; export type { PaginatedResults_GraphExecutionState_ } from './models/PaginatedResults_GraphExecutionState_'; diff --git a/invokeai/frontend/web/src/services/api/models/BoardRecord.ts b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts similarity index 62% rename from invokeai/frontend/web/src/services/api/models/BoardRecord.ts rename to invokeai/frontend/web/src/services/api/models/BoardDTO.ts index 50db9b22eb..1b72f452ac 100644 --- a/invokeai/frontend/web/src/services/api/models/BoardRecord.ts +++ b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts @@ -3,9 +3,9 @@ /* eslint-disable */ /** - * Deserialized board record. + * Deserialized board record with cover image URL and image count. */ -export type BoardRecord = { +export type BoardDTO = { /** * The unique ID of the board. */ @@ -26,5 +26,13 @@ export type BoardRecord = { * The name of the cover image of the board. */ cover_image_name?: string; + /** + * The URL of the thumbnail of the board's cover image. + */ + cover_image_url?: string; + /** + * The number of images in the board. + */ + image_count: number; }; diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index e148954f16..0a724e2724 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -73,7 +73,7 @@ export type Graph = { /** * The nodes in this graph */ - nodes?: Record; + nodes?: Record; /** * The connections between nodes and their fields in this graph */ diff --git a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts index 602e7a2ebc..156cdc6092 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts @@ -48,7 +48,7 @@ export type GraphExecutionState = { /** * The results of node executions */ - results: Record; + results: Record; /** * Errors raised when executing nodes */ diff --git a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardDTO_.ts similarity index 71% rename from invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts rename to invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardDTO_.ts index 7a6736dc54..2e4734f469 100644 --- a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts +++ b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardDTO_.ts @@ -2,16 +2,16 @@ /* tslint:disable */ /* eslint-disable */ -import type { BoardRecord } from './BoardRecord'; +import type { BoardDTO } from './BoardDTO'; /** * Offset-paginated results */ -export type OffsetPaginatedResults_BoardRecord_ = { +export type OffsetPaginatedResults_BoardDTO_ = { /** * Items */ - items: Array; + items: Array; /** * Offset from which to retrieve items */ diff --git a/invokeai/frontend/web/src/services/api/services/BoardsService.ts b/invokeai/frontend/web/src/services/api/services/BoardsService.ts index 6533c09f03..9108e3fd51 100644 --- a/invokeai/frontend/web/src/services/api/services/BoardsService.ts +++ b/invokeai/frontend/web/src/services/api/services/BoardsService.ts @@ -2,9 +2,10 @@ /* tslint:disable */ /* eslint-disable */ import type { BoardChanges } from '../models/BoardChanges'; +import type { BoardDTO } from '../models/BoardDTO'; import type { Body_create_board_image } from '../models/Body_create_board_image'; import type { Body_remove_board_image } from '../models/Body_remove_board_image'; -import type { OffsetPaginatedResults_BoardRecord_ } from '../models/OffsetPaginatedResults_BoardRecord_'; +import type { OffsetPaginatedResults_BoardDTO_ } from '../models/OffsetPaginatedResults_BoardDTO_'; import type { OffsetPaginatedResults_ImageDTO_ } from '../models/OffsetPaginatedResults_ImageDTO_'; import type { CancelablePromise } from '../core/CancelablePromise'; @@ -16,7 +17,7 @@ export class BoardsService { /** * List Boards * Gets a list of boards - * @returns OffsetPaginatedResults_BoardRecord_ Successful Response + * @returns OffsetPaginatedResults_BoardDTO_ Successful Response * @throws ApiError */ public static listBoards({ @@ -31,7 +32,7 @@ export class BoardsService { * The number of boards per page */ limit?: number, - }): CancelablePromise { + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/boards/', @@ -48,14 +49,14 @@ export class BoardsService { /** * Create Board * Creates a board - * @returns any The board was created successfully + * @returns BoardDTO The board was created successfully * @throws ApiError */ public static createBoard({ requestBody, }: { requestBody: string, - }): CancelablePromise { + }): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/boards/', @@ -95,8 +96,8 @@ export class BoardsService { /** * Update Board - * Creates a board - * @returns any The board was updated successfully + * Updates a board + * @returns BoardDTO The board was updated successfully * @throws ApiError */ public static updateBoard({ @@ -108,7 +109,7 @@ export class BoardsService { */ boardId: string, requestBody: BoardChanges, - }): CancelablePromise { + }): CancelablePromise { return __request(OpenAPI, { method: 'PATCH', url: '/api/v1/boards/{board_id}', diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index 2e4a83b25f..6e9ce83aaf 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -175,7 +175,7 @@ export class SessionsService { * The id of the session */ sessionId: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -212,7 +212,7 @@ export class SessionsService { * The path to the node in the graph */ nodePath: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'PUT', diff --git a/invokeai/frontend/web/src/services/types/guards.ts b/invokeai/frontend/web/src/services/types/guards.ts index 469b485cf3..7ac0d95e6a 100644 --- a/invokeai/frontend/web/src/services/types/guards.ts +++ b/invokeai/frontend/web/src/services/types/guards.ts @@ -11,7 +11,7 @@ import { LatentsOutput, ResourceOrigin, ImageDTO, - BoardRecord, + BoardDTO, } from 'services/api'; export const isImageDTO = (obj: unknown): obj is ImageDTO => { @@ -30,7 +30,7 @@ export const isImageDTO = (obj: unknown): obj is ImageDTO => { ); }; -export const isBoardRecord = (obj: unknown): obj is BoardRecord => { +export const isBoardDTO = (obj: unknown): obj is BoardDTO => { return ( isObject(obj) && 'board_id' in obj && From 163ef2c941f36778ebcf518c38bda2882cef3f7b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:44:06 +1000 Subject: [PATCH 16/99] feat(ui): remove refs to BoardRecord in UI UI should only work w/ BoardDTO --- .../features/gallery/components/HoverableBoard.tsx | 4 ++-- .../gallery/components/ImageGalleryContent.tsx | 12 ++++++------ .../web/src/features/gallery/store/boardSlice.ts | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx index c6762dbe54..dc06fb389b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx @@ -6,7 +6,7 @@ import { ContextMenu } from 'chakra-ui-contextmenu'; import { useTranslation } from 'react-i18next'; import { ExternalLinkIcon } from '@chakra-ui/icons'; import { useAppToaster } from 'app/components/Toaster'; -import { BoardRecord } from 'services/api'; +import { BoardDTO } from 'services/api'; import { EntityId, createSelector } from '@reduxjs/toolkit'; import { selectFilteredImagesIds, @@ -17,7 +17,7 @@ import { defaultSelectorOptions } from '../../../app/store/util/defaultMemoizeOp import { useSelector } from 'react-redux'; interface HoverableBoardProps { - board: BoardRecord; + board: BoardDTO; } /** diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index a9752d4447..12194095df 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -55,8 +55,8 @@ import { } from '../store/imagesSlice'; import { receivedPageOfImages } from 'services/thunks/image'; import { boardSelector } from '../store/boardSelectors'; -import { BoardRecord, ImageDTO } from '../../../services/api'; -import { isBoardRecord, isImageDTO } from '../../../services/types/guards'; +import { BoardDTO, ImageDTO } from '../../../services/api'; +import { isBoardDTO, isImageDTO } from '../../../services/types/guards'; import HoverableBoard from './HoverableBoard'; import IAIInput from '../../../common/components/IAIInput'; import { boardCreated } from '../../../services/thunks/board'; @@ -66,7 +66,7 @@ const itemSelector = createSelector( (state) => { const { images, boards, gallery } = state; - let items: Array = []; + let items: Array = []; let areMoreAvailable = false; let isLoading = true; @@ -78,7 +78,7 @@ const itemSelector = createSelector( areMoreAvailable = items.length < images.total; isLoading = images.isLoading; } else if (gallery.galleryView === 'boards') { - items = Object.values(boards.entities) as BoardRecord[]; + items = Object.values(boards.entities) as BoardDTO[]; areMoreAvailable = items.length < boards.total; isLoading = boards.isLoading; } @@ -365,7 +365,7 @@ const ImageGalleryContent = () => { /> ); - } else if (isBoardRecord(item)) { + } else if (isBoardDTO(item)) { return ( @@ -395,7 +395,7 @@ const ImageGalleryContent = () => { } /> ); - } else if (isBoardRecord(item)) { + } else if (isBoardDTO(item)) { return ( ); diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index 6b4f1e9431..58457de527 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -5,11 +5,11 @@ import { createSlice, } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; -import { BoardRecord } from 'services/api'; +import { BoardDTO } from 'services/api'; import { dateComparator } from 'common/util/dateComparator'; import { receivedBoards } from '../../../services/thunks/board'; -export const boardsAdapter = createEntityAdapter({ +export const boardsAdapter = createEntityAdapter({ selectId: (board) => board.board_id, sortComparer: (a, b) => dateComparator(b.updated_at, a.updated_at), }); @@ -35,10 +35,10 @@ const boardsSlice = createSlice({ name: 'boards', initialState: initialBoardsState, reducers: { - boardUpserted: (state, action: PayloadAction) => { + boardUpserted: (state, action: PayloadAction) => { boardsAdapter.upsertOne(state, action.payload); }, - boardUpdatedOne: (state, action: PayloadAction>) => { + boardUpdatedOne: (state, action: PayloadAction>) => { boardsAdapter.updateOne(state, action.payload); }, boardRemoved: (state, action: PayloadAction) => { From 498bf0d0ba95b8dd598214a67759b1dab7cf8678 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:26:34 +1000 Subject: [PATCH 17/99] feat(db): add indices for `board_images` --- .../app/services/board_image_record_storage.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index abebe8c0a0..851e8502e1 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -102,6 +102,20 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): """ ) + # Add index for board id + self._cursor.execute( + """--sql + CREATE INDEX IF NOT EXISTS idx_board_images_board_id ON board_images (board_id); + """ + ) + + # Add index for board id, sorted by created_at + self._cursor.execute( + """--sql + CREATE INDEX IF NOT EXISTS idx_board_images_board_id_created_at ON board_images (board_id, created_at); + """ + ) + # Add trigger for `updated_at`. self._cursor.execute( """--sql From e1f9685b02fbbe30a4a8f9bd8ee44d5e2fb37749 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:26:57 +1000 Subject: [PATCH 18/99] feat(db): add index for `boards` --- invokeai/app/services/board_record_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index 8207470e6d..e7ba477a89 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -128,7 +128,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): self._cursor.execute( """--sql - CREATE INDEX IF NOT EXISTS idx_boards_created_at ON boards(created_at); + CREATE INDEX IF NOT EXISTS idx_boards_created_at ON boards (created_at); """ ) From 5865ecd530b0d33a9a5e5f48b12c98ce78c3f720 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:27:25 +1000 Subject: [PATCH 19/99] feat(db): add FK for `boards.cover_image_name` --- invokeai/app/services/board_record_storage.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index e7ba477a89..8b849a0501 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -121,7 +121,8 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): -- Updated via trigger updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- Soft delete, currently unused - deleted_at DATETIME + deleted_at DATETIME, + FOREIGN KEY (cover_image_name) REFERENCES images (image_name) ON DELETE SET NULL ); """ ) From d306a844478049ba398d4301c90ed5bc68263f42 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 16:43:50 +1000 Subject: [PATCH 20/99] feat(ui): rough out boards UI --- .../components/Boards/AddBoardButton.tsx | 36 ++++++++ .../gallery/components/Boards/BoardsList.tsx | 53 ++++++++++++ .../{ => Boards}/HoverableBoard.tsx | 86 +++++++++++-------- .../components/ImageGalleryContent.tsx | 47 +++++----- .../src/features/gallery/store/boardSlice.ts | 10 ++- 5 files changed, 172 insertions(+), 60 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx rename invokeai/frontend/web/src/features/gallery/components/{ => Boards}/HoverableBoard.tsx (51%) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx new file mode 100644 index 0000000000..2f60ea2dac --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx @@ -0,0 +1,36 @@ +import { Flex, Icon, Text } from '@chakra-ui/react'; +import { FaPlus } from 'react-icons/fa'; + +const AddBoardButton = () => { + return ( + + + + + New Board + + ); +}; + +export default AddBoardButton; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx new file mode 100644 index 0000000000..064d40b2d3 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -0,0 +1,53 @@ +import { Grid } from '@chakra-ui/react'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { selectBoardsAll } from 'features/gallery/store/boardSlice'; +import { memo } from 'react'; +import HoverableBoard from './HoverableBoard'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import AddBoardButton from './AddBoardButton'; + +const selector = createSelector( + selectBoardsAll, + (boards) => { + return { boards }; + }, + defaultSelectorOptions +); + +const BoardsList = () => { + const { boards } = useAppSelector(selector); + + return ( + + + + {boards.map((board) => ( + + ))} + + + ); +}; + +export default memo(BoardsList); diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx similarity index 51% rename from invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx rename to invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index dc06fb389b..fa1caeac7a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -1,7 +1,15 @@ -import { Box, Image, MenuItem, MenuList, Text } from '@chakra-ui/react'; +import { + Box, + Flex, + Icon, + Image, + MenuItem, + MenuList, + Text, +} from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { memo, useCallback, useState } from 'react'; -import { FaImage } from 'react-icons/fa'; +import { PropsWithChildren, memo, useCallback, useState } from 'react'; +import { FaFolder, FaImage } from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; import { useTranslation } from 'react-i18next'; import { ExternalLinkIcon } from '@chakra-ui/icons'; @@ -11,10 +19,12 @@ import { EntityId, createSelector } from '@reduxjs/toolkit'; import { selectFilteredImagesIds, selectImagesById, -} from '../store/imagesSlice'; -import { RootState } from '../../../app/store/store'; -import { defaultSelectorOptions } from '../../../app/store/util/defaultMemoizeOptions'; +} from '../../store/imagesSlice'; +import { RootState } from '../../../../app/store/store'; +import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions'; import { useSelector } from 'react-redux'; +import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { boardIdSelected } from 'features/gallery/store/boardSlice'; interface HoverableBoardProps { board: BoardDTO; @@ -26,20 +36,16 @@ interface HoverableBoardProps { const HoverableBoard = memo(({ board }: HoverableBoardProps) => { const dispatch = useAppDispatch(); - const { board_name, board_id, cover_image_name } = board; - - const coverImage = useAppSelector((state) => - selectImagesById(state, cover_image_name as EntityId) - ); + const { board_name, board_id, cover_image_url } = board; const { t } = useTranslation(); const handleSelectBoard = useCallback(() => { - // dispatch(imageSelected(board_id)); - }, []); + dispatch(boardIdSelected(board_id)); + }, [board_id, dispatch]); return ( - + menuProps={{ size: 'sm', isLazy: true }} renderMenu={() => ( @@ -54,42 +60,50 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => { )} > {(ref) => ( - - } + - {board_name} - + > + {cover_image_url ? ( + } + sx={{}} + /> + ) : ( + + )} + + {board_name} + )} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 12194095df..d97d814adf 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -57,9 +57,11 @@ import { receivedPageOfImages } from 'services/thunks/image'; import { boardSelector } from '../store/boardSelectors'; import { BoardDTO, ImageDTO } from '../../../services/api'; import { isBoardDTO, isImageDTO } from '../../../services/types/guards'; -import HoverableBoard from './HoverableBoard'; +import HoverableBoard from './Boards/HoverableBoard'; import IAIInput from '../../../common/components/IAIInput'; import { boardCreated } from '../../../services/thunks/board'; +import BoardsList from './Boards/BoardsList'; +import { selectBoardsById } from '../store/boardSlice'; const itemSelector = createSelector( [(state: RootState) => state], @@ -70,24 +72,23 @@ const itemSelector = createSelector( let areMoreAvailable = false; let isLoading = true; - if (gallery.galleryView === 'images' || gallery.galleryView === 'assets') { - const { categories } = images; + const { categories } = images; - const allImages = selectImagesAll(state); - items = allImages.filter((i) => categories.includes(i.image_category)); - areMoreAvailable = items.length < images.total; - isLoading = images.isLoading; - } else if (gallery.galleryView === 'boards') { - items = Object.values(boards.entities) as BoardDTO[]; - areMoreAvailable = items.length < boards.total; - isLoading = boards.isLoading; - } + const allImages = selectImagesAll(state); + items = allImages.filter((i) => categories.includes(i.image_category)); + areMoreAvailable = items.length < images.total; + isLoading = images.isLoading; + + const selectedBoard = boards.selectedBoardId + ? selectBoardsById(state, boards.selectedBoardId) + : undefined; return { items, isLoading, areMoreAvailable, categories: images.categories, + selectedBoard, }; }, defaultSelectorOptions @@ -153,7 +154,7 @@ const ImageGalleryContent = () => { boards, } = useAppSelector(mainSelector); - const { items, areMoreAvailable, isLoading, categories } = + const { items, areMoreAvailable, isLoading, categories, selectedBoard } = useAppSelector(itemSelector); const handleLoadMoreImages = useCallback(() => { @@ -247,17 +248,14 @@ const ImageGalleryContent = () => { size="sm" icon={} /> - } - /> + {selectedBoard && ( + + {selectedBoard.board_name} + + )} - { Create - + */} { /> + + + {items.length || areMoreAvailable ? ( <> diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index 58457de527..de7ea1e828 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -1,4 +1,5 @@ import { + EntityId, PayloadAction, Update, createEntityAdapter, @@ -19,6 +20,7 @@ type AdditionalBoardsState = { limit: number; total: number; isLoading: boolean; + selectedBoardId: EntityId | null; }; export const initialBoardsState = @@ -27,6 +29,7 @@ export const initialBoardsState = limit: 0, total: 0, isLoading: false, + selectedBoardId: null, }); export type BoardsState = typeof initialBoardsState; @@ -44,6 +47,9 @@ const boardsSlice = createSlice({ boardRemoved: (state, action: PayloadAction) => { boardsAdapter.removeOne(state, action.payload); }, + boardIdSelected: (state, action: PayloadAction) => { + state.selectedBoardId = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(receivedBoards.pending, (state) => { @@ -71,7 +77,9 @@ export const { selectTotal: selectBoardsTotal, } = boardsAdapter.getSelectors((state) => state.boards); -export const { boardUpserted, boardUpdatedOne, boardRemoved } = +export const { boardUpserted, boardUpdatedOne, boardRemoved, boardIdSelected } = boardsSlice.actions; +export const boardsSelector = (state: RootState) => state.boards; + export default boardsSlice.reducer; From 8aac683319b617742d432978addb87958dd4d8f8 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 15 Jun 2023 13:31:24 -0400 Subject: [PATCH 21/99] can delete and rename boards --- .../components/Boards/AddBoardButton.tsx | 10 +++ .../components/Boards/AllImagesBoard.tsx | 45 +++++++++++ .../gallery/components/Boards/BoardsList.tsx | 23 ++++-- .../components/Boards/HoverableBoard.tsx | 74 +++++++++++------- .../components/ImageGalleryContent.tsx | 78 +++++++------------ .../src/features/gallery/store/boardSlice.ts | 24 +++++- .../frontend/web/src/services/thunks/board.ts | 18 +++++ 7 files changed, 185 insertions(+), 87 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx index 2f60ea2dac..d8828fe736 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx @@ -1,9 +1,19 @@ import { Flex, Icon, Text } from '@chakra-ui/react'; +import { useCallback } from 'react'; import { FaPlus } from 'react-icons/fa'; +import { useAppDispatch } from '../../../../app/store/storeHooks'; +import { boardCreated } from '../../../../services/thunks/board'; const AddBoardButton = () => { + const dispatch = useAppDispatch(); + + const handleCreateBoard = useCallback(() => { + dispatch(boardCreated({ requestBody: 'My Board' })); + }, [dispatch]); + return ( { + const dispatch = useDispatch(); + + const handleAllImagesBoardClick = () => { + dispatch(boardIdSelected(null)); + }; + + return ( + + + + + All Images + + ); +}; + +export default AllImagesBoard; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index 064d40b2d3..8603f28c9c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -2,22 +2,26 @@ import { Grid } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import { selectBoardsAll } from 'features/gallery/store/boardSlice'; -import { memo } from 'react'; +import { + boardsSelector, + selectBoardsAll, +} from 'features/gallery/store/boardSlice'; +import { memo, useState } from 'react'; import HoverableBoard from './HoverableBoard'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import AddBoardButton from './AddBoardButton'; +import AllImagesBoard from './AllImagesBoard'; const selector = createSelector( - selectBoardsAll, - (boards) => { - return { boards }; + [selectBoardsAll, boardsSelector], + (boards, boardsState) => { + return { boards, selectedBoardId: boardsState.selectedBoardId }; }, defaultSelectorOptions ); const BoardsList = () => { - const { boards } = useAppSelector(selector); + const { boards, selectedBoardId } = useAppSelector(selector); return ( { }} > + {boards.map((board) => ( - + ))} diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index fa1caeac7a..4bb63dbb5e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -1,49 +1,51 @@ import { Box, + Editable, + EditableInput, + EditablePreview, Flex, Icon, Image, MenuItem, MenuList, - Text, } from '@chakra-ui/react'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { PropsWithChildren, memo, useCallback, useState } from 'react'; -import { FaFolder, FaImage } from 'react-icons/fa'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { memo, useCallback } from 'react'; +import { FaFolder, FaTrash } from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; import { useTranslation } from 'react-i18next'; -import { ExternalLinkIcon } from '@chakra-ui/icons'; -import { useAppToaster } from 'app/components/Toaster'; import { BoardDTO } from 'services/api'; -import { EntityId, createSelector } from '@reduxjs/toolkit'; -import { - selectFilteredImagesIds, - selectImagesById, -} from '../../store/imagesSlice'; -import { RootState } from '../../../../app/store/store'; -import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions'; -import { useSelector } from 'react-redux'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; import { boardIdSelected } from 'features/gallery/store/boardSlice'; +import { boardDeleted, boardUpdated } from '../../../../services/thunks/board'; interface HoverableBoardProps { board: BoardDTO; + isSelected: boolean; } -/** - * Gallery image component with delete/use all/use seed buttons on hover. - */ -const HoverableBoard = memo(({ board }: HoverableBoardProps) => { +const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { const dispatch = useAppDispatch(); const { board_name, board_id, cover_image_url } = board; - const { t } = useTranslation(); - const handleSelectBoard = useCallback(() => { dispatch(boardIdSelected(board_id)); }, [board_id, dispatch]); + const handleDeleteBoard = useCallback(() => { + dispatch(boardDeleted(board_id)); + }, [board_id, dispatch]); + + const handleUpdateBoardName = (newBoardName: string) => { + dispatch( + boardUpdated({ + boardId: board_id, + requestBody: { board_name: newBoardName }, + }) + ); + }; + return ( @@ -51,10 +53,11 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => { renderMenu={() => ( } - // onClickCapture={handleOpenInNewTab} + sx={{ color: 'error.300' }} + icon={} + onClickCapture={handleDeleteBoard} > - Sample Menu Item + Delete Board )} @@ -64,7 +67,6 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => { position="relative" key={board_id} userSelect="none" - onClick={handleSelectBoard} ref={ref} sx={{ flexDir: 'column', @@ -77,12 +79,13 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => { }} > { )} - {board_name} + + { + handleUpdateBoardName(nextValue); + }} + > + + + )} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index d97d814adf..95c37cba61 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -37,7 +37,7 @@ import { } from 'react'; import { useTranslation } from 'react-i18next'; import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs'; -import { FaFolder, FaImage, FaPlus, FaServer, FaWrench } from 'react-icons/fa'; +import { FaImage, FaServer, FaWrench } from 'react-icons/fa'; import { MdPhotoLibrary } from 'react-icons/md'; import HoverableImage from './HoverableImage'; @@ -55,10 +55,6 @@ import { } from '../store/imagesSlice'; import { receivedPageOfImages } from 'services/thunks/image'; import { boardSelector } from '../store/boardSelectors'; -import { BoardDTO, ImageDTO } from '../../../services/api'; -import { isBoardDTO, isImageDTO } from '../../../services/types/guards'; -import HoverableBoard from './Boards/HoverableBoard'; -import IAIInput from '../../../common/components/IAIInput'; import { boardCreated } from '../../../services/thunks/board'; import BoardsList from './Boards/BoardsList'; import { selectBoardsById } from '../store/boardSlice'; @@ -66,18 +62,16 @@ import { selectBoardsById } from '../store/boardSlice'; const itemSelector = createSelector( [(state: RootState) => state], (state) => { - const { images, boards, gallery } = state; - - let items: Array = []; - let areMoreAvailable = false; - let isLoading = true; + const { images, boards } = state; const { categories } = images; const allImages = selectImagesAll(state); - items = allImages.filter((i) => categories.includes(i.image_category)); - areMoreAvailable = items.length < images.total; - isLoading = images.isLoading; + const items = allImages.filter((i) => + categories.includes(i.image_category) + ); + const areMoreAvailable = items.length < images.total; + const isLoading = images.isLoading; const selectedBoard = boards.selectedBoardId ? selectBoardsById(state, boards.selectedBoardId) @@ -353,27 +347,17 @@ const ImageGalleryContent = () => { data={items} endReached={handleEndReached} scrollerRef={(ref) => setScrollerRef(ref)} - itemContent={(index, item) => { - if (isImageDTO(item)) { - return ( - - - - ); - } else if (isBoardDTO(item)) { - return ( - - - - ); - } - }} + itemContent={(index, item) => ( + + + + )} /> ) : ( { List: ListContainer, }} scrollerRef={setScroller} - itemContent={(index, item) => { - if (isImageDTO(item)) { - return ( - - ); - } else if (isBoardDTO(item)) { - return ( - - ); - } - }} + itemContent={(index, item) => ( + + )} /> )} diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index de7ea1e828..d2e9a451d3 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -8,7 +8,12 @@ import { import { RootState } from 'app/store/store'; import { BoardDTO } from 'services/api'; import { dateComparator } from 'common/util/dateComparator'; -import { receivedBoards } from '../../../services/thunks/board'; +import { + boardCreated, + boardDeleted, + boardUpdated, + receivedBoards, +} from '../../../services/thunks/board'; export const boardsAdapter = createEntityAdapter({ selectId: (board) => board.board_id, @@ -26,7 +31,7 @@ type AdditionalBoardsState = { export const initialBoardsState = boardsAdapter.getInitialState({ offset: 0, - limit: 0, + limit: 50, total: 0, isLoading: false, selectedBoardId: null, @@ -47,7 +52,7 @@ const boardsSlice = createSlice({ boardRemoved: (state, action: PayloadAction) => { boardsAdapter.removeOne(state, action.payload); }, - boardIdSelected: (state, action: PayloadAction) => { + boardIdSelected: (state, action: PayloadAction) => { state.selectedBoardId = action.payload; }, }, @@ -66,6 +71,19 @@ const boardsSlice = createSlice({ state.total = total; boardsAdapter.upsertMany(state, items); }); + builder.addCase(boardCreated.fulfilled, (state, action) => { + const board = action.payload; + boardsAdapter.upsertOne(state, board); + }); + builder.addCase(boardUpdated.fulfilled, (state, action) => { + const board = action.payload; + boardsAdapter.upsertOne(state, board); + }); + builder.addCase(boardDeleted.pending, (state, action) => { + const boardId = action.meta.arg; + console.log({ boardId }); + boardsAdapter.removeOne(state, boardId); + }); }, }); diff --git a/invokeai/frontend/web/src/services/thunks/board.ts b/invokeai/frontend/web/src/services/thunks/board.ts index 4ead87c4d4..a536a3fdb0 100644 --- a/invokeai/frontend/web/src/services/thunks/board.ts +++ b/invokeai/frontend/web/src/services/thunks/board.ts @@ -21,3 +21,21 @@ export const boardCreated = createAppAsyncThunk( return response; } ); + +export const boardDeleted = createAppAsyncThunk( + 'api/boardDeleted', + async (boardId: string) => { + await BoardsService.deleteBoard({ boardId }); + return boardId; + } +); + +type BoardUpdatedArg = Parameters<(typeof BoardsService)['updateBoard']>[0]; + +export const boardUpdated = createAppAsyncThunk( + 'api/boardUpdated', + async (arg: BoardUpdatedArg) => { + const response = await BoardsService.updateBoard(arg); + return response; + } +); From dcfee2e1e4eeb4d52bb69c6c0a042606d148748e Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 15 Jun 2023 14:26:40 -0400 Subject: [PATCH 22/99] add searching to boards list --- .../gallery/components/Boards/BoardsList.tsx | 38 +++++++++++++++---- .../components/ImageGalleryContent.tsx | 5 +-- .../features/gallery/store/boardSelectors.ts | 22 ++++++++++- .../src/features/gallery/store/boardSlice.ts | 13 ++++++- 4 files changed, 64 insertions(+), 14 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index 8603f28c9c..9e7d1ab960 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -1,16 +1,19 @@ -import { Grid } from '@chakra-ui/react'; +import { Box, Grid, Input, Spacer } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; -import { useAppSelector } from 'app/store/storeHooks'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { boardsSelector, selectBoardsAll, + setBoardSearchText, } from 'features/gallery/store/boardSlice'; -import { memo, useState } from 'react'; +import { memo, useEffect, useState } from 'react'; import HoverableBoard from './HoverableBoard'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import AddBoardButton from './AddBoardButton'; import AllImagesBoard from './AllImagesBoard'; +import { searchBoardsSelector } from '../../store/boardSelectors'; +import { useSelector } from 'react-redux'; const selector = createSelector( [selectBoardsAll, boardsSelector], @@ -21,7 +24,16 @@ const selector = createSelector( ); const BoardsList = () => { - const { boards, selectedBoardId } = useAppSelector(selector); + const dispatch = useAppDispatch(); + const { selectedBoardId } = useAppSelector(selector); + const filteredBoards = useSelector(searchBoardsSelector); + + const [searchMode, setSearchMode] = useState(false); + + const handleBoardSearch = (searchTerm: string) => { + setSearchMode(searchTerm.length > 0); + dispatch(setBoardSearchText(searchTerm)); + }; return ( { }, }} > + + { + handleBoardSearch(e.target.value); + }} + /> + { gridAutoColumns: '4rem', }} > - - - {boards.map((board) => ( + {!searchMode && ( + <> + + + + )} + {filteredBoards.map((board) => ( { + (gallery, ui, boards) => { const { galleryImageMinimumWidth, galleryImageObjectFit, @@ -101,9 +101,6 @@ const mainSelector = createSelector( } = gallery; const { shouldPinGallery } = ui; - - const { entities: boards } = boardState; - return { shouldPinGallery, galleryImageMinimumWidth, diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts b/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts index 4bc98553af..3dac2b6e50 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts @@ -1,3 +1,23 @@ +import { createSelector } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; +import { selectBoardsAll } from './boardSlice'; -export const boardSelector = (state: RootState) => state.boards; +export const boardSelector = (state: RootState) => state.boards.entities; + +export const searchBoardsSelector = createSelector( + (state: RootState) => state, + (state) => { + const { + boards: { searchText }, + } = state; + + if (!searchText) { + // If no search text provided, return all entities + return selectBoardsAll(state); + } + + return selectBoardsAll(state).filter((i) => + i.board_name.toLowerCase().includes(searchText.toLowerCase()) + ); + } +); diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index d2e9a451d3..fc67b4f26a 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -26,6 +26,7 @@ type AdditionalBoardsState = { total: number; isLoading: boolean; selectedBoardId: EntityId | null; + searchText?: string; }; export const initialBoardsState = @@ -55,6 +56,9 @@ const boardsSlice = createSlice({ boardIdSelected: (state, action: PayloadAction) => { state.selectedBoardId = action.payload; }, + setBoardSearchText: (state, action: PayloadAction) => { + state.searchText = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(receivedBoards.pending, (state) => { @@ -95,8 +99,13 @@ export const { selectTotal: selectBoardsTotal, } = boardsAdapter.getSelectors((state) => state.boards); -export const { boardUpserted, boardUpdatedOne, boardRemoved, boardIdSelected } = - boardsSlice.actions; +export const { + boardUpserted, + boardUpdatedOne, + boardRemoved, + boardIdSelected, + setBoardSearchText, +} = boardsSlice.actions; export const boardsSelector = (state: RootState) => state.boards; From bd29e5e6552353495626c9e7862dd70de79fe67f Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 15 Jun 2023 14:57:14 -0400 Subject: [PATCH 23/99] UI tweaks --- .../src/features/gallery/components/Boards/HoverableBoard.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index 4bb63dbb5e..d368d4ab0b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -13,7 +13,6 @@ import { useAppDispatch } from 'app/store/storeHooks'; import { memo, useCallback } from 'react'; import { FaFolder, FaTrash } from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; -import { useTranslation } from 'react-i18next'; import { BoardDTO } from 'services/api'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; import { boardIdSelected } from 'features/gallery/store/boardSlice'; @@ -89,6 +88,7 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { w: 'full', h: 'full', aspectRatio: '1/1', + overflow: 'hidden', }} > {cover_image_url ? ( @@ -115,6 +115,7 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { > Date: Thu, 15 Jun 2023 18:40:29 -0400 Subject: [PATCH 24/99] [half-baked] adding image to board modal --- .../frontend/web/src/app/components/App.tsx | 2 + .../web/src/app/components/InvokeAIUI.tsx | 14 +- .../app/contexts/AddImageToBoardContext.tsx | 151 ++++++++++++++++++ .../Boards/UpdateImageBoardModal.tsx | 85 ++++++++++ .../gallery/components/HoverableImage.tsx | 14 +- .../src/features/gallery/store/boardSlice.ts | 6 + .../frontend/web/src/services/thunks/board.ts | 12 ++ 7 files changed, 274 insertions(+), 10 deletions(-) create mode 100644 invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index ddc6dace27..a11d8d048c 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -23,6 +23,7 @@ import GlobalHotkeys from './GlobalHotkeys'; import Toaster from './Toaster'; import DeleteImageModal from 'features/gallery/components/DeleteImageModal'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; +import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal'; const DEFAULT_CONFIG = {}; @@ -143,6 +144,7 @@ const App = ({ + diff --git a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx index 0537d1de2a..141e62652d 100644 --- a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx +++ b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx @@ -21,6 +21,8 @@ import { DeleteImageContext, DeleteImageContextProvider, } from 'app/contexts/DeleteImageContext'; +import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal'; +import { AddImageToBoardContextProvider } from '../contexts/AddImageToBoardContext'; const App = lazy(() => import('./App')); const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider')); @@ -76,11 +78,13 @@ const InvokeAIUI = ({ - + + + diff --git a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx new file mode 100644 index 0000000000..cf541dca01 --- /dev/null +++ b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx @@ -0,0 +1,151 @@ +import { useDisclosure } from '@chakra-ui/react'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { requestedImageDeletion } from 'features/gallery/store/actions'; +import { systemSelector } from 'features/system/store/systemSelectors'; +import { + PropsWithChildren, + createContext, + useCallback, + useEffect, + useState, +} from 'react'; +import { ImageDTO } from 'services/api'; +import { RootState } from 'app/store/store'; +import { canvasSelector } from 'features/canvas/store/canvasSelectors'; +import { controlNetSelector } from 'features/controlNet/store/controlNetSlice'; +import { nodesSelecter } from 'features/nodes/store/nodesSlice'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { some } from 'lodash-es'; +import { imageAddedToBoard } from '../../services/thunks/board'; + +export type ImageUsage = { + isInitialImage: boolean; + isCanvasImage: boolean; + isNodesImage: boolean; + isControlNetImage: boolean; +}; + +export const selectImageUsage = createSelector( + [ + generationSelector, + canvasSelector, + nodesSelecter, + controlNetSelector, + (state: RootState, image_name?: string) => image_name, + ], + (generation, canvas, nodes, controlNet, image_name) => { + const isInitialImage = generation.initialImage?.image_name === image_name; + + const isCanvasImage = canvas.layerState.objects.some( + (obj) => obj.kind === 'image' && obj.image.image_name === image_name + ); + + const isNodesImage = nodes.nodes.some((node) => { + return some( + node.data.inputs, + (input) => + input.type === 'image' && input.value?.image_name === image_name + ); + }); + + const isControlNetImage = some( + controlNet.controlNets, + (c) => + c.controlImage?.image_name === image_name || + c.processedControlImage?.image_name === image_name + ); + + const imageUsage: ImageUsage = { + isInitialImage, + isCanvasImage, + isNodesImage, + isControlNetImage, + }; + + return imageUsage; + }, + defaultSelectorOptions +); + +type AddImageToBoardContextValue = { + /** + * Whether the move image dialog is open. + */ + isOpen: boolean; + /** + * Closes the move image dialog. + */ + onClose: () => void; + /** + * The image pending movement + */ + image?: ImageDTO; + onClickAddToBoard: (image: ImageDTO) => void; + handleAddToBoard: (boardId: string) => void; +}; + +export const AddImageToBoardContext = + createContext({ + isOpen: false, + onClose: () => undefined, + onClickAddToBoard: () => undefined, + handleAddToBoard: () => undefined, + }); + +type Props = PropsWithChildren; + +export const AddImageToBoardContextProvider = (props: Props) => { + const [imageToMove, setImageToMove] = useState(); + const dispatch = useAppDispatch(); + const { isOpen, onOpen, onClose } = useDisclosure(); + + // Clean up after deleting or dismissing the modal + const closeAndClearImageToDelete = useCallback(() => { + setImageToMove(undefined); + onClose(); + }, [onClose]); + + const onClickAddToBoard = useCallback( + (image?: ImageDTO) => { + if (!image) { + return; + } + setImageToMove(image); + onOpen(); + }, + [setImageToMove, onOpen] + ); + + const handleAddToBoard = useCallback( + (boardId: string) => { + if (imageToMove) { + dispatch( + imageAddedToBoard({ + requestBody: { + board_id: boardId, + image_name: imageToMove.image_name, + }, + }) + ); + closeAndClearImageToDelete(); + } + }, + [closeAndClearImageToDelete, dispatch, imageToMove] + ); + + return ( + + {props.children} + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx new file mode 100644 index 0000000000..8a94764ab1 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx @@ -0,0 +1,85 @@ +import { + AlertDialog, + AlertDialogBody, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogOverlay, + Box, + Divider, + Flex, + Select, + Text, +} from '@chakra-ui/react'; +import IAIButton from 'common/components/IAIButton'; + +import { memo, useContext, useRef, useState } from 'react'; +import { AddImageToBoardContext } from '../../../../app/contexts/AddImageToBoardContext'; +import { useSelector } from 'react-redux'; +import { selectBoardsAll } from '../../store/boardSlice'; +import IAISelect from '../../../../common/components/IAISelect'; + +const UpdateImageBoardModal = () => { + const boards = useSelector(selectBoardsAll); + const [selectedBoard, setSelectedBoard] = useState( + undefined + ); + + const { isOpen, onClose, handleAddToBoard, image } = useContext( + AddImageToBoardContext + ); + + const cancelRef = useRef(null); + + const currentBoard = boards.filter( + (board) => board.board_id === image?.board_id + )[0]; + + return ( + + + + + Move Image to Board + + + + + + + Moving this image to a board will remove it from its existing + board. + + setSelectedBoard(e.target.value)} + validValues={boards.map((board) => board.board_name)} + /> + + + + + Cancel + { + if (selectedBoard) handleAddToBoard(selectedBoard); + }} + ml={3} + > + Add to Board + + + + + + ); +}; + +export default memo(UpdateImageBoardModal); diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index d52ba89d8f..b21c62785b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -34,6 +34,9 @@ import { useAppToaster } from 'app/components/Toaster'; import { ImageDTO } from 'services/api'; import { useDraggable } from '@dnd-kit/core'; import { DeleteImageContext } from 'app/contexts/DeleteImageContext'; +import { imageAddedToBoard } from '../../../services/thunks/board'; +import { setUpdateBoardModalOpen } from '../store/boardSlice'; +import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext'; export const selector = createSelector( [gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector], @@ -100,6 +103,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled; const { onDelete } = useContext(DeleteImageContext); + const { onClickAddToBoard } = useContext(AddImageToBoardContext); const handleDelete = useCallback(() => { onDelete(image); }, [image, onDelete]); @@ -175,9 +179,9 @@ const HoverableImage = memo((props: HoverableImageProps) => { // dispatch(setIsLightboxOpen(true)); }; - const handleAddToFolder = useCallback(() => { - // dispatch(addImageToFolder(image)); - }, []); + const handleAddToBoard = useCallback(() => { + onClickAddToBoard(image); + }, [image, onClickAddToBoard]); const handleOpenInNewTab = () => { window.open(image.image_url, '_blank'); @@ -255,8 +259,8 @@ const HoverableImage = memo((props: HoverableImageProps) => { {t('parameters.sendToUnifiedCanvas')} )} - } onClickCapture={handleAddToFolder}> - Add to Folder + } onClickCapture={handleAddToBoard}> + Add to Board ) => { state.searchText = action.payload; }, + setUpdateBoardModalOpen: (state, action: PayloadAction) => { + state.updateBoardModalOpen = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(receivedBoards.pending, (state) => { @@ -105,6 +110,7 @@ export const { boardRemoved, boardIdSelected, setBoardSearchText, + setUpdateBoardModalOpen, } = boardsSlice.actions; export const boardsSelector = (state: RootState) => state.boards; diff --git a/invokeai/frontend/web/src/services/thunks/board.ts b/invokeai/frontend/web/src/services/thunks/board.ts index a536a3fdb0..4535081e47 100644 --- a/invokeai/frontend/web/src/services/thunks/board.ts +++ b/invokeai/frontend/web/src/services/thunks/board.ts @@ -39,3 +39,15 @@ export const boardUpdated = createAppAsyncThunk( return response; } ); + +type ImageAddedToBoardArg = Parameters< + (typeof BoardsService)['createBoardImage'] +>[0]; + +export const imageAddedToBoard = createAppAsyncThunk( + 'api/imageAddedToBoard', + async (arg: ImageAddedToBoardArg) => { + const response = await BoardsService.createBoardImage(arg); + return response; + } +); From ca8f1a78289408f39df502461913a13092ee1b5e Mon Sep 17 00:00:00 2001 From: maryhipp Date: Thu, 15 Jun 2023 08:31:14 -0700 Subject: [PATCH 25/99] (api) use most recently generated image for cover photo --- invokeai/app/services/boards.py | 11 ++++---- invokeai/app/services/image_record_storage.py | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index ddfaacf79c..148af50103 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -136,11 +136,12 @@ class BoardService(BoardServiceABC): board_records = self._services.board_records.get_many(offset, limit) board_dtos = [] for r in board_records.items: - cover_image_url = ( - self._services.urls.get_image_url(r.cover_image_name, True) - if r.cover_image_name - else None - ) + cover_image = self._services.image_records.get_most_recent_image_for_board(r.board_id) + if (cover_image): + cover_image_url = self._services.urls.get_image_url(cover_image.image_name, True) + else: + cover_image_url = None + image_count = self._services.board_image_records.get_image_count_for_board( r.board_id ) diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 677e1d3445..bc59c4a27c 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -109,6 +109,11 @@ class ImageRecordStorageBase(ABC): """Saves an image record.""" pass + @abstractmethod + def get_most_recent_image_for_board(self, board_id: str) -> ImageRecord | None: + """Gets the most recent image for a board.""" + pass + class SqliteImageRecordStorage(ImageRecordStorageBase): _filename: str @@ -414,3 +419,26 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): raise ImageRecordSaveException from e finally: self._lock.release() + + def get_most_recent_image_for_board(self, board_id: str) -> Union[ImageRecord, None]: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT images.* + FROM images + JOIN board_images ON images.image_name = board_images.image_name + WHERE board_images.board_id = ? + ORDER BY images.created_at DESC + LIMIT 1; + """, + (board_id,), + ) + + result = cast(Union[sqlite3.Row, None], self._cursor.fetchone()) + finally: + self._lock.release() + if result is None: + return None + + return deserialize_image_record(dict(result)) From 4a0a718b96990fe471558a24f92b914fb4cf2514 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Thu, 15 Jun 2023 09:25:56 -0700 Subject: [PATCH 26/99] foiled by a comma --- invokeai/app/services/board_record_storage.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index 8b849a0501..65e256d040 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -154,12 +154,15 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): DELETE FROM boards WHERE board_id = ?; """, - (board_id), + (board_id,), ) self._conn.commit() except sqlite3.Error as e: self._conn.rollback() raise BoardRecordDeleteException from e + except Exception as e: + self._conn.rollback() + raise BoardRecordDeleteException from e finally: self._lock.release() @@ -192,7 +195,6 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): return BoardRecord(**result) except sqlite3.Error as e: self._conn.rollback() - print(e) raise BoardRecordSaveException from e finally: self._lock.release() From e4893e4031ff3d57171fe554e1f6931e6599ca04 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 16 Jun 2023 11:00:40 +1000 Subject: [PATCH 27/99] fix(db): return board records from CRUD methods --- invokeai/app/services/board_record_storage.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index 65e256d040..20a9683b02 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from datetime import datetime from typing import Optional, cast import sqlite3 import threading @@ -181,23 +180,12 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): (board_id, board_name), ) self._conn.commit() - - self._cursor.execute( - """--sql - SELECT * - FROM boards - WHERE board_id = ?; - """, - (board_id,), - ) - - result = self._cursor.fetchone() - return BoardRecord(**result) except sqlite3.Error as e: self._conn.rollback() raise BoardRecordSaveException from e finally: self._lock.release() + return self.get(board_id) def get( self, @@ -228,7 +216,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): self, board_id: str, changes: BoardChanges, - ) -> None: + ) -> BoardRecord: try: self._lock.acquire() @@ -260,6 +248,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): raise BoardRecordSaveException from e finally: self._lock.release() + return self.get(board_id) def get_many( self, From 70cc037a9cfd278e765744616cc9797441055906 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 16 Jun 2023 11:03:13 +1000 Subject: [PATCH 28/99] fix(ui): do not persist boards --- invokeai/frontend/web/src/app/store/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 6198a414bf..4032db3159 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -67,7 +67,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [ 'system', 'ui', 'controlNet', - 'boards', + // 'boards', // 'hotkeys', // 'config', ]; From d604d986f9bdbad1e9120b8163f019b10e324697 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 16 Jun 2023 15:52:32 +1000 Subject: [PATCH 29/99] feat(db, api): update get_board_for_image & service dependencies - previously was `get_boards_for_image`, returning a list of `BoardDTO`, now returns a single `board_id` --- invokeai/app/api/dependencies.py | 19 +++-- .../services/board_image_record_storage.py | 48 ++++------- invokeai/app/services/board_images.py | 42 ++-------- invokeai/app/services/images.py | 83 ++++++------------- invokeai/app/services/models/image_record.py | 9 +- 5 files changed, 69 insertions(+), 132 deletions(-) diff --git a/invokeai/app/api/dependencies.py b/invokeai/app/api/dependencies.py index 8889c70674..60f8c1b09d 100644 --- a/invokeai/app/api/dependencies.py +++ b/invokeai/app/api/dependencies.py @@ -12,7 +12,7 @@ from invokeai.app.services.board_images import ( 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 +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 @@ -106,13 +106,16 @@ class ApiDependencies: ) images = ImageService( - image_record_storage=image_record_storage, - image_file_storage=image_file_storage, - metadata=metadata, - url=urls, - logger=logger, - names=names, - graph_execution_manager=graph_execution_manager, + services=ImageServiceDependencies( + 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, + graph_execution_manager=graph_execution_manager, + ) ) services = InvocationServices( diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index 851e8502e1..2f1603be82 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod import sqlite3 import threading -from typing import cast +from typing import Union, cast from invokeai.app.services.board_record_storage import BoardRecord from invokeai.app.services.image_record_storage import OffsetPaginatedResults @@ -41,11 +41,11 @@ class BoardImageRecordStorageBase(ABC): pass @abstractmethod - def get_boards_for_image( + def get_board_for_image( self, - board_id: str, - ) -> OffsetPaginatedResults[BoardRecord]: - """Gets boards for an image.""" + image_name: str, + ) -> Union[str, None]: + """Gets an image's board id, if it has one.""" pass @abstractmethod @@ -134,7 +134,6 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): board_id: str, image_name: str, ) -> None: - """Adds an image to a board.""" try: self._lock.acquire() self._cursor.execute( @@ -156,7 +155,6 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): board_id: str, image_name: str, ) -> None: - """Removes an image from a board.""" try: self._lock.acquire() self._cursor.execute( @@ -179,7 +177,6 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): offset: int = 0, limit: int = 10, ) -> OffsetPaginatedResults[ImageRecord]: - """Gets images for a board.""" try: self._lock.acquire() self._cursor.execute( @@ -211,46 +208,31 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): items=images, offset=offset, limit=limit, total=count ) - def get_boards_for_image( + def get_board_for_image( self, - board_id: str, - offset: int = 0, - limit: int = 10, - ) -> OffsetPaginatedResults[BoardRecord]: - """Gets boards for an image.""" + image_name: str, + ) -> Union[str, None]: try: self._lock.acquire() self._cursor.execute( """--sql - SELECT boards.* + SELECT board_id FROM board_images - INNER JOIN boards ON board_images.board_id = boards.board_id - WHERE board_images.image_name = ? - ORDER BY board_images.updated_at DESC; + WHERE image_name = ?; """, - (board_id,), + (image_name,), ) - result = cast(list[sqlite3.Row], self._cursor.fetchall()) - boards = list(map(lambda r: BoardRecord(**r), result)) - - self._cursor.execute( - """--sql - SELECT COUNT(*) FROM boards WHERE 1=1; - """ - ) - count = cast(int, self._cursor.fetchone()[0]) - + result = self._cursor.fetchone() + if result is None: + return None + return cast(str, result[0]) except sqlite3.Error as e: self._conn.rollback() raise e finally: self._lock.release() - return OffsetPaginatedResults( - items=boards, offset=offset, limit=limit, total=count - ) def get_image_count_for_board(self, board_id: str) -> int: - """Gets the number of images for a board.""" try: self._lock.acquire() self._cursor.execute( diff --git a/invokeai/app/services/board_images.py b/invokeai/app/services/board_images.py index df2af4bbcf..cf16993a7a 100644 --- a/invokeai/app/services/board_images.py +++ b/invokeai/app/services/board_images.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod from logging import Logger +from typing import Union from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase from invokeai.app.services.board_record_storage import ( BoardRecord, @@ -45,11 +46,11 @@ class BoardImagesServiceABC(ABC): pass @abstractmethod - def get_boards_for_image( + def get_board_for_image( self, image_name: str, - ) -> OffsetPaginatedResults[BoardDTO]: - """Gets boards for an image.""" + ) -> Union[str, None]: + """Gets an image's board id, if it has one.""" pass @@ -110,6 +111,7 @@ class BoardImagesService(BoardImagesServiceABC): r, self._services.urls.get_image_url(r.image_name), self._services.urls.get_image_url(r.image_name, True), + board_id, ), image_records.items, ) @@ -121,38 +123,12 @@ class BoardImagesService(BoardImagesServiceABC): total=image_records.total, ) - def get_boards_for_image( + def get_board_for_image( self, image_name: str, - ) -> OffsetPaginatedResults[BoardDTO]: - board_records = self._services.board_image_records.get_boards_for_image( - image_name - ) - board_dtos = [] - - for r in board_records.items: - cover_image_url = ( - self._services.urls.get_image_url(r.cover_image_name, True) - if r.cover_image_name - else None - ) - image_count = self._services.board_image_records.get_image_count_for_board( - r.board_id - ) - board_dtos.append( - board_record_to_dto( - r, - cover_image_url, - image_count, - ) - ) - - return OffsetPaginatedResults[BoardDTO]( - items=board_dtos, - offset=board_records.offset, - limit=board_records.limit, - total=board_records.total, - ) + ) -> Union[str, None]: + board_id = self._services.board_image_records.get_board_for_image(image_name) + return board_id def board_record_to_dto( diff --git a/invokeai/app/services/images.py b/invokeai/app/services/images.py index aa27e38d17..5959116161 100644 --- a/invokeai/app/services/images.py +++ b/invokeai/app/services/images.py @@ -10,6 +10,7 @@ from invokeai.app.models.image import ( 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, @@ -114,8 +115,9 @@ class ImageServiceABC(ABC): class ImageServiceDependencies: """Service dependencies for the ImageService.""" - records: ImageRecordStorageBase - files: ImageFileStorageBase + image_records: ImageRecordStorageBase + image_files: ImageFileStorageBase + board_image_records: BoardImageRecordStorageBase metadata: MetadataServiceBase urls: UrlServiceBase logger: Logger @@ -126,14 +128,16 @@ class ImageServiceDependencies: self, image_record_storage: ImageRecordStorageBase, image_file_storage: ImageFileStorageBase, + board_image_record_storage: BoardImageRecordStorageBase, metadata: MetadataServiceBase, url: UrlServiceBase, logger: Logger, names: NameServiceBase, graph_execution_manager: ItemStorageABC["GraphExecutionState"], ): - self.records = image_record_storage - self.files = image_file_storage + 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 @@ -144,25 +148,8 @@ class ImageServiceDependencies: class ImageService(ImageServiceABC): _services: ImageServiceDependencies - def __init__( - self, - image_record_storage: ImageRecordStorageBase, - image_file_storage: ImageFileStorageBase, - metadata: MetadataServiceBase, - url: UrlServiceBase, - logger: Logger, - names: NameServiceBase, - graph_execution_manager: ItemStorageABC["GraphExecutionState"], - ): - self._services = ImageServiceDependencies( - image_record_storage=image_record_storage, - image_file_storage=image_file_storage, - metadata=metadata, - url=url, - logger=logger, - names=names, - graph_execution_manager=graph_execution_manager, - ) + def __init__(self, services: ImageServiceDependencies): + self._services = services def create( self, @@ -187,7 +174,7 @@ class ImageService(ImageServiceABC): try: # TODO: Consider using a transaction here to ensure consistency between storage and database - created_at = self._services.records.save( + self._services.image_records.save( # Non-nullable fields image_name=image_name, image_origin=image_origin, @@ -202,35 +189,15 @@ class ImageService(ImageServiceABC): metadata=metadata, ) - self._services.files.save( + self._services.image_files.save( image_name=image_name, image=image, metadata=metadata, ) - image_url = self._services.urls.get_image_url(image_name) - thumbnail_url = self._services.urls.get_image_url(image_name, True) + image_dto = self.get_dto(image_name) - return ImageDTO( - # Non-nullable fields - image_name=image_name, - image_origin=image_origin, - image_category=image_category, - width=width, - height=height, - # Nullable fields - node_id=node_id, - session_id=session_id, - metadata=metadata, - # Meta fields - created_at=created_at, - updated_at=created_at, # this is always the same as the created_at at this time - deleted_at=None, - is_intermediate=is_intermediate, - # Extra non-nullable fields for DTO - image_url=image_url, - thumbnail_url=thumbnail_url, - ) + return image_dto except ImageRecordSaveException: self._services.logger.error("Failed to save image record") raise @@ -247,7 +214,7 @@ class ImageService(ImageServiceABC): changes: ImageRecordChanges, ) -> ImageDTO: try: - self._services.records.update(image_name, changes) + self._services.image_records.update(image_name, changes) return self.get_dto(image_name) except ImageRecordSaveException: self._services.logger.error("Failed to update image record") @@ -258,7 +225,7 @@ class ImageService(ImageServiceABC): def get_pil_image(self, image_name: str) -> PILImageType: try: - return self._services.files.get(image_name) + return self._services.image_files.get(image_name) except ImageFileNotFoundException: self._services.logger.error("Failed to get image file") raise @@ -268,7 +235,7 @@ class ImageService(ImageServiceABC): def get_record(self, image_name: str) -> ImageRecord: try: - return self._services.records.get(image_name) + return self._services.image_records.get(image_name) except ImageRecordNotFoundException: self._services.logger.error("Image record not found") raise @@ -278,12 +245,13 @@ class ImageService(ImageServiceABC): def get_dto(self, image_name: str) -> ImageDTO: try: - image_record = self._services.records.get(image_name) + image_record = self._services.image_records.get(image_name) image_dto = image_record_to_dto( image_record, self._services.urls.get_image_url(image_name), self._services.urls.get_image_url(image_name, True), + self._services.board_image_records.get_board_for_image(image_name), ) return image_dto @@ -296,14 +264,14 @@ class ImageService(ImageServiceABC): def get_path(self, image_name: str, thumbnail: bool = False) -> str: try: - return self._services.files.get_path(image_name, thumbnail) + return self._services.image_files.get_path(image_name, thumbnail) except Exception as e: self._services.logger.error("Problem getting image path") raise e def validate_path(self, path: str) -> bool: try: - return self._services.files.validate_path(path) + return self._services.image_files.validate_path(path) except Exception as e: self._services.logger.error("Problem validating image path") raise e @@ -324,7 +292,7 @@ class ImageService(ImageServiceABC): is_intermediate: Optional[bool] = None, ) -> OffsetPaginatedResults[ImageDTO]: try: - results = self._services.records.get_many( + results = self._services.image_records.get_many( offset, limit, image_origin, @@ -338,6 +306,9 @@ class ImageService(ImageServiceABC): r, self._services.urls.get_image_url(r.image_name), self._services.urls.get_image_url(r.image_name, True), + self._services.board_image_records.get_board_for_image( + r.image_name + ), ), results.items, ) @@ -355,8 +326,8 @@ class ImageService(ImageServiceABC): def delete(self, image_name: str): try: - self._services.files.delete(image_name) - self._services.records.delete(image_name) + self._services.image_files.delete(image_name) + self._services.image_records.delete(image_name) except ImageRecordDeleteException: self._services.logger.error(f"Failed to delete image record") raise diff --git a/invokeai/app/services/models/image_record.py b/invokeai/app/services/models/image_record.py index d971d65916..cc02016cf9 100644 --- a/invokeai/app/services/models/image_record.py +++ b/invokeai/app/services/models/image_record.py @@ -86,19 +86,24 @@ class ImageUrlsDTO(BaseModel): class ImageDTO(ImageRecord, ImageUrlsDTO): - """Deserialized image record, enriched for the frontend with URLs.""" + """Deserialized image record, enriched for the frontend.""" + board_id: Union[str, None] = Field( + description="The id of the board the image belongs to, if one exists." + ) + """The id of the board the image belongs to, if one exists.""" pass def image_record_to_dto( - image_record: ImageRecord, image_url: str, thumbnail_url: str + image_record: ImageRecord, image_url: str, thumbnail_url: str, board_id: Union[str, None] ) -> ImageDTO: """Converts an image record to an image DTO.""" return ImageDTO( **image_record.dict(), image_url=image_url, thumbnail_url=thumbnail_url, + board_id=board_id, ) From 49a02c157bf29eefecca75f40e04ae9df040931b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 16 Jun 2023 15:53:17 +1000 Subject: [PATCH 30/99] feat(ui): fix UpdateImageBoardModal select --- .../components/Boards/UpdateImageBoardModal.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx index 8a94764ab1..b5e00d7801 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx @@ -18,12 +18,11 @@ import { AddImageToBoardContext } from '../../../../app/contexts/AddImageToBoard import { useSelector } from 'react-redux'; import { selectBoardsAll } from '../../store/boardSlice'; import IAISelect from '../../../../common/components/IAISelect'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; const UpdateImageBoardModal = () => { const boards = useSelector(selectBoardsAll); - const [selectedBoard, setSelectedBoard] = useState( - undefined - ); + const [selectedBoard, setSelectedBoard] = useState(null); const { isOpen, onClose, handleAddToBoard, image } = useContext( AddImageToBoardContext @@ -55,10 +54,14 @@ const UpdateImageBoardModal = () => { Moving this image to a board will remove it from its existing board. - setSelectedBoard(e.target.value)} - validValues={boards.map((board) => board.board_name)} + onChange={(v) => setSelectedBoard(v)} + value={selectedBoard} + data={boards.map((board) => ({ + label: board.board_name, + value: board.board_id, + }))} /> From 95b9c8e505534ff065ed9660c0459b5cba724bd1 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Fri, 16 Jun 2023 10:21:55 -0700 Subject: [PATCH 31/99] return cover_image_name since urls change, override one from db for now --- invokeai/app/services/board_images.py | 8 ++--- invokeai/app/services/boards.py | 31 ++++++++++---------- invokeai/app/services/models/board_record.py | 4 +-- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/invokeai/app/services/board_images.py b/invokeai/app/services/board_images.py index cf16993a7a..072effbfae 100644 --- a/invokeai/app/services/board_images.py +++ b/invokeai/app/services/board_images.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from logging import Logger -from typing import Union +from typing import List, Union from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase from invokeai.app.services.board_record_storage import ( BoardRecord, @@ -132,11 +132,11 @@ class BoardImagesService(BoardImagesServiceABC): def board_record_to_dto( - board_record: BoardRecord, cover_image_url: str | None, image_count: int + board_record: BoardRecord, cover_image_name: str | None, image_count: int ) -> BoardDTO: """Converts a board record to a board DTO.""" return BoardDTO( - **board_record.dict(), - cover_image_url=cover_image_url, + **board_record.dict(exclude={'cover_image_name'}), + cover_image_name=cover_image_name, image_count=image_count, ) diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index 148af50103..93def0cafd 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -101,15 +101,15 @@ class BoardService(BoardServiceABC): def get_dto(self, board_id: str) -> BoardDTO: board_record = self._services.board_records.get(board_id) - cover_image_url = ( - self._services.urls.get_image_url(board_record.cover_image_name, True) - if board_record.cover_image_name - else None - ) + cover_image = self._services.image_records.get_most_recent_image_for_board(board_recordboard_id) + if (cover_image): + cover_image_name = cover_image.image_name + else: + cover_image_name = None image_count = self._services.board_image_records.get_image_count_for_board( board_id ) - return board_record_to_dto(board_record, cover_image_url, image_count) + return board_record_to_dto(board_record, cover_image_name, image_count) def update( self, @@ -117,15 +117,16 @@ class BoardService(BoardServiceABC): changes: BoardChanges, ) -> BoardDTO: board_record = self._services.board_records.update(board_id, changes) - cover_image_url = ( - self._services.urls.get_image_url(board_record.cover_image_name, True) - if board_record.cover_image_name - else None - ) + cover_image = self._services.image_records.get_most_recent_image_for_board(board_record.board_id) + if (cover_image): + cover_image_name = cover_image.image_name + else: + cover_image_name = None + image_count = self._services.board_image_records.get_image_count_for_board( board_id ) - return board_record_to_dto(board_record, cover_image_url, image_count) + return board_record_to_dto(board_record, cover_image_name, image_count) def delete(self, board_id: str) -> None: self._services.board_records.delete(board_id) @@ -138,14 +139,14 @@ class BoardService(BoardServiceABC): for r in board_records.items: cover_image = self._services.image_records.get_most_recent_image_for_board(r.board_id) if (cover_image): - cover_image_url = self._services.urls.get_image_url(cover_image.image_name, True) + cover_image_name = cover_image.image_name else: - cover_image_url = None + cover_image_name = None image_count = self._services.board_image_records.get_image_count_for_board( r.board_id ) - board_dtos.append(board_record_to_dto(r, cover_image_url, image_count)) + board_dtos.append(board_record_to_dto(r, cover_image_name, image_count)) return OffsetPaginatedResults[BoardDTO]( items=board_dtos, offset=offset, limit=limit, total=len(board_dtos) diff --git a/invokeai/app/services/models/board_record.py b/invokeai/app/services/models/board_record.py index 35c4cea9b0..16748570c4 100644 --- a/invokeai/app/services/models/board_record.py +++ b/invokeai/app/services/models/board_record.py @@ -27,8 +27,8 @@ class BoardRecord(BaseModel): class BoardDTO(BoardRecord): """Deserialized board record with cover image URL and image count.""" - cover_image_url: Optional[str] = Field( - description="The URL of the thumbnail of the board's cover image." + cover_image_name: Optional[str] = Field( + description="The name of the board's cover image." ) """The URL of the thumbnail of the most recent image in the board.""" image_count: int = Field(description="The number of images in the board.") From f9f3c91a83a37ae2d280f7912d8c18466c99eb06 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Fri, 16 Jun 2023 13:06:59 -0400 Subject: [PATCH 32/99] drag and drop to move image to board, a bit of board list UI --- .../app/contexts/AddImageToBoardContext.tsx | 62 +-------- .../gallery/components/Boards/BoardsList.tsx | 123 +++++++++++------- .../components/Boards/HoverableBoard.tsx | 64 ++++++--- .../Boards/UpdateImageBoardModal.tsx | 13 +- .../components/ImageGalleryContent.tsx | 35 +---- .../web/src/services/api/models/ImageDTO.ts | 6 +- 6 files changed, 139 insertions(+), 164 deletions(-) diff --git a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx index cf541dca01..da3dcb2239 100644 --- a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx +++ b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx @@ -1,23 +1,7 @@ import { useDisclosure } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import { requestedImageDeletion } from 'features/gallery/store/actions'; -import { systemSelector } from 'features/system/store/systemSelectors'; -import { - PropsWithChildren, - createContext, - useCallback, - useEffect, - useState, -} from 'react'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { PropsWithChildren, createContext, useCallback, useState } from 'react'; import { ImageDTO } from 'services/api'; -import { RootState } from 'app/store/store'; -import { canvasSelector } from 'features/canvas/store/canvasSelectors'; -import { controlNetSelector } from 'features/controlNet/store/controlNetSlice'; -import { nodesSelecter } from 'features/nodes/store/nodesSlice'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { some } from 'lodash-es'; import { imageAddedToBoard } from '../../services/thunks/board'; export type ImageUsage = { @@ -27,48 +11,6 @@ export type ImageUsage = { isControlNetImage: boolean; }; -export const selectImageUsage = createSelector( - [ - generationSelector, - canvasSelector, - nodesSelecter, - controlNetSelector, - (state: RootState, image_name?: string) => image_name, - ], - (generation, canvas, nodes, controlNet, image_name) => { - const isInitialImage = generation.initialImage?.image_name === image_name; - - const isCanvasImage = canvas.layerState.objects.some( - (obj) => obj.kind === 'image' && obj.image.image_name === image_name - ); - - const isNodesImage = nodes.nodes.some((node) => { - return some( - node.data.inputs, - (input) => - input.type === 'image' && input.value?.image_name === image_name - ); - }); - - const isControlNetImage = some( - controlNet.controlNets, - (c) => - c.controlImage?.image_name === image_name || - c.processedControlImage?.image_name === image_name - ); - - const imageUsage: ImageUsage = { - isInitialImage, - isCanvasImage, - isNodesImage, - isControlNetImage, - }; - - return imageUsage; - }, - defaultSelectorOptions -); - type AddImageToBoardContextValue = { /** * Whether the move image dialog is open. diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index 9e7d1ab960..1f84d3be0e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -1,4 +1,13 @@ -import { Box, Grid, Input, Spacer } from '@chakra-ui/react'; +import { + Box, + Divider, + Grid, + Input, + InputGroup, + InputRightElement, + Spacer, + useDisclosure, +} from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; @@ -14,19 +23,25 @@ import AddBoardButton from './AddBoardButton'; import AllImagesBoard from './AllImagesBoard'; import { searchBoardsSelector } from '../../store/boardSelectors'; import { useSelector } from 'react-redux'; +import IAICollapse from '../../../../common/components/IAICollapse'; +import { CloseIcon } from '@chakra-ui/icons'; const selector = createSelector( [selectBoardsAll, boardsSelector], (boards, boardsState) => { - return { boards, selectedBoardId: boardsState.selectedBoardId }; + const selectedBoard = boards.find( + (board) => board.board_id === boardsState.selectedBoardId + ); + return { selectedBoard, searchText: boardsState.searchText }; }, defaultSelectorOptions ); const BoardsList = () => { const dispatch = useAppDispatch(); - const { selectedBoardId } = useAppSelector(selector); + const { selectedBoard, searchText } = useAppSelector(selector); const filteredBoards = useSelector(searchBoardsSelector); + const { isOpen, onToggle } = useDisclosure(); const [searchMode, setSearchMode] = useState(false); @@ -34,52 +49,68 @@ const BoardsList = () => { setSearchMode(searchTerm.length > 0); dispatch(setBoardSearchText(searchTerm)); }; + const clearBoardSearch = () => { + setSearchMode(false); + dispatch(setBoardSearchText('')); + }; return ( - - - { - handleBoardSearch(e.target.value); + + <> + + + { + handleBoardSearch(e.target.value); + }} + /> + {searchText && searchText.length && ( + + + + )} + + + - - - {!searchMode && ( - <> - - - - )} - {filteredBoards.map((board) => ( - - ))} - - + > + + {!searchMode && ( + <> + + + + )} + {filteredBoards.map((board) => ( + + ))} + + + + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index d368d4ab0b..6fd6ac41be 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -4,19 +4,34 @@ import { EditableInput, EditablePreview, Flex, - Icon, - Image, MenuItem, MenuList, } from '@chakra-ui/react'; -import { useAppDispatch } from 'app/store/storeHooks'; + +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { memo, useCallback } from 'react'; -import { FaFolder, FaTrash } from 'react-icons/fa'; +import { FaTrash } from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; -import { BoardDTO } from 'services/api'; +import { BoardDTO, ImageDTO } from 'services/api'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; import { boardIdSelected } from 'features/gallery/store/boardSlice'; -import { boardDeleted, boardUpdated } from '../../../../services/thunks/board'; +import { + boardDeleted, + boardUpdated, + imageAddedToBoard, +} from '../../../../services/thunks/board'; +import { selectImagesAll } from '../../store/imagesSlice'; +import IAIDndImage from '../../../../common/components/IAIDndImage'; +import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions'; +import { createSelector } from '@reduxjs/toolkit'; + +const selector = createSelector( + [selectImagesAll], + (images) => { + return { images }; + }, + defaultSelectorOptions +); interface HoverableBoardProps { board: BoardDTO; @@ -25,6 +40,7 @@ interface HoverableBoardProps { const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { const dispatch = useAppDispatch(); + const { images } = useAppSelector(selector); const { board_name, board_id, cover_image_url } = board; @@ -45,6 +61,23 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { ); }; + const handleDrop = useCallback( + (droppedImage: ImageDTO) => { + if (droppedImage.board_id === board_id) { + return; + } + dispatch( + imageAddedToBoard({ + requestBody: { + board_id, + image_name: droppedImage.image_name, + }, + }) + ); + }, + [board_id, dispatch] + ); + return ( @@ -91,19 +124,12 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { overflow: 'hidden', }} > - {cover_image_url ? ( - } - sx={{}} - /> - ) : ( - - )} + } + isUploadDisabled={true} + /> { const boards = useSelector(selectBoardsAll); - const [selectedBoard, setSelectedBoard] = useState(null); - const { isOpen, onClose, handleAddToBoard, image } = useContext( AddImageToBoardContext ); + const [selectedBoard, setSelectedBoard] = useState(); const cancelRef = useRef(null); @@ -50,10 +49,12 @@ const UpdateImageBoardModal = () => { - - Moving this image to a board will remove it from its existing - board. - + {currentBoard && ( + + Moving this image from{' '} + {currentBoard.board_name} to + + )} setSelectedBoard(v)} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 647477ce39..c1c414ad8b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -240,39 +240,10 @@ const ImageGalleryContent = () => { icon={} /> - {selectedBoard && ( - - {selectedBoard.board_name} - - )} + + {selectedBoard ? selectedBoard.board_name : 'All Images'} + - {/* } - /> - } - > - - setNewBoardName(e.target.value)} - /> - - Create - - - */} Date: Fri, 16 Jun 2023 13:38:37 -0400 Subject: [PATCH 33/99] handle long board names --- .../src/features/gallery/components/ImageGalleryContent.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index c1c414ad8b..adb7791afb 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -221,6 +221,7 @@ const ImageGalleryContent = () => { ref={resizeObserverRef} alignItems="center" justifyContent="space-between" + gap={1} > { /> - {selectedBoard ? selectedBoard.board_name : 'All Images'} + + {selectedBoard ? selectedBoard.board_name : 'All Images'} + Date: Fri, 16 Jun 2023 14:40:12 -0400 Subject: [PATCH 34/99] add boardToAddTo state so that result can be added to board when generation is complete --- .../socketio/socketInvocationComplete.ts | 15 ++++++++++++++- .../web/src/features/gallery/store/boardSlice.ts | 5 ++--- .../web/src/features/system/store/systemSlice.ts | 3 +++ .../frontend/web/src/services/events/actions.ts | 4 ++-- .../web/src/services/events/middleware.ts | 1 + .../src/services/events/util/setEventListeners.ts | 1 + 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts index c9ab894ddb..24e8eb312f 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts @@ -9,6 +9,7 @@ import { imageMetadataReceived } from 'services/thunks/image'; import { sessionCanceled } from 'services/thunks/session'; import { isImageOutput } from 'services/types/guards'; import { progressImageSet } from 'features/system/store/systemSlice'; +import { imageAddedToBoard } from '../../../../../../services/thunks/board'; const moduleLog = log.child({ namespace: 'socketio' }); const nodeDenylist = ['dataURL_image']; @@ -24,7 +25,8 @@ export const addInvocationCompleteEventListener = () => { const sessionId = action.payload.data.graph_execution_state_id; - const { cancelType, isCancelScheduled } = getState().system; + const { cancelType, isCancelScheduled, boardIdToAddTo } = + getState().system; // Handle scheduled cancelation if (cancelType === 'scheduled' && isCancelScheduled) { @@ -38,6 +40,17 @@ export const addInvocationCompleteEventListener = () => { if (isImageOutput(result) && !nodeDenylist.includes(node.type)) { const { image_name } = result.image; + if (boardIdToAddTo) { + dispatch( + imageAddedToBoard({ + requestBody: { + board_id: boardIdToAddTo, + image_name, + }, + }) + ); + } + // Get its metadata dispatch( imageMetadataReceived({ diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index 390d43d033..76ab757e5e 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -25,7 +25,7 @@ type AdditionalBoardsState = { limit: number; total: number; isLoading: boolean; - selectedBoardId: EntityId | null; + selectedBoardId?: string; searchText?: string; updateBoardModalOpen: boolean; }; @@ -36,7 +36,6 @@ export const initialBoardsState = limit: 50, total: 0, isLoading: false, - selectedBoardId: null, updateBoardModalOpen: false, }); @@ -55,7 +54,7 @@ const boardsSlice = createSlice({ boardRemoved: (state, action: PayloadAction) => { boardsAdapter.removeOne(state, action.payload); }, - boardIdSelected: (state, action: PayloadAction) => { + boardIdSelected: (state, action: PayloadAction) => { state.selectedBoardId = action.payload; }, setBoardSearchText: (state, action: PayloadAction) => { diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index b17f497f6c..f86415cf37 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -95,6 +95,7 @@ export interface SystemState { shouldAntialiasProgressImage: boolean; language: keyof typeof LANGUAGES; isUploading: boolean; + boardIdToAddTo?: string; } export const initialSystemState: SystemState = { @@ -225,6 +226,7 @@ export const systemSlice = createSlice({ */ builder.addCase(appSocketSubscribed, (state, action) => { state.sessionId = action.payload.sessionId; + state.boardIdToAddTo = action.payload.boardId; state.canceledSession = ''; }); @@ -233,6 +235,7 @@ export const systemSlice = createSlice({ */ builder.addCase(appSocketUnsubscribed, (state) => { state.sessionId = null; + state.boardIdToAddTo = undefined; }); /** diff --git a/invokeai/frontend/web/src/services/events/actions.ts b/invokeai/frontend/web/src/services/events/actions.ts index 5832cb24b1..ed154b9cd8 100644 --- a/invokeai/frontend/web/src/services/events/actions.ts +++ b/invokeai/frontend/web/src/services/events/actions.ts @@ -53,14 +53,14 @@ export const appSocketDisconnected = createAction( * Do not use. Only for use in middleware. */ export const socketSubscribed = createAction< - BaseSocketPayload & { sessionId: string } + BaseSocketPayload & { sessionId: string; boardId: string | undefined } >('socket/socketSubscribed'); /** * App-level Socket.IO Subscribed */ export const appSocketSubscribed = createAction< - BaseSocketPayload & { sessionId: string } + BaseSocketPayload & { sessionId: string; boardId: string | undefined } >('socket/appSocketSubscribed'); /** diff --git a/invokeai/frontend/web/src/services/events/middleware.ts b/invokeai/frontend/web/src/services/events/middleware.ts index f1eb844f2c..5b427b1690 100644 --- a/invokeai/frontend/web/src/services/events/middleware.ts +++ b/invokeai/frontend/web/src/services/events/middleware.ts @@ -85,6 +85,7 @@ export const socketMiddleware = () => { socketSubscribed({ sessionId: sessionId, timestamp: getTimestamp(), + boardId: getState().boards.selectedBoardId, }) ); } diff --git a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts index 2c4cba510a..62b5864185 100644 --- a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts +++ b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts @@ -44,6 +44,7 @@ export const setEventListeners = (arg: SetEventListenersArg) => { socketSubscribed({ sessionId, timestamp: getTimestamp(), + boardId: getState().boards.selectedBoardId, }) ); } From fe10a9f74786ca3fc706085da8279df0c3f2fa09 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Fri, 16 Jun 2023 15:01:22 -0400 Subject: [PATCH 35/99] render cover image based on URL in image entities --- .../components/Boards/HoverableBoard.tsx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index 6fd6ac41be..055fa8f68b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -20,18 +20,26 @@ import { boardUpdated, imageAddedToBoard, } from '../../../../services/thunks/board'; -import { selectImagesAll } from '../../store/imagesSlice'; +import { selectImagesAll, selectImagesById } from '../../store/imagesSlice'; import IAIDndImage from '../../../../common/components/IAIDndImage'; import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions'; import { createSelector } from '@reduxjs/toolkit'; +import { RootState } from '../../../../app/store/store'; -const selector = createSelector( - [selectImagesAll], - (images) => { - return { images }; - }, - defaultSelectorOptions -); +const coverImageSelector = (imageName: string | undefined) => + createSelector( + [(state: RootState) => state], + (state) => { + const coverImage = imageName + ? selectImagesById(state, imageName) + : undefined; + + return { + coverImage, + }; + }, + defaultSelectorOptions + ); interface HoverableBoardProps { board: BoardDTO; @@ -40,9 +48,11 @@ interface HoverableBoardProps { const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { const dispatch = useAppDispatch(); - const { images } = useAppSelector(selector); + const { coverImage } = useAppSelector( + coverImageSelector(board?.cover_image_name) + ); - const { board_name, board_id, cover_image_url } = board; + const { board_name, board_id } = board; const handleSelectBoard = useCallback(() => { dispatch(boardIdSelected(board_id)); @@ -125,7 +135,7 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { }} > } isUploadDisabled={true} From daadf6ebfdf0586fe8266f056dee77e92007ee40 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 16 Jun 2023 17:50:54 +1000 Subject: [PATCH 36/99] feat(ui): add board image count badge --- .../components/Boards/HoverableBoard.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index 055fa8f68b..fdde7528cb 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -6,6 +6,7 @@ import { Flex, MenuItem, MenuList, + Text, } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; @@ -162,6 +163,22 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { }} /> + + {board.image_count} + )} From 8bce234542f135d913f6a5b0142e55b35663f480 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:49:14 +1000 Subject: [PATCH 37/99] feat(db): update image-board relationships on add Functionally, `add_image_to_board()` now moves images between boards. --- invokeai/app/services/board_image_record_storage.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index 2f1603be82..73fad13406 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -139,9 +139,10 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): self._cursor.execute( """--sql INSERT INTO board_images (board_id, image_name) - VALUES (?, ?); + VALUES (?, ?) + ON CONFLICT (image_name) DO UPDATE SET board_id = ?; """, - (board_id, image_name), + (board_id, image_name, board_id), ) self._conn.commit() except sqlite3.Error as e: From 21f0d0b0c1ce170b09628b366f449f15b40b68eb Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:50:02 +1000 Subject: [PATCH 38/99] fix(db): fix `deserialize_board_record()` It was not adding `cover_image_name` --- invokeai/app/services/models/board_record.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/app/services/models/board_record.py b/invokeai/app/services/models/board_record.py index 16748570c4..bf7a33e8b9 100644 --- a/invokeai/app/services/models/board_record.py +++ b/invokeai/app/services/models/board_record.py @@ -35,8 +35,6 @@ class BoardDTO(BoardRecord): """The number of images in the board.""" - - def deserialize_board_record(board_dict: dict) -> BoardRecord: """Deserializes a board record.""" @@ -44,14 +42,16 @@ def deserialize_board_record(board_dict: dict) -> BoardRecord: board_id = board_dict.get("board_id", "unknown") board_name = board_dict.get("board_name", "unknown") + cover_image_name = board_dict.get("cover_image_name", "unknown") created_at = board_dict.get("created_at", get_iso_timestamp()) updated_at = board_dict.get("updated_at", get_iso_timestamp()) - deleted_at = board_dict.get("deleted_at", get_iso_timestamp()) + # deleted_at = board_dict.get("deleted_at", get_iso_timestamp()) return BoardRecord( board_id=board_id, board_name=board_name, + cover_image_name=cover_image_name, created_at=created_at, updated_at=updated_at, - deleted_at=deleted_at, + # deleted_at=deleted_at, ) From 9ef64016c7381043dee3cb3794a560ba993fccc9 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:50:33 +1000 Subject: [PATCH 39/99] feat(db): sort board by `created_at` --- invokeai/app/services/board_record_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index 20a9683b02..ec56122c9f 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -263,7 +263,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): """--sql SELECT * FROM boards - ORDER BY updated_at DESC + ORDER BY created_at DESC LIMIT ? OFFSET ?; """, (limit, offset), From 661a94b3de30caaebd27c07f072f77704852efa2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:51:05 +1000 Subject: [PATCH 40/99] feat(db): add `get_all()` method for boards This is needed to show the full list of boards in the update boards modal. --- invokeai/app/api/routers/boards.py | 32 ++++++++----- invokeai/app/services/board_record_storage.py | 39 +++++++++++++++- invokeai/app/services/boards.py | 46 ++++++++++++++++--- invokeai/app/services/models/board_record.py | 1 + 4 files changed, 98 insertions(+), 20 deletions(-) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index 7935db58f2..a6f226fef8 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -1,3 +1,4 @@ +from typing import Optional, Union from fastapi import Body, HTTPException, Path, Query from fastapi.routing import APIRouter from invokeai.app.services.board_record_storage import BoardChanges @@ -19,7 +20,7 @@ boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) response_model=BoardDTO, ) async def create_board( - board_name: str = Body(description="The name of the board to create"), + board_name: str = Query(description="The name of the board to create"), ) -> BoardDTO: """Creates a board""" try: @@ -70,16 +71,25 @@ async def delete_board( @boards_router.get( "/", operation_id="list_boards", - response_model=OffsetPaginatedResults[BoardDTO], + response_model=Union[OffsetPaginatedResults[BoardDTO], list[BoardDTO]], ) async def list_boards( - offset: int = Query(default=0, description="The page offset"), - limit: int = Query(default=10, description="The number of boards per page"), -) -> OffsetPaginatedResults[BoardDTO]: + all: Optional[bool] = Query(default=None, description="Whether to list all boards"), + offset: Optional[int] = Query(default=None, description="The page offset"), + limit: Optional[int] = Query( + default=None, description="The number of boards per page" + ), +) -> Union[OffsetPaginatedResults[BoardDTO], list[BoardDTO]]: """Gets a list of boards""" - - results = ApiDependencies.invoker.services.boards.get_many( - offset, - limit, - ) - return results + if all: + return ApiDependencies.invoker.services.boards.get_all() + elif offset is not None and limit is not None: + return ApiDependencies.invoker.services.boards.get_many( + offset, + limit, + ) + else: + raise HTTPException( + status_code=400, + detail="Invalid request: Must provide either 'all' or both 'offset' and 'limit'", + ) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index ec56122c9f..15ea9cc5a7 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -5,12 +5,14 @@ import threading from typing import Optional, Union import uuid from invokeai.app.services.image_record_storage import OffsetPaginatedResults -from invokeai.app.services.models.board_record import BoardRecord, deserialize_board_record +from invokeai.app.services.models.board_record import ( + BoardRecord, + deserialize_board_record, +) from pydantic import BaseModel, Field, Extra - class BoardChanges(BaseModel, extra=Extra.forbid): board_name: Optional[str] = Field(description="The board's new name.") cover_image_name: Optional[str] = Field( @@ -81,6 +83,13 @@ class BoardRecordStorageBase(ABC): """Gets many board records.""" pass + @abstractmethod + def get_all( + self, + ) -> list[BoardRecord]: + """Gets all board records.""" + pass + class SqliteBoardRecordStorage(BoardRecordStorageBase): _filename: str @@ -292,3 +301,29 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): raise e finally: self._lock.release() + + def get_all( + self, + ) -> list[BoardRecord]: + try: + self._lock.acquire() + + # Get all the boards + self._cursor.execute( + """--sql + SELECT * + FROM boards + ORDER BY created_at DESC + """ + ) + + result = cast(list[sqlite3.Row], self._cursor.fetchall()) + boards = list(map(lambda r: deserialize_board_record(dict(r)), result)) + + return boards + + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index 93def0cafd..9361322e6c 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -61,6 +61,13 @@ class BoardServiceABC(ABC): """Gets many boards.""" pass + @abstractmethod + def get_all( + self, + ) -> list[BoardDTO]: + """Gets all boards.""" + pass + class BoardServiceDependencies: """Service dependencies for the BoardService.""" @@ -101,8 +108,10 @@ class BoardService(BoardServiceABC): def get_dto(self, board_id: str) -> BoardDTO: board_record = self._services.board_records.get(board_id) - cover_image = self._services.image_records.get_most_recent_image_for_board(board_recordboard_id) - if (cover_image): + cover_image = self._services.image_records.get_most_recent_image_for_board( + board_record.board_id + ) + if cover_image: cover_image_name = cover_image.image_name else: cover_image_name = None @@ -117,12 +126,14 @@ class BoardService(BoardServiceABC): changes: BoardChanges, ) -> BoardDTO: board_record = self._services.board_records.update(board_id, changes) - cover_image = self._services.image_records.get_most_recent_image_for_board(board_record.board_id) - if (cover_image): + cover_image = self._services.image_records.get_most_recent_image_for_board( + board_record.board_id + ) + if cover_image: cover_image_name = cover_image.image_name else: cover_image_name = None - + image_count = self._services.board_image_records.get_image_count_for_board( board_id ) @@ -137,8 +148,10 @@ class BoardService(BoardServiceABC): board_records = self._services.board_records.get_many(offset, limit) board_dtos = [] for r in board_records.items: - cover_image = self._services.image_records.get_most_recent_image_for_board(r.board_id) - if (cover_image): + cover_image = self._services.image_records.get_most_recent_image_for_board( + r.board_id + ) + if cover_image: cover_image_name = cover_image.image_name else: cover_image_name = None @@ -151,3 +164,22 @@ class BoardService(BoardServiceABC): return OffsetPaginatedResults[BoardDTO]( items=board_dtos, offset=offset, limit=limit, total=len(board_dtos) ) + + def get_all(self) -> list[BoardDTO]: + board_records = self._services.board_records.get_all() + board_dtos = [] + for r in board_records: + cover_image = self._services.image_records.get_most_recent_image_for_board( + r.board_id + ) + if cover_image: + cover_image_name = cover_image.image_name + else: + cover_image_name = None + + image_count = self._services.board_image_records.get_image_count_for_board( + r.board_id + ) + board_dtos.append(board_record_to_dto(r, cover_image_name, image_count)) + + return board_dtos \ No newline at end of file diff --git a/invokeai/app/services/models/board_record.py b/invokeai/app/services/models/board_record.py index bf7a33e8b9..325ddd094e 100644 --- a/invokeai/app/services/models/board_record.py +++ b/invokeai/app/services/models/board_record.py @@ -3,6 +3,7 @@ from datetime import datetime from pydantic import BaseModel, Extra, Field, StrictBool, StrictStr from invokeai.app.util.misc import get_iso_timestamp + class BoardRecord(BaseModel): """Deserialized board record.""" From cfda128e06d87c67140c4be008eadd4c585031fe Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 00:07:24 +1000 Subject: [PATCH 41/99] feat(ui): wip boards via `rtk-query` --- .../app/contexts/AddImageToBoardContext.tsx | 17 +-- .../middleware/listenerMiddleware/index.ts | 8 + .../listeners/imageAddedToBoard.ts | 40 +++++ .../listeners/imageDeleted.ts | 21 ++- invokeai/frontend/web/src/app/store/store.ts | 3 + .../components/Boards/AddBoardButton.tsx | 21 ++- .../gallery/components/Boards/BoardsList.tsx | 26 +++- .../components/Boards/HoverableBoard.tsx | 23 +-- .../Boards/UpdateImageBoardModal.tsx | 35 +++-- .../components/CurrentImageButtons.tsx | 15 +- .../components/CurrentImagePreview.tsx | 13 +- .../gallery/components/HoverableImage.tsx | 19 +-- .../components/ImageGalleryContent.tsx | 14 +- .../ImageMetadataViewer.tsx | 16 +- .../components/NextPrevImageButtons.tsx | 18 ++- .../features/gallery/store/gallerySlice.ts | 20 +-- .../web/src/services/api/models/BoardDTO.ts | 6 +- .../services/api/services/BoardsService.ts | 24 ++- .../frontend/web/src/services/apiSlice.ts | 144 ++++++++++++++++++ .../frontend/web/src/services/thunks/board.ts | 2 +- 20 files changed, 356 insertions(+), 129 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts create mode 100644 invokeai/frontend/web/src/services/apiSlice.ts diff --git a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx index da3dcb2239..d29c1c8a48 100644 --- a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx +++ b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx @@ -3,6 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks'; import { PropsWithChildren, createContext, useCallback, useState } from 'react'; import { ImageDTO } from 'services/api'; import { imageAddedToBoard } from '../../services/thunks/board'; +import { useAddImageToBoardMutation } from 'services/apiSlice'; export type ImageUsage = { isInitialImage: boolean; @@ -43,6 +44,8 @@ export const AddImageToBoardContextProvider = (props: Props) => { const dispatch = useAppDispatch(); const { isOpen, onOpen, onClose } = useDisclosure(); + const [addImageToBoard, result] = useAddImageToBoardMutation(); + // Clean up after deleting or dismissing the modal const closeAndClearImageToDelete = useCallback(() => { setImageToMove(undefined); @@ -63,18 +66,14 @@ export const AddImageToBoardContextProvider = (props: Props) => { const handleAddToBoard = useCallback( (boardId: string) => { if (imageToMove) { - dispatch( - imageAddedToBoard({ - requestBody: { - board_id: boardId, - image_name: imageToMove.image_name, - }, - }) - ); + addImageToBoard({ + board_id: boardId, + image_name: imageToMove.image_name, + }); closeAndClearImageToDelete(); } }, - [closeAndClearImageToDelete, dispatch, imageToMove] + [addImageToBoard, closeAndClearImageToDelete, imageToMove] ); return ( diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 8c073e81d6..15fd48fbb2 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -73,6 +73,10 @@ import { addImageCategoriesChangedListener } from './listeners/imageCategoriesCh import { addControlNetImageProcessedListener } from './listeners/controlNetImageProcessed'; import { addControlNetAutoProcessListener } from './listeners/controlNetAutoProcess'; import { addUpdateImageUrlsOnConnectListener } from './listeners/updateImageUrlsOnConnect'; +import { + addImageAddedToBoardFulfilledListener, + addImageAddedToBoardRejectedListener, +} from './listeners/imageAddedToBoard'; export const listenerMiddleware = createListenerMiddleware(); @@ -183,3 +187,7 @@ addControlNetAutoProcessListener(); // Update image URLs on connect addUpdateImageUrlsOnConnectListener(); + +// Boards +addImageAddedToBoardFulfilledListener(); +addImageAddedToBoardRejectedListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts new file mode 100644 index 0000000000..0f404cab68 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts @@ -0,0 +1,40 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { imageMetadataReceived } from 'services/thunks/image'; +import { api } from 'services/apiSlice'; + +const moduleLog = log.child({ namespace: 'boards' }); + +export const addImageAddedToBoardFulfilledListener = () => { + startAppListening({ + matcher: api.endpoints.addImageToBoard.matchFulfilled, + effect: (action, { getState, dispatch }) => { + const { board_id, image_name } = action.meta.arg.originalArgs; + + moduleLog.debug( + { data: { board_id, image_name } }, + 'Image added to board' + ); + + dispatch( + imageMetadataReceived({ + imageName: image_name, + }) + ); + }, + }); +}; + +export const addImageAddedToBoardRejectedListener = () => { + startAppListening({ + matcher: api.endpoints.addImageToBoard.matchRejected, + effect: (action, { getState, dispatch }) => { + const { board_id, image_name } = action.meta.arg.originalArgs; + + moduleLog.debug( + { data: { board_id, image_name } }, + 'Problem adding image to board' + ); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts index 4c0c057242..9792137bbe 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts @@ -13,6 +13,7 @@ import { resetCanvas } from 'features/canvas/store/canvasSlice'; import { controlNetReset } from 'features/controlNet/store/controlNetSlice'; import { clearInitialImage } from 'features/parameters/store/generationSlice'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; +import { api } from 'services/apiSlice'; const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' }); @@ -22,7 +23,7 @@ const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' }); export const addRequestedImageDeletionListener = () => { startAppListening({ actionCreator: requestedImageDeletion, - effect: (action, { dispatch, getState }) => { + effect: async (action, { dispatch, getState, condition }) => { const { image, imageUsage } = action.payload; const { image_name } = image; @@ -30,7 +31,7 @@ export const addRequestedImageDeletionListener = () => { const state = getState(); const selectedImage = state.gallery.selectedImage; - if (selectedImage && selectedImage.image_name === image_name) { + if (selectedImage && selectedImage === image_name) { const ids = selectImagesIds(state); const entities = selectImagesEntities(state); @@ -51,7 +52,7 @@ export const addRequestedImageDeletionListener = () => { const newSelectedImage = entities[newSelectedImageId]; if (newSelectedImageId) { - dispatch(imageSelected(newSelectedImage)); + dispatch(imageSelected(newSelectedImageId)); } else { dispatch(imageSelected()); } @@ -79,7 +80,19 @@ export const addRequestedImageDeletionListener = () => { dispatch(imageRemoved(image_name)); // Delete from server - dispatch(imageDeleted({ imageName: image_name })); + const { requestId } = dispatch(imageDeleted({ imageName: image_name })); + + // Wait for successful deletion, then trigger boards to re-fetch + const wasImageDeleted = await condition( + (action) => action.meta.requestId === requestId, + 30000 + ); + + if (wasImageDeleted) { + dispatch( + api.util.invalidateTags([{ type: 'Board', id: image.board_id }]) + ); + } }, }); }; diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 4032db3159..a9011f9356 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -33,6 +33,7 @@ import { actionsDenylist } from './middleware/devtools/actionsDenylist'; import { serialize } from './enhancers/reduxRemember/serialize'; import { unserialize } from './enhancers/reduxRemember/unserialize'; import { LOCALSTORAGE_PREFIX } from './constants'; +import { api } from 'services/apiSlice'; const allReducers = { canvas: canvasReducer, @@ -49,6 +50,7 @@ const allReducers = { images: imagesReducer, controlNet: controlNetReducer, boards: boardsReducer, + [api.reducerPath]: api.reducer, // session: sessionReducer, }; @@ -87,6 +89,7 @@ export const store = configureStore({ immutableCheck: false, serializableCheck: false, }) + .concat(api.middleware) .concat(dynamicMiddlewares) .prepend(listenerMiddleware.middleware), devTools: { diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx index d8828fe736..284e6558ac 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx @@ -1,19 +1,20 @@ -import { Flex, Icon, Text } from '@chakra-ui/react'; +import { Flex, Icon, Spinner, Text } from '@chakra-ui/react'; import { useCallback } from 'react'; import { FaPlus } from 'react-icons/fa'; -import { useAppDispatch } from '../../../../app/store/storeHooks'; -import { boardCreated } from '../../../../services/thunks/board'; +import { useCreateBoardMutation } from 'services/apiSlice'; + +const DEFAULT_BOARD_NAME = 'My Board'; const AddBoardButton = () => { - const dispatch = useAppDispatch(); + const [createBoard, { isLoading }] = useCreateBoardMutation(); const handleCreateBoard = useCallback(() => { - dispatch(boardCreated({ requestBody: 'My Board' })); - }, [dispatch]); + createBoard(DEFAULT_BOARD_NAME); + }, [createBoard]); return ( { aspectRatio: '1/1', }} > - + {isLoading ? ( + + ) : ( + + )} New Board diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index 1f84d3be0e..be849e625e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -25,6 +25,7 @@ import { searchBoardsSelector } from '../../store/boardSelectors'; import { useSelector } from 'react-redux'; import IAICollapse from '../../../../common/components/IAICollapse'; import { CloseIcon } from '@chakra-ui/icons'; +import { useListBoardsQuery } from 'services/apiSlice'; const selector = createSelector( [selectBoardsAll, boardsSelector], @@ -40,9 +41,17 @@ const selector = createSelector( const BoardsList = () => { const dispatch = useAppDispatch(); const { selectedBoard, searchText } = useAppSelector(selector); - const filteredBoards = useSelector(searchBoardsSelector); + // const filteredBoards = useSelector(searchBoardsSelector); const { isOpen, onToggle } = useDisclosure(); + const { data } = useListBoardsQuery({ offset: 0, limit: 8 }); + + const filteredBoards = searchText + ? data?.items.filter((board) => + board.board_name.toLowerCase().includes(searchText.toLowerCase()) + ) + : data.items; + const [searchMode, setSearchMode] = useState(false); const handleBoardSearch = (searchTerm: string) => { @@ -100,13 +109,14 @@ const BoardsList = () => { )} - {filteredBoards.map((board) => ( - - ))} + {filteredBoards && + filteredBoards.map((board) => ( + + ))} diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index fdde7528cb..7ae864f55b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -26,6 +26,10 @@ import IAIDndImage from '../../../../common/components/IAIDndImage'; import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions'; import { createSelector } from '@reduxjs/toolkit'; import { RootState } from '../../../../app/store/store'; +import { + useDeleteBoardMutation, + useUpdateBoardMutation, +} from 'services/apiSlice'; const coverImageSelector = (imageName: string | undefined) => createSelector( @@ -59,19 +63,20 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { dispatch(boardIdSelected(board_id)); }, [board_id, dispatch]); - const handleDeleteBoard = useCallback(() => { - dispatch(boardDeleted(board_id)); - }, [board_id, dispatch]); + const [updateBoard, { isLoading: isUpdateBoardLoading }] = + useUpdateBoardMutation(); + + const [deleteBoard, { isLoading: isDeleteBoardLoading }] = + useDeleteBoardMutation(); const handleUpdateBoardName = (newBoardName: string) => { - dispatch( - boardUpdated({ - boardId: board_id, - requestBody: { board_name: newBoardName }, - }) - ); + updateBoard({ board_id, changes: { board_name: newBoardName } }); }; + const handleDeleteBoard = useCallback(() => { + deleteBoard(board_id); + }, [board_id, deleteBoard]); + const handleDrop = useCallback( (droppedImage: ImageDTO) => { if (droppedImage.board_id === board_id) { diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx index 9136e23e03..edd4d215af 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx @@ -9,6 +9,7 @@ import { Divider, Flex, Select, + Spinner, Text, } from '@chakra-ui/react'; import IAIButton from 'common/components/IAIButton'; @@ -19,9 +20,11 @@ import { useSelector } from 'react-redux'; import { selectBoardsAll } from '../../store/boardSlice'; import IAISelect from '../../../../common/components/IAISelect'; import IAIMantineSelect from 'common/components/IAIMantineSelect'; +import { useListAllBoardsQuery } from 'services/apiSlice'; const UpdateImageBoardModal = () => { - const boards = useSelector(selectBoardsAll); + // const boards = useSelector(selectBoardsAll); + const { data: boards, isFetching } = useListAllBoardsQuery(); const { isOpen, onClose, handleAddToBoard, image } = useContext( AddImageToBoardContext ); @@ -29,9 +32,9 @@ const UpdateImageBoardModal = () => { const cancelRef = useRef(null); - const currentBoard = boards.filter( + const currentBoard = boards?.find( (board) => board.board_id === image?.board_id - )[0]; + ); return ( { {currentBoard.board_name} to )} - setSelectedBoard(v)} - value={selectedBoard} - data={boards.map((board) => ({ - label: board.board_name, - value: board.board_id, - }))} - /> + {isFetching ? ( + + ) : ( + setSelectedBoard(v)} + value={selectedBoard} + data={(boards ?? []).map((board) => ({ + label: board.board_name, + value: board.board_id, + }))} + /> + )} @@ -73,7 +80,9 @@ const UpdateImageBoardModal = () => { isDisabled={!selectedBoard} colorScheme="accent" onClick={() => { - if (selectedBoard) handleAddToBoard(selectedBoard); + if (selectedBoard) { + handleAddToBoard(selectedBoard); + } }} ml={3} > diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index a5eaeb4c71..169a965be0 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -51,9 +51,12 @@ import { useAppToaster } from 'app/components/Toaster'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { DeleteImageContext } from 'app/contexts/DeleteImageContext'; import { DeleteImageButton } from './DeleteImageModal'; +import { selectImagesById } from '../store/imagesSlice'; +import { RootState } from 'app/store/store'; const currentImageButtonsSelector = createSelector( [ + (state: RootState) => state, systemSelector, gallerySelector, postprocessingSelector, @@ -61,7 +64,7 @@ const currentImageButtonsSelector = createSelector( lightboxSelector, activeTabNameSelector, ], - (system, gallery, postprocessing, ui, lightbox, activeTabName) => { + (state, system, gallery, postprocessing, ui, lightbox, activeTabName) => { const { isProcessing, isConnected, @@ -81,6 +84,8 @@ const currentImageButtonsSelector = createSelector( shouldShowProgressInViewer, } = ui; + const imageDTO = selectImagesById(state, gallery.selectedImage ?? ''); + const { selectedImage } = gallery; return { @@ -97,10 +102,10 @@ const currentImageButtonsSelector = createSelector( activeTabName, isLightboxOpen, shouldHidePreview, - image: selectedImage, - seed: selectedImage?.metadata?.seed, - prompt: selectedImage?.metadata?.positive_conditioning, - negativePrompt: selectedImage?.metadata?.negative_conditioning, + image: imageDTO, + seed: imageDTO?.metadata?.seed, + prompt: imageDTO?.metadata?.positive_conditioning, + negativePrompt: imageDTO?.metadata?.negative_conditioning, shouldShowProgressInViewer, }; }, diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index c591206a27..649cae7682 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -15,6 +15,8 @@ import { imageSelected } from '../store/gallerySlice'; import IAIDndImage from 'common/components/IAIDndImage'; import { ImageDTO } from 'services/api'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { RootState } from 'app/store/store'; +import { selectImagesById } from '../store/imagesSlice'; export const imagesSelector = createSelector( [uiSelector, gallerySelector, systemSelector], @@ -29,7 +31,7 @@ export const imagesSelector = createSelector( return { shouldShowImageDetails, shouldHidePreview, - image: selectedImage, + selectedImage, progressImage, shouldShowProgressInViewer, shouldAntialiasProgressImage, @@ -45,11 +47,16 @@ export const imagesSelector = createSelector( const CurrentImagePreview = () => { const { shouldShowImageDetails, - image, + selectedImage, progressImage, shouldShowProgressInViewer, shouldAntialiasProgressImage, } = useAppSelector(imagesSelector); + + const image = useAppSelector((state: RootState) => + selectImagesById(state, selectedImage ?? '') + ); + const dispatch = useAppDispatch(); const handleDrop = useCallback( @@ -57,7 +64,7 @@ const CurrentImagePreview = () => { if (droppedImage.image_name === image?.image_name) { return; } - dispatch(imageSelected(droppedImage)); + dispatch(imageSelected(droppedImage.image_name)); }, [dispatch, image?.image_name] ); diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index b21c62785b..86ec3436f0 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -72,17 +72,10 @@ interface HoverableImageProps { isSelected: boolean; } -const memoEqualityCheck = ( - prev: HoverableImageProps, - next: HoverableImageProps -) => - prev.image.image_name === next.image.image_name && - prev.isSelected === next.isSelected; - /** * Gallery image component with delete/use all/use seed buttons on hover. */ -const HoverableImage = memo((props: HoverableImageProps) => { +const HoverableImage = (props: HoverableImageProps) => { const dispatch = useAppDispatch(); const { activeTabName, @@ -121,7 +114,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { const handleMouseOut = () => setIsHovered(false); const handleSelectImage = useCallback(() => { - dispatch(imageSelected(image)); + dispatch(imageSelected(image.image_name)); }, [image, dispatch]); // Recall parameters handlers @@ -260,7 +253,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { )} } onClickCapture={handleAddToBoard}> - Add to Board + {image.board_id ? 'Change Board' : 'Add to Board'} { ); -}, memoEqualityCheck); +}; -HoverableImage.displayName = 'HoverableImage'; - -export default HoverableImage; +export default memo(HoverableImage); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index adb7791afb..48bd2bde74 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -201,12 +201,6 @@ const ImageGalleryContent = () => { dispatch(setGalleryView('boards')); }, [dispatch]); - const [newBoardName, setNewBoardName] = useState(''); - - const handleCreateNewBoard = () => { - dispatch(boardCreated({ requestBody: newBoardName })); - }; - return ( { )} @@ -344,9 +336,7 @@ const ImageGalleryContent = () => { )} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx index 892516a3cc..e5cb4cf4a8 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx @@ -93,19 +93,11 @@ type ImageMetadataViewerProps = { image: ImageDTO; }; -// TODO: I don't know if this is needed. -const memoEqualityCheck = ( - prev: ImageMetadataViewerProps, - next: ImageMetadataViewerProps -) => prev.image.image_name === next.image.image_name; - -// TODO: Show more interesting information in this component. - /** * Image metadata viewer overlays currently selected image and provides * access to any of its metadata for use in processing. */ -const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => { +const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => { const dispatch = useAppDispatch(); const { recallBothPrompts, @@ -333,8 +325,6 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => { ); -}, memoEqualityCheck); +}; -ImageMetadataViewer.displayName = 'ImageMetadataViewer'; - -export default ImageMetadataViewer; +export default memo(ImageMetadataViewer); diff --git a/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx index 82e7a0d623..b1f06ad433 100644 --- a/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx @@ -42,7 +42,7 @@ export const nextPrevImageButtonsSelector = createSelector( } const currentImageIndex = filteredImageIds.findIndex( - (i) => i === selectedImage.image_name + (i) => i === selectedImage ); const nextImageIndex = clamp( @@ -71,6 +71,8 @@ export const nextPrevImageButtonsSelector = createSelector( !isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1, nextImage, prevImage, + nextImageId, + prevImageId, }; }, { @@ -84,7 +86,7 @@ const NextPrevImageButtons = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const { isOnFirstImage, isOnLastImage, nextImage, prevImage } = + const { isOnFirstImage, isOnLastImage, nextImageId, prevImageId } = useAppSelector(nextPrevImageButtonsSelector); const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] = @@ -99,19 +101,19 @@ const NextPrevImageButtons = () => { }, []); const handlePrevImage = useCallback(() => { - dispatch(imageSelected(prevImage)); - }, [dispatch, prevImage]); + dispatch(imageSelected(prevImageId)); + }, [dispatch, prevImageId]); const handleNextImage = useCallback(() => { - dispatch(imageSelected(nextImage)); - }, [dispatch, nextImage]); + dispatch(imageSelected(nextImageId)); + }, [dispatch, nextImageId]); useHotkeys( 'left', () => { handlePrevImage(); }, - [prevImage] + [prevImageId] ); useHotkeys( @@ -119,7 +121,7 @@ const NextPrevImageButtons = () => { () => { handleNextImage(); }, - [nextImage] + [nextImageId] ); return ( diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index a8237a711d..b07ab487ae 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -7,7 +7,7 @@ import { imageUrlsReceived } from 'services/thunks/image'; type GalleryImageObjectFitType = 'contain' | 'cover'; export interface GalleryState { - selectedImage?: ImageDTO; + selectedImage?: string; galleryImageMinimumWidth: number; galleryImageObjectFit: GalleryImageObjectFitType; shouldAutoSwitchToNewImages: boolean; @@ -27,7 +27,7 @@ export const gallerySlice = createSlice({ name: 'gallery', initialState: initialGalleryState, reducers: { - imageSelected: (state, action: PayloadAction) => { + imageSelected: (state, action: PayloadAction) => { state.selectedImage = action.payload; // TODO: if the user selects an image, disable the auto switch? // state.shouldAutoSwitchToNewImages = false; @@ -63,17 +63,17 @@ export const gallerySlice = createSlice({ state.shouldAutoSwitchToNewImages && action.payload.image_category === 'general' ) { - state.selectedImage = action.payload; + state.selectedImage = action.payload.image_name; } }); - builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - const { image_name, image_url, thumbnail_url } = action.payload; + // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { + // const { image_name, image_url, thumbnail_url } = action.payload; - if (state.selectedImage?.image_name === image_name) { - state.selectedImage.image_url = image_url; - state.selectedImage.thumbnail_url = thumbnail_url; - } - }); + // if (state.selectedImage?.image_name === image_name) { + // state.selectedImage.image_url = image_url; + // state.selectedImage.thumbnail_url = thumbnail_url; + // } + // }); }, }); diff --git a/invokeai/frontend/web/src/services/api/models/BoardDTO.ts b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts index 1b72f452ac..ee3c29a797 100644 --- a/invokeai/frontend/web/src/services/api/models/BoardDTO.ts +++ b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts @@ -23,13 +23,9 @@ export type BoardDTO = { */ updated_at: string; /** - * The name of the cover image of the board. + * The name of the board's cover image. */ cover_image_name?: string; - /** - * The URL of the thumbnail of the board's cover image. - */ - cover_image_url?: string; /** * The number of images in the board. */ diff --git a/invokeai/frontend/web/src/services/api/services/BoardsService.ts b/invokeai/frontend/web/src/services/api/services/BoardsService.ts index 9108e3fd51..bda2b70e75 100644 --- a/invokeai/frontend/web/src/services/api/services/BoardsService.ts +++ b/invokeai/frontend/web/src/services/api/services/BoardsService.ts @@ -17,13 +17,18 @@ export class BoardsService { /** * List Boards * Gets a list of boards - * @returns OffsetPaginatedResults_BoardDTO_ Successful Response + * @returns any Successful Response * @throws ApiError */ public static listBoards({ + all, offset, - limit = 10, + limit, }: { + /** + * Whether to list all boards + */ + all?: boolean, /** * The page offset */ @@ -32,11 +37,12 @@ export class BoardsService { * The number of boards per page */ limit?: number, - }): CancelablePromise { + }): CancelablePromise<(OffsetPaginatedResults_BoardDTO_ | Array)> { return __request(OpenAPI, { method: 'GET', url: '/api/v1/boards/', query: { + 'all': all, 'offset': offset, 'limit': limit, }, @@ -53,15 +59,19 @@ export class BoardsService { * @throws ApiError */ public static createBoard({ - requestBody, + boardName, }: { - requestBody: string, + /** + * The name of the board to create + */ + boardName: string, }): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/boards/', - body: requestBody, - mediaType: 'application/json', + query: { + 'board_name': boardName, + }, errors: { 422: `Validation Error`, }, diff --git a/invokeai/frontend/web/src/services/apiSlice.ts b/invokeai/frontend/web/src/services/apiSlice.ts new file mode 100644 index 0000000000..09eb061e29 --- /dev/null +++ b/invokeai/frontend/web/src/services/apiSlice.ts @@ -0,0 +1,144 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { BoardDTO } from './api/models/BoardDTO'; +import { OffsetPaginatedResults_BoardDTO_ } from './api/models/OffsetPaginatedResults_BoardDTO_'; +import { BoardChanges } from './api/models/BoardChanges'; +import { OffsetPaginatedResults_ImageDTO_ } from './api/models/OffsetPaginatedResults_ImageDTO_'; + +type ListBoardsArg = { offset: number; limit: number }; +type UpdateBoardArg = { board_id: string; changes: BoardChanges }; +type AddImageToBoardArg = { board_id: string; image_name: string }; +type RemoveImageFromBoardArg = { board_id: string; image_name: string }; +type ListBoardImagesArg = { board_id: string; offset: number; limit: number }; + +export const api = createApi({ + baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5173/api/v1/' }), + reducerPath: 'api', + tagTypes: ['Board'], + endpoints: (build) => ({ + /** + * Boards Queries + */ + listBoards: build.query({ + query: (arg) => ({ url: 'boards/', params: arg }), + providesTags: (result, error, arg) => { + if (!result) { + // Provide the broad 'Board' tag until there is a response + return ['Board']; + } + + // Provide the broad 'Board' tab, and individual tags for each board + return [ + ...result.items.map(({ board_id }) => ({ + type: 'Board' as const, + id: board_id, + })), + 'Board', + ]; + }, + }), + + listAllBoards: build.query, void>({ + query: () => ({ + url: 'boards/', + params: { all: true }, + }), + providesTags: (result, error, arg) => { + if (!result) { + // Provide the broad 'Board' tag until there is a response + return ['Board']; + } + + // Provide the broad 'Board' tab, and individual tags for each board + return [ + ...result.map(({ board_id }) => ({ + type: 'Board' as const, + id: board_id, + })), + 'Board', + ]; + }, + }), + + /** + * Boards Mutations + */ + + createBoard: build.mutation({ + query: (board_name) => ({ + url: `boards/`, + method: 'POST', + params: { board_name }, + }), + invalidatesTags: ['Board'], + }), + + updateBoard: build.mutation({ + query: ({ board_id, changes }) => ({ + url: `boards/${board_id}`, + method: 'PATCH', + body: changes, + }), + invalidatesTags: (result, error, arg) => [ + { type: 'Board', id: arg.board_id }, + ], + }), + + deleteBoard: build.mutation({ + query: (board_id) => ({ url: `boards/${board_id}`, method: 'DELETE' }), + invalidatesTags: (result, error, arg) => [{ type: 'Board', id: arg }], + }), + + /** + * Board Images Queries + */ + + listBoardImages: build.query< + OffsetPaginatedResults_ImageDTO_, + ListBoardImagesArg + >({ + query: ({ board_id, offset, limit }) => ({ + url: `board_images/${board_id}`, + method: 'DELETE', + body: { offset, limit }, + }), + }), + + /** + * Board Images Mutations + */ + + addImageToBoard: build.mutation({ + query: ({ board_id, image_name }) => ({ + url: `board_images/`, + method: 'POST', + body: { board_id, image_name }, + }), + invalidatesTags: ['Board'], + // invalidatesTags: (result, error, arg) => [ + // { type: 'Board', id: arg.board_id }, + // ], + }), + + removeImageFromBoard: build.mutation({ + query: ({ board_id, image_name }) => ({ + url: `board_images/`, + method: 'DELETE', + body: { board_id, image_name }, + }), + invalidatesTags: (result, error, arg) => [ + { type: 'Board', id: arg.board_id }, + ], + }), + }), +}); + +export const { + useListBoardsQuery, + useListAllBoardsQuery, + useCreateBoardMutation, + useUpdateBoardMutation, + useDeleteBoardMutation, + useAddImageToBoardMutation, + useRemoveImageFromBoardMutation, + useListBoardImagesQuery, +} = api; diff --git a/invokeai/frontend/web/src/services/thunks/board.ts b/invokeai/frontend/web/src/services/thunks/board.ts index 4535081e47..03c59dba10 100644 --- a/invokeai/frontend/web/src/services/thunks/board.ts +++ b/invokeai/frontend/web/src/services/thunks/board.ts @@ -42,7 +42,7 @@ export const boardUpdated = createAppAsyncThunk( type ImageAddedToBoardArg = Parameters< (typeof BoardsService)['createBoardImage'] ->[0]; +>[0]['requestBody']; export const imageAddedToBoard = createAppAsyncThunk( 'api/imageAddedToBoard', From 8d3bec57d52cadf1c4037d788d2a1bdb2e13d982 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:48:59 +1000 Subject: [PATCH 42/99] feat(ui): store only image name in parameters Images that are used as parameters (e.g. init image, canvas images) are stored as full `ImageDTO` objects in state, separate from and duplicating any object representing those same objects in the `imagesSlice`. We cannot store only image names as parameters, then pull the full `ImageDTO` from `imagesSlice`, because if an image is not on a loaded page, it doesn't exist in `imagesSlice`. For example, if you scroll down a few pages in the gallery and send that image to canvas, on reloading the app, the canvas will be unable to load that image. We solved this temporarily by storing the full `ImageDTO` object wherever it was needed, but this is both inefficient and allows for stale `ImageDTO`s across the app. One other possible solution was to just fetch the `ImageDTO` for all images at startup, and insert them into the `imagesSlice`, but then we run into an issue where we are displaying images in the gallery totally out of context. For example, if an image from several pages into the gallery was sent to canvas, and the user refreshes, we'd display the first 20 images in gallery. Then to populate the canvas, we'd fetch that image we sent to canvas and add it to `imagesSlice`. Now we'd have 21 images in the gallery: 1 to 20 and whichever image we sent to canvas. Weird. Using `rtk-query` solves this by allowing us to very easily fetch individual images in the components that need them, and not directly interact with `imagesSlice`. This commit changes all references to images-as-parameters to store only the name of the image, and not the full `ImageDTO` object. Then, we use an `rtk-query` generated `useGetImageDTOQuery()` hook in each of those components to fetch the image. We can use cache invalidation when we mutate any image to trigger automated re-running of the query and all the images are automatically kept up to date. This also obviates the need for the convoluted URL fetching scheme for images that are used as parameters. The `imagesSlice` still need this handling unfortunately. --- .../src/app/contexts/DeleteImageContext.tsx | 10 +- .../listeners/controlNetImageProcessed.ts | 4 +- .../socketio/socketInvocationComplete.ts | 9 +- .../listeners/updateImageUrlsOnConnect.ts | 14 +- .../canvas/components/IAICanvasImage.tsx | 19 +- .../components/IAICanvasObjectRenderer.tsx | 9 +- .../components/IAICanvasStagingArea.tsx | 6 +- .../src/features/canvas/store/canvasSlice.ts | 38 +- .../src/features/canvas/store/canvasTypes.ts | 2 +- .../components/ControlNetImagePreview.tsx | 33 +- .../controlNet/store/controlNetSlice.ts | 40 +- .../gallery/components/Boards/BoardsList.tsx | 2 +- .../components/CurrentImagePreview.tsx | 15 +- .../fields/ImageInputFieldComponent.tsx | 17 +- .../web/src/features/nodes/types/types.ts | 2 +- .../nodes/util/addControlNetToLinearGraph.ts | 6 +- .../graphBuilders/buildImageToImageGraph.ts | 416 ++++++++++++++++++ .../nodeBuilders/buildImageToImageNode.ts | 2 +- .../ImageToImage/InitialImagePreview.tsx | 19 +- .../parameters/store/generationSlice.ts | 18 +- .../frontend/web/src/services/apiSlice.ts | 89 ++-- 21 files changed, 630 insertions(+), 140 deletions(-) create mode 100644 invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts diff --git a/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx index 8263b48114..50d80dcf28 100644 --- a/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx +++ b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx @@ -35,25 +35,23 @@ export const selectImageUsage = createSelector( (state: RootState, image_name?: string) => image_name, ], (generation, canvas, nodes, controlNet, image_name) => { - const isInitialImage = generation.initialImage?.image_name === image_name; + const isInitialImage = generation.initialImage === image_name; const isCanvasImage = canvas.layerState.objects.some( - (obj) => obj.kind === 'image' && obj.image.image_name === image_name + (obj) => obj.kind === 'image' && obj.imageName === image_name ); const isNodesImage = nodes.nodes.some((node) => { return some( node.data.inputs, - (input) => - input.type === 'image' && input.value?.image_name === image_name + (input) => input.type === 'image' && input.value === image_name ); }); const isControlNetImage = some( controlNet.controlNets, (c) => - c.controlImage?.image_name === image_name || - c.processedControlImage?.image_name === image_name + c.controlImage === image_name || c.processedControlImage === image_name ); const imageUsage: ImageUsage = { diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts index ce1b515b84..7ff9a5118c 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts @@ -34,7 +34,7 @@ export const addControlNetImageProcessedListener = () => { [controlNet.processorNode.id]: { ...controlNet.processorNode, is_intermediate: true, - image: pick(controlNet.controlImage, ['image_name']), + image: { image_name: controlNet.controlImage }, }, }, }; @@ -81,7 +81,7 @@ export const addControlNetImageProcessedListener = () => { dispatch( controlNetProcessedImageChanged({ controlNetId, - processedControlImage, + processedControlImage: processedControlImage.image_name, }) ); } diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts index 24e8eb312f..680f9c7041 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts @@ -10,6 +10,7 @@ import { sessionCanceled } from 'services/thunks/session'; import { isImageOutput } from 'services/types/guards'; import { progressImageSet } from 'features/system/store/systemSlice'; import { imageAddedToBoard } from '../../../../../../services/thunks/board'; +import { api } from 'services/apiSlice'; const moduleLog = log.child({ namespace: 'socketio' }); const nodeDenylist = ['dataURL_image']; @@ -42,11 +43,9 @@ export const addInvocationCompleteEventListener = () => { if (boardIdToAddTo) { dispatch( - imageAddedToBoard({ - requestBody: { - board_id: boardIdToAddTo, - image_name, - }, + api.endpoints.addImageToBoard.initiate({ + board_id: boardIdToAddTo, + image_name, }) ); } diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts index 7cb8012848..22182833b0 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts @@ -22,7 +22,7 @@ const selectAllUsedImages = createSelector( selectImagesEntities, ], (generation, canvas, nodes, controlNet, imageEntities) => { - const allUsedImages: ImageDTO[] = []; + const allUsedImages: string[] = []; if (generation.initialImage) { allUsedImages.push(generation.initialImage); @@ -30,30 +30,30 @@ const selectAllUsedImages = createSelector( canvas.layerState.objects.forEach((obj) => { if (obj.kind === 'image') { - allUsedImages.push(obj.image); + allUsedImages.push(obj.image.image_name); } }); nodes.nodes.forEach((node) => { forEach(node.data.inputs, (input) => { if (input.type === 'image' && input.value) { - allUsedImages.push(input.value); + allUsedImages.push(input.value.image_name); } }); }); forEach(controlNet.controlNets, (c) => { if (c.controlImage) { - allUsedImages.push(c.controlImage); + allUsedImages.push(c.controlImage.image_name); } if (c.processedControlImage) { - allUsedImages.push(c.processedControlImage); + allUsedImages.push(c.processedControlImage.image_name); } }); forEach(imageEntities, (image) => { if (image) { - allUsedImages.push(image); + allUsedImages.push(image.image_name); } }); @@ -80,7 +80,7 @@ export const addUpdateImageUrlsOnConnectListener = () => { `Fetching new image URLs for ${allUsedImages.length} images` ); - allUsedImages.forEach(({ image_name }) => { + allUsedImages.forEach((image_name) => { dispatch( imageUrlsReceived({ imageName: image_name, diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasImage.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasImage.tsx index b8757eff0c..c3132f0285 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasImage.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasImage.tsx @@ -1,14 +1,21 @@ -import { Image } from 'react-konva'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; +import { Image, Rect } from 'react-konva'; +import { useGetImageDTOQuery } from 'services/apiSlice'; import useImage from 'use-image'; +import { CanvasImage } from '../store/canvasTypes'; type IAICanvasImageProps = { - url: string; - x: number; - y: number; + canvasImage: CanvasImage; }; const IAICanvasImage = (props: IAICanvasImageProps) => { - const { url, x, y } = props; - const [image] = useImage(url, 'anonymous'); + const { width, height, x, y, imageName } = props.canvasImage; + const { data: imageDTO } = useGetImageDTOQuery(imageName ?? skipToken); + const [image] = useImage(imageDTO?.image_url ?? '', 'anonymous'); + + if (!imageDTO) { + return ; + } + return ; }; diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasObjectRenderer.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasObjectRenderer.tsx index ea04aa95c8..ec1e87cca7 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasObjectRenderer.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasObjectRenderer.tsx @@ -39,14 +39,7 @@ const IAICanvasObjectRenderer = () => { {objects.map((obj, i) => { if (isCanvasBaseImage(obj)) { - return ( - - ); + return ; } else if (isCanvasBaseLine(obj)) { const line = ( { return ( {shouldShowStagingImage && currentStagingAreaImage && ( - + )} {shouldShowStagingOutline && ( diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index b7092bf7e0..3e40c1211d 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -203,7 +203,7 @@ export const canvasSlice = createSlice({ y: 0, width: width, height: height, - image: image, + imageName: image.image_name, }, ], }; @@ -325,7 +325,7 @@ export const canvasSlice = createSlice({ kind: 'image', layer: 'base', ...state.layerState.stagingArea.boundingBox, - image, + imageName: image.image_name, }); state.layerState.stagingArea.selectedImageIndex = @@ -865,25 +865,25 @@ export const canvasSlice = createSlice({ state.doesCanvasNeedScaling = true; }); - builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - const { image_name, image_url, thumbnail_url } = action.payload; + // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { + // const { image_name, image_url, thumbnail_url } = action.payload; - state.layerState.objects.forEach((object) => { - if (object.kind === 'image') { - if (object.image.image_name === image_name) { - object.image.image_url = image_url; - object.image.thumbnail_url = thumbnail_url; - } - } - }); + // state.layerState.objects.forEach((object) => { + // if (object.kind === 'image') { + // if (object.image.image_name === image_name) { + // object.image.image_url = image_url; + // object.image.thumbnail_url = thumbnail_url; + // } + // } + // }); - state.layerState.stagingArea.images.forEach((stagedImage) => { - if (stagedImage.image.image_name === image_name) { - stagedImage.image.image_url = image_url; - stagedImage.image.thumbnail_url = thumbnail_url; - } - }); - }); + // state.layerState.stagingArea.images.forEach((stagedImage) => { + // if (stagedImage.image.image_name === image_name) { + // stagedImage.image.image_url = image_url; + // stagedImage.image.thumbnail_url = thumbnail_url; + // } + // }); + // }); }, }); diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts index ae78287a7b..9294e10d32 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts @@ -38,7 +38,7 @@ export type CanvasImage = { y: number; width: number; height: number; - image: ImageDTO; + imageName: string; }; export type CanvasMaskLine = { diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx index b8d8896dad..a121875f59 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx @@ -14,6 +14,8 @@ import { AnimatePresence, motion } from 'framer-motion'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; import IAIIconButton from 'common/components/IAIIconButton'; import { FaUndo } from 'react-icons/fa'; +import { useGetImageDTOQuery } from 'services/apiSlice'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; const selector = createSelector( controlNetSelector, @@ -31,24 +33,45 @@ type Props = { const ControlNetImagePreview = (props: Props) => { const { imageSx } = props; - const { controlNetId, controlImage, processedControlImage, processorType } = - props.controlNet; + const { + controlNetId, + controlImage: controlImageName, + processedControlImage: processedControlImageName, + processorType, + } = props.controlNet; const dispatch = useAppDispatch(); const { pendingControlImages } = useAppSelector(selector); const [isMouseOverImage, setIsMouseOverImage] = useState(false); + const { + data: controlImage, + isLoading: isLoadingControlImage, + isError: isErrorControlImage, + isSuccess: isSuccessControlImage, + } = useGetImageDTOQuery(controlImageName ?? skipToken); + + const { + data: processedControlImage, + isLoading: isLoadingProcessedControlImage, + isError: isErrorProcessedControlImage, + isSuccess: isSuccessProcessedControlImage, + } = useGetImageDTOQuery(processedControlImageName ?? skipToken); + const handleDrop = useCallback( (droppedImage: ImageDTO) => { - if (controlImage?.image_name === droppedImage.image_name) { + if (controlImageName === droppedImage.image_name) { return; } setIsMouseOverImage(false); dispatch( - controlNetImageChanged({ controlNetId, controlImage: droppedImage }) + controlNetImageChanged({ + controlNetId, + controlImage: droppedImage.image_name, + }) ); }, - [controlImage, controlNetId, dispatch] + [controlImageName, controlNetId, dispatch] ); const handleResetControlImage = useCallback(() => { diff --git a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts index f1b62cd997..5a54bdcd74 100644 --- a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts +++ b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts @@ -39,8 +39,8 @@ export type ControlNetConfig = { weight: number; beginStepPct: number; endStepPct: number; - controlImage: ImageDTO | null; - processedControlImage: ImageDTO | null; + controlImage: string | null; + processedControlImage: string | null; processorType: ControlNetProcessorType; processorNode: RequiredControlNetProcessorNode; shouldAutoConfig: boolean; @@ -80,7 +80,7 @@ export const controlNetSlice = createSlice({ }, controlNetAddedFromImage: ( state, - action: PayloadAction<{ controlNetId: string; controlImage: ImageDTO }> + action: PayloadAction<{ controlNetId: string; controlImage: string }> ) => { const { controlNetId, controlImage } = action.payload; state.controlNets[controlNetId] = { @@ -108,7 +108,7 @@ export const controlNetSlice = createSlice({ state, action: PayloadAction<{ controlNetId: string; - controlImage: ImageDTO | null; + controlImage: string | null; }> ) => { const { controlNetId, controlImage } = action.payload; @@ -125,7 +125,7 @@ export const controlNetSlice = createSlice({ state, action: PayloadAction<{ controlNetId: string; - processedControlImage: ImageDTO | null; + processedControlImage: string | null; }> ) => { const { controlNetId, processedControlImage } = action.payload; @@ -260,30 +260,30 @@ export const controlNetSlice = createSlice({ // Preemptively remove the image from the gallery const { imageName } = action.meta.arg; forEach(state.controlNets, (c) => { - if (c.controlImage?.image_name === imageName) { + if (c.controlImage === imageName) { c.controlImage = null; c.processedControlImage = null; } - if (c.processedControlImage?.image_name === imageName) { + if (c.processedControlImage === imageName) { c.processedControlImage = null; } }); }); - builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - const { image_name, image_url, thumbnail_url } = action.payload; + // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { + // const { image_name, image_url, thumbnail_url } = action.payload; - forEach(state.controlNets, (c) => { - if (c.controlImage?.image_name === image_name) { - c.controlImage.image_url = image_url; - c.controlImage.thumbnail_url = thumbnail_url; - } - if (c.processedControlImage?.image_name === image_name) { - c.processedControlImage.image_url = image_url; - c.processedControlImage.thumbnail_url = thumbnail_url; - } - }); - }); + // forEach(state.controlNets, (c) => { + // if (c.controlImage?.image_name === image_name) { + // c.controlImage.image_url = image_url; + // c.controlImage.thumbnail_url = thumbnail_url; + // } + // if (c.processedControlImage?.image_name === image_name) { + // c.processedControlImage.image_url = image_url; + // c.processedControlImage.thumbnail_url = thumbnail_url; + // } + // }); + // }); builder.addCase(appSocketInvocationError, (state, action) => { state.pendingControlImages = []; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index be849e625e..5854c3fe7c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -50,7 +50,7 @@ const BoardsList = () => { ? data?.items.filter((board) => board.board_name.toLowerCase().includes(searchText.toLowerCase()) ) - : data.items; + : data?.items; const [searchMode, setSearchMode] = useState(false); diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index 649cae7682..bff32f1d78 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -17,6 +17,8 @@ import { ImageDTO } from 'services/api'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; import { RootState } from 'app/store/store'; import { selectImagesById } from '../store/imagesSlice'; +import { useGetImageDTOQuery } from 'services/apiSlice'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; export const imagesSelector = createSelector( [uiSelector, gallerySelector, systemSelector], @@ -53,9 +55,16 @@ const CurrentImagePreview = () => { shouldAntialiasProgressImage, } = useAppSelector(imagesSelector); - const image = useAppSelector((state: RootState) => - selectImagesById(state, selectedImage ?? '') - ); + // const image = useAppSelector((state: RootState) => + // selectImagesById(state, selectedImage ?? '') + // ); + + const { + data: image, + isLoading, + isError, + isSuccess, + } = useGetImageDTOQuery(selectedImage ?? skipToken); const dispatch = useAppDispatch(); diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx index dc4590e6ca..c5a3a1970b 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx @@ -11,6 +11,8 @@ import { FieldComponentProps } from './types'; import IAIDndImage from 'common/components/IAIDndImage'; import { ImageDTO } from 'services/api'; import { Flex } from '@chakra-ui/react'; +import { useGetImageDTOQuery } from 'services/apiSlice'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; const ImageInputFieldComponent = ( props: FieldComponentProps @@ -19,9 +21,16 @@ const ImageInputFieldComponent = ( const dispatch = useAppDispatch(); + const { + data: image, + isLoading, + isError, + isSuccess, + } = useGetImageDTOQuery(field.value ?? skipToken); + const handleDrop = useCallback( (droppedImage: ImageDTO) => { - if (field.value?.image_name === droppedImage.image_name) { + if (field.value === droppedImage.image_name) { return; } @@ -29,11 +38,11 @@ const ImageInputFieldComponent = ( fieldValueChanged({ nodeId, fieldName: field.name, - value: droppedImage, + value: droppedImage.image_name, }) ); }, - [dispatch, field.name, field.value?.image_name, nodeId] + [dispatch, field.name, field.value, nodeId] ); const handleReset = useCallback(() => { @@ -56,7 +65,7 @@ const ImageInputFieldComponent = ( }} > { + const { + positivePrompt, + negativePrompt, + model, + cfgScale: cfg_scale, + scheduler, + steps, + initialImage, + img2imgStrength: strength, + shouldFitToWidthHeight, + width, + height, + iterations, + seed, + shouldRandomizeSeed, + } = state.generation; + + if (!initialImage) { + moduleLog.error('No initial image found in state'); + throw new Error('No initial image found in state'); + } + + const graph: NonNullableGraph = { + nodes: {}, + edges: [], + }; + + // Create the positive conditioning (prompt) node + const positiveConditioningNode: CompelInvocation = { + id: POSITIVE_CONDITIONING, + type: 'compel', + prompt: positivePrompt, + model, + }; + + // Negative conditioning + const negativeConditioningNode: CompelInvocation = { + id: NEGATIVE_CONDITIONING, + type: 'compel', + prompt: negativePrompt, + model, + }; + + // This will encode the raster image to latents - but it may get its `image` from a resize node, + // so we do not set its `image` property yet + const imageToLatentsNode: ImageToLatentsInvocation = { + id: IMAGE_TO_LATENTS, + type: 'i2l', + model, + }; + + // This does the actual img2img inference + const latentsToLatentsNode: LatentsToLatentsInvocation = { + id: LATENTS_TO_LATENTS, + type: 'l2l', + cfg_scale, + model, + scheduler, + steps, + strength, + }; + + // Finally we decode the latents back to an image + const latentsToImageNode: LatentsToImageInvocation = { + id: LATENTS_TO_IMAGE, + type: 'l2i', + model, + }; + + // Add all those nodes to the graph + graph.nodes[POSITIVE_CONDITIONING] = positiveConditioningNode; + graph.nodes[NEGATIVE_CONDITIONING] = negativeConditioningNode; + graph.nodes[IMAGE_TO_LATENTS] = imageToLatentsNode; + graph.nodes[LATENTS_TO_LATENTS] = latentsToLatentsNode; + graph.nodes[LATENTS_TO_IMAGE] = latentsToImageNode; + + // Connect the prompt nodes to the imageToLatents node + graph.edges.push({ + source: { node_id: POSITIVE_CONDITIONING, field: 'conditioning' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'positive_conditioning', + }, + }); + graph.edges.push({ + source: { node_id: NEGATIVE_CONDITIONING, field: 'conditioning' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'negative_conditioning', + }, + }); + + // Connect the image-encoding node + graph.edges.push({ + source: { node_id: IMAGE_TO_LATENTS, field: 'latents' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'latents', + }, + }); + + // Connect the image-decoding node + graph.edges.push({ + source: { node_id: LATENTS_TO_LATENTS, field: 'latents' }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'latents', + }, + }); + + /** + * Now we need to handle iterations and random seeds. There are four possible scenarios: + * - Single iteration, explicit seed + * - Single iteration, random seed + * - Multiple iterations, explicit seed + * - Multiple iterations, random seed + * + * They all have different graphs and connections. + */ + + // Single iteration, explicit seed + if (!shouldRandomizeSeed && iterations === 1) { + // Noise node using the explicit seed + const noiseNode: NoiseInvocation = { + id: NOISE, + type: 'noise', + seed: seed, + }; + + graph.nodes[NOISE] = noiseNode; + + // Connect noise to l2l + graph.edges.push({ + source: { node_id: NOISE, field: 'noise' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'noise', + }, + }); + } + + // Single iteration, random seed + if (shouldRandomizeSeed && iterations === 1) { + // Random int node to generate the seed + const randomIntNode: RandomIntInvocation = { + id: RANDOM_INT, + type: 'rand_int', + }; + + // Noise node without any seed + const noiseNode: NoiseInvocation = { + id: NOISE, + type: 'noise', + }; + + graph.nodes[RANDOM_INT] = randomIntNode; + graph.nodes[NOISE] = noiseNode; + + // Connect random int to the seed of the noise node + graph.edges.push({ + source: { node_id: RANDOM_INT, field: 'a' }, + destination: { + node_id: NOISE, + field: 'seed', + }, + }); + + // Connect noise to l2l + graph.edges.push({ + source: { node_id: NOISE, field: 'noise' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'noise', + }, + }); + } + + // Multiple iterations, explicit seed + if (!shouldRandomizeSeed && iterations > 1) { + // Range of size node to generate `iterations` count of seeds - range of size generates a collection + // of ints from `start` to `start + size`. The `start` is the seed, and the `size` is the number of + // iterations. + const rangeOfSizeNode: RangeOfSizeInvocation = { + id: RANGE_OF_SIZE, + type: 'range_of_size', + start: seed, + size: iterations, + }; + + // Iterate node to iterate over the seeds generated by the range of size node + const iterateNode: IterateInvocation = { + id: ITERATE, + type: 'iterate', + }; + + // Noise node without any seed + const noiseNode: NoiseInvocation = { + id: NOISE, + type: 'noise', + }; + + // Adding to the graph + graph.nodes[RANGE_OF_SIZE] = rangeOfSizeNode; + graph.nodes[ITERATE] = iterateNode; + graph.nodes[NOISE] = noiseNode; + + // Connect range of size to iterate + graph.edges.push({ + source: { node_id: RANGE_OF_SIZE, field: 'collection' }, + destination: { + node_id: ITERATE, + field: 'collection', + }, + }); + + // Connect iterate to noise + graph.edges.push({ + source: { + node_id: ITERATE, + field: 'item', + }, + destination: { + node_id: NOISE, + field: 'seed', + }, + }); + + // Connect noise to l2l + graph.edges.push({ + source: { node_id: NOISE, field: 'noise' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'noise', + }, + }); + } + + // Multiple iterations, random seed + if (shouldRandomizeSeed && iterations > 1) { + // Random int node to generate the seed + const randomIntNode: RandomIntInvocation = { + id: RANDOM_INT, + type: 'rand_int', + }; + + // Range of size node to generate `iterations` count of seeds - range of size generates a collection + const rangeOfSizeNode: RangeOfSizeInvocation = { + id: RANGE_OF_SIZE, + type: 'range_of_size', + size: iterations, + }; + + // Iterate node to iterate over the seeds generated by the range of size node + const iterateNode: IterateInvocation = { + id: ITERATE, + type: 'iterate', + }; + + // Noise node without any seed + const noiseNode: NoiseInvocation = { + id: NOISE, + type: 'noise', + width, + height, + }; + + // Adding to the graph + graph.nodes[RANDOM_INT] = randomIntNode; + graph.nodes[RANGE_OF_SIZE] = rangeOfSizeNode; + graph.nodes[ITERATE] = iterateNode; + graph.nodes[NOISE] = noiseNode; + + // Connect random int to the start of the range of size so the range starts on the random first seed + graph.edges.push({ + source: { node_id: RANDOM_INT, field: 'a' }, + destination: { node_id: RANGE_OF_SIZE, field: 'start' }, + }); + + // Connect range of size to iterate + graph.edges.push({ + source: { node_id: RANGE_OF_SIZE, field: 'collection' }, + destination: { + node_id: ITERATE, + field: 'collection', + }, + }); + + // Connect iterate to noise + graph.edges.push({ + source: { + node_id: ITERATE, + field: 'item', + }, + destination: { + node_id: NOISE, + field: 'seed', + }, + }); + + // Connect noise to l2l + graph.edges.push({ + source: { node_id: NOISE, field: 'noise' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'noise', + }, + }); + } + + if ( + shouldFitToWidthHeight && + (initialImage.width !== width || initialImage.height !== height) + ) { + // The init image needs to be resized to the specified width and height before being passed to `IMAGE_TO_LATENTS` + + // Create a resize node, explicitly setting its image + const resizeNode: ImageResizeInvocation = { + id: RESIZE, + type: 'img_resize', + image: { + image_name: initialImage, + }, + is_intermediate: true, + height, + width, + }; + + graph.nodes[RESIZE] = resizeNode; + + // The `RESIZE` node then passes its image to `IMAGE_TO_LATENTS` + graph.edges.push({ + source: { node_id: RESIZE, field: 'image' }, + destination: { + node_id: IMAGE_TO_LATENTS, + field: 'image', + }, + }); + + // The `RESIZE` node also passes its width and height to `NOISE` + graph.edges.push({ + source: { node_id: RESIZE, field: 'width' }, + destination: { + node_id: NOISE, + field: 'width', + }, + }); + + graph.edges.push({ + source: { node_id: RESIZE, field: 'height' }, + destination: { + node_id: NOISE, + field: 'height', + }, + }); + } else { + // We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly + set(graph.nodes[IMAGE_TO_LATENTS], 'image', { + image_name: initialImage, + }); + + // Pass the image's dimensions to the `NOISE` node + graph.edges.push({ + source: { node_id: IMAGE_TO_LATENTS, field: 'width' }, + destination: { + node_id: NOISE, + field: 'width', + }, + }); + graph.edges.push({ + source: { node_id: IMAGE_TO_LATENTS, field: 'height' }, + destination: { + node_id: NOISE, + field: 'height', + }, + }); + } + + addControlNetToLinearGraph(graph, LATENTS_TO_LATENTS, state); + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts index e29b46af70..cc88328729 100644 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts @@ -57,7 +57,7 @@ export const buildImg2ImgNode = ( } imageToImageNode.image = { - image_name: initialImage.image_name, + image_name: initialImage, }; } diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx index fa415074e6..d1f473b833 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx @@ -11,6 +11,8 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAIDndImage from 'common/components/IAIDndImage'; import { ImageDTO } from 'services/api'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { useGetImageDTOQuery } from 'services/apiSlice'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; const selector = createSelector( [generationSelector], @@ -27,14 +29,21 @@ const InitialImagePreview = () => { const { initialImage } = useAppSelector(selector); const dispatch = useAppDispatch(); + const { + data: image, + isLoading, + isError, + isSuccess, + } = useGetImageDTOQuery(initialImage ?? skipToken); + const handleDrop = useCallback( - (droppedImage: ImageDTO) => { - if (droppedImage.image_name === initialImage?.image_name) { + ({ image_name }: ImageDTO) => { + if (image_name === initialImage) { return; } - dispatch(initialImageChanged(droppedImage)); + dispatch(initialImageChanged(image_name)); }, - [dispatch, initialImage?.image_name] + [dispatch, initialImage] ); const handleReset = useCallback(() => { @@ -53,7 +62,7 @@ const InitialImagePreview = () => { }} > } diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 961ea1b8af..001fc35138 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -24,7 +24,7 @@ export interface GenerationState { height: HeightParam; img2imgStrength: StrengthParam; infillMethod: string; - initialImage?: ImageDTO; + initialImage?: string; iterations: number; perlin: number; positivePrompt: PositivePromptParam; @@ -211,7 +211,7 @@ export const generationSlice = createSlice({ setShouldUseNoiseSettings: (state, action: PayloadAction) => { state.shouldUseNoiseSettings = action.payload; }, - initialImageChanged: (state, action: PayloadAction) => { + initialImageChanged: (state, action: PayloadAction) => { state.initialImage = action.payload; }, modelSelected: (state, action: PayloadAction) => { @@ -233,14 +233,14 @@ export const generationSlice = createSlice({ } }); - builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - const { image_name, image_url, thumbnail_url } = action.payload; + // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { + // const { image_name, image_url, thumbnail_url } = action.payload; - if (state.initialImage?.image_name === image_name) { - state.initialImage.image_url = image_url; - state.initialImage.thumbnail_url = thumbnail_url; - } - }); + // if (state.initialImage?.image_name === image_name) { + // state.initialImage.image_url = image_url; + // state.initialImage.thumbnail_url = thumbnail_url; + // } + // }); }, }); diff --git a/invokeai/frontend/web/src/services/apiSlice.ts b/invokeai/frontend/web/src/services/apiSlice.ts index 09eb061e29..9a1521ce5a 100644 --- a/invokeai/frontend/web/src/services/apiSlice.ts +++ b/invokeai/frontend/web/src/services/apiSlice.ts @@ -1,8 +1,18 @@ -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { + TagDescription, + createApi, + fetchBaseQuery, +} from '@reduxjs/toolkit/query/react'; import { BoardDTO } from './api/models/BoardDTO'; import { OffsetPaginatedResults_BoardDTO_ } from './api/models/OffsetPaginatedResults_BoardDTO_'; import { BoardChanges } from './api/models/BoardChanges'; import { OffsetPaginatedResults_ImageDTO_ } from './api/models/OffsetPaginatedResults_ImageDTO_'; +import { ImageDTO } from './api/models/ImageDTO'; +import { + FullTagDescription, + TagTypesFrom, + TagTypesFromApi, +} from '@reduxjs/toolkit/dist/query/endpointDefinitions'; type ListBoardsArg = { offset: number; limit: number }; type UpdateBoardArg = { board_id: string; changes: BoardChanges }; @@ -10,10 +20,15 @@ type AddImageToBoardArg = { board_id: string; image_name: string }; type RemoveImageFromBoardArg = { board_id: string; image_name: string }; type ListBoardImagesArg = { board_id: string; offset: number; limit: number }; +const tagTypes = ['Board', 'Image']; +type ApiFullTagDescription = FullTagDescription<(typeof tagTypes)[number]>; + +const LIST = 'LIST'; + export const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5173/api/v1/' }), reducerPath: 'api', - tagTypes: ['Board'], + tagTypes, endpoints: (build) => ({ /** * Boards Queries @@ -21,19 +36,20 @@ export const api = createApi({ listBoards: build.query({ query: (arg) => ({ url: 'boards/', params: arg }), providesTags: (result, error, arg) => { - if (!result) { - // Provide the broad 'Board' tag until there is a response - return ['Board']; + // any list of boards + const tags: ApiFullTagDescription[] = [{ id: 'Board', type: LIST }]; + + if (result) { + // and individual tags for each board + tags.push( + ...result.items.map(({ board_id }) => ({ + type: 'Board' as const, + id: board_id, + })) + ); } - // Provide the broad 'Board' tab, and individual tags for each board - return [ - ...result.items.map(({ board_id }) => ({ - type: 'Board' as const, - id: board_id, - })), - 'Board', - ]; + return tags; }, }), @@ -43,19 +59,20 @@ export const api = createApi({ params: { all: true }, }), providesTags: (result, error, arg) => { - if (!result) { - // Provide the broad 'Board' tag until there is a response - return ['Board']; + // any list of boards + const tags: ApiFullTagDescription[] = [{ id: 'Board', type: LIST }]; + + if (result) { + // and individual tags for each board + tags.push( + ...result.map(({ board_id }) => ({ + type: 'Board' as const, + id: board_id, + })) + ); } - // Provide the broad 'Board' tab, and individual tags for each board - return [ - ...result.map(({ board_id }) => ({ - type: 'Board' as const, - id: board_id, - })), - 'Board', - ]; + return tags; }, }), @@ -113,10 +130,10 @@ export const api = createApi({ method: 'POST', body: { board_id, image_name }, }), - invalidatesTags: ['Board'], - // invalidatesTags: (result, error, arg) => [ - // { type: 'Board', id: arg.board_id }, - // ], + invalidatesTags: (result, error, arg) => [ + { type: 'Board', id: arg.board_id }, + { type: 'Image', id: arg.image_name }, + ], }), removeImageFromBoard: build.mutation({ @@ -127,8 +144,23 @@ export const api = createApi({ }), invalidatesTags: (result, error, arg) => [ { type: 'Board', id: arg.board_id }, + { type: 'Image', id: arg.image_name }, ], }), + + /** + * Image Queries + */ + getImageDTO: build.query({ + query: (image_name) => ({ url: `images/${image_name}/metadata` }), + providesTags: (result, error, arg) => { + const tags: ApiFullTagDescription[] = [{ type: 'Image', id: arg }]; + if (result?.board_id) { + tags.push({ type: 'Board', id: result.board_id }); + } + return tags; + }, + }), }), }); @@ -141,4 +173,5 @@ export const { useAddImageToBoardMutation, useRemoveImageFromBoardMutation, useListBoardImagesQuery, + useGetImageDTOQuery, } = api; From 3e0ee838cff792def0134c879222e40b1ec3a917 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:24:30 +1000 Subject: [PATCH 43/99] fix(ui): add initial image dimensions to state We need to access the initial image dimensions during the creation of the `ImageToImage` graph to determine if we need to resize the image. Because the `initialImage` is now just an image name, we need to either store (easy) or dynamically retrieve its dimensions during graph creation (a bit less easy). Took the easiest path. May need to revise this in the future. --- .../web/src/app/contexts/DeleteImageContext.tsx | 2 +- .../listenerMiddleware/listeners/imageUploaded.ts | 7 ++++++- .../listeners/updateImageUrlsOnConnect.ts | 10 +++++----- .../nodes/util/graphBuilders/buildImageToImageGraph.ts | 4 ++-- .../nodes/util/nodeBuilders/buildImageToImageNode.ts | 2 +- .../Parameters/ImageToImage/InitialImagePreview.tsx | 8 ++++---- .../src/features/parameters/store/generationSlice.ts | 7 ++++--- 7 files changed, 23 insertions(+), 17 deletions(-) diff --git a/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx index 50d80dcf28..d01298944b 100644 --- a/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx +++ b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx @@ -35,7 +35,7 @@ export const selectImageUsage = createSelector( (state: RootState, image_name?: string) => image_name, ], (generation, canvas, nodes, controlNet, image_name) => { - const isInitialImage = generation.initialImage === image_name; + const isInitialImage = generation.initialImage?.imageName === image_name; const isCanvasImage = canvas.layerState.objects.some( (obj) => obj.kind === 'image' && obj.imageName === image_name diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts index 40ed062353..fc44d206c8 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -46,7 +46,12 @@ export const addImageUploadedFulfilledListener = () => { if (postUploadAction?.type === 'SET_CONTROLNET_IMAGE') { const { controlNetId } = postUploadAction; - dispatch(controlNetImageChanged({ controlNetId, controlImage: image })); + dispatch( + controlNetImageChanged({ + controlNetId, + controlImage: image.image_name, + }) + ); return; } diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts index 22182833b0..b9ddcea4c3 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts @@ -25,29 +25,29 @@ const selectAllUsedImages = createSelector( const allUsedImages: string[] = []; if (generation.initialImage) { - allUsedImages.push(generation.initialImage); + allUsedImages.push(generation.initialImage.imageName); } canvas.layerState.objects.forEach((obj) => { if (obj.kind === 'image') { - allUsedImages.push(obj.image.image_name); + allUsedImages.push(obj.imageName); } }); nodes.nodes.forEach((node) => { forEach(node.data.inputs, (input) => { if (input.type === 'image' && input.value) { - allUsedImages.push(input.value.image_name); + allUsedImages.push(input.value); } }); }); forEach(controlNet.controlNets, (c) => { if (c.controlImage) { - allUsedImages.push(c.controlImage.image_name); + allUsedImages.push(c.controlImage); } if (c.processedControlImage) { - allUsedImages.push(c.processedControlImage.image_name); + allUsedImages.push(c.processedControlImage); } }); diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts index efd490c292..a9c5f85462 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts @@ -353,7 +353,7 @@ export const buildImageToImageGraph = (state: RootState): Graph => { id: RESIZE, type: 'img_resize', image: { - image_name: initialImage, + image_name: initialImage.imageName, }, is_intermediate: true, height, @@ -390,7 +390,7 @@ export const buildImageToImageGraph = (state: RootState): Graph => { } else { // We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly set(graph.nodes[IMAGE_TO_LATENTS], 'image', { - image_name: initialImage, + image_name: initialImage.imageName, }); // Pass the image's dimensions to the `NOISE` node diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts index cc88328729..6ebd014876 100644 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts @@ -57,7 +57,7 @@ export const buildImg2ImgNode = ( } imageToImageNode.image = { - image_name: initialImage, + image_name: initialImage.imageName, }; } diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx index d1f473b833..e4b3a9191e 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx @@ -34,14 +34,14 @@ const InitialImagePreview = () => { isLoading, isError, isSuccess, - } = useGetImageDTOQuery(initialImage ?? skipToken); + } = useGetImageDTOQuery(initialImage?.imageName ?? skipToken); const handleDrop = useCallback( - ({ image_name }: ImageDTO) => { - if (image_name === initialImage) { + (droppedImage: ImageDTO) => { + if (droppedImage.image_name === initialImage?.imageName) { return; } - dispatch(initialImageChanged(image_name)); + dispatch(initialImageChanged(droppedImage)); }, [dispatch, initialImage] ); diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 001fc35138..2facb65f04 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -24,7 +24,7 @@ export interface GenerationState { height: HeightParam; img2imgStrength: StrengthParam; infillMethod: string; - initialImage?: string; + initialImage?: { imageName: string; width: number; height: number }; iterations: number; perlin: number; positivePrompt: PositivePromptParam; @@ -211,8 +211,9 @@ export const generationSlice = createSlice({ setShouldUseNoiseSettings: (state, action: PayloadAction) => { state.shouldUseNoiseSettings = action.payload; }, - initialImageChanged: (state, action: PayloadAction) => { - state.initialImage = action.payload; + initialImageChanged: (state, action: PayloadAction) => { + const { image_name, width, height } = action.payload; + state.initialImage = { imageName: image_name, width, height }; }, modelSelected: (state, action: PayloadAction) => { state.model = action.payload; From be3bdae8477623f3af612fbd6569931659418411 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:54:57 +1000 Subject: [PATCH 44/99] fix: resolve rebase conflicts --- invokeai/app/api/dependencies.py | 1 - .../graphBuilders/buildImageToImageGraph.ts | 416 ------------------ .../buildLinearImageToImageGraph.ts | 4 +- 3 files changed, 2 insertions(+), 419 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts diff --git a/invokeai/app/api/dependencies.py b/invokeai/app/api/dependencies.py index 60f8c1b09d..efeb778922 100644 --- a/invokeai/app/api/dependencies.py +++ b/invokeai/app/api/dependencies.py @@ -29,7 +29,6 @@ from ..services.invoker import Invoker from ..services.processor import DefaultInvocationProcessor from ..services.sqlite import SqliteItemStorage from ..services.model_manager_service import ModelManagerService -from ..services.boards import SqliteBoardStorage from .events import FastAPIEventService diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts deleted file mode 100644 index a9c5f85462..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts +++ /dev/null @@ -1,416 +0,0 @@ -import { RootState } from 'app/store/store'; -import { - CompelInvocation, - Graph, - ImageResizeInvocation, - ImageToLatentsInvocation, - IterateInvocation, - LatentsToImageInvocation, - LatentsToLatentsInvocation, - NoiseInvocation, - RandomIntInvocation, - RangeOfSizeInvocation, -} from 'services/api'; -import { NonNullableGraph } from 'features/nodes/types/types'; -import { log } from 'app/logging/useLogger'; -import { set } from 'lodash-es'; -import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; - -const moduleLog = log.child({ namespace: 'nodes' }); - -const POSITIVE_CONDITIONING = 'positive_conditioning'; -const NEGATIVE_CONDITIONING = 'negative_conditioning'; -const IMAGE_TO_LATENTS = 'image_to_latents'; -const LATENTS_TO_LATENTS = 'latents_to_latents'; -const LATENTS_TO_IMAGE = 'latents_to_image'; -const RESIZE = 'resize_image'; -const NOISE = 'noise'; -const RANDOM_INT = 'rand_int'; -const RANGE_OF_SIZE = 'range_of_size'; -const ITERATE = 'iterate'; - -/** - * Builds the Image to Image tab graph. - */ -export const buildImageToImageGraph = (state: RootState): Graph => { - const { - positivePrompt, - negativePrompt, - model, - cfgScale: cfg_scale, - scheduler, - steps, - initialImage, - img2imgStrength: strength, - shouldFitToWidthHeight, - width, - height, - iterations, - seed, - shouldRandomizeSeed, - } = state.generation; - - if (!initialImage) { - moduleLog.error('No initial image found in state'); - throw new Error('No initial image found in state'); - } - - const graph: NonNullableGraph = { - nodes: {}, - edges: [], - }; - - // Create the positive conditioning (prompt) node - const positiveConditioningNode: CompelInvocation = { - id: POSITIVE_CONDITIONING, - type: 'compel', - prompt: positivePrompt, - model, - }; - - // Negative conditioning - const negativeConditioningNode: CompelInvocation = { - id: NEGATIVE_CONDITIONING, - type: 'compel', - prompt: negativePrompt, - model, - }; - - // This will encode the raster image to latents - but it may get its `image` from a resize node, - // so we do not set its `image` property yet - const imageToLatentsNode: ImageToLatentsInvocation = { - id: IMAGE_TO_LATENTS, - type: 'i2l', - model, - }; - - // This does the actual img2img inference - const latentsToLatentsNode: LatentsToLatentsInvocation = { - id: LATENTS_TO_LATENTS, - type: 'l2l', - cfg_scale, - model, - scheduler, - steps, - strength, - }; - - // Finally we decode the latents back to an image - const latentsToImageNode: LatentsToImageInvocation = { - id: LATENTS_TO_IMAGE, - type: 'l2i', - model, - }; - - // Add all those nodes to the graph - graph.nodes[POSITIVE_CONDITIONING] = positiveConditioningNode; - graph.nodes[NEGATIVE_CONDITIONING] = negativeConditioningNode; - graph.nodes[IMAGE_TO_LATENTS] = imageToLatentsNode; - graph.nodes[LATENTS_TO_LATENTS] = latentsToLatentsNode; - graph.nodes[LATENTS_TO_IMAGE] = latentsToImageNode; - - // Connect the prompt nodes to the imageToLatents node - graph.edges.push({ - source: { node_id: POSITIVE_CONDITIONING, field: 'conditioning' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'positive_conditioning', - }, - }); - graph.edges.push({ - source: { node_id: NEGATIVE_CONDITIONING, field: 'conditioning' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'negative_conditioning', - }, - }); - - // Connect the image-encoding node - graph.edges.push({ - source: { node_id: IMAGE_TO_LATENTS, field: 'latents' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'latents', - }, - }); - - // Connect the image-decoding node - graph.edges.push({ - source: { node_id: LATENTS_TO_LATENTS, field: 'latents' }, - destination: { - node_id: LATENTS_TO_IMAGE, - field: 'latents', - }, - }); - - /** - * Now we need to handle iterations and random seeds. There are four possible scenarios: - * - Single iteration, explicit seed - * - Single iteration, random seed - * - Multiple iterations, explicit seed - * - Multiple iterations, random seed - * - * They all have different graphs and connections. - */ - - // Single iteration, explicit seed - if (!shouldRandomizeSeed && iterations === 1) { - // Noise node using the explicit seed - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - seed: seed, - }; - - graph.nodes[NOISE] = noiseNode; - - // Connect noise to l2l - graph.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'noise', - }, - }); - } - - // Single iteration, random seed - if (shouldRandomizeSeed && iterations === 1) { - // Random int node to generate the seed - const randomIntNode: RandomIntInvocation = { - id: RANDOM_INT, - type: 'rand_int', - }; - - // Noise node without any seed - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - }; - - graph.nodes[RANDOM_INT] = randomIntNode; - graph.nodes[NOISE] = noiseNode; - - // Connect random int to the seed of the noise node - graph.edges.push({ - source: { node_id: RANDOM_INT, field: 'a' }, - destination: { - node_id: NOISE, - field: 'seed', - }, - }); - - // Connect noise to l2l - graph.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'noise', - }, - }); - } - - // Multiple iterations, explicit seed - if (!shouldRandomizeSeed && iterations > 1) { - // Range of size node to generate `iterations` count of seeds - range of size generates a collection - // of ints from `start` to `start + size`. The `start` is the seed, and the `size` is the number of - // iterations. - const rangeOfSizeNode: RangeOfSizeInvocation = { - id: RANGE_OF_SIZE, - type: 'range_of_size', - start: seed, - size: iterations, - }; - - // Iterate node to iterate over the seeds generated by the range of size node - const iterateNode: IterateInvocation = { - id: ITERATE, - type: 'iterate', - }; - - // Noise node without any seed - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - }; - - // Adding to the graph - graph.nodes[RANGE_OF_SIZE] = rangeOfSizeNode; - graph.nodes[ITERATE] = iterateNode; - graph.nodes[NOISE] = noiseNode; - - // Connect range of size to iterate - graph.edges.push({ - source: { node_id: RANGE_OF_SIZE, field: 'collection' }, - destination: { - node_id: ITERATE, - field: 'collection', - }, - }); - - // Connect iterate to noise - graph.edges.push({ - source: { - node_id: ITERATE, - field: 'item', - }, - destination: { - node_id: NOISE, - field: 'seed', - }, - }); - - // Connect noise to l2l - graph.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'noise', - }, - }); - } - - // Multiple iterations, random seed - if (shouldRandomizeSeed && iterations > 1) { - // Random int node to generate the seed - const randomIntNode: RandomIntInvocation = { - id: RANDOM_INT, - type: 'rand_int', - }; - - // Range of size node to generate `iterations` count of seeds - range of size generates a collection - const rangeOfSizeNode: RangeOfSizeInvocation = { - id: RANGE_OF_SIZE, - type: 'range_of_size', - size: iterations, - }; - - // Iterate node to iterate over the seeds generated by the range of size node - const iterateNode: IterateInvocation = { - id: ITERATE, - type: 'iterate', - }; - - // Noise node without any seed - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - width, - height, - }; - - // Adding to the graph - graph.nodes[RANDOM_INT] = randomIntNode; - graph.nodes[RANGE_OF_SIZE] = rangeOfSizeNode; - graph.nodes[ITERATE] = iterateNode; - graph.nodes[NOISE] = noiseNode; - - // Connect random int to the start of the range of size so the range starts on the random first seed - graph.edges.push({ - source: { node_id: RANDOM_INT, field: 'a' }, - destination: { node_id: RANGE_OF_SIZE, field: 'start' }, - }); - - // Connect range of size to iterate - graph.edges.push({ - source: { node_id: RANGE_OF_SIZE, field: 'collection' }, - destination: { - node_id: ITERATE, - field: 'collection', - }, - }); - - // Connect iterate to noise - graph.edges.push({ - source: { - node_id: ITERATE, - field: 'item', - }, - destination: { - node_id: NOISE, - field: 'seed', - }, - }); - - // Connect noise to l2l - graph.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'noise', - }, - }); - } - - if ( - shouldFitToWidthHeight && - (initialImage.width !== width || initialImage.height !== height) - ) { - // The init image needs to be resized to the specified width and height before being passed to `IMAGE_TO_LATENTS` - - // Create a resize node, explicitly setting its image - const resizeNode: ImageResizeInvocation = { - id: RESIZE, - type: 'img_resize', - image: { - image_name: initialImage.imageName, - }, - is_intermediate: true, - height, - width, - }; - - graph.nodes[RESIZE] = resizeNode; - - // The `RESIZE` node then passes its image to `IMAGE_TO_LATENTS` - graph.edges.push({ - source: { node_id: RESIZE, field: 'image' }, - destination: { - node_id: IMAGE_TO_LATENTS, - field: 'image', - }, - }); - - // The `RESIZE` node also passes its width and height to `NOISE` - graph.edges.push({ - source: { node_id: RESIZE, field: 'width' }, - destination: { - node_id: NOISE, - field: 'width', - }, - }); - - graph.edges.push({ - source: { node_id: RESIZE, field: 'height' }, - destination: { - node_id: NOISE, - field: 'height', - }, - }); - } else { - // We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly - set(graph.nodes[IMAGE_TO_LATENTS], 'image', { - image_name: initialImage.imageName, - }); - - // Pass the image's dimensions to the `NOISE` node - graph.edges.push({ - source: { node_id: IMAGE_TO_LATENTS, field: 'width' }, - destination: { - node_id: NOISE, - field: 'width', - }, - }); - graph.edges.push({ - source: { node_id: IMAGE_TO_LATENTS, field: 'height' }, - destination: { - node_id: NOISE, - field: 'height', - }, - }); - } - - addControlNetToLinearGraph(graph, LATENTS_TO_LATENTS, state); - - return graph; -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts index 1f2c8327e0..78a6623a16 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts @@ -274,7 +274,7 @@ export const buildLinearImageToImageGraph = ( id: RESIZE, type: 'img_resize', image: { - image_name: initialImage.image_name, + image_name: initialImage.imageName, }, is_intermediate: true, width, @@ -311,7 +311,7 @@ export const buildLinearImageToImageGraph = ( } else { // We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly set(graph.nodes[IMAGE_TO_LATENTS], 'image', { - image_name: initialImage.image_name, + image_name: initialImage.imageName, }); // Pass the image's dimensions to the `NOISE` node From ac477cf5d6e44e610f9c5e5a0952fe5cb8cd17b0 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 15:10:51 +1000 Subject: [PATCH 45/99] fix(ui): improve image deletion handling --- .../listeners/imageDeleted.ts | 10 +++---- .../components/Boards/HoverableBoard.tsx | 26 ++++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts index 9792137bbe..32bfcbba07 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts @@ -6,7 +6,6 @@ import { clamp } from 'lodash-es'; import { imageSelected } from 'features/gallery/store/gallerySlice'; import { imageRemoved, - selectImagesEntities, selectImagesIds, } from 'features/gallery/store/imagesSlice'; import { resetCanvas } from 'features/canvas/store/canvasSlice'; @@ -33,7 +32,6 @@ export const addRequestedImageDeletionListener = () => { if (selectedImage && selectedImage === image_name) { const ids = selectImagesIds(state); - const entities = selectImagesEntities(state); const deletedImageIndex = ids.findIndex( (result) => result.toString() === image_name @@ -49,10 +47,8 @@ export const addRequestedImageDeletionListener = () => { const newSelectedImageId = filteredIds[newSelectedImageIndex]; - const newSelectedImage = entities[newSelectedImageId]; - if (newSelectedImageId) { - dispatch(imageSelected(newSelectedImageId)); + dispatch(imageSelected(newSelectedImageId as string)); } else { dispatch(imageSelected()); } @@ -84,7 +80,9 @@ export const addRequestedImageDeletionListener = () => { // Wait for successful deletion, then trigger boards to re-fetch const wasImageDeleted = await condition( - (action) => action.meta.requestId === requestId, + (action): action is ReturnType => + imageDeleted.fulfilled.match(action) && + action.meta.requestId === requestId, 30000 ); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index 7ae864f55b..71e080ff17 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -27,9 +27,12 @@ import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoiz import { createSelector } from '@reduxjs/toolkit'; import { RootState } from '../../../../app/store/store'; import { + useAddImageToBoardMutation, useDeleteBoardMutation, + useGetImageDTOQuery, useUpdateBoardMutation, } from 'services/apiSlice'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; const coverImageSelector = (imageName: string | undefined) => createSelector( @@ -53,8 +56,9 @@ interface HoverableBoardProps { const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { const dispatch = useAppDispatch(); - const { coverImage } = useAppSelector( - coverImageSelector(board?.cover_image_name) + + const { data: coverImage } = useGetImageDTOQuery( + board.cover_image_name ?? skipToken ); const { board_name, board_id } = board; @@ -69,6 +73,9 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { const [deleteBoard, { isLoading: isDeleteBoardLoading }] = useDeleteBoardMutation(); + const [addImageToBoard, { isLoading: isAddImageToBoardLoading }] = + useAddImageToBoardMutation(); + const handleUpdateBoardName = (newBoardName: string) => { updateBoard({ board_id, changes: { board_name: newBoardName } }); }; @@ -82,16 +89,9 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { if (droppedImage.board_id === board_id) { return; } - dispatch( - imageAddedToBoard({ - requestBody: { - board_id, - image_name: droppedImage.image_name, - }, - }) - ); + addImageToBoard({ board_id, image_name: droppedImage.image_name }); }, - [board_id, dispatch] + [addImageToBoard, board_id] ); return ( @@ -141,7 +141,9 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { }} > } isUploadDisabled={true} From 2489d5459fbf8b8400f445b6315322ae08df9fee Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 15:51:14 +1000 Subject: [PATCH 46/99] chore(ui): regen api client --- .../listenerMiddleware/listeners/socketio/socketConnected.ts | 3 --- invokeai/frontend/web/src/services/api/index.ts | 1 + invokeai/frontend/web/src/services/api/models/Graph.ts | 2 +- .../web/src/services/api/models/GraphExecutionState.ts | 2 +- invokeai/frontend/web/src/services/api/models/ModelsList.ts | 2 +- .../frontend/web/src/services/api/services/SessionsService.ts | 4 ++-- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index 5bb02f60fa..3049d2c933 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -4,7 +4,6 @@ import { appSocketConnected, socketConnected } from 'services/events/actions'; import { receivedPageOfImages } from 'services/thunks/image'; import { receivedModels } from 'services/thunks/model'; import { receivedOpenAPISchema } from 'services/thunks/schema'; -import { receivedBoards } from '../../../../../../services/thunks/board'; const moduleLog = log.child({ namespace: 'socketio' }); @@ -20,8 +19,6 @@ export const addSocketConnectedEventListener = () => { const { disabledTabs } = config; - dispatch(receivedBoards()); - if (!images.ids.length) { dispatch(receivedPageOfImages()); } diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index ef62245553..acb7a411f7 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -7,6 +7,7 @@ export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; export type { AddInvocation } from './models/AddInvocation'; +export type { BaseModelType } from './models/BaseModelType'; export type { BoardChanges } from './models/BoardChanges'; export type { BoardDTO } from './models/BoardDTO'; export type { Body_create_board_image } from './models/Body_create_board_image'; diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index 0a724e2724..e148954f16 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -73,7 +73,7 @@ export type Graph = { /** * The nodes in this graph */ - nodes?: Record; + nodes?: Record; /** * The connections between nodes and their fields in this graph */ diff --git a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts index 156cdc6092..602e7a2ebc 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts @@ -48,7 +48,7 @@ export type GraphExecutionState = { /** * The results of node executions */ - results: Record; + results: Record; /** * Errors raised when executing nodes */ diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index a2d88d1967..40c2ebb1b9 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -12,6 +12,6 @@ import type { invokeai__backend__model_management__models__textual_inversion__Te import type { invokeai__backend__model_management__models__vae__VaeModel__Config } from './invokeai__backend__model_management__models__vae__VaeModel__Config'; export type ModelsList = { - models: Record>>; + models: Record>>; }; diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index 6e9ce83aaf..2e4a83b25f 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -175,7 +175,7 @@ export class SessionsService { * The id of the session */ sessionId: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -212,7 +212,7 @@ export class SessionsService { * The path to the node in the graph */ nodePath: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'PUT', From bd533426fc0d238a5afdec9a2e6aeb308f5b5d51 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:58:22 +1000 Subject: [PATCH 47/99] feat(ui): first pass at boards styling --- .../web/src/common/components/IAIDndImage.tsx | 4 +- .../common/components/IAIImageFallback.tsx | 46 +++++- .../components/ControlNetImagePreview.tsx | 4 +- .../components/Boards/AddBoardButton.tsx | 42 ++--- .../components/Boards/AllImagesBoard.tsx | 26 ++- .../gallery/components/Boards/BoardsList.tsx | 73 +++++---- .../components/Boards/HoverableBoard.tsx | 149 +++++++++--------- .../components/CurrentImagePreview.tsx | 4 +- .../components/ImageGalleryContent.tsx | 126 +++++++++------ .../components/SelectedItemOverlay.tsx | 26 +++ .../ImageToImage/InitialImagePreview.tsx | 4 +- 11 files changed, 300 insertions(+), 204 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/SelectedItemOverlay.tsx diff --git a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx index 669a68c88a..e54b4a8872 100644 --- a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx +++ b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx @@ -9,7 +9,7 @@ import { import { useDraggable, useDroppable } from '@dnd-kit/core'; import { useCombinedRefs } from '@dnd-kit/utilities'; import IAIIconButton from 'common/components/IAIIconButton'; -import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; import { AnimatePresence } from 'framer-motion'; import { ReactElement, SyntheticEvent, useCallback } from 'react'; @@ -53,7 +53,7 @@ const IAIDndImage = (props: IAIDndImageProps) => { isDropDisabled = false, isDragDisabled = false, isUploadDisabled = false, - fallback = , + fallback = , payloadImage, minSize = 24, postUploadAction, diff --git a/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx b/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx index 3d34fbca9e..03a00d5b1c 100644 --- a/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx +++ b/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx @@ -1,10 +1,20 @@ -import { Flex, FlexProps, Spinner, SpinnerProps } from '@chakra-ui/react'; +import { + As, + Flex, + FlexProps, + Icon, + IconProps, + Spinner, + SpinnerProps, +} from '@chakra-ui/react'; +import { ReactElement } from 'react'; +import { FaImage } from 'react-icons/fa'; type Props = FlexProps & { spinnerProps?: SpinnerProps; }; -export const IAIImageFallback = (props: Props) => { +export const IAIImageLoadingFallback = (props: Props) => { const { spinnerProps, ...rest } = props; const { sx, ...restFlexProps } = rest; return ( @@ -25,3 +35,35 @@ export const IAIImageFallback = (props: Props) => { ); }; + +type IAINoImageFallbackProps = { + flexProps?: FlexProps; + iconProps?: IconProps; + as?: As; +}; + +export const IAINoImageFallback = (props: IAINoImageFallbackProps) => { + const { sx: flexSx, ...restFlexProps } = props.flexProps ?? { sx: {} }; + const { sx: iconSx, ...restIconProps } = props.iconProps ?? { sx: {} }; + return ( + + + + ); +}; diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx index a121875f59..217caf9461 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx @@ -11,7 +11,7 @@ import IAIDndImage from 'common/components/IAIDndImage'; import { createSelector } from '@reduxjs/toolkit'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { AnimatePresence, motion } from 'framer-motion'; -import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; import IAIIconButton from 'common/components/IAIIconButton'; import { FaUndo } from 'react-icons/fa'; import { useGetImageDTOQuery } from 'services/apiSlice'; @@ -173,7 +173,7 @@ const ControlNetImagePreview = (props: Props) => { h: 'full', }} > - + )} {controlImage && ( diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx index 284e6558ac..632cebcb33 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx @@ -1,6 +1,5 @@ -import { Flex, Icon, Spinner, Text } from '@chakra-ui/react'; +import IAIButton from 'common/components/IAIButton'; import { useCallback } from 'react'; -import { FaPlus } from 'react-icons/fa'; import { useCreateBoardMutation } from 'services/apiSlice'; const DEFAULT_BOARD_NAME = 'My Board'; @@ -13,38 +12,15 @@ const AddBoardButton = () => { }, [createBoard]); return ( - - - {isLoading ? ( - - ) : ( - - )} - - New Board - + Add Board + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx index 51a7609678..51e95b64c4 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx @@ -2,12 +2,15 @@ import { Flex, Icon, Text } from '@chakra-ui/react'; import { FaImages } from 'react-icons/fa'; import { boardIdSelected } from '../../store/boardSlice'; import { useDispatch } from 'react-redux'; +import { IAINoImageFallback } from 'common/components/IAIImageFallback'; +import { AnimatePresence } from 'framer-motion'; +import { SelectedItemOverlay } from '../SelectedItemOverlay'; const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => { const dispatch = useDispatch(); const handleAllImagesBoardClick = () => { - dispatch(boardIdSelected(null)); + dispatch(boardIdSelected()); }; return ( @@ -19,25 +22,34 @@ const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => { cursor: 'pointer', w: 'full', h: 'full', - gap: 1, + borderRadius: 'base', }} onClick={handleAllImagesBoardClick} > - + + + {isSelected && } + - All Images + + All Images + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index 5854c3fe7c..fb68021dee 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -1,12 +1,11 @@ import { - Box, - Divider, + Collapse, + Flex, Grid, + IconButton, Input, InputGroup, InputRightElement, - Spacer, - useDisclosure, } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; @@ -16,33 +15,36 @@ import { selectBoardsAll, setBoardSearchText, } from 'features/gallery/store/boardSlice'; -import { memo, useEffect, useState } from 'react'; +import { memo, useState } from 'react'; import HoverableBoard from './HoverableBoard'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import AddBoardButton from './AddBoardButton'; import AllImagesBoard from './AllImagesBoard'; -import { searchBoardsSelector } from '../../store/boardSelectors'; -import { useSelector } from 'react-redux'; -import IAICollapse from '../../../../common/components/IAICollapse'; import { CloseIcon } from '@chakra-ui/icons'; import { useListBoardsQuery } from 'services/apiSlice'; const selector = createSelector( [selectBoardsAll, boardsSelector], (boards, boardsState) => { - const selectedBoard = boards.find( - (board) => board.board_id === boardsState.selectedBoardId - ); - return { selectedBoard, searchText: boardsState.searchText }; + // const selectedBoard = boards.find( + // (board) => board.board_id === boardsState.selectedBoardId + // ); + // return { selectedBoard, searchText: boardsState.searchText }; + const { selectedBoardId, searchText } = boardsState; + return { selectedBoardId, searchText }; }, defaultSelectorOptions ); -const BoardsList = () => { +type Props = { + isOpen: boolean; +}; + +const BoardsList = (props: Props) => { + const { isOpen } = props; const dispatch = useAppDispatch(); - const { selectedBoard, searchText } = useAppSelector(selector); + const { selectedBoardId, searchText } = useAppSelector(selector); // const filteredBoards = useSelector(searchBoardsSelector); - const { isOpen, onToggle } = useDisclosure(); const { data } = useListBoardsQuery({ offset: 0, limit: 8 }); @@ -64,9 +66,18 @@ const BoardsList = () => { }; return ( - - <> - + + + { /> {searchText && searchText.length && ( - + } + /> )} - + + { className="list-container" sx={{ gap: 2, - gridTemplateRows: '5rem 5rem', + gridTemplateRows: '5.5rem 5.5rem', gridAutoFlow: 'column dense', gridAutoColumns: '4rem', }} > - {!searchMode && ( - <> - - - - )} + {!searchMode && } {filteredBoards && filteredBoards.map((board) => ( ))} - - + + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index 71e080ff17..a2c07e4870 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -1,31 +1,22 @@ import { + Badge, Box, Editable, EditableInput, EditablePreview, Flex, + Image, MenuItem, MenuList, - Text, } from '@chakra-ui/react'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useAppDispatch } from 'app/store/storeHooks'; import { memo, useCallback } from 'react'; -import { FaTrash } from 'react-icons/fa'; +import { FaFolder, FaTrash } from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; import { BoardDTO, ImageDTO } from 'services/api'; -import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { IAINoImageFallback } from 'common/components/IAIImageFallback'; import { boardIdSelected } from 'features/gallery/store/boardSlice'; -import { - boardDeleted, - boardUpdated, - imageAddedToBoard, -} from '../../../../services/thunks/board'; -import { selectImagesAll, selectImagesById } from '../../store/imagesSlice'; -import IAIDndImage from '../../../../common/components/IAIDndImage'; -import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions'; -import { createSelector } from '@reduxjs/toolkit'; -import { RootState } from '../../../../app/store/store'; import { useAddImageToBoardMutation, useDeleteBoardMutation, @@ -33,21 +24,10 @@ import { useUpdateBoardMutation, } from 'services/apiSlice'; import { skipToken } from '@reduxjs/toolkit/dist/query'; - -const coverImageSelector = (imageName: string | undefined) => - createSelector( - [(state: RootState) => state], - (state) => { - const coverImage = imageName - ? selectImagesById(state, imageName) - : undefined; - - return { - coverImage, - }; - }, - defaultSelectorOptions - ); +import { useDroppable } from '@dnd-kit/core'; +import { AnimatePresence } from 'framer-motion'; +import IAIDropOverlay from 'common/components/IAIDropOverlay'; +import { SelectedItemOverlay } from '../SelectedItemOverlay'; interface HoverableBoardProps { board: BoardDTO; @@ -94,6 +74,17 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { [addImageToBoard, board_id] ); + const { + isOver, + setNodeRef, + active: isDropActive, + } = useDroppable({ + id: `board_droppable_${board_id}`, + data: { + handleDrop, + }, + }); + return ( @@ -112,7 +103,6 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { > {(ref) => ( { cursor: 'pointer', w: 'full', h: 'full', - gap: 1, }} > - } - isUploadDisabled={true} - /> + {board.cover_image_name && coverImage?.image_url && ( + + )} + {!(board.cover_image_name && coverImage?.image_url) && ( + + )} + + {board.image_count} + + + {isSelected && } + + + {isDropActive && } + - { - handleUpdateBoardName(nextValue); - }} - > - - + { + handleUpdateBoardName(nextValue); }} - /> - - - {board.image_count} - + > + + + + )} diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index bff32f1d78..49376b4807 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -14,7 +14,7 @@ import { useAppToaster } from 'app/components/Toaster'; import { imageSelected } from '../store/gallerySlice'; import IAIDndImage from 'common/components/IAIDndImage'; import { ImageDTO } from 'services/api'; -import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; import { RootState } from 'app/store/store'; import { selectImagesById } from '../store/imagesSlice'; import { useGetImageDTOQuery } from 'services/apiSlice'; @@ -116,7 +116,7 @@ const CurrentImagePreview = () => { } + fallback={} isUploadDisabled={true} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 48bd2bde74..8648962c8c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -1,12 +1,15 @@ import { Box, + Button, ButtonGroup, Flex, FlexProps, Grid, Icon, Text, + VStack, forwardRef, + useDisclosure, } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; @@ -54,10 +57,10 @@ import { selectImagesAll, } from '../store/imagesSlice'; import { receivedPageOfImages } from 'services/thunks/image'; -import { boardSelector } from '../store/boardSelectors'; -import { boardCreated } from '../../../services/thunks/board'; import BoardsList from './Boards/BoardsList'; -import { selectBoardsById } from '../store/boardSlice'; +import { boardsSelector, selectBoardsById } from '../store/boardSlice'; +import { ChevronUpIcon } from '@chakra-ui/icons'; +import { useListAllBoardsQuery } from 'services/apiSlice'; const itemSelector = createSelector( [(state: RootState) => state], @@ -89,7 +92,7 @@ const itemSelector = createSelector( ); const mainSelector = createSelector( - [gallerySelector, uiSelector, boardSelector], + [gallerySelector, uiSelector, boardsSelector], (gallery, ui, boards) => { const { galleryImageMinimumWidth, @@ -109,7 +112,7 @@ const mainSelector = createSelector( shouldUseSingleGalleryColumn, selectedImage, galleryView, - boards, + selectedBoardId: boards.selectedBoardId, }; }, defaultSelectorOptions @@ -142,12 +145,18 @@ const ImageGalleryContent = () => { shouldUseSingleGalleryColumn, selectedImage, galleryView, - boards, + selectedBoardId, } = useAppSelector(mainSelector); - const { items, areMoreAvailable, isLoading, categories, selectedBoard } = + const { items, areMoreAvailable, isLoading, categories } = useAppSelector(itemSelector); + const { selectedBoard } = useListAllBoardsQuery(undefined, { + selectFromResult: ({ data }) => ({ + selectedBoard: data?.find((b) => b.board_id === selectedBoardId), + }), + }); + const handleLoadMoreImages = useCallback(() => { dispatch(receivedPageOfImages()); }, [dispatch]); @@ -159,6 +168,8 @@ const ImageGalleryContent = () => { return undefined; }, [areMoreAvailable, handleLoadMoreImages, isLoading]); + const { isOpen: isBoardListOpen, onToggle } = useDisclosure(); + const handleChangeGalleryImageMinimumWidth = (v: number) => { dispatch(setGalleryImageMinimumWidth(v)); }; @@ -197,50 +208,71 @@ const ImageGalleryContent = () => { dispatch(setGalleryView('assets')); }, [dispatch]); - const handleClickBoardsView = useCallback(() => { - dispatch(setGalleryView('boards')); - }, [dispatch]); - return ( - - - - + + + } + /> + } + /> + + } - /> - } - /> - - - - {selectedBoard ? selectedBoard.board_name : 'All Images'} - - - + variant="ghost" + sx={{ + w: 'full', + justifyContent: 'center', + alignItems: 'center', + px: 2, + _hover: { + bg: 'base.800', + }, + }} + > + + {selectedBoard ? selectedBoard.board_name : 'All Images'} + + + { icon={shouldPinGallery ? : } /> - - - + + + - + {items.length || areMoreAvailable ? ( <> @@ -378,7 +410,7 @@ const ImageGalleryContent = () => { )} - + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/SelectedItemOverlay.tsx b/invokeai/frontend/web/src/features/gallery/components/SelectedItemOverlay.tsx new file mode 100644 index 0000000000..7038b4b64f --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/SelectedItemOverlay.tsx @@ -0,0 +1,26 @@ +import { motion } from 'framer-motion'; + +export const SelectedItemOverlay = () => ( + +); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx index e4b3a9191e..fbfa00e2a1 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx @@ -10,7 +10,7 @@ import { generationSelector } from 'features/parameters/store/generationSelector import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAIDndImage from 'common/components/IAIDndImage'; import { ImageDTO } from 'services/api'; -import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; import { useGetImageDTOQuery } from 'services/apiSlice'; import { skipToken } from '@reduxjs/toolkit/dist/query'; @@ -65,7 +65,7 @@ const InitialImagePreview = () => { image={image} onDrop={handleDrop} onReset={handleReset} - fallback={} + fallback={} postUploadAction={{ type: 'SET_INITIAL_IMAGE' }} withResetIcon /> From abd6561140bc1b6280506828bbd17a860d4c9ede Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 18:10:26 +1000 Subject: [PATCH 48/99] feat(ui): just fetch all boards instead of paginating them --- .../features/gallery/components/Boards/BoardsList.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index fb68021dee..81ee4be725 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -21,7 +21,7 @@ import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import AddBoardButton from './AddBoardButton'; import AllImagesBoard from './AllImagesBoard'; import { CloseIcon } from '@chakra-ui/icons'; -import { useListBoardsQuery } from 'services/apiSlice'; +import { useListAllBoardsQuery } from 'services/apiSlice'; const selector = createSelector( [selectBoardsAll, boardsSelector], @@ -44,15 +44,14 @@ const BoardsList = (props: Props) => { const { isOpen } = props; const dispatch = useAppDispatch(); const { selectedBoardId, searchText } = useAppSelector(selector); - // const filteredBoards = useSelector(searchBoardsSelector); - const { data } = useListBoardsQuery({ offset: 0, limit: 8 }); + const { data: boards } = useListAllBoardsQuery(); const filteredBoards = searchText - ? data?.items.filter((board) => + ? boards?.filter((board) => board.board_name.toLowerCase().includes(searchText.toLowerCase()) ) - : data?.items; + : boards; const [searchMode, setSearchMode] = useState(false); From 3c032c0767154eb7caa0e964a9b1accac98982c2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 19:55:00 +1000 Subject: [PATCH 49/99] feat(ui): only auto-add image to board if is not intermediate --- .../socketio/socketInvocationComplete.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts index 680f9c7041..bef0a2ccdb 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts @@ -41,15 +41,6 @@ export const addInvocationCompleteEventListener = () => { if (isImageOutput(result) && !nodeDenylist.includes(node.type)) { const { image_name } = result.image; - if (boardIdToAddTo) { - dispatch( - api.endpoints.addImageToBoard.initiate({ - board_id: boardIdToAddTo, - image_name, - }) - ); - } - // Get its metadata dispatch( imageMetadataReceived({ @@ -69,6 +60,15 @@ export const addInvocationCompleteEventListener = () => { dispatch(addImageToStagingArea(imageDTO)); } + if (boardIdToAddTo && !imageDTO.is_intermediate) { + dispatch( + api.endpoints.addImageToBoard.initiate({ + board_id: boardIdToAddTo, + image_name, + }) + ); + } + dispatch(progressImageSet(null)); } // pass along the socket event as an application action From 67a75f68952cdac27a88b17c61f86d9fadf8b8e8 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 19:56:19 +1000 Subject: [PATCH 50/99] feat(api, db): support `board_id` filter on images service `get_many()` --- invokeai/app/api/routers/images.py | 4 ++ .../services/board_image_record_storage.py | 1 + invokeai/app/services/image_record_storage.py | 49 ++++++++++++++++--- invokeai/app/services/images.py | 3 ++ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/invokeai/app/api/routers/images.py b/invokeai/app/api/routers/images.py index 11453d97f1..a8c84b81b9 100644 --- a/invokeai/app/api/routers/images.py +++ b/invokeai/app/api/routers/images.py @@ -221,6 +221,9 @@ async def list_images_with_metadata( is_intermediate: Optional[bool] = Query( default=None, description="Whether to list intermediate images" ), + board_id: Optional[str] = Query( + default=None, description="The board id to filter by" + ), offset: int = Query(default=0, description="The page offset"), limit: int = Query(default=10, description="The number of images per page"), ) -> OffsetPaginatedResults[ImageDTO]: @@ -232,6 +235,7 @@ async def list_images_with_metadata( image_origin, categories, is_intermediate, + board_id, ) return image_dtos diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index 73fad13406..45d21520f8 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -178,6 +178,7 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): offset: int = 0, limit: int = 10, ) -> OffsetPaginatedResults[ImageRecord]: + # TODO: this isn't paginated yet? try: self._lock.acquire() self._cursor.execute( diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index bc59c4a27c..8b5a77da2c 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -82,6 +82,7 @@ class ImageRecordStorageBase(ABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageRecord]: """Gets a page of image records.""" pass @@ -280,20 +281,42 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageRecord]: try: self._lock.acquire() # Manually build two queries - one for the count, one for the records - count_query = f"""SELECT COUNT(*) FROM images WHERE 1=1\n""" - images_query = f"""SELECT * FROM images WHERE 1=1\n""" + # count_query = """--sql + # SELECT COUNT(*) FROM images WHERE 1=1 + # """ + + count_query = """--sql + SELECT COUNT(*) + FROM images + LEFT JOIN board_images ON board_images.image_name = images.image_name + WHERE 1=1 + """ + + images_query = """--sql + SELECT images.* + FROM images + LEFT JOIN board_images ON board_images.image_name = images.image_name + WHERE 1=1 + """ + + # images_query = """--sql + # SELECT * FROM images WHERE 1=1 + # """ query_conditions = "" query_params = [] if image_origin is not None: - query_conditions += f"""AND image_origin = ?\n""" + query_conditions += """--sql + AND images.image_origin = ? + """ query_params.append(image_origin.value) if categories is not None: @@ -301,17 +324,31 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): category_strings = list(map(lambda c: c.value, set(categories))) # Create the correct length of placeholders placeholders = ",".join("?" * len(category_strings)) - query_conditions += f"AND image_category IN ( {placeholders} )\n" + + query_conditions += f"""--sql + AND images.image_category IN ( {placeholders} ) + """ # Unpack the included categories into the query params for c in category_strings: query_params.append(c) if is_intermediate is not None: - query_conditions += f"""AND is_intermediate = ?\n""" + query_conditions += """--sql + AND images.is_intermediate = ? + """ + query_params.append(is_intermediate) - query_pagination = f"""ORDER BY created_at DESC LIMIT ? OFFSET ?\n""" + if board_id is not None: + query_conditions += """--sql + AND board_images.board_id = ? + """ + query_params.append(board_id) + + query_pagination = """--sql + ORDER BY images.created_at DESC LIMIT ? OFFSET ? + """ # Final images query with pagination images_query += query_conditions + query_pagination + ";" diff --git a/invokeai/app/services/images.py b/invokeai/app/services/images.py index 5959116161..542f874f1d 100644 --- a/invokeai/app/services/images.py +++ b/invokeai/app/services/images.py @@ -102,6 +102,7 @@ class ImageServiceABC(ABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageDTO]: """Gets a paginated list of image DTOs.""" pass @@ -290,6 +291,7 @@ class ImageService(ImageServiceABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageDTO]: try: results = self._services.image_records.get_many( @@ -298,6 +300,7 @@ class ImageService(ImageServiceABC): image_origin, categories, is_intermediate, + board_id, ) image_dtos = list( From d501986610c9bfbb8f70aa9f7f8cf6bc05c754a8 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 19:56:55 +1000 Subject: [PATCH 51/99] chore(ui): regen api client --- invokeai/frontend/web/src/services/api/models/ModelsList.ts | 2 +- .../frontend/web/src/services/api/services/ImagesService.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index 40c2ebb1b9..a2d88d1967 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -12,6 +12,6 @@ import type { invokeai__backend__model_management__models__textual_inversion__Te import type { invokeai__backend__model_management__models__vae__VaeModel__Config } from './invokeai__backend__model_management__models__vae__VaeModel__Config'; export type ModelsList = { - models: Record>>; + models: Record>>; }; diff --git a/invokeai/frontend/web/src/services/api/services/ImagesService.ts b/invokeai/frontend/web/src/services/api/services/ImagesService.ts index d0eae92d4b..bfdef887a0 100644 --- a/invokeai/frontend/web/src/services/api/services/ImagesService.ts +++ b/invokeai/frontend/web/src/services/api/services/ImagesService.ts @@ -25,6 +25,7 @@ export class ImagesService { imageOrigin, categories, isIntermediate, + boardId, offset, limit = 10, }: { @@ -40,6 +41,10 @@ export class ImagesService { * Whether to list intermediate images */ isIntermediate?: boolean, + /** + * The board id to filter by + */ + boardId?: string, /** * The page offset */ @@ -56,6 +61,7 @@ export class ImagesService { 'image_origin': imageOrigin, 'categories': categories, 'is_intermediate': isIntermediate, + 'board_id': boardId, 'offset': offset, 'limit': limit, }, From f560a462a02412627649284c4325641f12216f0f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 19:57:22 +1000 Subject: [PATCH 52/99] feat(ui): rudimentary categorized gallery image fetching --- .../components/ImageGalleryContent.tsx | 56 ++++++++++++------- .../frontend/web/src/services/thunks/image.ts | 53 ++++++++++++++---- 2 files changed, 78 insertions(+), 31 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 8648962c8c..bd69425525 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -56,36 +56,39 @@ import { imageCategoriesChanged, selectImagesAll, } from '../store/imagesSlice'; -import { receivedPageOfImages } from 'services/thunks/image'; +import { + IMAGES_PER_PAGE, + receivedImages, + receivedPageOfImages, +} from 'services/thunks/image'; import BoardsList from './Boards/BoardsList'; -import { boardsSelector, selectBoardsById } from '../store/boardSlice'; +import { boardsSelector } from '../store/boardSlice'; import { ChevronUpIcon } from '@chakra-ui/icons'; import { useListAllBoardsQuery } from 'services/apiSlice'; const itemSelector = createSelector( [(state: RootState) => state], (state) => { - const { images, boards } = state; - - const { categories } = images; + const { categories, total, isLoading } = state.images; + const { selectedBoardId } = state.boards; const allImages = selectImagesAll(state); - const items = allImages.filter((i) => - categories.includes(i.image_category) - ); - const areMoreAvailable = items.length < images.total; - const isLoading = images.isLoading; - const selectedBoard = boards.selectedBoardId - ? selectBoardsById(state, boards.selectedBoardId) - : undefined; + const images = allImages.filter((i) => { + const isInCategory = categories.includes(i.image_category); + const isInSelectedBoard = selectedBoardId + ? i.board_id === selectedBoardId + : true; + return isInCategory && isInSelectedBoard; + }); + + const areMoreAvailable = images.length < total; return { - items, + images, isLoading, areMoreAvailable, - categories: images.categories, - selectedBoard, + categories, }; }, defaultSelectorOptions @@ -148,7 +151,7 @@ const ImageGalleryContent = () => { selectedBoardId, } = useAppSelector(mainSelector); - const { items, areMoreAvailable, isLoading, categories } = + const { images, areMoreAvailable, isLoading, categories } = useAppSelector(itemSelector); const { selectedBoard } = useListAllBoardsQuery(undefined, { @@ -158,7 +161,7 @@ const ImageGalleryContent = () => { }); const handleLoadMoreImages = useCallback(() => { - dispatch(receivedPageOfImages()); + dispatch(receivedPageOfImages({})); }, [dispatch]); const handleEndReached = useMemo(() => { @@ -208,6 +211,17 @@ const ImageGalleryContent = () => { dispatch(setGalleryView('assets')); }, [dispatch]); + useEffect(() => { + if (images.length < 20) { + dispatch( + receivedPageOfImages({ + categories, + boardId: selectedBoardId, + }) + ); + } + }, [categories, dispatch, images.length, selectedBoardId]); + return ( { - {items.length || areMoreAvailable ? ( + {images.length || areMoreAvailable ? ( <> {shouldUseSingleGalleryColumn ? ( setScrollerRef(ref)} itemContent={(index, item) => ( @@ -357,7 +371,7 @@ const ImageGalleryContent = () => { ) : ( { + async (arg: ImagesListedArg, { getState }) => { const state = getState(); const { categories } = state.images; + const { selectedBoardId } = state.boards; - const totalImagesInFilter = selectImagesAll(state).filter((i) => - categories.includes(i.image_category) - ).length; - - const response = await ImagesService.listImagesWithMetadata({ - categories, - isIntermediate: false, - offset: totalImagesInFilter, - limit: IMAGES_PER_PAGE, + 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 queryArg: ReceivedImagesArg = {}; + + if (size(arg)) { + queryArg = { ...DEFAULT_IMAGES_LISTED_ARG, ...arg }; + } else { + queryArg = { + ...DEFAULT_IMAGES_LISTED_ARG, + categories, + offset: images.length, + }; + } + + const response = await ImagesService.listImagesWithMetadata(queryArg); + return response; + } +); + +type ReceivedImagesArg = Parameters< + (typeof ImagesService)['listImagesWithMetadata'] +>[0]; + +/** + * `ImagesService.listImagesWithMetadata()` thunk + */ +export const receivedImages = createAppAsyncThunk( + 'api/receivedImages', + async (arg: ReceivedImagesArg, { getState }) => { + const response = await ImagesService.listImagesWithMetadata(arg); return response; } ); From 26b75b85f7e69e9e61e2cdc8a6686ffc15165598 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 20:00:29 +1000 Subject: [PATCH 53/99] fix(ui): if deleting selected board, deselect it --- .../web/src/features/gallery/store/boardSlice.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index 76ab757e5e..96c1c055e4 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -14,6 +14,7 @@ import { boardUpdated, receivedBoards, } from '../../../services/thunks/board'; +import { api } from 'services/apiSlice'; export const boardsAdapter = createEntityAdapter({ selectId: (board) => board.board_id, @@ -92,6 +93,14 @@ const boardsSlice = createSlice({ console.log({ boardId }); boardsAdapter.removeOne(state, boardId); }); + builder.addMatcher( + api.endpoints.deleteBoard.matchFulfilled, + (state, action) => { + if (action.meta.arg.originalArgs === state.selectedBoardId) { + state.selectedBoardId = undefined; + } + } + ); }, }); From 083a0fc4cfd6bebfd44e40712cedcd46c928c12f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 20:10:28 +1000 Subject: [PATCH 54/99] tidy(ui): remove references to boardsAdapter --- .../gallery/components/Boards/BoardsList.tsx | 9 +- .../src/features/gallery/store/boardSlice.ts | 97 ++----------------- 2 files changed, 11 insertions(+), 95 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index 81ee4be725..738693a278 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -12,7 +12,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { boardsSelector, - selectBoardsAll, setBoardSearchText, } from 'features/gallery/store/boardSlice'; import { memo, useState } from 'react'; @@ -24,12 +23,8 @@ import { CloseIcon } from '@chakra-ui/icons'; import { useListAllBoardsQuery } from 'services/apiSlice'; const selector = createSelector( - [selectBoardsAll, boardsSelector], - (boards, boardsState) => { - // const selectedBoard = boards.find( - // (board) => board.board_id === boardsState.selectedBoardId - // ); - // return { selectedBoard, searchText: boardsState.searchText }; + [boardsSelector], + (boardsState) => { const { selectedBoardId, searchText } = boardsState; return { selectedBoardId, searchText }; }, diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index 96c1c055e4..8fc9bfa486 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -1,60 +1,22 @@ -import { - EntityId, - PayloadAction, - Update, - createEntityAdapter, - createSlice, -} from '@reduxjs/toolkit'; +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; -import { BoardDTO } from 'services/api'; -import { dateComparator } from 'common/util/dateComparator'; -import { - boardCreated, - boardDeleted, - boardUpdated, - receivedBoards, -} from '../../../services/thunks/board'; import { api } from 'services/apiSlice'; -export const boardsAdapter = createEntityAdapter({ - selectId: (board) => board.board_id, - sortComparer: (a, b) => dateComparator(b.updated_at, a.updated_at), -}); - -type AdditionalBoardsState = { - offset: number; - limit: number; - total: number; - isLoading: boolean; +type BoardsState = { + searchText: string; selectedBoardId?: string; - searchText?: string; updateBoardModalOpen: boolean; }; -export const initialBoardsState = - boardsAdapter.getInitialState({ - offset: 0, - limit: 50, - total: 0, - isLoading: false, - updateBoardModalOpen: false, - }); - -export type BoardsState = typeof initialBoardsState; +export const initialBoardsState: BoardsState = { + updateBoardModalOpen: false, + searchText: '', +}; const boardsSlice = createSlice({ name: 'boards', initialState: initialBoardsState, reducers: { - boardUpserted: (state, action: PayloadAction) => { - boardsAdapter.upsertOne(state, action.payload); - }, - boardUpdatedOne: (state, action: PayloadAction>) => { - boardsAdapter.updateOne(state, action.payload); - }, - boardRemoved: (state, action: PayloadAction) => { - boardsAdapter.removeOne(state, action.payload); - }, boardIdSelected: (state, action: PayloadAction) => { state.selectedBoardId = action.payload; }, @@ -66,33 +28,6 @@ const boardsSlice = createSlice({ }, }, extraReducers: (builder) => { - builder.addCase(receivedBoards.pending, (state) => { - state.isLoading = true; - }); - builder.addCase(receivedBoards.rejected, (state) => { - state.isLoading = false; - }); - builder.addCase(receivedBoards.fulfilled, (state, action) => { - state.isLoading = false; - const { items, offset, limit, total } = action.payload; - state.offset = offset; - state.limit = limit; - state.total = total; - boardsAdapter.upsertMany(state, items); - }); - builder.addCase(boardCreated.fulfilled, (state, action) => { - const board = action.payload; - boardsAdapter.upsertOne(state, board); - }); - builder.addCase(boardUpdated.fulfilled, (state, action) => { - const board = action.payload; - boardsAdapter.upsertOne(state, board); - }); - builder.addCase(boardDeleted.pending, (state, action) => { - const boardId = action.meta.arg; - console.log({ boardId }); - boardsAdapter.removeOne(state, boardId); - }); builder.addMatcher( api.endpoints.deleteBoard.matchFulfilled, (state, action) => { @@ -104,22 +39,8 @@ const boardsSlice = createSlice({ }, }); -export const { - selectAll: selectBoardsAll, - selectById: selectBoardsById, - selectEntities: selectBoardsEntities, - selectIds: selectBoardsIds, - selectTotal: selectBoardsTotal, -} = boardsAdapter.getSelectors((state) => state.boards); - -export const { - boardUpserted, - boardUpdatedOne, - boardRemoved, - boardIdSelected, - setBoardSearchText, - setUpdateBoardModalOpen, -} = boardsSlice.actions; +export const { boardIdSelected, setBoardSearchText, setUpdateBoardModalOpen } = + boardsSlice.actions; export const boardsSelector = (state: RootState) => state.boards; From e2ee8102c2411e28514e77aa8372151ec390ff0a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 20:11:09 +1000 Subject: [PATCH 55/99] tidy(db): tidy `image_record_storage.py` --- invokeai/app/services/image_record_storage.py | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 8b5a77da2c..c34d2ca5c8 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -273,7 +273,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): raise ImageRecordSaveException from e finally: self._lock.release() - + def get_many( self, offset: int = 0, @@ -287,11 +287,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): self._lock.acquire() # Manually build two queries - one for the count, one for the records - - # count_query = """--sql - # SELECT COUNT(*) FROM images WHERE 1=1 - # """ - count_query = """--sql SELECT COUNT(*) FROM images @@ -306,10 +301,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): WHERE 1=1 """ - # images_query = """--sql - # SELECT * FROM images WHERE 1=1 - # """ - query_conditions = "" query_params = [] @@ -320,7 +311,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): query_params.append(image_origin.value) if categories is not None: - ## Convert the enum values to unique list of strings + # Convert the enum values to unique list of strings category_strings = list(map(lambda c: c.value, set(categories))) # Create the correct length of placeholders placeholders = ",".join("?" * len(category_strings)) @@ -337,13 +328,14 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): query_conditions += """--sql AND images.is_intermediate = ? """ - + query_params.append(is_intermediate) if board_id is not None: query_conditions += """--sql AND board_images.board_id = ? """ + query_params.append(board_id) query_pagination = """--sql @@ -457,7 +449,9 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): finally: self._lock.release() - def get_most_recent_image_for_board(self, board_id: str) -> Union[ImageRecord, None]: + def get_most_recent_image_for_board( + self, board_id: str + ) -> Union[ImageRecord, None]: try: self._lock.acquire() self._cursor.execute( @@ -477,5 +471,5 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): self._lock.release() if result is None: return None - + return deserialize_image_record(dict(result)) From 4545f3209fe0328a90cc165ed0f56ed356e512da Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 20:40:12 +1000 Subject: [PATCH 56/99] fix(ui): fix bug with image deletion not removing image from gallery --- .../middleware/listenerMiddleware/index.ts | 2 ++ .../listeners/boardIdSelected.ts | 28 +++++++++++++++++++ .../listeners/imageDeleted.ts | 4 +-- .../components/ImageGalleryContent.tsx | 11 -------- 4 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 15fd48fbb2..4ec185bd83 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -77,6 +77,7 @@ import { addImageAddedToBoardFulfilledListener, addImageAddedToBoardRejectedListener, } from './listeners/imageAddedToBoard'; +import { addBoardIdSelectedListener } from './listeners/boardIdSelected'; export const listenerMiddleware = createListenerMiddleware(); @@ -191,3 +192,4 @@ addUpdateImageUrlsOnConnectListener(); // Boards addImageAddedToBoardFulfilledListener(); addImageAddedToBoardRejectedListener(); +addBoardIdSelectedListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts new file mode 100644 index 0000000000..3889130a9c --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts @@ -0,0 +1,28 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { boardIdSelected } from 'features/gallery/store/boardSlice'; +import { selectImagesAll } from 'features/gallery/store/imagesSlice'; +import { receivedPageOfImages } from 'services/thunks/image'; + +const moduleLog = log.child({ namespace: 'boards' }); + +export const addBoardIdSelectedListener = () => { + startAppListening({ + actionCreator: boardIdSelected, + effect: (action, { getState, dispatch }) => { + const boardId = action.payload; + const state = getState(); + const { categories } = state.images; + + const images = selectImagesAll(state).filter((i) => { + const isInCategory = categories.includes(i.image_category); + const isInSelectedBoard = boardId ? i.board_id === boardId : true; + return isInCategory && isInSelectedBoard; + }); + + if (images.length === 0) { + dispatch(receivedPageOfImages({ categories, boardId })); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts index 32bfcbba07..224aa0d2aa 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts @@ -14,7 +14,7 @@ import { clearInitialImage } from 'features/parameters/store/generationSlice'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { api } from 'services/apiSlice'; -const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' }); +const moduleLog = log.child({ namespace: 'image' }); /** * Called when the user requests an image deletion @@ -30,7 +30,7 @@ export const addRequestedImageDeletionListener = () => { const state = getState(); const selectedImage = state.gallery.selectedImage; - if (selectedImage && selectedImage === image_name) { + if (selectedImage === image_name) { const ids = selectImagesIds(state); const deletedImageIndex = ids.findIndex( diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index bd69425525..866a68f0b6 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -211,17 +211,6 @@ const ImageGalleryContent = () => { dispatch(setGalleryView('assets')); }, [dispatch]); - useEffect(() => { - if (images.length < 20) { - dispatch( - receivedPageOfImages({ - categories, - boardId: selectedBoardId, - }) - ); - } - }, [categories, dispatch, images.length, selectedBoardId]); - return ( Date: Wed, 21 Jun 2023 21:27:15 +1000 Subject: [PATCH 57/99] fix(ui): fix gallery image fetching for board categories --- .../listeners/boardIdSelected.ts | 27 ++++++++++++++++--- .../components/ImageGalleryContent.tsx | 18 ++++++++----- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts index 3889130a9c..30fb696ac3 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts @@ -2,7 +2,8 @@ import { log } from 'app/logging/useLogger'; import { startAppListening } from '..'; import { boardIdSelected } from 'features/gallery/store/boardSlice'; import { selectImagesAll } from 'features/gallery/store/imagesSlice'; -import { receivedPageOfImages } from 'services/thunks/image'; +import { IMAGES_PER_PAGE, receivedPageOfImages } from 'services/thunks/image'; +import { api } from 'services/apiSlice'; const moduleLog = log.child({ namespace: 'boards' }); @@ -11,16 +12,36 @@ export const addBoardIdSelectedListener = () => { actionCreator: boardIdSelected, effect: (action, { getState, dispatch }) => { const boardId = action.payload; + + // we need to check if we need to fetch more images + + if (!boardId) { + // a board was unselected - we don't need to do anything + return; + } + const state = getState(); const { categories } = state.images; - const images = selectImagesAll(state).filter((i) => { + const filteredImages = selectImagesAll(state).filter((i) => { const isInCategory = categories.includes(i.image_category); const isInSelectedBoard = boardId ? i.board_id === boardId : true; return isInCategory && isInSelectedBoard; }); - if (images.length === 0) { + // get the board from the cache + const { data: boards } = api.endpoints.listAllBoards.select()(state); + const board = boards?.find((b) => b.board_id === boardId); + if (!board) { + // can't find the board in cache... + return; + } + + // if we haven't loaded one full page of images from this board, load more + if ( + filteredImages.length < board.image_count && + filteredImages.length < IMAGES_PER_PAGE + ) { dispatch(receivedPageOfImages({ categories, boardId })); } }, diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 866a68f0b6..c2f8a30409 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -69,7 +69,7 @@ import { useListAllBoardsQuery } from 'services/apiSlice'; const itemSelector = createSelector( [(state: RootState) => state], (state) => { - const { categories, total, isLoading } = state.images; + const { categories, total: allImagesTotal, isLoading } = state.images; const { selectedBoardId } = state.boards; const allImages = selectImagesAll(state); @@ -82,12 +82,10 @@ const itemSelector = createSelector( return isInCategory && isInSelectedBoard; }); - const areMoreAvailable = images.length < total; - return { images, + allImagesTotal, isLoading, - areMoreAvailable, categories, }; }, @@ -151,8 +149,7 @@ const ImageGalleryContent = () => { selectedBoardId, } = useAppSelector(mainSelector); - const { images, areMoreAvailable, isLoading, categories } = - useAppSelector(itemSelector); + const { images, isLoading, allImagesTotal } = useAppSelector(itemSelector); const { selectedBoard } = useListAllBoardsQuery(undefined, { selectFromResult: ({ data }) => ({ @@ -160,6 +157,15 @@ const ImageGalleryContent = () => { }), }); + const filteredImagesTotal = useMemo( + () => selectedBoard?.image_count ?? allImagesTotal, + [allImagesTotal, selectedBoard?.image_count] + ); + + const areMoreAvailable = useMemo(() => { + return images.length < filteredImagesTotal; + }, [images.length, filteredImagesTotal]); + const handleLoadMoreImages = useCallback(() => { dispatch(receivedPageOfImages({})); }, [dispatch]); From d3e6f0130c0ed71e711982c59034e99114685571 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 22:00:14 +1000 Subject: [PATCH 58/99] fix(ui): fix issue with gallery not letting you load more images To determine whether the Load More button should work, we need to keep track of how many images are left to load for a given board or category. The Assets tab doesn't work, though. Need to figure out a better way to handle this. --- .../listeners/boardIdSelected.ts | 53 ++++++++++++++++++- .../listeners/imageCategoriesChanged.ts | 12 +++-- .../listeners/socketio/socketConnected.ts | 7 ++- .../components/CurrentImagePreview.tsx | 10 ++-- .../components/ImageGalleryContent.tsx | 20 +++---- .../features/gallery/store/gallerySlice.ts | 2 - .../src/features/gallery/store/imagesSlice.ts | 13 ++++- .../frontend/web/src/services/thunks/image.ts | 6 ++- 8 files changed, 96 insertions(+), 27 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts index 30fb696ac3..8e4667fd14 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts @@ -4,6 +4,7 @@ import { boardIdSelected } from 'features/gallery/store/boardSlice'; import { selectImagesAll } from 'features/gallery/store/imagesSlice'; import { IMAGES_PER_PAGE, receivedPageOfImages } from 'services/thunks/image'; import { api } from 'services/apiSlice'; +import { imageSelected } from 'features/gallery/store/gallerySlice'; const moduleLog = log.child({ namespace: 'boards' }); @@ -15,12 +16,62 @@ export const addBoardIdSelectedListener = () => { // we need to check if we need to fetch more images + const state = getState(); + const allImages = selectImagesAll(state); + + if (!boardId) { + // a board was unselected + dispatch(imageSelected(allImages[0]?.image_name)); + return; + } + + const { categories } = state.images; + + const filteredImages = allImages.filter((i) => { + const isInCategory = categories.includes(i.image_category); + const isInSelectedBoard = boardId ? i.board_id === boardId : true; + return isInCategory && isInSelectedBoard; + }); + + // get the board from the cache + const { data: boards } = api.endpoints.listAllBoards.select()(state); + const board = boards?.find((b) => b.board_id === boardId); + + if (!board) { + // can't find the board in cache... + dispatch(imageSelected(allImages[0]?.image_name)); + return; + } + + console.log('setting image'); + dispatch(imageSelected(board.cover_image_name)); + + // if we haven't loaded one full page of images from this board, load more + if ( + filteredImages.length < board.image_count && + filteredImages.length < IMAGES_PER_PAGE + ) { + dispatch(receivedPageOfImages({ categories, boardId })); + } + }, + }); +}; + +export const addBoardIdSelected_changeSelectedImage_listener = () => { + startAppListening({ + actionCreator: boardIdSelected, + effect: (action, { getState, dispatch }) => { + const boardId = action.payload; + + const state = getState(); + + // we need to check if we need to fetch more images + if (!boardId) { // a board was unselected - we don't need to do anything return; } - const state = getState(); const { categories } = state.images; const filteredImages = selectImagesAll(state).filter((i) => { diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageCategoriesChanged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageCategoriesChanged.ts index 85d56d3913..8f01b8d7b8 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageCategoriesChanged.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageCategoriesChanged.ts @@ -12,12 +12,16 @@ export const addImageCategoriesChangedListener = () => { startAppListening({ actionCreator: imageCategoriesChanged, effect: (action, { getState, dispatch }) => { - const filteredImagesCount = selectFilteredImagesAsArray( - getState() - ).length; + const state = getState(); + const filteredImagesCount = selectFilteredImagesAsArray(state).length; if (!filteredImagesCount) { - dispatch(receivedPageOfImages()); + dispatch( + receivedPageOfImages({ + categories: action.payload, + boardId: state.boards.selectedBoardId, + }) + ); } }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index 3049d2c933..9fe554fee1 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -20,7 +20,12 @@ export const addSocketConnectedEventListener = () => { const { disabledTabs } = config; if (!images.ids.length) { - dispatch(receivedPageOfImages()); + dispatch( + receivedPageOfImages({ + categories: ['general'], + isIntermediate: false, + }) + ); } if (!models.ids.length) { diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index 49376b4807..5426fee3b1 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -9,14 +9,10 @@ import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer'; import NextPrevImageButtons from './NextPrevImageButtons'; import { memo, useCallback } from 'react'; import { systemSelector } from 'features/system/store/systemSelectors'; -import { configSelector } from '../../system/store/configSelectors'; -import { useAppToaster } from 'app/components/Toaster'; import { imageSelected } from '../store/gallerySlice'; import IAIDndImage from 'common/components/IAIDndImage'; import { ImageDTO } from 'services/api'; import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; -import { RootState } from 'app/store/store'; -import { selectImagesById } from '../store/imagesSlice'; import { useGetImageDTOQuery } from 'services/apiSlice'; import { skipToken } from '@reduxjs/toolkit/dist/query'; @@ -114,14 +110,14 @@ const CurrentImagePreview = () => { }} > } isUploadDisabled={true} /> )} - {shouldShowImageDetails && image && ( + {shouldShowImageDetails && image && selectedImage && ( { )} - {!shouldShowImageDetails && image && ( + {!shouldShowImageDetails && image && selectedImage && ( { shouldUseSingleGalleryColumn, selectedImage, galleryView, - selectedBoardId, } = useAppSelector(mainSelector); - const { images, isLoading, allImagesTotal } = useAppSelector(itemSelector); + const { images, isLoading, allImagesTotal, categories, selectedBoardId } = + useAppSelector(itemSelector); const { selectedBoard } = useListAllBoardsQuery(undefined, { selectFromResult: ({ data }) => ({ @@ -167,8 +164,13 @@ const ImageGalleryContent = () => { }, [images.length, filteredImagesTotal]); const handleLoadMoreImages = useCallback(() => { - dispatch(receivedPageOfImages({})); - }, [dispatch]); + dispatch( + receivedPageOfImages({ + categories, + boardId: selectedBoardId, + }) + ); + }, [categories, dispatch, selectedBoardId]); const handleEndReached = useMemo(() => { if (areMoreAvailable && !isLoading) { diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index b07ab487ae..b7fc0809a6 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -1,8 +1,6 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import { ImageDTO } from 'services/api'; import { imageUpserted } from './imagesSlice'; -import { imageUrlsReceived } from 'services/thunks/image'; type GalleryImageObjectFitType = 'contain' | 'cover'; diff --git a/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts b/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts index 0b2b9f0f58..25a3341532 100644 --- a/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts @@ -11,7 +11,6 @@ import { dateComparator } from 'common/util/dateComparator'; import { keyBy } from 'lodash-es'; import { imageDeleted, - imageMetadataReceived, imageUrlsReceived, receivedPageOfImages, } from 'services/thunks/image'; @@ -74,11 +73,21 @@ const imagesSlice = createSlice({ }); builder.addCase(receivedPageOfImages.fulfilled, (state, action) => { state.isLoading = false; + const { boardId, categories, imageOrigin, isIntermediate } = + action.meta.arg; + const { items, offset, limit, total } = action.payload; + imagesAdapter.upsertMany(state, items); + + if (!categories?.includes('general') || boardId) { + // need to skip updating the total images count if the images recieved were for a specific board + // TODO: this doesn't work when on the Asset tab/category... + return; + } + state.offset = offset; state.limit = limit; state.total = total; - imagesAdapter.upsertMany(state, items); }); builder.addCase(imageDeleted.pending, (state, action) => { // Image deleted diff --git a/invokeai/frontend/web/src/services/thunks/image.ts b/invokeai/frontend/web/src/services/thunks/image.ts index 88da7fbcb4..fe198cf6f9 100644 --- a/invokeai/frontend/web/src/services/thunks/image.ts +++ b/invokeai/frontend/web/src/services/thunks/image.ts @@ -148,7 +148,11 @@ export const receivedPageOfImages = createAppAsyncThunk( let queryArg: ReceivedImagesArg = {}; if (size(arg)) { - queryArg = { ...DEFAULT_IMAGES_LISTED_ARG, ...arg }; + queryArg = { + ...DEFAULT_IMAGES_LISTED_ARG, + offset: images.length, + ...arg, + }; } else { queryArg = { ...DEFAULT_IMAGES_LISTED_ARG, From 6ee0e197bb21e95befb0bb39007fa7936ecf528f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:42:30 +1000 Subject: [PATCH 59/99] feat(db): add `deleted_at` to `board_images` --- invokeai/app/services/board_image_record_storage.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index 45d21520f8..ae2f1a9895 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -95,6 +95,8 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- enforce one-to-many relationship between boards and images using PK -- (we can extend this to many-to-many later) + -- Soft delete, currently unused + deleted_at DATETIME, PRIMARY KEY (image_name), FOREIGN KEY (board_id) REFERENCES boards (board_id) ON DELETE CASCADE, FOREIGN KEY (image_name) REFERENCES images (image_name) ON DELETE CASCADE From 922319cb84a9aff5ba4d83014b2c20d300bc7e52 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:48:23 +1000 Subject: [PATCH 60/99] fix(ui): fix first added board doesn't show until refresh Had incorrect `invalidatesTags` array for the mutation. --- invokeai/frontend/web/src/services/apiSlice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/services/apiSlice.ts b/invokeai/frontend/web/src/services/apiSlice.ts index 9a1521ce5a..2d42931b0b 100644 --- a/invokeai/frontend/web/src/services/apiSlice.ts +++ b/invokeai/frontend/web/src/services/apiSlice.ts @@ -86,7 +86,7 @@ export const api = createApi({ method: 'POST', params: { board_name }, }), - invalidatesTags: ['Board'], + invalidatesTags: [{ id: 'Board', type: LIST }], }), updateBoard: build.mutation({ From 2ffead000c12158145f553b211237d5f23bb5c77 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:48:51 +1000 Subject: [PATCH 61/99] tidy(ui): remove `console.log()` --- .../middleware/listenerMiddleware/listeners/boardIdSelected.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts index 8e4667fd14..eab4389ceb 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts @@ -43,7 +43,6 @@ export const addBoardIdSelectedListener = () => { return; } - console.log('setting image'); dispatch(imageSelected(board.cover_image_name)); // if we haven't loaded one full page of images from this board, load more From a00ad6ac03da84519873d24f74578b050028df32 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:58:15 +1000 Subject: [PATCH 62/99] feat(ui): dropping image on `All Images` board removes it from board --- .../components/Boards/AllImagesBoard.tsx | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx index 51e95b64c4..e506c88e2d 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx @@ -1,10 +1,15 @@ -import { Flex, Icon, Text } from '@chakra-ui/react'; +import { Flex, Text } from '@chakra-ui/react'; import { FaImages } from 'react-icons/fa'; import { boardIdSelected } from '../../store/boardSlice'; import { useDispatch } from 'react-redux'; import { IAINoImageFallback } from 'common/components/IAIImageFallback'; import { AnimatePresence } from 'framer-motion'; import { SelectedItemOverlay } from '../SelectedItemOverlay'; +import { useCallback } from 'react'; +import { ImageDTO } from 'services/api'; +import { useRemoveImageFromBoardMutation } from 'services/apiSlice'; +import { useDroppable } from '@dnd-kit/core'; +import IAIDropOverlay from 'common/components/IAIDropOverlay'; const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => { const dispatch = useDispatch(); @@ -13,6 +18,33 @@ const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => { dispatch(boardIdSelected()); }; + const [removeImageFromBoard, { isLoading }] = + useRemoveImageFromBoardMutation(); + + const handleDrop = useCallback( + (droppedImage: ImageDTO) => { + if (!droppedImage.board_id) { + return; + } + removeImageFromBoard({ + board_id: droppedImage.board_id, + image_name: droppedImage.image_name, + }); + }, + [removeImageFromBoard] + ); + + const { + isOver, + setNodeRef, + active: isDropActive, + } = useDroppable({ + id: `board_droppable_all_images`, + data: { + handleDrop, + }, + }); + return ( { onClick={handleAllImagesBoardClick} > { {isSelected && } + + {isDropActive && } + Date: Thu, 22 Jun 2023 11:20:11 +1000 Subject: [PATCH 63/99] fix(ui): fix board's image list not updating when image removed from board --- .../middleware/listenerMiddleware/index.ts | 12 ++++++ .../listeners/imageRemovedFromBoard.ts | 40 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageRemovedFromBoard.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 4ec185bd83..cb641d00db 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -78,6 +78,10 @@ import { addImageAddedToBoardRejectedListener, } from './listeners/imageAddedToBoard'; import { addBoardIdSelectedListener } from './listeners/boardIdSelected'; +import { + addImageRemovedFromBoardFulfilledListener, + addImageRemovedFromBoardRejectedListener, +} from './listeners/imageRemovedFromBoard'; export const listenerMiddleware = createListenerMiddleware(); @@ -97,6 +101,12 @@ export type AppListenerEffect = ListenerEffect< AppDispatch >; +/** + * The RTK listener middleware is a lightweight alternative sagas/observables. + * + * Most side effect logic should live in a listener. + */ + // Image uploaded addImageUploadedFulfilledListener(); addImageUploadedRejectedListener(); @@ -192,4 +202,6 @@ addUpdateImageUrlsOnConnectListener(); // Boards addImageAddedToBoardFulfilledListener(); addImageAddedToBoardRejectedListener(); +addImageRemovedFromBoardFulfilledListener(); +addImageRemovedFromBoardRejectedListener(); addBoardIdSelectedListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageRemovedFromBoard.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageRemovedFromBoard.ts new file mode 100644 index 0000000000..40847ade3a --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageRemovedFromBoard.ts @@ -0,0 +1,40 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { imageMetadataReceived } from 'services/thunks/image'; +import { api } from 'services/apiSlice'; + +const moduleLog = log.child({ namespace: 'boards' }); + +export const addImageRemovedFromBoardFulfilledListener = () => { + startAppListening({ + matcher: api.endpoints.removeImageFromBoard.matchFulfilled, + effect: (action, { getState, dispatch }) => { + const { board_id, image_name } = action.meta.arg.originalArgs; + + moduleLog.debug( + { data: { board_id, image_name } }, + 'Image added to board' + ); + + dispatch( + imageMetadataReceived({ + imageName: image_name, + }) + ); + }, + }); +}; + +export const addImageRemovedFromBoardRejectedListener = () => { + startAppListening({ + matcher: api.endpoints.removeImageFromBoard.matchRejected, + effect: (action, { getState, dispatch }) => { + const { board_id, image_name } = action.meta.arg.originalArgs; + + moduleLog.debug( + { data: { board_id, image_name } }, + 'Problem adding image to board' + ); + }, + }); +}; From 79f0c4d3c4b918e49fd2a84cf6778482e6634b24 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 11:21:12 +1000 Subject: [PATCH 64/99] feat(ui): add remove from board to image context menu --- .../gallery/components/HoverableImage.tsx | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index 86ec3436f0..2482e5a1d0 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -34,9 +34,8 @@ import { useAppToaster } from 'app/components/Toaster'; import { ImageDTO } from 'services/api'; import { useDraggable } from '@dnd-kit/core'; import { DeleteImageContext } from 'app/contexts/DeleteImageContext'; -import { imageAddedToBoard } from '../../../services/thunks/board'; -import { setUpdateBoardModalOpen } from '../store/boardSlice'; import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext'; +import { useRemoveImageFromBoardMutation } from 'services/apiSlice'; export const selector = createSelector( [gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector], @@ -110,6 +109,8 @@ const HoverableImage = (props: HoverableImageProps) => { }, }); + const [removeFromBoard] = useRemoveImageFromBoardMutation(); + const handleMouseOver = () => setIsHovered(true); const handleMouseOut = () => setIsHovered(false); @@ -176,6 +177,13 @@ const HoverableImage = (props: HoverableImageProps) => { 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'); }; @@ -255,6 +263,14 @@ const HoverableImage = (props: HoverableImageProps) => { } onClickCapture={handleAddToBoard}> {image.board_id ? 'Change Board' : 'Add to Board'} + {image.board_id && ( + } + onClickCapture={handleRemoveFromBoard} + > + Remove from Board + + )} } From 3c04340f3f649a3f1720380c29de43069531f7ac Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 11:31:51 +1000 Subject: [PATCH 65/99] tidy(ui): tidy up update image board modal --- .../web/src/app/contexts/AddImageToBoardContext.tsx | 3 --- .../gallery/components/Boards/UpdateImageBoardModal.tsx | 9 ++------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx index d29c1c8a48..f5a856d3d8 100644 --- a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx +++ b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx @@ -1,8 +1,6 @@ import { useDisclosure } from '@chakra-ui/react'; -import { useAppDispatch } from 'app/store/storeHooks'; import { PropsWithChildren, createContext, useCallback, useState } from 'react'; import { ImageDTO } from 'services/api'; -import { imageAddedToBoard } from '../../services/thunks/board'; import { useAddImageToBoardMutation } from 'services/apiSlice'; export type ImageUsage = { @@ -41,7 +39,6 @@ type Props = PropsWithChildren; export const AddImageToBoardContextProvider = (props: Props) => { const [imageToMove, setImageToMove] = useState(); - const dispatch = useAppDispatch(); const { isOpen, onOpen, onClose } = useDisclosure(); const [addImageToBoard, result] = useAddImageToBoardMutation(); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx index edd4d215af..b16bddd6b4 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx @@ -6,9 +6,7 @@ import { AlertDialogHeader, AlertDialogOverlay, Box, - Divider, Flex, - Select, Spinner, Text, } from '@chakra-ui/react'; @@ -16,9 +14,6 @@ import IAIButton from 'common/components/IAIButton'; import { memo, useContext, useRef, useState } from 'react'; import { AddImageToBoardContext } from '../../../../app/contexts/AddImageToBoardContext'; -import { useSelector } from 'react-redux'; -import { selectBoardsAll } from '../../store/boardSlice'; -import IAISelect from '../../../../common/components/IAISelect'; import IAIMantineSelect from 'common/components/IAIMantineSelect'; import { useListAllBoardsQuery } from 'services/apiSlice'; @@ -46,7 +41,7 @@ const UpdateImageBoardModal = () => { - Move Image to Board + {currentBoard ? 'Move Image to Board' : 'Add Image to Board'} @@ -86,7 +81,7 @@ const UpdateImageBoardModal = () => { }} ml={3} > - Add to Board + {currentBoard ? 'Move' : 'Add'} From 10008859a43c872fded94381ae1349b4458dfbcd Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 11:34:18 +1000 Subject: [PATCH 66/99] tidy(ui): remove all refs to boards thunks --- .../socketio/socketInvocationComplete.ts | 1 - .../frontend/web/src/services/thunks/board.ts | 53 ------------------- 2 files changed, 54 deletions(-) delete mode 100644 invokeai/frontend/web/src/services/thunks/board.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts index bef0a2ccdb..c204f0bdfb 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts @@ -9,7 +9,6 @@ import { imageMetadataReceived } from 'services/thunks/image'; import { sessionCanceled } from 'services/thunks/session'; import { isImageOutput } from 'services/types/guards'; import { progressImageSet } from 'features/system/store/systemSlice'; -import { imageAddedToBoard } from '../../../../../../services/thunks/board'; import { api } from 'services/apiSlice'; const moduleLog = log.child({ namespace: 'socketio' }); diff --git a/invokeai/frontend/web/src/services/thunks/board.ts b/invokeai/frontend/web/src/services/thunks/board.ts deleted file mode 100644 index 03c59dba10..0000000000 --- a/invokeai/frontend/web/src/services/thunks/board.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { createAppAsyncThunk } from '../../app/store/storeUtils'; -import { BoardsService } from '../api'; - -/** - * `BoardsService.listBoards()` thunk - */ -export const receivedBoards = createAppAsyncThunk( - 'api/receivedBoards', - async (_, { getState }) => { - const response = await BoardsService.listBoards({}); - return response; - } -); - -type BoardCreatedArg = Parameters<(typeof BoardsService)['createBoard']>[0]; - -export const boardCreated = createAppAsyncThunk( - 'api/boardCreated', - async (arg: BoardCreatedArg) => { - const response = await BoardsService.createBoard(arg); - return response; - } -); - -export const boardDeleted = createAppAsyncThunk( - 'api/boardDeleted', - async (boardId: string) => { - await BoardsService.deleteBoard({ boardId }); - return boardId; - } -); - -type BoardUpdatedArg = Parameters<(typeof BoardsService)['updateBoard']>[0]; - -export const boardUpdated = createAppAsyncThunk( - 'api/boardUpdated', - async (arg: BoardUpdatedArg) => { - const response = await BoardsService.updateBoard(arg); - return response; - } -); - -type ImageAddedToBoardArg = Parameters< - (typeof BoardsService)['createBoardImage'] ->[0]['requestBody']; - -export const imageAddedToBoard = createAppAsyncThunk( - 'api/imageAddedToBoard', - async (arg: ImageAddedToBoardArg) => { - const response = await BoardsService.createBoardImage(arg); - return response; - } -); From 285195bf72c788406829277c541d8ea2f535a74d Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 12:41:41 +1000 Subject: [PATCH 67/99] feat(api): add `get_board` route --- invokeai/app/api/routers/boards.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index a6f226fef8..55cd7c8ca2 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -30,6 +30,19 @@ async def create_board( raise HTTPException(status_code=500, detail="Failed to create board") +@boards_router.get("/{board_id}", operation_id="get_board", response_model=BoardDTO) +async def get_board( + board_id: str = Path(description="The id of board to get"), +) -> BoardDTO: + """Gets a board""" + + try: + result = ApiDependencies.invoker.services.boards.get_dto(board_id=board_id) + return result + except Exception as e: + raise HTTPException(status_code=404, detail="Board not found") + + @boards_router.patch( "/{board_id}", operation_id="update_board", From 19a6e5dad877e67ed938361c6de178a34778faf2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 12:42:02 +1000 Subject: [PATCH 68/99] chore(ui): regen api client --- .../services/api/services/BoardsService.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/invokeai/frontend/web/src/services/api/services/BoardsService.ts b/invokeai/frontend/web/src/services/api/services/BoardsService.ts index bda2b70e75..236c765cb9 100644 --- a/invokeai/frontend/web/src/services/api/services/BoardsService.ts +++ b/invokeai/frontend/web/src/services/api/services/BoardsService.ts @@ -78,6 +78,32 @@ export class BoardsService { }); } + /** + * Get Board + * Gets a board + * @returns BoardDTO Successful Response + * @throws ApiError + */ + public static getBoard({ + boardId, + }: { + /** + * The id of board to get + */ + boardId: string, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/boards/{board_id}', + path: { + 'board_id': boardId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** * Delete Board * Deletes a board From 6779f1a5ad2f669f3976249f32d8e8a739b1a23c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:11:25 +1000 Subject: [PATCH 69/99] fix(db): update models for boards w/ nullable `deleted_at` --- invokeai/app/services/board_image_record_storage.py | 4 ++-- invokeai/app/services/models/board_record.py | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index ae2f1a9895..7aff41860c 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -93,10 +93,10 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- updated via trigger updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), - -- enforce one-to-many relationship between boards and images using PK - -- (we can extend this to many-to-many later) -- Soft delete, currently unused deleted_at DATETIME, + -- enforce one-to-many relationship between boards and images using PK + -- (we can extend this to many-to-many later) PRIMARY KEY (image_name), FOREIGN KEY (board_id) REFERENCES boards (board_id) ON DELETE CASCADE, FOREIGN KEY (image_name) REFERENCES images (image_name) ON DELETE CASCADE diff --git a/invokeai/app/services/models/board_record.py b/invokeai/app/services/models/board_record.py index 325ddd094e..bf5401b209 100644 --- a/invokeai/app/services/models/board_record.py +++ b/invokeai/app/services/models/board_record.py @@ -19,6 +19,10 @@ class BoardRecord(BaseModel): description="The updated timestamp of the board." ) """The updated timestamp of the image.""" + deleted_at: Union[datetime, str, None] = Field( + description="The deleted timestamp of the board." + ) + """The updated timestamp of the image.""" cover_image_name: Optional[str] = Field( description="The name of the cover image of the board." ) @@ -46,7 +50,7 @@ def deserialize_board_record(board_dict: dict) -> BoardRecord: cover_image_name = board_dict.get("cover_image_name", "unknown") created_at = board_dict.get("created_at", get_iso_timestamp()) updated_at = board_dict.get("updated_at", get_iso_timestamp()) - # deleted_at = board_dict.get("deleted_at", get_iso_timestamp()) + deleted_at = board_dict.get("deleted_at", get_iso_timestamp()) return BoardRecord( board_id=board_id, @@ -54,5 +58,5 @@ def deserialize_board_record(board_dict: dict) -> BoardRecord: cover_image_name=cover_image_name, created_at=created_at, updated_at=updated_at, - # deleted_at=deleted_at, + deleted_at=deleted_at, ) From 2d889e133ddff03d4745df52a66fab69eca6c2d5 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:11:47 +1000 Subject: [PATCH 70/99] chore(ui): regen api client --- invokeai/frontend/web/src/services/api/models/BoardDTO.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/invokeai/frontend/web/src/services/api/models/BoardDTO.ts b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts index ee3c29a797..bbcc6f1dd6 100644 --- a/invokeai/frontend/web/src/services/api/models/BoardDTO.ts +++ b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts @@ -22,6 +22,10 @@ export type BoardDTO = { * The updated timestamp of the board. */ updated_at: string; + /** + * The deleted timestamp of the board. + */ + deleted_at?: string; /** * The name of the board's cover image. */ From 9838dda1b76f0bd6c347ceab663ad2385e528537 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sat, 17 Jun 2023 21:14:37 +1200 Subject: [PATCH 71/99] chore: Update model config type names --- .../model_management/models/controlnet.py | 4 ++-- .../backend/model_management/models/lora.py | 2 +- .../models/stable_diffusion.py | 18 +++++++++--------- .../models/textual_inversion.py | 2 +- .../backend/model_management/models/vae.py | 3 ++- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/invokeai/backend/model_management/models/controlnet.py b/invokeai/backend/model_management/models/controlnet.py index d75c55010a..687afbffbd 100644 --- a/invokeai/backend/model_management/models/controlnet.py +++ b/invokeai/backend/model_management/models/controlnet.py @@ -18,7 +18,7 @@ class ControlNetModel(ModelBase): #model_class: Type #model_size: int - class Config(ModelConfigBase): + class ControlNetModelConfig(ModelConfigBase): format: Union[Literal["checkpoint"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): @@ -82,6 +82,6 @@ class ControlNetModel(ModelBase): base_model: BaseModelType, ) -> str: if cls.detect_format(model_path) != "diffusers": - raise NotImlemetedError("Checkpoint controlnet models currently unsupported") + raise NotImplementedError("Checkpoint controlnet models currently unsupported") else: return model_path diff --git a/invokeai/backend/model_management/models/lora.py b/invokeai/backend/model_management/models/lora.py index bcf3224ece..60865817b9 100644 --- a/invokeai/backend/model_management/models/lora.py +++ b/invokeai/backend/model_management/models/lora.py @@ -15,7 +15,7 @@ from ..lora import LoRAModel as LoRAModelRaw class LoRAModel(ModelBase): #model_size: int - class Config(ModelConfigBase): + class LoraModelConfig(ModelConfigBase): format: Union[Literal["lycoris"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): diff --git a/invokeai/backend/model_management/models/stable_diffusion.py b/invokeai/backend/model_management/models/stable_diffusion.py index bd519c88c8..9856069ea5 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -22,12 +22,12 @@ from omegaconf import OmegaConf class StableDiffusion1Model(DiffusersModel): - class DiffusersConfig(ModelConfigBase): + class StableDiffusion1DiffusersModelConfig(ModelConfigBase): format: Literal["diffusers"] vae: Optional[str] = Field(None) variant: ModelVariantType - class CheckpointConfig(ModelConfigBase): + class StableDiffusion1CheckpointModelConfig(ModelConfigBase): format: Literal["checkpoint"] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) @@ -107,7 +107,7 @@ class StableDiffusion1Model(DiffusersModel): ) -> str: assert model_path == config.path - if isinstance(config, cls.CheckpointConfig): + if isinstance(config, cls.CheckpointModelConfig): return _convert_ckpt_and_cache( version=BaseModelType.StableDiffusion1, model_config=config, @@ -120,14 +120,14 @@ class StableDiffusion1Model(DiffusersModel): class StableDiffusion2Model(DiffusersModel): # TODO: check that configs overwriten properly - class DiffusersConfig(ModelConfigBase): + class StableDiffusion2DiffusersModelConfig(ModelConfigBase): format: Literal["diffusers"] vae: Optional[str] = Field(None) variant: ModelVariantType prediction_type: SchedulerPredictionType upcast_attention: bool - class CheckpointConfig(ModelConfigBase): + class StableDiffusion2CheckpointModelConfig(ModelConfigBase): format: Literal["checkpoint"] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) @@ -220,7 +220,7 @@ class StableDiffusion2Model(DiffusersModel): ) -> str: assert model_path == config.path - if isinstance(config, cls.CheckpointConfig): + if isinstance(config, cls.CheckpointModelConfig): return _convert_ckpt_and_cache( version=BaseModelType.StableDiffusion2, model_config=config, @@ -256,7 +256,7 @@ def _select_ckpt_config(version: BaseModelType, variant: ModelVariantType): # TODO: rework def _convert_ckpt_and_cache( version: BaseModelType, - model_config: Union[StableDiffusion1Model.CheckpointConfig, StableDiffusion2Model.CheckpointConfig], + model_config: Union[StableDiffusion1Model.StableDiffusion1CheckpointModelConfig, StableDiffusion2Model.StableDiffusion2CheckpointModelConfig], output_path: str, ) -> str: """ @@ -281,8 +281,8 @@ def _convert_ckpt_and_cache( prediction_type = SchedulerPredictionType.Epsilon elif version == BaseModelType.StableDiffusion2: - upcast_attention = config.upcast_attention - prediction_type = config.prediction_type + upcast_attention = model_config.upcast_attention + prediction_type = model_config.prediction_type else: raise Exception(f"Unknown model provided: {version}") diff --git a/invokeai/backend/model_management/models/textual_inversion.py b/invokeai/backend/model_management/models/textual_inversion.py index 66847f53eb..453a8ad671 100644 --- a/invokeai/backend/model_management/models/textual_inversion.py +++ b/invokeai/backend/model_management/models/textual_inversion.py @@ -15,7 +15,7 @@ from ..lora import TextualInversionModel as TextualInversionModelRaw class TextualInversionModel(ModelBase): #model_size: int - class Config(ModelConfigBase): + class TextualInversionModelConfig(ModelConfigBase): format: None def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): diff --git a/invokeai/backend/model_management/models/vae.py b/invokeai/backend/model_management/models/vae.py index 1edb57ccc4..f285648323 100644 --- a/invokeai/backend/model_management/models/vae.py +++ b/invokeai/backend/model_management/models/vae.py @@ -1,5 +1,6 @@ import os import torch +import safetensors from pathlib import Path from typing import Optional, Union, Literal from .base import ( @@ -22,7 +23,7 @@ class VaeModel(ModelBase): #vae_class: Type #model_size: int - class Config(ModelConfigBase): + class VAEModelConfig(ModelConfigBase): format: Union[Literal["checkpoint"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): From 663f4935f58c9097186288a82ae7ed76aefe9141 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sat, 17 Jun 2023 21:29:32 +1200 Subject: [PATCH 72/99] chore: Rebuild API --- .../frontend/web/src/services/api/index.ts | 20 ++ .../src/services/api/models/AddInvocation.ts | 1 - .../services/api/models/Body_upload_image.ts | 1 - .../models/CannyImageProcessorInvocation.ts | 1 - .../src/services/api/models/CkptModelInfo.ts | 1 - .../web/src/services/api/models/ClipField.ts | 3 + .../services/api/models/CollectInvocation.ts | 1 - .../api/models/CollectInvocationOutput.ts | 1 - .../web/src/services/api/models/ColorField.ts | 1 - .../services/api/models/CompelInvocation.ts | 1 - .../src/services/api/models/CompelOutput.ts | 1 - .../services/api/models/ConditioningField.ts | 1 - .../ContentShuffleImageProcessorInvocation.ts | 1 - .../src/services/api/models/ControlField.ts | 1 - .../api/models/ControlNetInvocation.ts | 1 - .../api/models/ControlNetModelConfig.ts | 13 ++ .../src/services/api/models/ControlOutput.ts | 1 - .../services/api/models/CreateModelRequest.ts | 1 - .../api/models/CvInpaintInvocation.ts | 1 - .../services/api/models/DiffusersModelInfo.ts | 1 - .../services/api/models/DivideInvocation.ts | 1 - .../api/models/DynamicPromptInvocation.ts | 1 - .../web/src/services/api/models/Edge.ts | 1 - .../src/services/api/models/EdgeConnection.ts | 1 - .../api/models/FloatCollectionOutput.ts | 1 - .../api/models/FloatLinearRangeInvocation.ts | 1 - .../src/services/api/models/FloatOutput.ts | 1 - .../web/src/services/api/models/Graph.ts | 5 +- .../api/models/GraphExecutionState.ts | 5 +- .../services/api/models/GraphInvocation.ts | 1 - .../api/models/GraphInvocationOutput.ts | 1 - .../api/models/HTTPValidationError.ts | 1 - .../api/models/HedImageProcessorInvocation.ts | 1 - .../api/models/ImageBlurInvocation.ts | 1 - .../api/models/ImageChannelInvocation.ts | 1 - .../api/models/ImageConvertInvocation.ts | 1 - .../api/models/ImageCropInvocation.ts | 1 - .../web/src/services/api/models/ImageDTO.ts | 1 - .../web/src/services/api/models/ImageField.ts | 1 - .../api/models/ImageInverseLerpInvocation.ts | 1 - .../api/models/ImageLerpInvocation.ts | 1 - .../src/services/api/models/ImageMetadata.ts | 1 - .../api/models/ImageMultiplyInvocation.ts | 1 - .../src/services/api/models/ImageOutput.ts | 1 - .../api/models/ImagePasteInvocation.ts | 1 - .../api/models/ImageProcessorInvocation.ts | 1 - .../services/api/models/ImageRecordChanges.ts | 1 - .../api/models/ImageResizeInvocation.ts | 1 - .../api/models/ImageScaleInvocation.ts | 1 - .../api/models/ImageToImageInvocation.ts | 76 ++++++ .../api/models/ImageToLatentsInvocation.ts | 1 - .../src/services/api/models/ImageUrlsDTO.ts | 1 - .../api/models/InfillColorInvocation.ts | 1 - .../api/models/InfillPatchMatchInvocation.ts | 1 - .../api/models/InfillTileInvocation.ts | 1 - .../services/api/models/InpaintInvocation.ts | 1 - .../api/models/IntCollectionOutput.ts | 1 - .../web/src/services/api/models/IntOutput.ts | 1 - .../services/api/models/IterateInvocation.ts | 1 - .../api/models/IterateInvocationOutput.ts | 1 - .../src/services/api/models/LatentsField.ts | 1 - .../src/services/api/models/LatentsOutput.ts | 1 - .../api/models/LatentsToImageInvocation.ts | 1 - .../api/models/LatentsToLatentsInvocation.ts | 1 - .../LineartAnimeImageProcessorInvocation.ts | 1 - .../models/LineartImageProcessorInvocation.ts | 1 - .../api/models/LoadImageInvocation.ts | 1 - .../web/src/services/api/models/LoraInfo.ts | 3 + .../api/models/LoraLoaderInvocation.ts | 3 + .../services/api/models/LoraLoaderOutput.ts | 3 + .../services/api/models/LoraModelConfig.ts | 13 ++ .../api/models/MaskFromAlphaInvocation.ts | 1 - .../web/src/services/api/models/MaskOutput.ts | 1 - .../MediapipeFaceProcessorInvocation.ts | 1 - .../MidasDepthImageProcessorInvocation.ts | 1 - .../models/MlsdImageProcessorInvocation.ts | 1 - .../web/src/services/api/models/ModelInfo.ts | 3 + .../services/api/models/ModelLoaderOutput.ts | 3 + .../web/src/services/api/models/ModelsList.ts | 15 +- .../services/api/models/MultiplyInvocation.ts | 1 - .../services/api/models/NoiseInvocation.ts | 1 - .../src/services/api/models/NoiseOutput.ts | 1 - .../NormalbaeImageProcessorInvocation.ts | 1 - .../OffsetPaginatedResults_ImageDTO_.ts | 1 - .../OpenposeImageProcessorInvocation.ts | 1 - .../PaginatedResults_GraphExecutionState_.ts | 1 - .../api/models/ParamFloatInvocation.ts | 1 - .../services/api/models/ParamIntInvocation.ts | 1 - .../models/PidiImageProcessorInvocation.ts | 1 - .../api/models/PromptCollectionOutput.ts | 1 - .../src/services/api/models/PromptOutput.ts | 1 - .../api/models/RandomIntInvocation.ts | 1 - .../api/models/RandomRangeInvocation.ts | 1 - .../services/api/models/RangeInvocation.ts | 1 - .../api/models/RangeOfSizeInvocation.ts | 1 - .../api/models/ResizeLatentsInvocation.ts | 1 - .../api/models/RestoreFaceInvocation.ts | 1 - .../api/models/SD1ModelLoaderInvocation.ts | 3 + .../api/models/SD2ModelLoaderInvocation.ts | 3 + .../api/models/ScaleLatentsInvocation.ts | 1 - .../api/models/ShowImageInvocation.ts | 1 - .../StableDiffusion1CheckpointModelConfig.ts | 17 ++ .../StableDiffusion1DiffusersModelConfig.ts | 16 ++ .../StableDiffusion2CheckpointModelConfig.ts | 20 ++ .../StableDiffusion2DiffusersModelConfig.ts | 19 ++ .../api/models/StepParamEasingInvocation.ts | 1 - .../services/api/models/SubtractInvocation.ts | 1 - .../api/models/TextToImageInvocation.ts | 64 +++++ .../api/models/TextToLatentsInvocation.ts | 1 - .../api/models/TextualInversionModelConfig.ts | 13 ++ .../web/src/services/api/models/UNetField.ts | 3 + .../services/api/models/UpscaleInvocation.ts | 1 - .../src/services/api/models/VAEModelConfig.ts | 13 ++ .../web/src/services/api/models/VaeField.ts | 3 + .../web/src/services/api/models/VaeRepo.ts | 1 - .../services/api/models/ValidationError.ts | 1 - .../ZoeDepthImageProcessorInvocation.ts | 1 - .../services/api/services/ImagesService.ts | 156 ++++++++----- .../services/api/services/ModelsService.ts | 31 ++- .../services/api/services/SessionsService.ts | 219 ++++++++++-------- 120 files changed, 576 insertions(+), 262 deletions(-) create mode 100644 invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index acb7a411f7..f3aec17eb6 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -8,10 +8,13 @@ export type { OpenAPIConfig } from './core/OpenAPI'; export type { AddInvocation } from './models/AddInvocation'; export type { BaseModelType } from './models/BaseModelType'; +<<<<<<< HEAD export type { BoardChanges } from './models/BoardChanges'; export type { BoardDTO } from './models/BoardDTO'; export type { Body_create_board_image } from './models/Body_create_board_image'; export type { Body_remove_board_image } from './models/Body_remove_board_image'; +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) export type { Body_upload_image } from './models/Body_upload_image'; export type { CannyImageProcessorInvocation } from './models/CannyImageProcessorInvocation'; export type { CkptModelInfo } from './models/CkptModelInfo'; @@ -25,6 +28,7 @@ export type { ConditioningField } from './models/ConditioningField'; export type { ContentShuffleImageProcessorInvocation } from './models/ContentShuffleImageProcessorInvocation'; export type { ControlField } from './models/ControlField'; export type { ControlNetInvocation } from './models/ControlNetInvocation'; +export type { ControlNetModelConfig } from './models/ControlNetModelConfig'; export type { ControlOutput } from './models/ControlOutput'; export type { CreateModelRequest } from './models/CreateModelRequest'; export type { CvInpaintInvocation } from './models/CvInpaintInvocation'; @@ -87,6 +91,10 @@ export type { LoadImageInvocation } from './models/LoadImageInvocation'; export type { LoraInfo } from './models/LoraInfo'; export type { LoraLoaderInvocation } from './models/LoraLoaderInvocation'; export type { LoraLoaderOutput } from './models/LoraLoaderOutput'; +<<<<<<< HEAD +======= +export type { LoraModelConfig } from './models/LoraModelConfig'; +>>>>>>> 76dd749b1 (chore: Rebuild API) export type { MaskFromAlphaInvocation } from './models/MaskFromAlphaInvocation'; export type { MaskOutput } from './models/MaskOutput'; export type { MediapipeFaceProcessorInvocation } from './models/MediapipeFaceProcessorInvocation'; @@ -123,13 +131,25 @@ export type { SchedulerPredictionType } from './models/SchedulerPredictionType'; export type { SD1ModelLoaderInvocation } from './models/SD1ModelLoaderInvocation'; export type { SD2ModelLoaderInvocation } from './models/SD2ModelLoaderInvocation'; export type { ShowImageInvocation } from './models/ShowImageInvocation'; +export type { StableDiffusion1CheckpointModelConfig } from './models/StableDiffusion1CheckpointModelConfig'; +export type { StableDiffusion1DiffusersModelConfig } from './models/StableDiffusion1DiffusersModelConfig'; +export type { StableDiffusion2CheckpointModelConfig } from './models/StableDiffusion2CheckpointModelConfig'; +export type { StableDiffusion2DiffusersModelConfig } from './models/StableDiffusion2DiffusersModelConfig'; export type { StepParamEasingInvocation } from './models/StepParamEasingInvocation'; export type { SubModelType } from './models/SubModelType'; export type { SubtractInvocation } from './models/SubtractInvocation'; export type { TextToLatentsInvocation } from './models/TextToLatentsInvocation'; +<<<<<<< HEAD export type { UNetField } from './models/UNetField'; export type { UpscaleInvocation } from './models/UpscaleInvocation'; export type { VaeField } from './models/VaeField'; +======= +export type { TextualInversionModelConfig } from './models/TextualInversionModelConfig'; +export type { UNetField } from './models/UNetField'; +export type { UpscaleInvocation } from './models/UpscaleInvocation'; +export type { VaeField } from './models/VaeField'; +export type { VAEModelConfig } from './models/VAEModelConfig'; +>>>>>>> 76dd749b1 (chore: Rebuild API) export type { VaeRepo } from './models/VaeRepo'; export type { ValidationError } from './models/ValidationError'; export type { ZoeDepthImageProcessorInvocation } from './models/ZoeDepthImageProcessorInvocation'; diff --git a/invokeai/frontend/web/src/services/api/models/AddInvocation.ts b/invokeai/frontend/web/src/services/api/models/AddInvocation.ts index e9671a918f..b7c1c88187 100644 --- a/invokeai/frontend/web/src/services/api/models/AddInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/AddInvocation.ts @@ -24,4 +24,3 @@ export type AddInvocation = { */ 'b'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts b/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts index b81146d3ab..fd26ed49e0 100644 --- a/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts +++ b/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts @@ -5,4 +5,3 @@ export type Body_upload_image = { file: Blob; }; - diff --git a/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts index d5203867ac..3f1a5b3d46 100644 --- a/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts @@ -30,4 +30,3 @@ export type CannyImageProcessorInvocation = { */ high_threshold?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts b/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts index cfa4357725..464474736f 100644 --- a/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts @@ -37,4 +37,3 @@ export type CkptModelInfo = { */ height?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ClipField.ts b/invokeai/frontend/web/src/services/api/models/ClipField.ts index f9ef2cc683..f267fe85d9 100644 --- a/invokeai/frontend/web/src/services/api/models/ClipField.ts +++ b/invokeai/frontend/web/src/services/api/models/ClipField.ts @@ -19,4 +19,7 @@ export type ClipField = { */ loras: Array; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts b/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts index f190ab7073..a0fe38613c 100644 --- a/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts @@ -24,4 +24,3 @@ export type CollectInvocation = { */ collection?: Array; }; - diff --git a/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts index a5976242ea..62c785f374 100644 --- a/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts @@ -12,4 +12,3 @@ export type CollectInvocationOutput = { */ collection: Array; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ColorField.ts b/invokeai/frontend/web/src/services/api/models/ColorField.ts index e0a609ec12..25167433d4 100644 --- a/invokeai/frontend/web/src/services/api/models/ColorField.ts +++ b/invokeai/frontend/web/src/services/api/models/ColorField.ts @@ -20,4 +20,3 @@ export type ColorField = { */ 'a': number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts b/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts index dd381ef22c..642e11023b 100644 --- a/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts @@ -26,4 +26,3 @@ export type CompelInvocation = { */ clip?: ClipField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/CompelOutput.ts b/invokeai/frontend/web/src/services/api/models/CompelOutput.ts index 94f1fcb282..b42ab73c74 100644 --- a/invokeai/frontend/web/src/services/api/models/CompelOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/CompelOutput.ts @@ -14,4 +14,3 @@ export type CompelOutput = { */ conditioning?: ConditioningField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ConditioningField.ts b/invokeai/frontend/web/src/services/api/models/ConditioningField.ts index 7e53a63b42..da227dd4f9 100644 --- a/invokeai/frontend/web/src/services/api/models/ConditioningField.ts +++ b/invokeai/frontend/web/src/services/api/models/ConditioningField.ts @@ -8,4 +8,3 @@ export type ConditioningField = { */ conditioning_name: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts index e3f67ec9be..43f6100db8 100644 --- a/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts @@ -42,4 +42,3 @@ export type ContentShuffleImageProcessorInvocation = { */ 'f'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ControlField.ts b/invokeai/frontend/web/src/services/api/models/ControlField.ts index 0479684d2c..8de332b82d 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlField.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlField.ts @@ -26,4 +26,3 @@ export type ControlField = { */ end_step_percent: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts b/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts index 42268b8295..31e22a1bba 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts @@ -38,4 +38,3 @@ export type ControlNetInvocation = { */ end_step_percent?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts b/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts new file mode 100644 index 0000000000..109fc39b7d --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; + +export type ControlNetModelConfig = { + path: string; + description?: string; + format: ('checkpoint' | 'diffusers'); + default?: boolean; + error?: ModelError; +}; diff --git a/invokeai/frontend/web/src/services/api/models/ControlOutput.ts b/invokeai/frontend/web/src/services/api/models/ControlOutput.ts index a3cc5530c1..625d8f670d 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlOutput.ts @@ -14,4 +14,3 @@ export type ControlOutput = { */ control?: ControlField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts b/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts index 0b0f52b8fe..80976f126f 100644 --- a/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts +++ b/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts @@ -15,4 +15,3 @@ export type CreateModelRequest = { */ info: (CkptModelInfo | DiffusersModelInfo); }; - diff --git a/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts index 874df93c30..4f1c33483e 100644 --- a/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts @@ -26,4 +26,3 @@ export type CvInpaintInvocation = { */ mask?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts b/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts index 4e722ddb80..ea175e351a 100644 --- a/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts @@ -31,4 +31,3 @@ export type DiffusersModelInfo = { */ path?: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts b/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts index fd5b3475ae..5b37dea710 100644 --- a/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts @@ -24,4 +24,3 @@ export type DivideInvocation = { */ 'b'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts b/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts index f7323a489b..79dc958d0f 100644 --- a/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts @@ -28,4 +28,3 @@ export type DynamicPromptInvocation = { */ combinatorial?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/Edge.ts b/invokeai/frontend/web/src/services/api/models/Edge.ts index bba275cb26..e72108f74a 100644 --- a/invokeai/frontend/web/src/services/api/models/Edge.ts +++ b/invokeai/frontend/web/src/services/api/models/Edge.ts @@ -14,4 +14,3 @@ export type Edge = { */ destination: EdgeConnection; }; - diff --git a/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts b/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts index ecbddccd76..ab4c6d354b 100644 --- a/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts +++ b/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts @@ -12,4 +12,3 @@ export type EdgeConnection = { */ field: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts b/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts index a3f08247a4..fb9e4164e6 100644 --- a/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts @@ -12,4 +12,3 @@ export type FloatCollectionOutput = { */ collection?: Array; }; - diff --git a/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts index e0fd4a1caa..a9e67d8135 100644 --- a/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts @@ -28,4 +28,3 @@ export type FloatLinearRangeInvocation = { */ steps?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/FloatOutput.ts b/invokeai/frontend/web/src/services/api/models/FloatOutput.ts index 2331936b30..db2784ed9f 100644 --- a/invokeai/frontend/web/src/services/api/models/FloatOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/FloatOutput.ts @@ -12,4 +12,3 @@ export type FloatOutput = { */ param?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index e148954f16..3f51247701 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -73,10 +73,13 @@ export type Graph = { /** * The nodes in this graph */ +<<<<<<< HEAD nodes?: Record; +======= + nodes?: Record; +>>>>>>> 76dd749b1 (chore: Rebuild API) /** * The connections between nodes and their fields in this graph */ edges?: Array; }; - diff --git a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts index 602e7a2ebc..24ec8c9d6d 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts @@ -48,7 +48,11 @@ export type GraphExecutionState = { /** * The results of node executions */ +<<<<<<< HEAD results: Record; +======= + results: Record; +>>>>>>> 76dd749b1 (chore: Rebuild API) /** * Errors raised when executing nodes */ @@ -62,4 +66,3 @@ export type GraphExecutionState = { */ source_prepared_mapping: Record>; }; - diff --git a/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts b/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts index 8512faae74..a7e3d6c948 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts @@ -22,4 +22,3 @@ export type GraphInvocation = { */ graph?: Graph; }; - diff --git a/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts index af0aae3edb..219a4a675d 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts @@ -8,4 +8,3 @@ export type GraphInvocationOutput = { type: 'graph_output'; }; - diff --git a/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts b/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts index 5e13adc4e5..69908c3bba 100644 --- a/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts +++ b/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts @@ -7,4 +7,3 @@ import type { ValidationError } from './ValidationError'; export type HTTPValidationError = { detail?: Array; }; - diff --git a/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts index 1132012c5a..387e8c8634 100644 --- a/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts @@ -34,4 +34,3 @@ export type HedImageProcessorInvocation = { */ scribble?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts index 3ba86d8fab..6466efcd82 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts @@ -30,4 +30,3 @@ export type ImageBlurInvocation = { */ blur_type?: 'gaussian' | 'box'; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts index 47bfd4110f..d6abae5eae 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts @@ -26,4 +26,3 @@ export type ImageChannelInvocation = { */ channel?: 'A' | 'R' | 'G' | 'B'; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts index 4bd59d03b0..d303c7ce79 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts @@ -26,4 +26,3 @@ export type ImageConvertInvocation = { */ mode?: 'L' | 'RGB' | 'RGBA' | 'CMYK' | 'YCbCr' | 'LAB' | 'HSV' | 'I' | 'F'; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts index 5207ebbf6d..e29e177aa7 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts @@ -38,4 +38,3 @@ export type ImageCropInvocation = { */ height?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageDTO.ts b/invokeai/frontend/web/src/services/api/models/ImageDTO.ts index 4e273e8854..1e0ea0648f 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageDTO.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageDTO.ts @@ -71,4 +71,3 @@ export type ImageDTO = { */ board_id?: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageField.ts b/invokeai/frontend/web/src/services/api/models/ImageField.ts index baf3bf2b54..c4c67a0674 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageField.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageField.ts @@ -11,4 +11,3 @@ export type ImageField = { */ image_name: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts index 0347d4dc38..63220c8047 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts @@ -30,4 +30,3 @@ export type ImageInverseLerpInvocation = { */ max?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts index 388c86061c..444c7e6467 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts @@ -30,4 +30,3 @@ export type ImageLerpInvocation = { */ max?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts b/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts index 0b2af78799..8aecdddc4f 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts @@ -78,4 +78,3 @@ export type ImageMetadata = { */ extra?: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts index 751ee49158..724061fce8 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts @@ -26,4 +26,3 @@ export type ImageMultiplyInvocation = { */ image2?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageOutput.ts b/invokeai/frontend/web/src/services/api/models/ImageOutput.ts index d7db0c11de..c030632926 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageOutput.ts @@ -22,4 +22,3 @@ export type ImageOutput = { */ height: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts index c883b9a5d8..5af28452b6 100644 --- a/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts @@ -38,4 +38,3 @@ export type ImagePasteInvocation = { */ 'y'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts index 0d995c4e68..e0058a78ca 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts @@ -22,4 +22,3 @@ export type ImageProcessorInvocation = { */ image?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts b/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts index e597cd907d..209b6d8e88 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts @@ -26,4 +26,3 @@ export type ImageRecordChanges = { */ is_intermediate?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts index 3b096c83b7..f277516ccd 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts @@ -34,4 +34,3 @@ export type ImageResizeInvocation = { */ resample_mode?: 'nearest' | 'box' | 'bilinear' | 'hamming' | 'bicubic' | 'lanczos'; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts index bf4da28a4a..709e1cc66a 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts @@ -30,4 +30,3 @@ export type ImageScaleInvocation = { */ resample_mode?: 'nearest' | 'box' | 'bilinear' | 'hamming' | 'bicubic' | 'lanczos'; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts new file mode 100644 index 0000000000..faa9d164a8 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts @@ -0,0 +1,76 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ImageField } from './ImageField'; + +/** + * Generates an image using img2img. + */ +export type ImageToImageInvocation = { + /** + * The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Whether or not this node is an intermediate node. + */ + is_intermediate?: boolean; + type?: 'img2img'; + /** + * The prompt to generate an image from + */ + prompt?: string; + /** + * The seed to use (omit for random) + */ + seed?: number; + /** + * The number of steps to use to generate the image + */ + steps?: number; + /** + * The width of the resulting image + */ + width?: number; + /** + * The height of the resulting image + */ + height?: number; + /** + * The Classifier-Free Guidance, higher values may result in a result closer to the prompt + */ + cfg_scale?: number; + /** + * The scheduler to use + */ + scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; + /** + * The model to use (currently ignored) + */ + model?: string; + /** + * Whether or not to produce progress images during generation + */ + progress_images?: boolean; + /** + * The control model to use + */ + control_model?: string; + /** + * The processed control image + */ + control_image?: ImageField; + /** + * The input image + */ + image?: ImageField; + /** + * The strength of the original image + */ + strength?: number; + /** + * Whether or not the result should be fit to the aspect ratio of the input image + */ + fit?: boolean; +}; diff --git a/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts index ace0ed8e3c..65df90351a 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts @@ -31,4 +31,3 @@ export type ImageToLatentsInvocation = { */ tiled?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts b/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts index 1e0ff322e8..ad45d2047e 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts @@ -19,4 +19,3 @@ export type ImageUrlsDTO = { */ thumbnail_url: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts index 3e637b299c..6d60bbe226 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts @@ -27,4 +27,3 @@ export type InfillColorInvocation = { */ color?: ColorField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts index 325bfe2080..bf6e8012b7 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts @@ -22,4 +22,3 @@ export type InfillPatchMatchInvocation = { */ image?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts index dfb1cbc61d..551e00da16 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts @@ -30,4 +30,3 @@ export type InfillTileInvocation = { */ seed?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts index 8fb9ad3d54..5e7d3f4b92 100644 --- a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts @@ -118,4 +118,3 @@ export type InpaintInvocation = { */ inpaint_replace?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts b/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts index 93a115f980..1e60ee8009 100644 --- a/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts @@ -12,4 +12,3 @@ export type IntCollectionOutput = { */ collection?: Array; }; - diff --git a/invokeai/frontend/web/src/services/api/models/IntOutput.ts b/invokeai/frontend/web/src/services/api/models/IntOutput.ts index eeea6c68b4..58655d0858 100644 --- a/invokeai/frontend/web/src/services/api/models/IntOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/IntOutput.ts @@ -12,4 +12,3 @@ export type IntOutput = { */ 'a'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts b/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts index 15bf92dfea..b6a70156c3 100644 --- a/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts @@ -24,4 +24,3 @@ export type IterateInvocation = { */ index?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts index ce8d9f8c4b..2eeffd05fd 100644 --- a/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts @@ -12,4 +12,3 @@ export type IterateInvocationOutput = { */ item: any; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LatentsField.ts b/invokeai/frontend/web/src/services/api/models/LatentsField.ts index bc6a525f7c..e7446e9cb3 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsField.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsField.ts @@ -11,4 +11,3 @@ export type LatentsField = { */ latents_name: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts b/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts index 3e9c2f60e4..edf388dbf6 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts @@ -22,4 +22,3 @@ export type LatentsOutput = { */ height: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts index 865eeff554..1235962d8f 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts @@ -31,4 +31,3 @@ export type LatentsToImageInvocation = { */ tiled?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts index 4273115963..4f0f49d961 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts @@ -61,4 +61,3 @@ export type LatentsToLatentsInvocation = { */ strength?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts index 5d239536d5..7c655480c7 100644 --- a/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts @@ -30,4 +30,3 @@ export type LineartAnimeImageProcessorInvocation = { */ image_resolution?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts index 17720e689b..af3a527f3a 100644 --- a/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts @@ -34,4 +34,3 @@ export type LineartImageProcessorInvocation = { */ coarse?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts index f20d983f9b..469e7200ad 100644 --- a/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts @@ -22,4 +22,3 @@ export type LoadImageInvocation = { */ image?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LoraInfo.ts b/invokeai/frontend/web/src/services/api/models/LoraInfo.ts index 1a575d4147..d4499530ac 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraInfo.ts @@ -28,4 +28,7 @@ export type LoraInfo = { */ weight: number; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts index b93281c5a7..b448a7a8ad 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts @@ -35,4 +35,7 @@ export type LoraLoaderInvocation = { */ clip?: ClipField; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts b/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts index 1fed1ebc58..54e02d49e5 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts @@ -19,4 +19,7 @@ export type LoraLoaderOutput = { */ clip?: ClipField; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts b/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts new file mode 100644 index 0000000000..d5b3a02eff --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; + +export type LoraModelConfig = { + path: string; + description?: string; + format: ('lycoris' | 'diffusers'); + default?: boolean; + error?: ModelError; +}; diff --git a/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts b/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts index e3693f6d98..a031c0e05f 100644 --- a/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts @@ -26,4 +26,3 @@ export type MaskFromAlphaInvocation = { */ invert?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/MaskOutput.ts b/invokeai/frontend/web/src/services/api/models/MaskOutput.ts index d4594fe6e9..05d2b36d58 100644 --- a/invokeai/frontend/web/src/services/api/models/MaskOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/MaskOutput.ts @@ -22,4 +22,3 @@ export type MaskOutput = { */ height?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts index aa7b966b4b..76e89422e9 100644 --- a/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts @@ -30,4 +30,3 @@ export type MediapipeFaceProcessorInvocation = { */ min_confidence?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts index bd274228db..14cf26f075 100644 --- a/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts @@ -30,4 +30,3 @@ export type MidasDepthImageProcessorInvocation = { */ bg_th?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts index 0e81c9a4b8..b2a15b5861 100644 --- a/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts @@ -38,4 +38,3 @@ export type MlsdImageProcessorInvocation = { */ thr_d?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ModelInfo.ts b/invokeai/frontend/web/src/services/api/models/ModelInfo.ts index e87799d142..d11bb523c7 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelInfo.ts @@ -24,4 +24,7 @@ export type ModelInfo = { */ submodel?: SubModelType; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts b/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts index 5b5b51e71f..2599d650cb 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts @@ -24,4 +24,7 @@ export type ModelLoaderOutput = { */ vae?: VaeField; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index a2d88d1967..bd6e8bf4da 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ +<<<<<<< HEAD import type { invokeai__backend__model_management__models__controlnet__ControlNetModel__Config } from './invokeai__backend__model_management__models__controlnet__ControlNetModel__Config'; import type { invokeai__backend__model_management__models__lora__LoRAModel__Config } from './invokeai__backend__model_management__models__lora__LoRAModel__Config'; import type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig } from './invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig'; @@ -13,5 +14,17 @@ import type { invokeai__backend__model_management__models__vae__VaeModel__Config export type ModelsList = { models: Record>>; -}; +======= +import type { ControlNetModelConfig } from './ControlNetModelConfig'; +import type { LoraModelConfig } from './LoraModelConfig'; +import type { StableDiffusion1CheckpointModelConfig } from './StableDiffusion1CheckpointModelConfig'; +import type { StableDiffusion1DiffusersModelConfig } from './StableDiffusion1DiffusersModelConfig'; +import type { StableDiffusion2CheckpointModelConfig } from './StableDiffusion2CheckpointModelConfig'; +import type { StableDiffusion2DiffusersModelConfig } from './StableDiffusion2DiffusersModelConfig'; +import type { TextualInversionModelConfig } from './TextualInversionModelConfig'; +import type { VAEModelConfig } from './VAEModelConfig'; +export type ModelsList = { + models: Record>>; +>>>>>>> 76dd749b1 (chore: Rebuild API) +}; diff --git a/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts b/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts index 9fd716f33d..6a3b17feea 100644 --- a/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts @@ -24,4 +24,3 @@ export type MultiplyInvocation = { */ 'b'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts b/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts index 239a24bfe5..22846f5345 100644 --- a/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts @@ -28,4 +28,3 @@ export type NoiseInvocation = { */ height?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts b/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts index f1832d7aa2..cb1b13ef25 100644 --- a/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts @@ -22,4 +22,3 @@ export type NoiseOutput = { */ height: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts index 400068171e..29fcebf567 100644 --- a/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts @@ -30,4 +30,3 @@ export type NormalbaeImageProcessorInvocation = { */ image_resolution?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts index 3408bea6db..2b22b247b4 100644 --- a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts +++ b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts @@ -25,4 +25,3 @@ export type OffsetPaginatedResults_ImageDTO_ = { */ total: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts index 982ce8ade7..80c136546d 100644 --- a/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts @@ -34,4 +34,3 @@ export type OpenposeImageProcessorInvocation = { */ image_resolution?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts b/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts index dd9f50cd4a..cb5914f677 100644 --- a/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts +++ b/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts @@ -29,4 +29,3 @@ export type PaginatedResults_GraphExecutionState_ = { */ total: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts b/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts index 87c01f847f..4e9087237b 100644 --- a/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts @@ -20,4 +20,3 @@ export type ParamFloatInvocation = { */ param?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts b/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts index 7a45d0a0ac..f47c3b8f01 100644 --- a/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts @@ -20,4 +20,3 @@ export type ParamIntInvocation = { */ 'a'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts index 91c9dc0ce5..96433218f7 100644 --- a/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts @@ -38,4 +38,3 @@ export type PidiImageProcessorInvocation = { */ scribble?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts b/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts index 4444ab4d33..ffab4ca09a 100644 --- a/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts @@ -16,4 +16,3 @@ export type PromptCollectionOutput = { */ count: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/PromptOutput.ts b/invokeai/frontend/web/src/services/api/models/PromptOutput.ts index 5bca3f3037..f9dcfd1b08 100644 --- a/invokeai/frontend/web/src/services/api/models/PromptOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/PromptOutput.ts @@ -12,4 +12,3 @@ export type PromptOutput = { */ prompt: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts b/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts index a2f7c2f02a..c4fa84cc8e 100644 --- a/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts @@ -24,4 +24,3 @@ export type RandomIntInvocation = { */ high?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts index 925511578d..5625324a1e 100644 --- a/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts @@ -32,4 +32,3 @@ export type RandomRangeInvocation = { */ seed?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts index 3681602a95..5292d32156 100644 --- a/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts @@ -28,4 +28,3 @@ export type RangeInvocation = { */ step?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts index 7dfac68d39..d97826099a 100644 --- a/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts @@ -28,4 +28,3 @@ export type RangeOfSizeInvocation = { */ step?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts index 9a7b6c61e4..500514f3c9 100644 --- a/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts @@ -38,4 +38,3 @@ export type ResizeLatentsInvocation = { */ antialias?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts b/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts index 0bacb5d805..5cfc165e23 100644 --- a/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts @@ -26,4 +26,3 @@ export type RestoreFaceInvocation = { */ strength?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts index 9a8a23077a..0f287be7bb 100644 --- a/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts @@ -20,4 +20,7 @@ export type SD1ModelLoaderInvocation = { */ model_name?: string; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts index f477c11a8d..5afc63a387 100644 --- a/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts @@ -20,4 +20,7 @@ export type SD2ModelLoaderInvocation = { */ model_name?: string; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts index 506b21e540..a65308dcba 100644 --- a/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts @@ -34,4 +34,3 @@ export type ScaleLatentsInvocation = { */ antialias?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts index 1b73055584..c6bceda651 100644 --- a/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts @@ -22,4 +22,3 @@ export type ShowImageInvocation = { */ image?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts new file mode 100644 index 0000000000..e9ed1bbfc2 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts @@ -0,0 +1,17 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; +import type { ModelVariantType } from './ModelVariantType'; + +export type StableDiffusion1CheckpointModelConfig = { + path: string; + description?: string; + format: 'checkpoint'; + default?: boolean; + error?: ModelError; + vae?: string; + config?: string; + variant: ModelVariantType; +}; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts new file mode 100644 index 0000000000..db51f6c7b9 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts @@ -0,0 +1,16 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; +import type { ModelVariantType } from './ModelVariantType'; + +export type StableDiffusion1DiffusersModelConfig = { + path: string; + description?: string; + format: 'diffusers'; + default?: boolean; + error?: ModelError; + vae?: string; + variant: ModelVariantType; +}; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts new file mode 100644 index 0000000000..74a341d861 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts @@ -0,0 +1,20 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; +import type { ModelVariantType } from './ModelVariantType'; +import type { SchedulerPredictionType } from './SchedulerPredictionType'; + +export type StableDiffusion2CheckpointModelConfig = { + path: string; + description?: string; + format: 'checkpoint'; + default?: boolean; + error?: ModelError; + vae?: string; + config?: string; + variant: ModelVariantType; + prediction_type: SchedulerPredictionType; + upcast_attention: boolean; +}; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts new file mode 100644 index 0000000000..1ac441b2a6 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts @@ -0,0 +1,19 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; +import type { ModelVariantType } from './ModelVariantType'; +import type { SchedulerPredictionType } from './SchedulerPredictionType'; + +export type StableDiffusion2DiffusersModelConfig = { + path: string; + description?: string; + format: 'diffusers'; + default?: boolean; + error?: ModelError; + vae?: string; + variant: ModelVariantType; + prediction_type: SchedulerPredictionType; + upcast_attention: boolean; +}; diff --git a/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts b/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts index 2cff38b3e5..dca4fa8e82 100644 --- a/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts @@ -56,4 +56,3 @@ export type StepParamEasingInvocation = { */ show_easing_plot?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts b/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts index 23334bd891..a1b8ca5628 100644 --- a/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts @@ -24,4 +24,3 @@ export type SubtractInvocation = { */ 'b'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts new file mode 100644 index 0000000000..26d6117573 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts @@ -0,0 +1,64 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ImageField } from './ImageField'; + +/** + * Generates an image using text2img. + */ +export type TextToImageInvocation = { + /** + * The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Whether or not this node is an intermediate node. + */ + is_intermediate?: boolean; + type?: 'txt2img'; + /** + * The prompt to generate an image from + */ + prompt?: string; + /** + * The seed to use (omit for random) + */ + seed?: number; + /** + * The number of steps to use to generate the image + */ + steps?: number; + /** + * The width of the resulting image + */ + width?: number; + /** + * The height of the resulting image + */ + height?: number; + /** + * The Classifier-Free Guidance, higher values may result in a result closer to the prompt + */ + cfg_scale?: number; + /** + * The scheduler to use + */ + scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; + /** + * The model to use (currently ignored) + */ + model?: string; + /** + * Whether or not to produce progress images during generation + */ + progress_images?: boolean; + /** + * The control model to use + */ + control_model?: string; + /** + * The processed control image + */ + control_image?: ImageField; +}; diff --git a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts index cf8229b1f7..b0b8ec5fc1 100644 --- a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts @@ -53,4 +53,3 @@ export type TextToLatentsInvocation = { */ control?: (ControlField | Array); }; - diff --git a/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts b/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts new file mode 100644 index 0000000000..34ef4791bc --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; + +export type TextualInversionModelConfig = { + path: string; + description?: string; + format: null; + default?: boolean; + error?: ModelError; +}; diff --git a/invokeai/frontend/web/src/services/api/models/UNetField.ts b/invokeai/frontend/web/src/services/api/models/UNetField.ts index ad3b1ddb5b..f0f247c860 100644 --- a/invokeai/frontend/web/src/services/api/models/UNetField.ts +++ b/invokeai/frontend/web/src/services/api/models/UNetField.ts @@ -19,4 +19,7 @@ export type UNetField = { */ loras: Array; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts b/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts index d0aca63964..3b42906e39 100644 --- a/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts @@ -30,4 +30,3 @@ export type UpscaleInvocation = { */ level?: 2 | 4; }; - diff --git a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts b/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts new file mode 100644 index 0000000000..ffaba2f808 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; + +export type VAEModelConfig = { + path: string; + description?: string; + format: ('checkpoint' | 'diffusers'); + default?: boolean; + error?: ModelError; +}; diff --git a/invokeai/frontend/web/src/services/api/models/VaeField.ts b/invokeai/frontend/web/src/services/api/models/VaeField.ts index bfe2793887..8d3b6f4e53 100644 --- a/invokeai/frontend/web/src/services/api/models/VaeField.ts +++ b/invokeai/frontend/web/src/services/api/models/VaeField.ts @@ -10,4 +10,7 @@ export type VaeField = { */ vae: ModelInfo; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/VaeRepo.ts b/invokeai/frontend/web/src/services/api/models/VaeRepo.ts index 0e233626c6..cb6e33c199 100644 --- a/invokeai/frontend/web/src/services/api/models/VaeRepo.ts +++ b/invokeai/frontend/web/src/services/api/models/VaeRepo.ts @@ -16,4 +16,3 @@ export type VaeRepo = { */ subfolder?: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ValidationError.ts b/invokeai/frontend/web/src/services/api/models/ValidationError.ts index 14e1fdecd0..92697e1d74 100644 --- a/invokeai/frontend/web/src/services/api/models/ValidationError.ts +++ b/invokeai/frontend/web/src/services/api/models/ValidationError.ts @@ -7,4 +7,3 @@ export type ValidationError = { msg: string; type: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts index 6caded8f04..0dbc99c9e3 100644 --- a/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts @@ -22,4 +22,3 @@ export type ZoeDepthImageProcessorInvocation = { */ image?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/services/ImagesService.ts b/invokeai/frontend/web/src/services/api/services/ImagesService.ts index bfdef887a0..f372d4fa87 100644 --- a/invokeai/frontend/web/src/services/api/services/ImagesService.ts +++ b/invokeai/frontend/web/src/services/api/services/ImagesService.ts @@ -22,6 +22,7 @@ export class ImagesService { * @throws ApiError */ public static listImagesWithMetadata({ +<<<<<<< HEAD imageOrigin, categories, isIntermediate, @@ -54,6 +55,35 @@ export class ImagesService { */ limit?: number, }): CancelablePromise { +======= +imageOrigin, +categories, +isIntermediate, +offset, +limit = 10, +}: { +/** + * The origin of images to list + */ +imageOrigin?: ResourceOrigin, +/** + * The categories of image to include + */ +categories?: Array, +/** + * Whether to list intermediate images + */ +isIntermediate?: boolean, +/** + * The page offset + */ +offset?: number, +/** + * The number of images per page + */ +limit?: number, +}): CancelablePromise { +>>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/', @@ -78,25 +108,25 @@ export class ImagesService { * @throws ApiError */ public static uploadImage({ - imageCategory, - isIntermediate, - formData, - sessionId, - }: { - /** - * The category of the image - */ - imageCategory: ImageCategory, - /** - * Whether this is an intermediate image - */ - isIntermediate: boolean, - formData: Body_upload_image, - /** - * The session ID associated with this upload, if any - */ - sessionId?: string, - }): CancelablePromise { +imageCategory, +isIntermediate, +formData, +sessionId, +}: { +/** + * The category of the image + */ +imageCategory: ImageCategory, +/** + * Whether this is an intermediate image + */ +isIntermediate: boolean, +formData: Body_upload_image, +/** + * The session ID associated with this upload, if any + */ +sessionId?: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/images/', @@ -121,13 +151,13 @@ export class ImagesService { * @throws ApiError */ public static getImageFull({ - imageName, - }: { - /** - * The name of full-resolution image file to get - */ - imageName: string, - }): CancelablePromise { +imageName, +}: { +/** + * The name of full-resolution image file to get + */ +imageName: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}', @@ -148,13 +178,13 @@ export class ImagesService { * @throws ApiError */ public static deleteImage({ - imageName, - }: { - /** - * The name of the image to delete - */ - imageName: string, - }): CancelablePromise { +imageName, +}: { +/** + * The name of the image to delete + */ +imageName: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/images/{image_name}', @@ -174,15 +204,15 @@ export class ImagesService { * @throws ApiError */ public static updateImage({ - imageName, - requestBody, - }: { - /** - * The name of the image to update - */ - imageName: string, - requestBody: ImageRecordChanges, - }): CancelablePromise { +imageName, +requestBody, +}: { +/** + * The name of the image to update + */ +imageName: string, +requestBody: ImageRecordChanges, +}): CancelablePromise { return __request(OpenAPI, { method: 'PATCH', url: '/api/v1/images/{image_name}', @@ -204,13 +234,13 @@ export class ImagesService { * @throws ApiError */ public static getImageMetadata({ - imageName, - }: { - /** - * The name of image to get - */ - imageName: string, - }): CancelablePromise { +imageName, +}: { +/** + * The name of image to get + */ +imageName: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}/metadata', @@ -230,13 +260,13 @@ export class ImagesService { * @throws ApiError */ public static getImageThumbnail({ - imageName, - }: { - /** - * The name of thumbnail image file to get - */ - imageName: string, - }): CancelablePromise { +imageName, +}: { +/** + * The name of thumbnail image file to get + */ +imageName: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}/thumbnail', @@ -257,13 +287,13 @@ export class ImagesService { * @throws ApiError */ public static getImageUrls({ - imageName, - }: { - /** - * The name of the image whose URL to get - */ - imageName: string, - }): CancelablePromise { +imageName, +}: { +/** + * The name of the image whose URL to get + */ +imageName: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}/urls', diff --git a/invokeai/frontend/web/src/services/api/services/ModelsService.ts b/invokeai/frontend/web/src/services/api/services/ModelsService.ts index 54580ce204..248f4a352e 100644 --- a/invokeai/frontend/web/src/services/api/services/ModelsService.ts +++ b/invokeai/frontend/web/src/services/api/services/ModelsService.ts @@ -19,6 +19,7 @@ export class ModelsService { * @throws ApiError */ public static listModels({ +<<<<<<< HEAD baseModel, modelType, }: { @@ -31,6 +32,20 @@ export class ModelsService { */ modelType?: ModelType, }): CancelablePromise { +======= +baseModel, +modelType, +}: { +/** + * Base model + */ +baseModel?: BaseModelType, +/** + * The type of model to get + */ +modelType?: ModelType, +}): CancelablePromise { +>>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'GET', url: '/api/v1/models/', @@ -51,10 +66,10 @@ export class ModelsService { * @throws ApiError */ public static updateModel({ - requestBody, - }: { - requestBody: CreateModelRequest, - }): CancelablePromise { +requestBody, +}: { +requestBody: CreateModelRequest, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/models/', @@ -73,10 +88,10 @@ export class ModelsService { * @throws ApiError */ public static delModel({ - modelName, - }: { - modelName: string, - }): CancelablePromise { +modelName, +}: { +modelName: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/models/{model_name}', diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index 2e4a83b25f..937cff9c05 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -80,23 +80,23 @@ export class SessionsService { * @throws ApiError */ public static listSessions({ - page, - perPage = 10, - query = '', - }: { - /** - * The page of results to get - */ - page?: number, - /** - * The number of results per page - */ - perPage?: number, - /** - * The query string to search for - */ - query?: string, - }): CancelablePromise { +page, +perPage = 10, +query = '', +}: { +/** + * The page of results to get + */ +page?: number, +/** + * The number of results per page + */ +perPage?: number, +/** + * The query string to search for + */ +query?: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/sessions/', @@ -118,10 +118,10 @@ export class SessionsService { * @throws ApiError */ public static createSession({ - requestBody, - }: { - requestBody?: Graph, - }): CancelablePromise { +requestBody, +}: { +requestBody?: Graph, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/sessions/', @@ -141,13 +141,13 @@ export class SessionsService { * @throws ApiError */ public static getSession({ - sessionId, - }: { - /** - * The id of the session to get - */ - sessionId: string, - }): CancelablePromise { +sessionId, +}: { +/** + * The id of the session to get + */ +sessionId: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/sessions/{session_id}', @@ -168,6 +168,7 @@ export class SessionsService { * @throws ApiError */ public static addNode({ +<<<<<<< HEAD sessionId, requestBody, }: { @@ -177,6 +178,17 @@ export class SessionsService { sessionId: string, requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { +======= +sessionId, +requestBody, +}: { +/** + * The id of the session + */ +sessionId: string, +requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | CompelInvocation | LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CvInpaintInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | DynamicPromptInvocation | RestoreFaceInvocation | UpscaleInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | ImageToImageInvocation | LatentsToLatentsInvocation | InpaintInvocation), +}): CancelablePromise { +>>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'POST', url: '/api/v1/sessions/{session_id}/nodes', @@ -200,6 +212,7 @@ export class SessionsService { * @throws ApiError */ public static updateNode({ +<<<<<<< HEAD sessionId, nodePath, requestBody, @@ -214,6 +227,22 @@ export class SessionsService { nodePath: string, requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { +======= +sessionId, +nodePath, +requestBody, +}: { +/** + * The id of the session + */ +sessionId: string, +/** + * The path to the node in the graph + */ +nodePath: string, +requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | CompelInvocation | LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CvInpaintInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | DynamicPromptInvocation | RestoreFaceInvocation | UpscaleInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | ImageToImageInvocation | LatentsToLatentsInvocation | InpaintInvocation), +}): CancelablePromise { +>>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'PUT', url: '/api/v1/sessions/{session_id}/nodes/{node_path}', @@ -238,18 +267,18 @@ export class SessionsService { * @throws ApiError */ public static deleteNode({ - sessionId, - nodePath, - }: { - /** - * The id of the session - */ - sessionId: string, - /** - * The path to the node to delete - */ - nodePath: string, - }): CancelablePromise { +sessionId, +nodePath, +}: { +/** + * The id of the session + */ +sessionId: string, +/** + * The path to the node to delete + */ +nodePath: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/sessions/{session_id}/nodes/{node_path}', @@ -272,15 +301,15 @@ export class SessionsService { * @throws ApiError */ public static addEdge({ - sessionId, - requestBody, - }: { - /** - * The id of the session - */ - sessionId: string, - requestBody: Edge, - }): CancelablePromise { +sessionId, +requestBody, +}: { +/** + * The id of the session + */ +sessionId: string, +requestBody: Edge, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/sessions/{session_id}/edges', @@ -304,33 +333,33 @@ export class SessionsService { * @throws ApiError */ public static deleteEdge({ - sessionId, - fromNodeId, - fromField, - toNodeId, - toField, - }: { - /** - * The id of the session - */ - sessionId: string, - /** - * The id of the node the edge is coming from - */ - fromNodeId: string, - /** - * The field of the node the edge is coming from - */ - fromField: string, - /** - * The id of the node the edge is going to - */ - toNodeId: string, - /** - * The field of the node the edge is going to - */ - toField: string, - }): CancelablePromise { +sessionId, +fromNodeId, +fromField, +toNodeId, +toField, +}: { +/** + * The id of the session + */ +sessionId: string, +/** + * The id of the node the edge is coming from + */ +fromNodeId: string, +/** + * The field of the node the edge is coming from + */ +fromField: string, +/** + * The id of the node the edge is going to + */ +toNodeId: string, +/** + * The field of the node the edge is going to + */ +toField: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/sessions/{session_id}/edges/{from_node_id}/{from_field}/{to_node_id}/{to_field}', @@ -356,18 +385,18 @@ export class SessionsService { * @throws ApiError */ public static invokeSession({ - sessionId, - all = false, - }: { - /** - * The id of the session to invoke - */ - sessionId: string, - /** - * Whether or not to invoke all remaining invocations - */ - all?: boolean, - }): CancelablePromise { +sessionId, +all = false, +}: { +/** + * The id of the session to invoke + */ +sessionId: string, +/** + * Whether or not to invoke all remaining invocations + */ +all?: boolean, +}): CancelablePromise { return __request(OpenAPI, { method: 'PUT', url: '/api/v1/sessions/{session_id}/invoke', @@ -392,13 +421,13 @@ export class SessionsService { * @throws ApiError */ public static cancelSessionInvoke({ - sessionId, - }: { - /** - * The id of the session to cancel - */ - sessionId: string, - }): CancelablePromise { +sessionId, +}: { +/** + * The id of the session to cancel + */ +sessionId: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/sessions/{session_id}/invoke', From bf0d5f4cfc0537a0ccb2834f4978b4dcb1cdbbe6 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sat, 17 Jun 2023 22:04:28 +1200 Subject: [PATCH 73/99] fix: Update missing name types to new names --- invokeai/backend/model_management/models/stable_diffusion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/backend/model_management/models/stable_diffusion.py b/invokeai/backend/model_management/models/stable_diffusion.py index 9856069ea5..0ac88c8a94 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -107,7 +107,7 @@ class StableDiffusion1Model(DiffusersModel): ) -> str: assert model_path == config.path - if isinstance(config, cls.CheckpointModelConfig): + if isinstance(config, cls.StableDiffusion1CheckpointModelConfig): return _convert_ckpt_and_cache( version=BaseModelType.StableDiffusion1, model_config=config, @@ -220,7 +220,7 @@ class StableDiffusion2Model(DiffusersModel): ) -> str: assert model_path == config.path - if isinstance(config, cls.CheckpointModelConfig): + if isinstance(config, cls.StableDiffusion2CheckpointModelConfig): return _convert_ckpt_and_cache( version=BaseModelType.StableDiffusion2, model_config=config, From 01d17601b84b974f6376b087617ed3b50a3344c2 Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Sat, 17 Jun 2023 17:15:36 +0300 Subject: [PATCH 74/99] Generate config names for openapi --- invokeai/app/api/routers/models.py | 4 ++-- .../backend/model_management/models/__init__.py | 16 ++++++++++++++-- .../model_management/models/controlnet.py | 2 +- invokeai/backend/model_management/models/lora.py | 2 +- .../model_management/models/stable_diffusion.py | 14 +++++++------- .../model_management/models/textual_inversion.py | 2 +- invokeai/backend/model_management/models/vae.py | 2 +- 7 files changed, 27 insertions(+), 15 deletions(-) diff --git a/invokeai/app/api/routers/models.py b/invokeai/app/api/routers/models.py index f510279f18..0abcc19dcf 100644 --- a/invokeai/app/api/routers/models.py +++ b/invokeai/app/api/routers/models.py @@ -7,8 +7,8 @@ from fastapi.routing import APIRouter, HTTPException from pydantic import BaseModel, Field, parse_obj_as from ..dependencies import ApiDependencies from invokeai.backend import BaseModelType, ModelType -from invokeai.backend.model_management.models import get_all_model_configs -MODEL_CONFIGS = Union[tuple(get_all_model_configs())] +from invokeai.backend.model_management.models import OPENAPI_MODEL_CONFIGS +MODEL_CONFIGS = Union[tuple(OPENAPI_MODEL_CONFIGS)] models_router = APIRouter(prefix="/v1/models", tags=["models"]) diff --git a/invokeai/backend/model_management/models/__init__.py b/invokeai/backend/model_management/models/__init__.py index 40995498bf..eff71798a5 100644 --- a/invokeai/backend/model_management/models/__init__.py +++ b/invokeai/backend/model_management/models/__init__.py @@ -29,10 +29,22 @@ MODEL_CLASSES = { #}, } -def get_all_model_configs(): +def _get_all_model_configs(): configs = set() for models in MODEL_CLASSES.values(): for _, model in models.items(): configs.update(model._get_configs().values()) configs.discard(None) - return list(configs) # TODO: set, list or tuple + return list(configs) + +MODEL_CONFIGS = _get_all_model_configs() +OPENAPI_MODEL_CONFIGS = list() + +for cfg in MODEL_CONFIGS: + model_name, cfg_name = cfg.__qualname__.split('.')[-2:] + openapi_cfg_name = model_name + cfg_name + name_wrapper = type(openapi_cfg_name, (cfg,), {}) + + #globals()[name] = value + vars()[openapi_cfg_name] = name_wrapper + OPENAPI_MODEL_CONFIGS.append(name_wrapper) diff --git a/invokeai/backend/model_management/models/controlnet.py b/invokeai/backend/model_management/models/controlnet.py index 687afbffbd..de9926c83e 100644 --- a/invokeai/backend/model_management/models/controlnet.py +++ b/invokeai/backend/model_management/models/controlnet.py @@ -18,7 +18,7 @@ class ControlNetModel(ModelBase): #model_class: Type #model_size: int - class ControlNetModelConfig(ModelConfigBase): + class Config(ModelConfigBase): format: Union[Literal["checkpoint"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): diff --git a/invokeai/backend/model_management/models/lora.py b/invokeai/backend/model_management/models/lora.py index 60865817b9..bcf3224ece 100644 --- a/invokeai/backend/model_management/models/lora.py +++ b/invokeai/backend/model_management/models/lora.py @@ -15,7 +15,7 @@ from ..lora import LoRAModel as LoRAModelRaw class LoRAModel(ModelBase): #model_size: int - class LoraModelConfig(ModelConfigBase): + class Config(ModelConfigBase): format: Union[Literal["lycoris"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): diff --git a/invokeai/backend/model_management/models/stable_diffusion.py b/invokeai/backend/model_management/models/stable_diffusion.py index 0ac88c8a94..20aaae23a6 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -22,12 +22,12 @@ from omegaconf import OmegaConf class StableDiffusion1Model(DiffusersModel): - class StableDiffusion1DiffusersModelConfig(ModelConfigBase): + class DiffusersConfig(ModelConfigBase): format: Literal["diffusers"] vae: Optional[str] = Field(None) variant: ModelVariantType - class StableDiffusion1CheckpointModelConfig(ModelConfigBase): + class CheckpointConfig(ModelConfigBase): format: Literal["checkpoint"] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) @@ -107,7 +107,7 @@ class StableDiffusion1Model(DiffusersModel): ) -> str: assert model_path == config.path - if isinstance(config, cls.StableDiffusion1CheckpointModelConfig): + if isinstance(config, cls.CheckpointConfig): return _convert_ckpt_and_cache( version=BaseModelType.StableDiffusion1, model_config=config, @@ -120,14 +120,14 @@ class StableDiffusion1Model(DiffusersModel): class StableDiffusion2Model(DiffusersModel): # TODO: check that configs overwriten properly - class StableDiffusion2DiffusersModelConfig(ModelConfigBase): + class DiffusersConfig(ModelConfigBase): format: Literal["diffusers"] vae: Optional[str] = Field(None) variant: ModelVariantType prediction_type: SchedulerPredictionType upcast_attention: bool - class StableDiffusion2CheckpointModelConfig(ModelConfigBase): + class CheckpointConfig(ModelConfigBase): format: Literal["checkpoint"] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) @@ -220,7 +220,7 @@ class StableDiffusion2Model(DiffusersModel): ) -> str: assert model_path == config.path - if isinstance(config, cls.StableDiffusion2CheckpointModelConfig): + if isinstance(config, cls.CheckpointConfig): return _convert_ckpt_and_cache( version=BaseModelType.StableDiffusion2, model_config=config, @@ -256,7 +256,7 @@ def _select_ckpt_config(version: BaseModelType, variant: ModelVariantType): # TODO: rework def _convert_ckpt_and_cache( version: BaseModelType, - model_config: Union[StableDiffusion1Model.StableDiffusion1CheckpointModelConfig, StableDiffusion2Model.StableDiffusion2CheckpointModelConfig], + model_config: Union[StableDiffusion1Model.CheckpointConfig, StableDiffusion2Model.CheckpointConfig], output_path: str, ) -> str: """ diff --git a/invokeai/backend/model_management/models/textual_inversion.py b/invokeai/backend/model_management/models/textual_inversion.py index 453a8ad671..66847f53eb 100644 --- a/invokeai/backend/model_management/models/textual_inversion.py +++ b/invokeai/backend/model_management/models/textual_inversion.py @@ -15,7 +15,7 @@ from ..lora import TextualInversionModel as TextualInversionModelRaw class TextualInversionModel(ModelBase): #model_size: int - class TextualInversionModelConfig(ModelConfigBase): + class Config(ModelConfigBase): format: None def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): diff --git a/invokeai/backend/model_management/models/vae.py b/invokeai/backend/model_management/models/vae.py index f285648323..b78617869a 100644 --- a/invokeai/backend/model_management/models/vae.py +++ b/invokeai/backend/model_management/models/vae.py @@ -23,7 +23,7 @@ class VaeModel(ModelBase): #vae_class: Type #model_size: int - class VAEModelConfig(ModelConfigBase): + class Config(ModelConfigBase): format: Union[Literal["checkpoint"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): From e37421131399e94c0b303478b9130df69e9731d5 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 03:00:16 +1200 Subject: [PATCH 75/99] chore: Rebuild API with new Model API names --- invokeai/frontend/web/src/services/api/index.ts | 16 ++++++++++++---- .../src/services/api/models/LoraModelConfig.ts | 2 +- .../web/src/services/api/models/ModelsList.ts | 16 ++++++++++------ ... => StableDiffusion1ModelCheckpointConfig.ts} | 2 +- ...s => StableDiffusion1ModelDiffusersConfig.ts} | 2 +- ... => StableDiffusion2ModelCheckpointConfig.ts} | 2 +- ...s => StableDiffusion2ModelDiffusersConfig.ts} | 2 +- .../src/services/api/models/VAEModelConfig.ts | 2 +- 8 files changed, 28 insertions(+), 16 deletions(-) rename invokeai/frontend/web/src/services/api/models/{StableDiffusion1CheckpointModelConfig.ts => StableDiffusion1ModelCheckpointConfig.ts} (86%) rename invokeai/frontend/web/src/services/api/models/{StableDiffusion1DiffusersModelConfig.ts => StableDiffusion1ModelDiffusersConfig.ts} (86%) rename invokeai/frontend/web/src/services/api/models/{StableDiffusion2CheckpointModelConfig.ts => StableDiffusion2ModelCheckpointConfig.ts} (90%) rename invokeai/frontend/web/src/services/api/models/{StableDiffusion2DiffusersModelConfig.ts => StableDiffusion2ModelDiffusersConfig.ts} (90%) diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index f3aec17eb6..a738a9aafd 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -92,9 +92,13 @@ export type { LoraInfo } from './models/LoraInfo'; export type { LoraLoaderInvocation } from './models/LoraLoaderInvocation'; export type { LoraLoaderOutput } from './models/LoraLoaderOutput'; <<<<<<< HEAD +<<<<<<< HEAD ======= export type { LoraModelConfig } from './models/LoraModelConfig'; >>>>>>> 76dd749b1 (chore: Rebuild API) +======= +export type { LoRAModelConfig } from './models/LoRAModelConfig'; +>>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) export type { MaskFromAlphaInvocation } from './models/MaskFromAlphaInvocation'; export type { MaskOutput } from './models/MaskOutput'; export type { MediapipeFaceProcessorInvocation } from './models/MediapipeFaceProcessorInvocation'; @@ -131,10 +135,10 @@ export type { SchedulerPredictionType } from './models/SchedulerPredictionType'; export type { SD1ModelLoaderInvocation } from './models/SD1ModelLoaderInvocation'; export type { SD2ModelLoaderInvocation } from './models/SD2ModelLoaderInvocation'; export type { ShowImageInvocation } from './models/ShowImageInvocation'; -export type { StableDiffusion1CheckpointModelConfig } from './models/StableDiffusion1CheckpointModelConfig'; -export type { StableDiffusion1DiffusersModelConfig } from './models/StableDiffusion1DiffusersModelConfig'; -export type { StableDiffusion2CheckpointModelConfig } from './models/StableDiffusion2CheckpointModelConfig'; -export type { StableDiffusion2DiffusersModelConfig } from './models/StableDiffusion2DiffusersModelConfig'; +export type { StableDiffusion1ModelCheckpointConfig } from './models/StableDiffusion1ModelCheckpointConfig'; +export type { StableDiffusion1ModelDiffusersConfig } from './models/StableDiffusion1ModelDiffusersConfig'; +export type { StableDiffusion2ModelCheckpointConfig } from './models/StableDiffusion2ModelCheckpointConfig'; +export type { StableDiffusion2ModelDiffusersConfig } from './models/StableDiffusion2ModelDiffusersConfig'; export type { StepParamEasingInvocation } from './models/StepParamEasingInvocation'; export type { SubModelType } from './models/SubModelType'; export type { SubtractInvocation } from './models/SubtractInvocation'; @@ -148,8 +152,12 @@ export type { TextualInversionModelConfig } from './models/TextualInversionModel export type { UNetField } from './models/UNetField'; export type { UpscaleInvocation } from './models/UpscaleInvocation'; export type { VaeField } from './models/VaeField'; +<<<<<<< HEAD export type { VAEModelConfig } from './models/VAEModelConfig'; >>>>>>> 76dd749b1 (chore: Rebuild API) +======= +export type { VaeModelConfig } from './models/VaeModelConfig'; +>>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) export type { VaeRepo } from './models/VaeRepo'; export type { ValidationError } from './models/ValidationError'; export type { ZoeDepthImageProcessorInvocation } from './models/ZoeDepthImageProcessorInvocation'; diff --git a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts b/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts index d5b3a02eff..a62d1a3f4b 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts @@ -4,7 +4,7 @@ import type { ModelError } from './ModelError'; -export type LoraModelConfig = { +export type LoRAModelConfig = { path: string; description?: string; format: ('lycoris' | 'diffusers'); diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index bd6e8bf4da..db41b33048 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -16,15 +16,19 @@ export type ModelsList = { models: Record>>; ======= import type { ControlNetModelConfig } from './ControlNetModelConfig'; -import type { LoraModelConfig } from './LoraModelConfig'; -import type { StableDiffusion1CheckpointModelConfig } from './StableDiffusion1CheckpointModelConfig'; -import type { StableDiffusion1DiffusersModelConfig } from './StableDiffusion1DiffusersModelConfig'; -import type { StableDiffusion2CheckpointModelConfig } from './StableDiffusion2CheckpointModelConfig'; -import type { StableDiffusion2DiffusersModelConfig } from './StableDiffusion2DiffusersModelConfig'; +import type { LoRAModelConfig } from './LoRAModelConfig'; +import type { StableDiffusion1ModelCheckpointConfig } from './StableDiffusion1ModelCheckpointConfig'; +import type { StableDiffusion1ModelDiffusersConfig } from './StableDiffusion1ModelDiffusersConfig'; +import type { StableDiffusion2ModelCheckpointConfig } from './StableDiffusion2ModelCheckpointConfig'; +import type { StableDiffusion2ModelDiffusersConfig } from './StableDiffusion2ModelDiffusersConfig'; import type { TextualInversionModelConfig } from './TextualInversionModelConfig'; -import type { VAEModelConfig } from './VAEModelConfig'; +import type { VaeModelConfig } from './VaeModelConfig'; export type ModelsList = { +<<<<<<< HEAD models: Record>>; >>>>>>> 76dd749b1 (chore: Rebuild API) +======= + models: Record>>; +>>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) }; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts similarity index 86% rename from invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts rename to invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts index e9ed1bbfc2..7b3f90cd0a 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts @@ -5,7 +5,7 @@ import type { ModelError } from './ModelError'; import type { ModelVariantType } from './ModelVariantType'; -export type StableDiffusion1CheckpointModelConfig = { +export type StableDiffusion1ModelCheckpointConfig = { path: string; description?: string; format: 'checkpoint'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts similarity index 86% rename from invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts rename to invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts index db51f6c7b9..ec634b9692 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts @@ -5,7 +5,7 @@ import type { ModelError } from './ModelError'; import type { ModelVariantType } from './ModelVariantType'; -export type StableDiffusion1DiffusersModelConfig = { +export type StableDiffusion1ModelDiffusersConfig = { path: string; description?: string; format: 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts similarity index 90% rename from invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts rename to invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts index 74a341d861..bcf9801314 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts @@ -6,7 +6,7 @@ import type { ModelError } from './ModelError'; import type { ModelVariantType } from './ModelVariantType'; import type { SchedulerPredictionType } from './SchedulerPredictionType'; -export type StableDiffusion2CheckpointModelConfig = { +export type StableDiffusion2ModelCheckpointConfig = { path: string; description?: string; format: 'checkpoint'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts similarity index 90% rename from invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts rename to invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts index 1ac441b2a6..3f3142dae3 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts @@ -6,7 +6,7 @@ import type { ModelError } from './ModelError'; import type { ModelVariantType } from './ModelVariantType'; import type { SchedulerPredictionType } from './SchedulerPredictionType'; -export type StableDiffusion2DiffusersModelConfig = { +export type StableDiffusion2ModelDiffusersConfig = { path: string; description?: string; format: 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts b/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts index ffaba2f808..8d0d2e8304 100644 --- a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts @@ -4,7 +4,7 @@ import type { ModelError } from './ModelError'; -export type VAEModelConfig = { +export type VaeModelConfig = { path: string; description?: string; format: ('checkpoint' | 'diffusers'); From f8d7477c7ae5dfa71cbf27fd4ad54874ed6c6819 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 07:01:44 +1200 Subject: [PATCH 76/99] wip: Add 2.x Models to the Model List --- .../enhancers/reduxRemember/serialize.ts | 3 +- .../enhancers/reduxRemember/unserialize.ts | 6 ++- .../listeners/socketio/socketConnected.ts | 14 +++-- invokeai/frontend/web/src/app/store/store.ts | 23 ++++---- .../fields/ModelInputFieldComponent.tsx | 32 +++-------- .../parameters/store/generationSlice.ts | 6 +-- .../system/components/ModelSelect.tsx | 36 ++++++++++--- .../src/features/system/store/modelSlice.ts | 47 ---------------- .../system/store/models/sd1ModelSlice.ts | 53 ++++++++++++++++++ .../system/store/models/sd2ModelSlice.ts | 53 ++++++++++++++++++ .../system/store/modelsPersistDenylist.ts | 7 ++- .../src/features/system/store/systemSlice.ts | 24 ++++----- .../frontend/web/src/services/thunks/model.ts | 54 +++++++++++++------ 13 files changed, 228 insertions(+), 130 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/system/store/modelSlice.ts create mode 100644 invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts create mode 100644 invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts index 5025ca081a..e498ecb749 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts @@ -18,7 +18,8 @@ const serializationDenylist: { gallery: galleryPersistDenylist, generation: generationPersistDenylist, lightbox: lightboxPersistDenylist, - models: modelsPersistDenylist, + sd1models: modelsPersistDenylist, + sd2models: modelsPersistDenylist, nodes: nodesPersistDenylist, postprocessing: postprocessingPersistDenylist, system: systemPersistDenylist, diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts index c6af5f3612..93cc19f832 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts @@ -7,7 +7,8 @@ import { initialNodesState } from 'features/nodes/store/nodesSlice'; import { initialGenerationState } from 'features/parameters/store/generationSlice'; import { initialPostprocessingState } from 'features/parameters/store/postprocessingSlice'; import { initialConfigState } from 'features/system/store/configSlice'; -import { initialModelsState } from 'features/system/store/modelSlice'; +import { sd1InitialModelsState } from 'features/system/store/models/sd1ModelSlice'; +import { sd2InitialModelsState } from 'features/system/store/models/sd2ModelSlice'; import { initialSystemState } from 'features/system/store/systemSlice'; import { initialHotkeysState } from 'features/ui/store/hotkeysSlice'; import { initialUIState } from 'features/ui/store/uiSlice'; @@ -21,7 +22,8 @@ const initialStates: { gallery: initialGalleryState, generation: initialGenerationState, lightbox: initialLightboxState, - models: initialModelsState, + sd1models: sd1InitialModelsState, + sd2models: sd2InitialModelsState, nodes: initialNodesState, postprocessing: initialPostprocessingState, system: initialSystemState, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index 9fe554fee1..b257b470bd 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -1,9 +1,9 @@ -import { startAppListening } from '../..'; import { log } from 'app/logging/useLogger'; import { appSocketConnected, socketConnected } from 'services/events/actions'; import { receivedPageOfImages } from 'services/thunks/image'; -import { receivedModels } from 'services/thunks/model'; +import { getModels } from 'services/thunks/model'; import { receivedOpenAPISchema } from 'services/thunks/schema'; +import { startAppListening } from '../..'; const moduleLog = log.child({ namespace: 'socketio' }); @@ -15,7 +15,7 @@ export const addSocketConnectedEventListener = () => { moduleLog.debug({ timestamp }, 'Connected'); - const { models, nodes, config, images } = getState(); + const { sd1models, sd2models, nodes, config, images } = getState(); const { disabledTabs } = config; @@ -28,8 +28,12 @@ export const addSocketConnectedEventListener = () => { ); } - if (!models.ids.length) { - dispatch(receivedModels()); + if (!sd1models.ids.length) { + dispatch(getModels({ baseModel: 'sd-1', modelType: 'pipeline' })); + } + + if (!sd2models.ids.length) { + dispatch(getModels({ baseModel: 'sd-2', modelType: 'pipeline' })); } if (!nodes.schema && !disabledTabs.includes('nodes')) { diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index a9011f9356..4ecc9eb9bf 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -5,34 +5,37 @@ import { configureStore, } from '@reduxjs/toolkit'; -import { rememberReducer, rememberEnhancer } from 'redux-remember'; import dynamicMiddlewares from 'redux-dynamic-middlewares'; +import { rememberEnhancer, rememberReducer } from 'redux-remember'; import canvasReducer from 'features/canvas/store/canvasSlice'; +import controlNetReducer from 'features/controlNet/store/controlNetSlice'; import galleryReducer from 'features/gallery/store/gallerySlice'; import imagesReducer from 'features/gallery/store/imagesSlice'; import lightboxReducer from 'features/lightbox/store/lightboxSlice'; import generationReducer from 'features/parameters/store/generationSlice'; -import controlNetReducer from 'features/controlNet/store/controlNetSlice'; import postprocessingReducer from 'features/parameters/store/postprocessingSlice'; import systemReducer from 'features/system/store/systemSlice'; // import sessionReducer from 'features/system/store/sessionSlice'; -import configReducer from 'features/system/store/configSlice'; -import uiReducer from 'features/ui/store/uiSlice'; -import hotkeysReducer from 'features/ui/store/hotkeysSlice'; -import modelsReducer from 'features/system/store/modelSlice'; import nodesReducer from 'features/nodes/store/nodesSlice'; import boardsReducer from 'features/gallery/store/boardSlice'; +import configReducer from 'features/system/store/configSlice'; +import hotkeysReducer from 'features/ui/store/hotkeysSlice'; +import uiReducer from 'features/ui/store/uiSlice'; import { listenerMiddleware } from './middleware/listenerMiddleware'; import { actionSanitizer } from './middleware/devtools/actionSanitizer'; -import { stateSanitizer } from './middleware/devtools/stateSanitizer'; import { actionsDenylist } from './middleware/devtools/actionsDenylist'; +import { stateSanitizer } from './middleware/devtools/stateSanitizer'; +// Model Reducers +import sd1ModelReducer from 'features/system/store/models/sd1ModelSlice'; +import sd2ModelReducer from 'features/system/store/models/sd2ModelSlice'; + +import { LOCALSTORAGE_PREFIX } from './constants'; import { serialize } from './enhancers/reduxRemember/serialize'; import { unserialize } from './enhancers/reduxRemember/unserialize'; -import { LOCALSTORAGE_PREFIX } from './constants'; import { api } from 'services/apiSlice'; const allReducers = { @@ -40,7 +43,8 @@ const allReducers = { gallery: galleryReducer, generation: generationReducer, lightbox: lightboxReducer, - models: modelsReducer, + sd1models: sd1ModelReducer, + sd2models: sd2ModelReducer, nodes: nodesReducer, postprocessing: postprocessingReducer, system: systemReducer, @@ -63,7 +67,6 @@ const rememberedKeys: (keyof typeof allReducers)[] = [ 'gallery', 'generation', 'lightbox', - // 'models', 'nodes', 'postprocessing', 'system', diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx index a1ef69de01..d3d37765f4 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx @@ -1,29 +1,14 @@ -import { Select } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; +import { NativeSelect } from '@mantine/core'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { fieldValueChanged } from 'features/nodes/store/nodesSlice'; import { ModelInputFieldTemplate, ModelInputFieldValue, } from 'features/nodes/types/types'; -import { selectModelsIds } from 'features/system/store/modelSlice'; -import { isEqual } from 'lodash-es'; +import { modelSelector } from 'features/system/components/ModelSelect'; import { ChangeEvent, memo } from 'react'; import { FieldComponentProps } from './types'; -const availableModelsSelector = createSelector( - [selectModelsIds], - (allModelNames) => { - return { allModelNames }; - // return map(modelList, (_, name) => name); - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - const ModelInputFieldComponent = ( props: FieldComponentProps ) => { @@ -31,7 +16,7 @@ const ModelInputFieldComponent = ( const dispatch = useAppDispatch(); - const { allModelNames } = useAppSelector(availableModelsSelector); + const { sd1ModelData, sd2ModelData } = useAppSelector(modelSelector); const handleValueChanged = (e: ChangeEvent) => { dispatch( @@ -44,14 +29,11 @@ const ModelInputFieldComponent = ( }; return ( - + value={field.value || sd1ModelData[0].value} + data={sd1ModelData.concat(sd2ModelData)} + > ); }; diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 2facb65f04..39f38e386c 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,10 +1,11 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; +import { Scheduler } from 'app/constants'; import { configChanged } from 'features/system/store/configSlice'; import { clamp, sortBy } from 'lodash-es'; import { ImageDTO } from 'services/api'; import { imageUrlsReceived } from 'services/thunks/image'; -import { receivedModels } from 'services/thunks/model'; +import { getModels } from 'services/thunks/model'; import { CfgScaleParam, HeightParam, @@ -17,7 +18,6 @@ import { StrengthParam, WidthParam, } from './parameterZodSchemas'; -import { DEFAULT_SCHEDULER_NAME } from 'app/constants'; export interface GenerationState { cfgScale: CfgScaleParam; @@ -220,7 +220,7 @@ export const generationSlice = createSlice({ }, }, extraReducers: (builder) => { - builder.addCase(receivedModels.fulfilled, (state, action) => { + builder.addCase(getModels.fulfilled, (state, action) => { if (!state.model) { const firstModel = sortBy(action.payload, 'name')[0]; state.model = firstModel.name; diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index a38ab150dd..a65c8501dc 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -10,22 +10,42 @@ import IAIMantineSelect, { } from 'common/components/IAIMantineSelect'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { modelSelected } from 'features/parameters/store/generationSlice'; -import { selectModelsAll, selectModelsById } from '../store/modelSlice'; +import { + selectAllSD1Models, + selectByIdSD1Models, +} from '../store/models/sd1ModelSlice'; +import { + selectAllSD2Models, + selectByIdSD2Models, +} from '../store/models/sd2ModelSlice'; -const selector = createSelector( +export const modelSelector = createSelector( [(state: RootState) => state, generationSelector], (state, generation) => { - const selectedModel = selectModelsById(state, generation.model); + let selectedModel = selectByIdSD1Models(state, generation.model); + if (selectedModel === undefined) + selectedModel = selectByIdSD2Models(state, generation.model); - const modelData = selectModelsAll(state) + const sd1ModelData = selectAllSD1Models(state) .map((m) => ({ value: m.name, label: m.name, + group: '1.x Models', })) .sort((a, b) => a.label.localeCompare(b.label)); + + const sd2ModelData = selectAllSD2Models(state) + .map((m) => ({ + value: m.name, + label: m.name, + group: '2.x Models', + })) + .sort((a, b) => a.label.localeCompare(b.label)); + return { selectedModel, - modelData, + sd1ModelData, + sd2ModelData, }; }, { @@ -38,7 +58,9 @@ const selector = createSelector( const ModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const { selectedModel, modelData } = useAppSelector(selector); + const { selectedModel, sd1ModelData, sd2ModelData } = + useAppSelector(modelSelector); + const handleChangeModel = useCallback( (v: string | null) => { if (!v) { @@ -55,7 +77,7 @@ const ModelSelect = () => { label={t('modelManager.model')} value={selectedModel?.name ?? ''} placeholder="Pick one" - data={modelData} + data={sd1ModelData.concat(sd2ModelData)} onChange={handleChangeModel} /> ); diff --git a/invokeai/frontend/web/src/features/system/store/modelSlice.ts b/invokeai/frontend/web/src/features/system/store/modelSlice.ts deleted file mode 100644 index ed38425872..0000000000 --- a/invokeai/frontend/web/src/features/system/store/modelSlice.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { createEntityAdapter } from '@reduxjs/toolkit'; -import { createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { CkptModelInfo, DiffusersModelInfo } from 'services/api'; -import { receivedModels } from 'services/thunks/model'; - -export type Model = (CkptModelInfo | DiffusersModelInfo) & { - name: string; -}; - -export const modelsAdapter = createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), -}); - -export const initialModelsState = modelsAdapter.getInitialState(); - -export type ModelsState = typeof initialModelsState; - -export const modelsSlice = createSlice({ - name: 'models', - initialState: initialModelsState, - reducers: { - modelAdded: modelsAdapter.upsertOne, - }, - extraReducers(builder) { - /** - * Received Models - FULFILLED - */ - builder.addCase(receivedModels.fulfilled, (state, action) => { - const models = action.payload; - modelsAdapter.setAll(state, models); - }); - }, -}); - -export const { - selectAll: selectModelsAll, - selectById: selectModelsById, - selectEntities: selectModelsEntities, - selectIds: selectModelsIds, - selectTotal: selectModelsTotal, -} = modelsAdapter.getSelectors((state) => state.models); - -export const { modelAdded } = modelsSlice.actions; - -export default modelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts new file mode 100644 index 0000000000..9f62fde264 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts @@ -0,0 +1,53 @@ +import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { + StableDiffusion1ModelCheckpointConfig, + StableDiffusion1ModelDiffusersConfig, +} from 'services/api'; + +import { getModels } from 'services/thunks/model'; + +export type SD1ModelType = ( + | StableDiffusion1ModelCheckpointConfig + | StableDiffusion1ModelDiffusersConfig +) & { + name: string; +}; + +export const sd1ModelsAdapter = createEntityAdapter({ + selectId: (model) => model.name, + sortComparer: (a, b) => a.name.localeCompare(b.name), +}); + +export const sd1InitialModelsState = sd1ModelsAdapter.getInitialState(); + +export type SD1ModelState = typeof sd1InitialModelsState; + +export const sd1ModelsSlice = createSlice({ + name: 'sd1models', + initialState: sd1InitialModelsState, + reducers: { + modelAdded: sd1ModelsAdapter.upsertOne, + }, + extraReducers(builder) { + /** + * Received Models - FULFILLED + */ + builder.addCase(getModels.fulfilled, (state, action) => { + if (action.meta.arg.baseModel !== 'sd-1') return; + sd1ModelsAdapter.setAll(state, action.payload); + }); + }, +}); + +export const { + selectAll: selectAllSD1Models, + selectById: selectByIdSD1Models, + selectEntities: selectEntitiesSD1Models, + selectIds: selectIdsSD1Models, + selectTotal: selectTotalSD1Models, +} = sd1ModelsAdapter.getSelectors((state) => state.sd1models); + +export const { modelAdded } = sd1ModelsSlice.actions; + +export default sd1ModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts new file mode 100644 index 0000000000..e8e1f5bedf --- /dev/null +++ b/invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts @@ -0,0 +1,53 @@ +import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { + StableDiffusion2ModelCheckpointConfig, + StableDiffusion2ModelDiffusersConfig, +} from 'services/api'; + +import { getModels } from 'services/thunks/model'; + +export type SD2ModelType = ( + | StableDiffusion2ModelCheckpointConfig + | StableDiffusion2ModelDiffusersConfig +) & { + name: string; +}; + +export const sd2ModelsAdapater = createEntityAdapter({ + selectId: (model) => model.name, + sortComparer: (a, b) => a.name.localeCompare(b.name), +}); + +export const sd2InitialModelsState = sd2ModelsAdapater.getInitialState(); + +export type SD2ModelState = typeof sd2InitialModelsState; + +export const sd2ModelsSlice = createSlice({ + name: 'sd2models', + initialState: sd2InitialModelsState, + reducers: { + modelAdded: sd2ModelsAdapater.upsertOne, + }, + extraReducers(builder) { + /** + * Received Models - FULFILLED + */ + builder.addCase(getModels.fulfilled, (state, action) => { + if (action.meta.arg.baseModel !== 'sd-2') return; + sd2ModelsAdapater.setAll(state, action.payload); + }); + }, +}); + +export const { + selectAll: selectAllSD2Models, + selectById: selectByIdSD2Models, + selectEntities: selectEntitiesSD2Models, + selectIds: selectIdsSD2Models, + selectTotal: selectTotalSD2Models, +} = sd2ModelsAdapater.getSelectors((state) => state.sd2models); + +export const { modelAdded } = sd2ModelsSlice.actions; + +export default sd2ModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts index aa9fb057e1..7b0d78d37e 100644 --- a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts @@ -1,6 +1,9 @@ -import { ModelsState } from './modelSlice'; +import { SD1ModelState } from './models/sd1ModelSlice'; +import { SD2ModelState } from './models/sd2ModelSlice'; /** * Models slice persist denylist */ -export const modelsPersistDenylist: (keyof ModelsState)[] = ['entities', 'ids']; +export const modelsPersistDenylist: + | (keyof SD1ModelState)[] + | (keyof SD2ModelState)[] = ['entities', 'ids']; diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index f86415cf37..fd9b8a0a08 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -1,20 +1,12 @@ import { UseToastOptions } from '@chakra-ui/react'; -import { PayloadAction } from '@reduxjs/toolkit'; -import { createSlice } from '@reduxjs/toolkit'; +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import * as InvokeAI from 'app/types/invokeai'; -import { ProgressImage } from 'services/events/types'; -import { makeToast } from '../../../app/components/Toaster'; -import { isAnySessionRejected, sessionCanceled } from 'services/thunks/session'; -import { receivedModels } from 'services/thunks/model'; -import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice'; -import { LogLevelName } from 'roarr'; import { InvokeLogLevel } from 'app/logging/useLogger'; -import { TFuncKey } from 'i18next'; -import { t } from 'i18next'; import { userInvoked } from 'app/store/actions'; -import { LANGUAGES } from '../components/LanguagePicker'; -import { imageUploaded } from 'services/thunks/image'; +import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice'; +import { TFuncKey, t } from 'i18next'; +import { LogLevelName } from 'roarr'; import { appSocketConnected, appSocketDisconnected, @@ -26,6 +18,12 @@ import { appSocketSubscribed, appSocketUnsubscribed, } from 'services/events/actions'; +import { ProgressImage } from 'services/events/types'; +import { imageUploaded } from 'services/thunks/image'; +import { getModels } from 'services/thunks/model'; +import { isAnySessionRejected, sessionCanceled } from 'services/thunks/session'; +import { makeToast } from '../../../app/components/Toaster'; +import { LANGUAGES } from '../components/LanguagePicker'; export type CancelStrategy = 'immediate' | 'scheduled'; @@ -382,7 +380,7 @@ export const systemSlice = createSlice({ /** * Received available models from the backend */ - builder.addCase(receivedModels.fulfilled, (state) => { + builder.addCase(getModels.fulfilled, (state) => { state.wereModelsReceived = true; }); diff --git a/invokeai/frontend/web/src/services/thunks/model.ts b/invokeai/frontend/web/src/services/thunks/model.ts index 97f2bd8016..4d134439f7 100644 --- a/invokeai/frontend/web/src/services/thunks/model.ts +++ b/invokeai/frontend/web/src/services/thunks/model.ts @@ -1,31 +1,55 @@ import { log } from 'app/logging/useLogger'; import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { Model } from 'features/system/store/modelSlice'; +import { SD1ModelType } from 'features/system/store/models/sd1ModelSlice'; import { reduce, size } from 'lodash-es'; -import { ModelsService } from 'services/api'; +import { BaseModelType, ModelType, ModelsService } from 'services/api'; const models = log.child({ namespace: 'model' }); export const IMAGES_PER_PAGE = 20; -export const receivedModels = createAppAsyncThunk( - 'models/receivedModels', - async (_) => { - const response = await ModelsService.listModels(); +type getModelsArg = { + baseModel: BaseModelType | undefined; + modelType: ModelType | undefined; +}; - const deserializedModels = reduce( - response.models['sd-1']['pipeline'], - (modelsAccumulator, model, modelName) => { - modelsAccumulator[modelName] = { ...model, name: modelName }; +export const getModels = createAppAsyncThunk( + 'models/getModels', + async (arg: getModelsArg) => { + const response = await ModelsService.listModels(arg); - return modelsAccumulator; - }, - {} as Record - ); + let deserializedModels = {}; + + if (arg.baseModel === undefined) return response.models; + if (arg.modelType === undefined) return response.models; + + if (arg.baseModel === 'sd-1') { + deserializedModels = reduce( + response.models[arg.baseModel][arg.modelType], + (modelsAccumulator, model, modelName) => { + modelsAccumulator[modelName] = { ...model, name: modelName }; + return modelsAccumulator; + }, + {} as Record + ); + } + + if (arg.baseModel === 'sd-2') { + deserializedModels = reduce( + response.models[arg.baseModel][arg.modelType], + (modelsAccumulator, model, modelName) => { + modelsAccumulator[modelName] = { ...model, name: modelName }; + return modelsAccumulator; + }, + {} as Record + ); + } models.info( { response }, - `Received ${size(response.models['sd-1']['pipeline'])} models` + `Received ${size(response.models[arg.baseModel][arg.modelType])} ${[ + arg.baseModel, + ]} models` ); return deserializedModels; From ef83a2fffe466e1800bd57b30f14384873010cb4 Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Sat, 17 Jun 2023 22:48:44 +0300 Subject: [PATCH 77/99] Add name, base_mode, type fields to model info --- invokeai/backend/model_management/model_manager.py | 4 +++- invokeai/backend/model_management/models/__init__.py | 8 +++++++- invokeai/backend/model_management/models/base.py | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index cbb319c6ea..e6cab04da7 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -530,6 +530,8 @@ class ModelManager(object): models[cur_base_model][cur_model_type][cur_model_name] = dict( **model_config.dict(exclude_defaults=True), + + # OpenAPIModelInfoBase name=cur_model_name, base_model=cur_base_model, type=cur_model_type, @@ -646,7 +648,7 @@ class ModelManager(object): model_class = MODEL_CLASSES[base_model][model_type] if model_class.save_to_config: # TODO: or exclude_unset better fits here? - data_to_save[model_key] = model_config.dict(exclude_defaults=True) + data_to_save[model_key] = model_config.dict(exclude_defaults=True, exclude={"error"}) yaml_str = OmegaConf.to_yaml(data_to_save) config_file_path = conf_file or self.config_path diff --git a/invokeai/backend/model_management/models/__init__.py b/invokeai/backend/model_management/models/__init__.py index eff71798a5..b22075991e 100644 --- a/invokeai/backend/model_management/models/__init__.py +++ b/invokeai/backend/model_management/models/__init__.py @@ -1,3 +1,4 @@ +from pydantic import BaseModel from .base import BaseModelType, ModelType, SubModelType, ModelBase, ModelConfigBase, ModelVariantType, SchedulerPredictionType, ModelError, SilenceWarnings from .stable_diffusion import StableDiffusion1Model, StableDiffusion2Model from .vae import VaeModel @@ -40,10 +41,15 @@ def _get_all_model_configs(): MODEL_CONFIGS = _get_all_model_configs() OPENAPI_MODEL_CONFIGS = list() +class OpenAPIModelInfoBase(BaseModel): + name: str + base_model: BaseModelType + type: ModelType + for cfg in MODEL_CONFIGS: model_name, cfg_name = cfg.__qualname__.split('.')[-2:] openapi_cfg_name = model_name + cfg_name - name_wrapper = type(openapi_cfg_name, (cfg,), {}) + name_wrapper = type(openapi_cfg_name, (cfg, OpenAPIModelInfoBase), {}) #globals()[name] = value vars()[openapi_cfg_name] = name_wrapper diff --git a/invokeai/backend/model_management/models/base.py b/invokeai/backend/model_management/models/base.py index 3bf0045918..ac43b938b0 100644 --- a/invokeai/backend/model_management/models/base.py +++ b/invokeai/backend/model_management/models/base.py @@ -53,7 +53,7 @@ class ModelConfigBase(BaseModel): format: Optional[str] = Field(None) default: Optional[bool] = Field(False) # do not save to config - error: Optional[ModelError] = Field(None, exclude=True) + error: Optional[ModelError] = Field(None) class Config: use_enum_values = True From d2f3500e1bac419904047c42746bdf6a933dee36 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 07:50:28 +1200 Subject: [PATCH 78/99] chore: Rebuild API - base_model and type added --- .../web/src/services/api/models/ControlNetModelConfig.ts | 5 +++++ .../frontend/web/src/services/api/models/LoraModelConfig.ts | 5 +++++ invokeai/frontend/web/src/services/api/models/ModelsList.ts | 4 ++++ .../api/models/StableDiffusion1ModelCheckpointConfig.ts | 5 +++++ .../api/models/StableDiffusion1ModelDiffusersConfig.ts | 5 +++++ .../api/models/StableDiffusion2ModelCheckpointConfig.ts | 5 +++++ .../api/models/StableDiffusion2ModelDiffusersConfig.ts | 5 +++++ .../src/services/api/models/TextualInversionModelConfig.ts | 5 +++++ .../frontend/web/src/services/api/models/VAEModelConfig.ts | 5 +++++ 9 files changed, 44 insertions(+) diff --git a/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts b/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts index 109fc39b7d..e4f77ba7bf 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts @@ -2,9 +2,14 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; export type ControlNetModelConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: ('checkpoint' | 'diffusers'); diff --git a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts b/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts index a62d1a3f4b..d300e38fd0 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts @@ -2,9 +2,14 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; export type LoRAModelConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: ('lycoris' | 'diffusers'); diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index db41b33048..42d0ddd8f6 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -25,10 +25,14 @@ import type { TextualInversionModelConfig } from './TextualInversionModelConfig' import type { VaeModelConfig } from './VaeModelConfig'; export type ModelsList = { +<<<<<<< HEAD <<<<<<< HEAD models: Record>>; >>>>>>> 76dd749b1 (chore: Rebuild API) ======= models: Record>>; >>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) +======= + models: Record>>; +>>>>>>> 24673fd85 (chore: Rebuild API - base_model and type added) }; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts index 7b3f90cd0a..c9708a0b6f 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts @@ -2,10 +2,15 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; export type StableDiffusion1ModelCheckpointConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: 'checkpoint'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts index ec634b9692..4b6f834216 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts @@ -2,10 +2,15 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; export type StableDiffusion1ModelDiffusersConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts index bcf9801314..27b6879703 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts @@ -2,11 +2,16 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; import type { SchedulerPredictionType } from './SchedulerPredictionType'; export type StableDiffusion2ModelCheckpointConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: 'checkpoint'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts index 3f3142dae3..a2b66d7157 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts @@ -2,11 +2,16 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; import type { SchedulerPredictionType } from './SchedulerPredictionType'; export type StableDiffusion2ModelDiffusersConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts b/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts index 34ef4791bc..7abfbec081 100644 --- a/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts @@ -2,9 +2,14 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; export type TextualInversionModelConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: null; diff --git a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts b/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts index 8d0d2e8304..ad7f70c3d4 100644 --- a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts @@ -2,9 +2,14 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; export type VaeModelConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: ('checkpoint' | 'diffusers'); From 727293d722a946d837139ad9a95e44a56ca36ca3 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 08:26:25 +1200 Subject: [PATCH 79/99] fix: 2.1 models breaking generation Co-Authored-By: StAlKeR7779 <7768370+StAlKeR7779@users.noreply.github.com> --- invokeai/backend/model_management/models/base.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/invokeai/backend/model_management/models/base.py b/invokeai/backend/model_management/models/base.py index ac43b938b0..f32e658aa1 100644 --- a/invokeai/backend/model_management/models/base.py +++ b/invokeai/backend/model_management/models/base.py @@ -94,6 +94,11 @@ class ModelBase(metaclass=ABCMeta): def _hf_definition_to_type(self, subtypes: List[str]) -> Type: if len(subtypes) < 2: raise Exception("Invalid subfolder definition!") + if all(t is None for t in subtypes): + return None + elif any(t is None for t in subtypes): + raise Exception(f"Unsupported definition: {subtypes}") + if subtypes[0] in ["diffusers", "transformers"]: res_type = sys.modules[subtypes[0]] subtypes = subtypes[1:] From 4847212d5beede6d79f5f3067be020d52aca8d33 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 08:27:13 +1200 Subject: [PATCH 80/99] feat: Enable 2.x Model Generation in Linear UI --- .../parameters/store/generationSlice.ts | 7 ++++++ .../system/components/ModelSelect.tsx | 24 +++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 39f38e386c..96a6070ad2 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,6 +1,7 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import { Scheduler } from 'app/constants'; +import { ModelLoaderTypes } from 'features/system/components/ModelSelect'; import { configChanged } from 'features/system/store/configSlice'; import { clamp, sortBy } from 'lodash-es'; import { ImageDTO } from 'services/api'; @@ -49,6 +50,7 @@ export interface GenerationState { horizontalSymmetrySteps: number; verticalSymmetrySteps: number; model: ModelParam; + currentModelType: ModelLoaderTypes; shouldUseSeamless: boolean; seamlessXAxis: boolean; seamlessYAxis: boolean; @@ -83,6 +85,7 @@ export const initialGenerationState: GenerationState = { horizontalSymmetrySteps: 0, verticalSymmetrySteps: 0, model: '', + currentModelType: 'sd1_model_loader', shouldUseSeamless: false, seamlessXAxis: true, seamlessYAxis: true, @@ -218,6 +221,9 @@ export const generationSlice = createSlice({ modelSelected: (state, action: PayloadAction) => { state.model = action.payload; }, + setCurrentModelType: (state, action: PayloadAction) => { + state.currentModelType = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(getModels.fulfilled, (state, action) => { @@ -278,6 +284,7 @@ export const { setVerticalSymmetrySteps, initialImageChanged, modelSelected, + setCurrentModelType, setShouldUseNoiseSettings, setSeamless, setSeamlessXAxis, diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index a65c8501dc..bf0775d52e 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -1,6 +1,6 @@ import { createSelector } from '@reduxjs/toolkit'; import { isEqual } from 'lodash-es'; -import { memo, useCallback } from 'react'; +import { memo, useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { RootState } from 'app/store/store'; @@ -9,7 +9,11 @@ import IAIMantineSelect, { IAISelectDataType, } from 'common/components/IAIMantineSelect'; import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { modelSelected } from 'features/parameters/store/generationSlice'; +import { + modelSelected, + setCurrentModelType, +} from 'features/parameters/store/generationSlice'; + import { selectAllSD1Models, selectByIdSD1Models, @@ -55,12 +59,28 @@ export const modelSelector = createSelector( } ); +export type ModelLoaderTypes = 'sd1_model_loader' | 'sd2_model_loader'; + +const MODEL_LOADER_MAP = { + 'sd-1': 'sd1_model_loader', + 'sd-2': 'sd2_model_loader', +}; + const ModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); const { selectedModel, sd1ModelData, sd2ModelData } = useAppSelector(modelSelector); + useEffect(() => { + if (selectedModel) + dispatch( + setCurrentModelType( + MODEL_LOADER_MAP[selectedModel?.base_model] as ModelLoaderTypes + ) + ); + }, [dispatch, selectedModel]); + const handleChangeModel = useCallback( (v: string | null) => { if (!v) { From 604cc1adcdd3c161ba013296265fbca71b534028 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 09:19:13 +1200 Subject: [PATCH 81/99] wip: Move Model Selector to own file --- .../fields/ModelInputFieldComponent.tsx | 10 ++-- .../system/components/ModelSelect.tsx | 57 ++----------------- .../features/system/store/modelSelectors.ts | 53 ++++++++++++++++- 3 files changed, 62 insertions(+), 58 deletions(-) diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx index d3d37765f4..3842e8da3a 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx @@ -5,7 +5,8 @@ import { ModelInputFieldTemplate, ModelInputFieldValue, } from 'features/nodes/types/types'; -import { modelSelector } from 'features/system/components/ModelSelect'; + +import { modelSelector } from 'features/system/store/modelSelectors'; import { ChangeEvent, memo } from 'react'; import { FieldComponentProps } from './types'; @@ -16,7 +17,8 @@ const ModelInputFieldComponent = ( const dispatch = useAppDispatch(); - const { sd1ModelData, sd2ModelData } = useAppSelector(modelSelector); + const { sd1ModelDropDownData, sd2ModelDropdownData } = + useAppSelector(modelSelector); const handleValueChanged = (e: ChangeEvent) => { dispatch( @@ -31,8 +33,8 @@ const ModelInputFieldComponent = ( return ( ); }; diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index bf0775d52e..43de144991 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -1,63 +1,14 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { isEqual } from 'lodash-es'; import { memo, useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIMantineSelect, { - IAISelectDataType, -} from 'common/components/IAIMantineSelect'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; import { modelSelected, setCurrentModelType, } from 'features/parameters/store/generationSlice'; -import { - selectAllSD1Models, - selectByIdSD1Models, -} from '../store/models/sd1ModelSlice'; -import { - selectAllSD2Models, - selectByIdSD2Models, -} from '../store/models/sd2ModelSlice'; - -export const modelSelector = createSelector( - [(state: RootState) => state, generationSelector], - (state, generation) => { - let selectedModel = selectByIdSD1Models(state, generation.model); - if (selectedModel === undefined) - selectedModel = selectByIdSD2Models(state, generation.model); - - const sd1ModelData = selectAllSD1Models(state) - .map((m) => ({ - value: m.name, - label: m.name, - group: '1.x Models', - })) - .sort((a, b) => a.label.localeCompare(b.label)); - - const sd2ModelData = selectAllSD2Models(state) - .map((m) => ({ - value: m.name, - label: m.name, - group: '2.x Models', - })) - .sort((a, b) => a.label.localeCompare(b.label)); - - return { - selectedModel, - sd1ModelData, - sd2ModelData, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); +import { modelSelector } from '../store/modelSelectors'; export type ModelLoaderTypes = 'sd1_model_loader' | 'sd2_model_loader'; @@ -69,7 +20,7 @@ const MODEL_LOADER_MAP = { const ModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const { selectedModel, sd1ModelData, sd2ModelData } = + const { selectedModel, sd1ModelDropDownData, sd2ModelDropdownData } = useAppSelector(modelSelector); useEffect(() => { @@ -97,7 +48,7 @@ const ModelSelect = () => { label={t('modelManager.model')} value={selectedModel?.name ?? ''} placeholder="Pick one" - data={sd1ModelData.concat(sd2ModelData)} + data={sd1ModelDropDownData.concat(sd2ModelDropdownData)} onChange={handleChangeModel} /> ); diff --git a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts b/invokeai/frontend/web/src/features/system/store/modelSelectors.ts index f857bc85bc..6e101da5f5 100644 --- a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts +++ b/invokeai/frontend/web/src/features/system/store/modelSelectors.ts @@ -1,3 +1,54 @@ +import { createSelector } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; +import { IAISelectDataType } from 'common/components/IAIMantineSelect'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { isEqual } from 'lodash-es'; +import { + selectAllSD1Models, + selectByIdSD1Models, +} from './models/sd1ModelSlice'; +import { + selectAllSD2Models, + selectByIdSD2Models, +} from './models/sd2ModelSlice'; -export const modelSelector = (state: RootState) => state.models; +export const modelSelector = createSelector( + [(state: RootState) => state, generationSelector], + (state, generation) => { + let selectedModel = selectByIdSD1Models(state, generation.model); + if (selectedModel === undefined) + selectedModel = selectByIdSD2Models(state, generation.model); + + const sd1Models = selectAllSD1Models(state); + const sd2Models = selectAllSD2Models(state); + + const sd1ModelDropDownData = selectAllSD1Models(state) + .map((m) => ({ + value: m.name, + label: m.name, + group: '1.x Models', + })) + .sort((a, b) => a.label.localeCompare(b.label)); + + const sd2ModelDropdownData = selectAllSD2Models(state) + .map((m) => ({ + value: m.name, + label: m.name, + group: '2.x Models', + })) + .sort((a, b) => a.label.localeCompare(b.label)); + + return { + selectedModel, + sd1Models, + sd2Models, + sd1ModelDropDownData, + sd2ModelDropdownData, + }; + }, + { + memoizeOptions: { + resultEqualityCheck: isEqual, + }, + } +); From 0c3616229e346844a68be393d8f17b5216149eae Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 17:36:23 +1200 Subject: [PATCH 82/99] cleanup: Updated model slice names to be more descriptive Basically updated all slices to be more descriptive in their names. Did so in order to make sure theres good naming scheme available for secondary models. --- .../enhancers/reduxRemember/unserialize.ts | 8 +-- .../listeners/socketio/socketConnected.ts | 7 ++- invokeai/frontend/web/src/app/store/store.ts | 8 +-- .../fields/ModelInputFieldComponent.tsx | 6 +- .../system/components/ModelSelect.tsx | 9 ++- .../features/system/store/modelSelectors.ts | 37 ++++++------ .../system/store/models/sd1ModelSlice.ts | 53 ----------------- .../store/models/sd1PipelineModelSlice.ts | 57 +++++++++++++++++++ .../system/store/models/sd2ModelSlice.ts | 53 ----------------- .../store/models/sd2PipelineModelSlice.ts | 57 +++++++++++++++++++ .../system/store/modelsPersistDenylist.ts | 8 +-- .../frontend/web/src/services/thunks/model.ts | 7 ++- 12 files changed, 164 insertions(+), 146 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts create mode 100644 invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts delete mode 100644 invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts create mode 100644 invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts index 93cc19f832..dc1c25c015 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts @@ -7,8 +7,8 @@ import { initialNodesState } from 'features/nodes/store/nodesSlice'; import { initialGenerationState } from 'features/parameters/store/generationSlice'; import { initialPostprocessingState } from 'features/parameters/store/postprocessingSlice'; import { initialConfigState } from 'features/system/store/configSlice'; -import { sd1InitialModelsState } from 'features/system/store/models/sd1ModelSlice'; -import { sd2InitialModelsState } from 'features/system/store/models/sd2ModelSlice'; +import { sd1InitialPipelineModelsState } from 'features/system/store/models/sd1PipelineModelSlice'; +import { sd2InitialPipelineModelsState } from 'features/system/store/models/sd2PipelineModelSlice'; import { initialSystemState } from 'features/system/store/systemSlice'; import { initialHotkeysState } from 'features/ui/store/hotkeysSlice'; import { initialUIState } from 'features/ui/store/uiSlice'; @@ -22,8 +22,8 @@ const initialStates: { gallery: initialGalleryState, generation: initialGenerationState, lightbox: initialLightboxState, - sd1models: sd1InitialModelsState, - sd2models: sd2InitialModelsState, + sd1pipelinemodels: sd1InitialPipelineModelsState, + sd2pipelinemodels: sd2InitialPipelineModelsState, nodes: initialNodesState, postprocessing: initialPostprocessingState, system: initialSystemState, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index b257b470bd..a88576565a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -15,7 +15,8 @@ export const addSocketConnectedEventListener = () => { moduleLog.debug({ timestamp }, 'Connected'); - const { sd1models, sd2models, nodes, config, images } = getState(); + const { sd1pipelinemodels, sd2pipelinemodels, nodes, config, images } = + getState(); const { disabledTabs } = config; @@ -28,11 +29,11 @@ export const addSocketConnectedEventListener = () => { ); } - if (!sd1models.ids.length) { + if (!sd1pipelinemodels.ids.length) { dispatch(getModels({ baseModel: 'sd-1', modelType: 'pipeline' })); } - if (!sd2models.ids.length) { + if (!sd2pipelinemodels.ids.length) { dispatch(getModels({ baseModel: 'sd-2', modelType: 'pipeline' })); } diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 4ecc9eb9bf..8489de85f0 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -30,8 +30,8 @@ import { actionsDenylist } from './middleware/devtools/actionsDenylist'; import { stateSanitizer } from './middleware/devtools/stateSanitizer'; // Model Reducers -import sd1ModelReducer from 'features/system/store/models/sd1ModelSlice'; -import sd2ModelReducer from 'features/system/store/models/sd2ModelSlice'; +import sd1PipelineModelReducer from 'features/system/store/models/sd1PipelineModelSlice'; +import sd2PipelineModelReducer from 'features/system/store/models/sd2PipelineModelSlice'; import { LOCALSTORAGE_PREFIX } from './constants'; import { serialize } from './enhancers/reduxRemember/serialize'; @@ -43,8 +43,8 @@ const allReducers = { gallery: galleryReducer, generation: generationReducer, lightbox: lightboxReducer, - sd1models: sd1ModelReducer, - sd2models: sd2ModelReducer, + sd1pipelinemodels: sd1PipelineModelReducer, + sd2pipelinemodels: sd2PipelineModelReducer, nodes: nodesReducer, postprocessing: postprocessingReducer, system: systemReducer, diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx index 3842e8da3a..480c8591bb 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx @@ -17,7 +17,7 @@ const ModelInputFieldComponent = ( const dispatch = useAppDispatch(); - const { sd1ModelDropDownData, sd2ModelDropdownData } = + const { sd1PipelineModelDropDownData, sd2PipelineModelDropdownData } = useAppSelector(modelSelector); const handleValueChanged = (e: ChangeEvent) => { @@ -33,8 +33,8 @@ const ModelInputFieldComponent = ( return ( ); }; diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index 43de144991..813bd9fb70 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -20,8 +20,11 @@ const MODEL_LOADER_MAP = { const ModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const { selectedModel, sd1ModelDropDownData, sd2ModelDropdownData } = - useAppSelector(modelSelector); + const { + selectedModel, + sd1PipelineModelDropDownData, + sd2PipelineModelDropdownData, + } = useAppSelector(modelSelector); useEffect(() => { if (selectedModel) @@ -48,7 +51,7 @@ const ModelSelect = () => { label={t('modelManager.model')} value={selectedModel?.name ?? ''} placeholder="Pick one" - data={sd1ModelDropDownData.concat(sd2ModelDropdownData)} + data={sd1PipelineModelDropDownData.concat(sd2PipelineModelDropdownData)} onChange={handleChangeModel} /> ); diff --git a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts b/invokeai/frontend/web/src/features/system/store/modelSelectors.ts index 6e101da5f5..b63c6d256c 100644 --- a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts +++ b/invokeai/frontend/web/src/features/system/store/modelSelectors.ts @@ -3,26 +3,30 @@ import { RootState } from 'app/store/store'; import { IAISelectDataType } from 'common/components/IAIMantineSelect'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { isEqual } from 'lodash-es'; + import { - selectAllSD1Models, - selectByIdSD1Models, -} from './models/sd1ModelSlice'; + selectAllSD1PipelineModels, + selectByIdSD1PipelineModels, +} from './models/sd1PipelineModelSlice'; + import { - selectAllSD2Models, - selectByIdSD2Models, -} from './models/sd2ModelSlice'; + selectAllSD2PipelineModels, + selectByIdSD2PipelineModels, +} from './models/sd2PipelineModelSlice'; export const modelSelector = createSelector( [(state: RootState) => state, generationSelector], (state, generation) => { - let selectedModel = selectByIdSD1Models(state, generation.model); + let selectedModel = selectByIdSD1PipelineModels(state, generation.model); if (selectedModel === undefined) - selectedModel = selectByIdSD2Models(state, generation.model); + selectedModel = selectByIdSD2PipelineModels(state, generation.model); - const sd1Models = selectAllSD1Models(state); - const sd2Models = selectAllSD2Models(state); + const sd1PipelineModels = selectAllSD1PipelineModels(state); + const sd2PipelineModels = selectAllSD2PipelineModels(state); - const sd1ModelDropDownData = selectAllSD1Models(state) + const allPipelineModels = sd1PipelineModels.concat(sd2PipelineModels); + + const sd1PipelineModelDropDownData = selectAllSD1PipelineModels(state) .map((m) => ({ value: m.name, label: m.name, @@ -30,7 +34,7 @@ export const modelSelector = createSelector( })) .sort((a, b) => a.label.localeCompare(b.label)); - const sd2ModelDropdownData = selectAllSD2Models(state) + const sd2PipelineModelDropdownData = selectAllSD2PipelineModels(state) .map((m) => ({ value: m.name, label: m.name, @@ -40,10 +44,11 @@ export const modelSelector = createSelector( return { selectedModel, - sd1Models, - sd2Models, - sd1ModelDropDownData, - sd2ModelDropdownData, + allPipelineModels, + sd1PipelineModels, + sd2PipelineModels, + sd1PipelineModelDropDownData, + sd2PipelineModelDropdownData, }; }, { diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts deleted file mode 100644 index 9f62fde264..0000000000 --- a/invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { - StableDiffusion1ModelCheckpointConfig, - StableDiffusion1ModelDiffusersConfig, -} from 'services/api'; - -import { getModels } from 'services/thunks/model'; - -export type SD1ModelType = ( - | StableDiffusion1ModelCheckpointConfig - | StableDiffusion1ModelDiffusersConfig -) & { - name: string; -}; - -export const sd1ModelsAdapter = createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), -}); - -export const sd1InitialModelsState = sd1ModelsAdapter.getInitialState(); - -export type SD1ModelState = typeof sd1InitialModelsState; - -export const sd1ModelsSlice = createSlice({ - name: 'sd1models', - initialState: sd1InitialModelsState, - reducers: { - modelAdded: sd1ModelsAdapter.upsertOne, - }, - extraReducers(builder) { - /** - * Received Models - FULFILLED - */ - builder.addCase(getModels.fulfilled, (state, action) => { - if (action.meta.arg.baseModel !== 'sd-1') return; - sd1ModelsAdapter.setAll(state, action.payload); - }); - }, -}); - -export const { - selectAll: selectAllSD1Models, - selectById: selectByIdSD1Models, - selectEntities: selectEntitiesSD1Models, - selectIds: selectIdsSD1Models, - selectTotal: selectTotalSD1Models, -} = sd1ModelsAdapter.getSelectors((state) => state.sd1models); - -export const { modelAdded } = sd1ModelsSlice.actions; - -export default sd1ModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts new file mode 100644 index 0000000000..5755b14886 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts @@ -0,0 +1,57 @@ +import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { + StableDiffusion1ModelCheckpointConfig, + StableDiffusion1ModelDiffusersConfig, +} from 'services/api'; + +import { getModels } from 'services/thunks/model'; + +export type SD1PipelineModelType = ( + | StableDiffusion1ModelCheckpointConfig + | StableDiffusion1ModelDiffusersConfig +) & { + name: string; +}; + +export const sd1PipelineModelsAdapter = + createEntityAdapter({ + selectId: (model) => model.name, + sortComparer: (a, b) => a.name.localeCompare(b.name), + }); + +export const sd1InitialPipelineModelsState = + sd1PipelineModelsAdapter.getInitialState(); + +export type SD1PipelineModelState = typeof sd1InitialPipelineModelsState; + +export const sd1PipelineModelsSlice = createSlice({ + name: 'sd1models', + initialState: sd1InitialPipelineModelsState, + reducers: { + modelAdded: sd1PipelineModelsAdapter.upsertOne, + }, + extraReducers(builder) { + /** + * Received Models - FULFILLED + */ + builder.addCase(getModels.fulfilled, (state, action) => { + if (action.meta.arg.baseModel !== 'sd-1') return; + sd1PipelineModelsAdapter.setAll(state, action.payload); + }); + }, +}); + +export const { + selectAll: selectAllSD1PipelineModels, + selectById: selectByIdSD1PipelineModels, + selectEntities: selectEntitiesSD1PipelineModels, + selectIds: selectIdsSD1PipelineModels, + selectTotal: selectTotalSD1PipelineModels, +} = sd1PipelineModelsAdapter.getSelectors( + (state) => state.sd1pipelinemodels +); + +export const { modelAdded } = sd1PipelineModelsSlice.actions; + +export default sd1PipelineModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts deleted file mode 100644 index e8e1f5bedf..0000000000 --- a/invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { - StableDiffusion2ModelCheckpointConfig, - StableDiffusion2ModelDiffusersConfig, -} from 'services/api'; - -import { getModels } from 'services/thunks/model'; - -export type SD2ModelType = ( - | StableDiffusion2ModelCheckpointConfig - | StableDiffusion2ModelDiffusersConfig -) & { - name: string; -}; - -export const sd2ModelsAdapater = createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), -}); - -export const sd2InitialModelsState = sd2ModelsAdapater.getInitialState(); - -export type SD2ModelState = typeof sd2InitialModelsState; - -export const sd2ModelsSlice = createSlice({ - name: 'sd2models', - initialState: sd2InitialModelsState, - reducers: { - modelAdded: sd2ModelsAdapater.upsertOne, - }, - extraReducers(builder) { - /** - * Received Models - FULFILLED - */ - builder.addCase(getModels.fulfilled, (state, action) => { - if (action.meta.arg.baseModel !== 'sd-2') return; - sd2ModelsAdapater.setAll(state, action.payload); - }); - }, -}); - -export const { - selectAll: selectAllSD2Models, - selectById: selectByIdSD2Models, - selectEntities: selectEntitiesSD2Models, - selectIds: selectIdsSD2Models, - selectTotal: selectTotalSD2Models, -} = sd2ModelsAdapater.getSelectors((state) => state.sd2models); - -export const { modelAdded } = sd2ModelsSlice.actions; - -export default sd2ModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts new file mode 100644 index 0000000000..0c307e23cc --- /dev/null +++ b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts @@ -0,0 +1,57 @@ +import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { + StableDiffusion2ModelCheckpointConfig, + StableDiffusion2ModelDiffusersConfig, +} from 'services/api'; + +import { getModels } from 'services/thunks/model'; + +export type SD2PipelineModelType = ( + | StableDiffusion2ModelCheckpointConfig + | StableDiffusion2ModelDiffusersConfig +) & { + name: string; +}; + +export const sd2PipelineModelsAdapater = + createEntityAdapter({ + selectId: (model) => model.name, + sortComparer: (a, b) => a.name.localeCompare(b.name), + }); + +export const sd2InitialPipelineModelsState = + sd2PipelineModelsAdapater.getInitialState(); + +export type SD2PipelineModelState = typeof sd2InitialPipelineModelsState; + +export const sd2PipelineModelsSlice = createSlice({ + name: 'sd2models', + initialState: sd2InitialPipelineModelsState, + reducers: { + modelAdded: sd2PipelineModelsAdapater.upsertOne, + }, + extraReducers(builder) { + /** + * Received Models - FULFILLED + */ + builder.addCase(getModels.fulfilled, (state, action) => { + if (action.meta.arg.baseModel !== 'sd-2') return; + sd2PipelineModelsAdapater.setAll(state, action.payload); + }); + }, +}); + +export const { + selectAll: selectAllSD2PipelineModels, + selectById: selectByIdSD2PipelineModels, + selectEntities: selectEntitiesSD2PipelineModels, + selectIds: selectIdsSD2PipelineModels, + selectTotal: selectTotalSD2PipelineModels, +} = sd2PipelineModelsAdapater.getSelectors( + (state) => state.sd2pipelinemodels +); + +export const { modelAdded } = sd2PipelineModelsSlice.actions; + +export default sd2PipelineModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts index 7b0d78d37e..417a399cf2 100644 --- a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts @@ -1,9 +1,9 @@ -import { SD1ModelState } from './models/sd1ModelSlice'; -import { SD2ModelState } from './models/sd2ModelSlice'; +import { SD1PipelineModelState } from './models/sd1PipelineModelSlice'; +import { SD2PipelineModelState } from './models/sd2PipelineModelSlice'; /** * Models slice persist denylist */ export const modelsPersistDenylist: - | (keyof SD1ModelState)[] - | (keyof SD2ModelState)[] = ['entities', 'ids']; + | (keyof SD1PipelineModelState)[] + | (keyof SD2PipelineModelState)[] = ['entities', 'ids']; diff --git a/invokeai/frontend/web/src/services/thunks/model.ts b/invokeai/frontend/web/src/services/thunks/model.ts index 4d134439f7..039748fa3f 100644 --- a/invokeai/frontend/web/src/services/thunks/model.ts +++ b/invokeai/frontend/web/src/services/thunks/model.ts @@ -1,6 +1,7 @@ import { log } from 'app/logging/useLogger'; import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { SD1ModelType } from 'features/system/store/models/sd1ModelSlice'; +import { SD1PipelineModelType } from 'features/system/store/models/sd1PipelineModelSlice'; +import { SD2PipelineModelType } from 'features/system/store/models/sd2PipelineModelSlice'; import { reduce, size } from 'lodash-es'; import { BaseModelType, ModelType, ModelsService } from 'services/api'; @@ -30,7 +31,7 @@ export const getModels = createAppAsyncThunk( modelsAccumulator[modelName] = { ...model, name: modelName }; return modelsAccumulator; }, - {} as Record + {} as Record ); } @@ -41,7 +42,7 @@ export const getModels = createAppAsyncThunk( modelsAccumulator[modelName] = { ...model, name: modelName }; return modelsAccumulator; }, - {} as Record + {} as Record ); } From 6bdf68dd4cd17e0e4fac0b38a9c5ce5e286dd5aa Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:31:53 +1200 Subject: [PATCH 83/99] feat: Port Schedulers to Mantine --- .../web/src/features/parameters/store/generationSlice.ts | 1 - .../system/components/SettingsModal/SettingsSchedulers.tsx | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 96a6070ad2..d93552809d 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,6 +1,5 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import { Scheduler } from 'app/constants'; import { ModelLoaderTypes } from 'features/system/components/ModelSelect'; import { configChanged } from 'features/system/store/configSlice'; import { clamp, sortBy } from 'lodash-es'; diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx index 2e0b3234c7..a27db43e6b 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx @@ -16,6 +16,7 @@ const data = map(SCHEDULER_NAMES, (s) => ({ export default function SettingsSchedulers() { const dispatch = useAppDispatch(); + const { t } = useTranslation(); const enabledSchedulers = useAppSelector( From e48528bbefc773728faf57b17aec075e6765e07b Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:35:46 +1200 Subject: [PATCH 84/99] revert: getModels to receivedModels --- .../listeners/socketio/socketConnected.ts | 6 +++--- .../web/src/features/parameters/store/generationSlice.ts | 4 ++-- .../features/system/store/models/sd1PipelineModelSlice.ts | 4 ++-- .../features/system/store/models/sd2PipelineModelSlice.ts | 4 ++-- .../frontend/web/src/features/system/store/systemSlice.ts | 4 ++-- invokeai/frontend/web/src/services/thunks/model.ts | 8 ++++---- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index a88576565a..0893066f1f 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -1,7 +1,7 @@ import { log } from 'app/logging/useLogger'; import { appSocketConnected, socketConnected } from 'services/events/actions'; import { receivedPageOfImages } from 'services/thunks/image'; -import { getModels } from 'services/thunks/model'; +import { receivedModels } from 'services/thunks/model'; import { receivedOpenAPISchema } from 'services/thunks/schema'; import { startAppListening } from '../..'; @@ -30,11 +30,11 @@ export const addSocketConnectedEventListener = () => { } if (!sd1pipelinemodels.ids.length) { - dispatch(getModels({ baseModel: 'sd-1', modelType: 'pipeline' })); + dispatch(receivedModels({ baseModel: 'sd-1', modelType: 'pipeline' })); } if (!sd2pipelinemodels.ids.length) { - dispatch(getModels({ baseModel: 'sd-2', modelType: 'pipeline' })); + dispatch(receivedModels({ baseModel: 'sd-2', modelType: 'pipeline' })); } if (!nodes.schema && !disabledTabs.includes('nodes')) { diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index d93552809d..946e9084d8 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -5,7 +5,7 @@ import { configChanged } from 'features/system/store/configSlice'; import { clamp, sortBy } from 'lodash-es'; import { ImageDTO } from 'services/api'; import { imageUrlsReceived } from 'services/thunks/image'; -import { getModels } from 'services/thunks/model'; +import { receivedModels } from 'services/thunks/model'; import { CfgScaleParam, HeightParam, @@ -225,7 +225,7 @@ export const generationSlice = createSlice({ }, }, extraReducers: (builder) => { - builder.addCase(getModels.fulfilled, (state, action) => { + builder.addCase(receivedModels.fulfilled, (state, action) => { if (!state.model) { const firstModel = sortBy(action.payload, 'name')[0]; state.model = firstModel.name; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts index 5755b14886..8c8fbbd4f2 100644 --- a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts @@ -5,7 +5,7 @@ import { StableDiffusion1ModelDiffusersConfig, } from 'services/api'; -import { getModels } from 'services/thunks/model'; +import { receivedModels } from 'services/thunks/model'; export type SD1PipelineModelType = ( | StableDiffusion1ModelCheckpointConfig @@ -35,7 +35,7 @@ export const sd1PipelineModelsSlice = createSlice({ /** * Received Models - FULFILLED */ - builder.addCase(getModels.fulfilled, (state, action) => { + builder.addCase(receivedModels.fulfilled, (state, action) => { if (action.meta.arg.baseModel !== 'sd-1') return; sd1PipelineModelsAdapter.setAll(state, action.payload); }); diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts index 0c307e23cc..fb94fae9d0 100644 --- a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts @@ -5,7 +5,7 @@ import { StableDiffusion2ModelDiffusersConfig, } from 'services/api'; -import { getModels } from 'services/thunks/model'; +import { receivedModels } from 'services/thunks/model'; export type SD2PipelineModelType = ( | StableDiffusion2ModelCheckpointConfig @@ -35,7 +35,7 @@ export const sd2PipelineModelsSlice = createSlice({ /** * Received Models - FULFILLED */ - builder.addCase(getModels.fulfilled, (state, action) => { + builder.addCase(receivedModels.fulfilled, (state, action) => { if (action.meta.arg.baseModel !== 'sd-2') return; sd2PipelineModelsAdapater.setAll(state, action.payload); }); diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index fd9b8a0a08..8a148ca38b 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -20,7 +20,7 @@ import { } from 'services/events/actions'; import { ProgressImage } from 'services/events/types'; import { imageUploaded } from 'services/thunks/image'; -import { getModels } from 'services/thunks/model'; +import { receivedModels } from 'services/thunks/model'; import { isAnySessionRejected, sessionCanceled } from 'services/thunks/session'; import { makeToast } from '../../../app/components/Toaster'; import { LANGUAGES } from '../components/LanguagePicker'; @@ -380,7 +380,7 @@ export const systemSlice = createSlice({ /** * Received available models from the backend */ - builder.addCase(getModels.fulfilled, (state) => { + builder.addCase(receivedModels.fulfilled, (state) => { state.wereModelsReceived = true; }); diff --git a/invokeai/frontend/web/src/services/thunks/model.ts b/invokeai/frontend/web/src/services/thunks/model.ts index 039748fa3f..05766de7b3 100644 --- a/invokeai/frontend/web/src/services/thunks/model.ts +++ b/invokeai/frontend/web/src/services/thunks/model.ts @@ -9,14 +9,14 @@ const models = log.child({ namespace: 'model' }); export const IMAGES_PER_PAGE = 20; -type getModelsArg = { +type receivedModelsArg = { baseModel: BaseModelType | undefined; modelType: ModelType | undefined; }; -export const getModels = createAppAsyncThunk( - 'models/getModels', - async (arg: getModelsArg) => { +export const receivedModels = createAppAsyncThunk( + 'models/receivedModels', + async (arg: receivedModelsArg) => { const response = await ModelsService.listModels(arg); let deserializedModels = {}; From 7033071934d725d4303c4249de3b45c651f9e231 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:38:15 +1200 Subject: [PATCH 85/99] fix: Unserialization key issue --- .../web/src/app/store/enhancers/reduxRemember/unserialize.ts | 4 ++-- .../src/features/system/store/models/sd1PipelineModelSlice.ts | 2 +- .../src/features/system/store/models/sd2PipelineModelSlice.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts index dc1c25c015..649b56316d 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts @@ -22,8 +22,8 @@ const initialStates: { gallery: initialGalleryState, generation: initialGenerationState, lightbox: initialLightboxState, - sd1pipelinemodels: sd1InitialPipelineModelsState, - sd2pipelinemodels: sd2InitialPipelineModelsState, + sd1PipelineModels: sd1InitialPipelineModelsState, + sd2PipelineModels: sd2InitialPipelineModelsState, nodes: initialNodesState, postprocessing: initialPostprocessingState, system: initialSystemState, diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts index 8c8fbbd4f2..a59c29d87a 100644 --- a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts @@ -26,7 +26,7 @@ export const sd1InitialPipelineModelsState = export type SD1PipelineModelState = typeof sd1InitialPipelineModelsState; export const sd1PipelineModelsSlice = createSlice({ - name: 'sd1models', + name: 'sd1PipelineModels', initialState: sd1InitialPipelineModelsState, reducers: { modelAdded: sd1PipelineModelsAdapter.upsertOne, diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts index fb94fae9d0..8e10767a7c 100644 --- a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts @@ -26,7 +26,7 @@ export const sd2InitialPipelineModelsState = export type SD2PipelineModelState = typeof sd2InitialPipelineModelsState; export const sd2PipelineModelsSlice = createSlice({ - name: 'sd2models', + name: 'sd2PipelineModels', initialState: sd2InitialPipelineModelsState, reducers: { modelAdded: sd2PipelineModelsAdapater.upsertOne, From 6256be480c9cb75afa8bf1a22ac4a1a2c3d89dff Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:41:30 +1200 Subject: [PATCH 86/99] fix: Remove type from Model type name --- .../system/store/models/sd1PipelineModelSlice.ts | 11 +++++------ .../system/store/models/sd2PipelineModelSlice.ts | 11 +++++------ invokeai/frontend/web/src/services/thunks/model.ts | 8 ++++---- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts index a59c29d87a..99f1514e6c 100644 --- a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts @@ -7,18 +7,17 @@ import { import { receivedModels } from 'services/thunks/model'; -export type SD1PipelineModelType = ( +export type SD1PipelineModel = ( | StableDiffusion1ModelCheckpointConfig | StableDiffusion1ModelDiffusersConfig ) & { name: string; }; -export const sd1PipelineModelsAdapter = - createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), - }); +export const sd1PipelineModelsAdapter = createEntityAdapter({ + selectId: (model) => model.name, + sortComparer: (a, b) => a.name.localeCompare(b.name), +}); export const sd1InitialPipelineModelsState = sd1PipelineModelsAdapter.getInitialState(); diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts index 8e10767a7c..69ff772222 100644 --- a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts @@ -7,18 +7,17 @@ import { import { receivedModels } from 'services/thunks/model'; -export type SD2PipelineModelType = ( +export type SD2PipelineModel = ( | StableDiffusion2ModelCheckpointConfig | StableDiffusion2ModelDiffusersConfig ) & { name: string; }; -export const sd2PipelineModelsAdapater = - createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), - }); +export const sd2PipelineModelsAdapater = createEntityAdapter({ + selectId: (model) => model.name, + sortComparer: (a, b) => a.name.localeCompare(b.name), +}); export const sd2InitialPipelineModelsState = sd2PipelineModelsAdapater.getInitialState(); diff --git a/invokeai/frontend/web/src/services/thunks/model.ts b/invokeai/frontend/web/src/services/thunks/model.ts index 05766de7b3..619aa4b7b2 100644 --- a/invokeai/frontend/web/src/services/thunks/model.ts +++ b/invokeai/frontend/web/src/services/thunks/model.ts @@ -1,7 +1,7 @@ import { log } from 'app/logging/useLogger'; import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { SD1PipelineModelType } from 'features/system/store/models/sd1PipelineModelSlice'; -import { SD2PipelineModelType } from 'features/system/store/models/sd2PipelineModelSlice'; +import { SD1PipelineModel } from 'features/system/store/models/sd1PipelineModelSlice'; +import { SD2PipelineModel } from 'features/system/store/models/sd2PipelineModelSlice'; import { reduce, size } from 'lodash-es'; import { BaseModelType, ModelType, ModelsService } from 'services/api'; @@ -31,7 +31,7 @@ export const receivedModels = createAppAsyncThunk( modelsAccumulator[modelName] = { ...model, name: modelName }; return modelsAccumulator; }, - {} as Record + {} as Record ); } @@ -42,7 +42,7 @@ export const receivedModels = createAppAsyncThunk( modelsAccumulator[modelName] = { ...model, name: modelName }; return modelsAccumulator; }, - {} as Record + {} as Record ); } From c4c3c96062dc8faafaa3990df99b9ab7703d5acd Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 22:22:56 +1200 Subject: [PATCH 87/99] Revert "feat: Port Schedulers to Mantine" This reverts commit e0c105f413dde29bc242a666b7b270d62ea03908. --- .../web/src/features/parameters/store/generationSlice.ts | 1 + .../system/components/SettingsModal/SettingsSchedulers.tsx | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 946e9084d8..e1de166b5c 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,5 +1,6 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; +import { DEFAULT_SCHEDULER_NAME, Scheduler } from 'app/constants'; import { ModelLoaderTypes } from 'features/system/components/ModelSelect'; import { configChanged } from 'features/system/store/configSlice'; import { clamp, sortBy } from 'lodash-es'; diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx index a27db43e6b..26c11604e1 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx @@ -1,6 +1,5 @@ import { SCHEDULER_LABEL_MAP, SCHEDULER_NAMES } from 'app/constants'; import { RootState } from 'app/store/store'; - import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIMantineMultiSelect from 'common/components/IAIMantineMultiSelect'; import { SchedulerParam } from 'features/parameters/store/parameterZodSchemas'; From 6c987007405c808b7981ed1d30eaeea2d2dcd893 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 19 Jun 2023 23:05:32 +1200 Subject: [PATCH 88/99] fix: Adjust the Schedular select width So the long names do not get cut off. --- .../components/Parameters/Core/ParamSchedulerAndModel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSchedulerAndModel.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSchedulerAndModel.tsx index 65da89b94d..5092893eed 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSchedulerAndModel.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSchedulerAndModel.tsx @@ -6,7 +6,7 @@ import ParamScheduler from './ParamScheduler'; const ParamSchedulerAndModel = () => { return ( - + From d3dec59cc3283ea719aa2198406b0c937b8f69e5 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 19 Jun 2023 23:16:14 +1200 Subject: [PATCH 89/99] tweal: UI colors --- invokeai/frontend/web/src/theme/colors/greenTea.ts | 6 +++--- invokeai/frontend/web/src/theme/colors/invokeAI.ts | 6 +++--- invokeai/frontend/web/src/theme/colors/lightTheme.ts | 2 +- invokeai/frontend/web/src/theme/colors/oceanBlue.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/invokeai/frontend/web/src/theme/colors/greenTea.ts b/invokeai/frontend/web/src/theme/colors/greenTea.ts index ffecbf2ffa..318aecbc61 100644 --- a/invokeai/frontend/web/src/theme/colors/greenTea.ts +++ b/invokeai/frontend/web/src/theme/colors/greenTea.ts @@ -4,8 +4,8 @@ import { generateColorPalette } from '../util/generateColorPalette'; export const greenTeaThemeColors: InvokeAIThemeColors = { base: generateColorPalette(223, 10), baseAlpha: generateColorPalette(223, 10, false, true), - accent: generateColorPalette(155, 80), - accentAlpha: generateColorPalette(155, 80, false, true), + accent: generateColorPalette(160, 60), + accentAlpha: generateColorPalette(160, 60, false, true), working: generateColorPalette(47, 68), workingAlpha: generateColorPalette(47, 68, false, true), warning: generateColorPalette(28, 75), @@ -14,5 +14,5 @@ export const greenTeaThemeColors: InvokeAIThemeColors = { okAlpha: generateColorPalette(122, 49, false, true), error: generateColorPalette(0, 50), errorAlpha: generateColorPalette(0, 50, false, true), - gridLineColor: 'rgba(255, 255, 255, 0.2)', + gridLineColor: 'rgba(255, 255, 255, 0.15)', }; diff --git a/invokeai/frontend/web/src/theme/colors/invokeAI.ts b/invokeai/frontend/web/src/theme/colors/invokeAI.ts index c39b3bed81..82db58bd35 100644 --- a/invokeai/frontend/web/src/theme/colors/invokeAI.ts +++ b/invokeai/frontend/web/src/theme/colors/invokeAI.ts @@ -2,8 +2,8 @@ import { InvokeAIThemeColors } from 'theme/themeTypes'; import { generateColorPalette } from 'theme/util/generateColorPalette'; export const invokeAIThemeColors: InvokeAIThemeColors = { - base: generateColorPalette(225, 15), - baseAlpha: generateColorPalette(225, 15, false, true), + base: generateColorPalette(220, 15), + baseAlpha: generateColorPalette(220, 15, false, true), accent: generateColorPalette(250, 50), accentAlpha: generateColorPalette(250, 50, false, true), working: generateColorPalette(47, 67), @@ -14,5 +14,5 @@ export const invokeAIThemeColors: InvokeAIThemeColors = { okAlpha: generateColorPalette(113, 70, false, true), error: generateColorPalette(0, 76), errorAlpha: generateColorPalette(0, 76, false, true), - gridLineColor: 'rgba(255, 255, 255, 0.2)', + gridLineColor: 'rgba(150, 150, 180, 0.15)', }; diff --git a/invokeai/frontend/web/src/theme/colors/lightTheme.ts b/invokeai/frontend/web/src/theme/colors/lightTheme.ts index 2a7a05bbd2..2fdbd1a769 100644 --- a/invokeai/frontend/web/src/theme/colors/lightTheme.ts +++ b/invokeai/frontend/web/src/theme/colors/lightTheme.ts @@ -14,5 +14,5 @@ export const lightThemeColors: InvokeAIThemeColors = { okAlpha: generateColorPalette(122, 49, true, true), error: generateColorPalette(0, 50, true), errorAlpha: generateColorPalette(0, 50, true, true), - gridLineColor: 'rgba(0, 0, 0, 0.2)', + gridLineColor: 'rgba(0, 0, 0, 0.15)', }; diff --git a/invokeai/frontend/web/src/theme/colors/oceanBlue.ts b/invokeai/frontend/web/src/theme/colors/oceanBlue.ts index adfb8ab288..952e0a5066 100644 --- a/invokeai/frontend/web/src/theme/colors/oceanBlue.ts +++ b/invokeai/frontend/web/src/theme/colors/oceanBlue.ts @@ -14,5 +14,5 @@ export const oceanBlueColors: InvokeAIThemeColors = { okAlpha: generateColorPalette(122, 49, false, true), error: generateColorPalette(0, 100), errorAlpha: generateColorPalette(0, 100, false, true), - gridLineColor: 'rgba(136, 148, 184, 0.2)', + gridLineColor: 'rgba(136, 148, 184, 0.15)', }; From aceadacad48be3e81d59c59f839d6da3a48a74e1 Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Tue, 20 Jun 2023 03:13:10 +0300 Subject: [PATCH 90/99] Remove default model logic --- .../app/services/model_manager_service.py | 24 -------------- .../backend/model_management/model_manager.py | 32 ------------------- .../backend/model_management/models/base.py | 2 -- 3 files changed, 58 deletions(-) diff --git a/invokeai/app/services/model_manager_service.py b/invokeai/app/services/model_manager_service.py index c212ff6a72..8956b55139 100644 --- a/invokeai/app/services/model_manager_service.py +++ b/invokeai/app/services/model_manager_service.py @@ -69,19 +69,6 @@ class ModelManagerServiceBase(ABC): ) -> bool: pass - @abstractmethod - def default_model(self) -> Optional[Tuple[str, BaseModelType, ModelType]]: - """ - Returns the name and typeof the default model, or None - if none is defined. - """ - pass - - @abstractmethod - def set_default_model(self, model_name: str, base_model: BaseModelType, model_type: ModelType): - """Sets the default model to the indicated name.""" - pass - @abstractmethod def model_info(self, model_name: str, base_model: BaseModelType, model_type: ModelType) -> dict: """ @@ -270,17 +257,6 @@ class ModelManagerService(ModelManagerServiceBase): model_type, ) - def default_model(self) -> Optional[Tuple[str, BaseModelType, ModelType]]: - """ - Returns the name of the default model, or None - if none is defined. - """ - return self.mgr.default_model() - - def set_default_model(self, model_name: str, base_model: BaseModelType, model_type: ModelType): - """Sets the default model to the indicated name.""" - self.mgr.set_default_model(model_name, base_model, model_type) - def model_info(self, model_name: str, base_model: BaseModelType, model_type: ModelType) -> dict: """ Given a model name returns a dict-like (OmegaConf) object describing it. diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index e6cab04da7..37798aaf6f 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -445,38 +445,6 @@ class ModelManager(object): _cache = self.cache, ) - def default_model(self) -> Optional[Tuple[str, BaseModelType, ModelType]]: - """ - Returns the name of the default model, or None - if none is defined. - """ - for model_key, model_config in self.models.items(): - if model_config.default: - return self.parse_key(model_key) - - for model_key, _ in self.models.items(): - return self.parse_key(model_key) - else: - return None # TODO: or redo as (None, None, None) - - def set_default_model( - self, - model_name: str, - base_model: BaseModelType, - model_type: ModelType, - ) -> None: - """ - Set the default model. The change will not take - effect until you call model_manager.commit() - """ - - model_key = self.model_key(model_name, base_model, model_type) - if model_key not in self.models: - raise Exception(f"Unknown model: {model_key}") - - for cur_model_key, config in self.models.items(): - config.default = cur_model_key == model_key - def model_info( self, model_name: str, diff --git a/invokeai/backend/model_management/models/base.py b/invokeai/backend/model_management/models/base.py index f32e658aa1..e57ba81c3c 100644 --- a/invokeai/backend/model_management/models/base.py +++ b/invokeai/backend/model_management/models/base.py @@ -48,10 +48,8 @@ class ModelError(str, Enum): class ModelConfigBase(BaseModel): path: str # or Path - #name: str # not included as present in model key description: Optional[str] = Field(None) format: Optional[str] = Field(None) - default: Optional[bool] = Field(False) # do not save to config error: Optional[ModelError] = Field(None) From e4dc9c5a04ab44019c74967ce08e22bb26bcae6f Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Tue, 20 Jun 2023 03:25:08 +0300 Subject: [PATCH 91/99] Rename format to model_format(still named format when work with config) --- .../backend/model_management/model_manager.py | 4 +++ .../backend/model_management/models/base.py | 26 +++++++++---------- .../model_management/models/controlnet.py | 2 +- .../backend/model_management/models/lora.py | 2 +- .../models/stable_diffusion.py | 12 ++++----- .../models/textual_inversion.py | 2 +- .../backend/model_management/models/vae.py | 2 +- 7 files changed, 27 insertions(+), 23 deletions(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 37798aaf6f..9a8c7e64c6 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -266,6 +266,8 @@ class ModelManager(object): for model_key, model_config in config.items(): model_name, base_model, model_type = self.parse_key(model_key) model_class = MODEL_CLASSES[base_model][model_type] + # alias for config file + model_config["model_format"] = model_config.pop("format") self.models[model_key] = model_class.create_config(**model_config) # check config version number and update on disk/RAM if necessary @@ -617,6 +619,8 @@ class ModelManager(object): if model_class.save_to_config: # TODO: or exclude_unset better fits here? data_to_save[model_key] = model_config.dict(exclude_defaults=True, exclude={"error"}) + # alias for config file + data_to_save[model_key]["format"] = data_to_save[model_key].pop("model_format") yaml_str = OmegaConf.to_yaml(data_to_save) config_file_path = conf_file or self.config_path diff --git a/invokeai/backend/model_management/models/base.py b/invokeai/backend/model_management/models/base.py index e57ba81c3c..06cc3db50f 100644 --- a/invokeai/backend/model_management/models/base.py +++ b/invokeai/backend/model_management/models/base.py @@ -49,7 +49,7 @@ class ModelError(str, Enum): class ModelConfigBase(BaseModel): path: str # or Path description: Optional[str] = Field(None) - format: Optional[str] = Field(None) + model_format: Optional[str] = Field(None) # do not save to config error: Optional[ModelError] = Field(None) @@ -125,20 +125,20 @@ class ModelBase(metaclass=ABCMeta): continue fields = inspect.get_annotations(value) - if "format" not in fields: - raise Exception("Invalid config definition - format field not found") + if "model_format" not in fields: + raise Exception("Invalid config definition - model_format field not found") - format_type = typing.get_origin(fields["format"]) + format_type = typing.get_origin(fields["model_format"]) if format_type not in {None, Literal, Union}: - raise Exception(f"Invalid config definition - unknown format type: {fields['format']}") + raise Exception(f"Invalid config definition - unknown format type: {fields['model_format']}") - if format_type is Union and not all(typing.get_origin(v) in {None, Literal} for v in fields["format"].__args__): - raise Exception(f"Invalid config definition - unknown format type: {fields['format']}") + if format_type is Union and not all(typing.get_origin(v) in {None, Literal} for v in fields["model_format"].__args__): + raise Exception(f"Invalid config definition - unknown format type: {fields['model_format']}") if format_type == Union: - f_fields = fields["format"].__args__ + f_fields = fields["model_format"].__args__ else: - f_fields = (fields["format"],) + f_fields = (fields["model_format"],) for field in f_fields: @@ -155,17 +155,17 @@ class ModelBase(metaclass=ABCMeta): @classmethod def create_config(cls, **kwargs) -> ModelConfigBase: - if "format" not in kwargs: - raise Exception("Field 'format' not found in model config") + if "model_format" not in kwargs: + raise Exception("Field 'model_format' not found in model config") configs = cls._get_configs() - return configs[kwargs["format"]](**kwargs) + return configs[kwargs["model_format"]](**kwargs) @classmethod def probe_config(cls, path: str, **kwargs) -> ModelConfigBase: return cls.create_config( path=path, - format=cls.detect_format(path), + model_format=cls.detect_format(path), ) @classmethod diff --git a/invokeai/backend/model_management/models/controlnet.py b/invokeai/backend/model_management/models/controlnet.py index de9926c83e..e452621eba 100644 --- a/invokeai/backend/model_management/models/controlnet.py +++ b/invokeai/backend/model_management/models/controlnet.py @@ -19,7 +19,7 @@ class ControlNetModel(ModelBase): #model_size: int class Config(ModelConfigBase): - format: Union[Literal["checkpoint"], Literal["diffusers"]] + model_format: Union[Literal["checkpoint"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.ControlNet diff --git a/invokeai/backend/model_management/models/lora.py b/invokeai/backend/model_management/models/lora.py index bcf3224ece..2e4309a161 100644 --- a/invokeai/backend/model_management/models/lora.py +++ b/invokeai/backend/model_management/models/lora.py @@ -16,7 +16,7 @@ class LoRAModel(ModelBase): #model_size: int class Config(ModelConfigBase): - format: Union[Literal["lycoris"], Literal["diffusers"]] + model_format: Union[Literal["lycoris"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.Lora diff --git a/invokeai/backend/model_management/models/stable_diffusion.py b/invokeai/backend/model_management/models/stable_diffusion.py index 20aaae23a6..50089b3338 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -23,12 +23,12 @@ from omegaconf import OmegaConf class StableDiffusion1Model(DiffusersModel): class DiffusersConfig(ModelConfigBase): - format: Literal["diffusers"] + model_format: Literal["diffusers"] vae: Optional[str] = Field(None) variant: ModelVariantType class CheckpointConfig(ModelConfigBase): - format: Literal["checkpoint"] + model_format: Literal["checkpoint"] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) variant: ModelVariantType @@ -80,7 +80,7 @@ class StableDiffusion1Model(DiffusersModel): return cls.create_config( path=path, - format=model_format, + model_format=model_format, config=ckpt_config_path, variant=variant, @@ -121,14 +121,14 @@ class StableDiffusion2Model(DiffusersModel): # TODO: check that configs overwriten properly class DiffusersConfig(ModelConfigBase): - format: Literal["diffusers"] + model_format: Literal["diffusers"] vae: Optional[str] = Field(None) variant: ModelVariantType prediction_type: SchedulerPredictionType upcast_attention: bool class CheckpointConfig(ModelConfigBase): - format: Literal["checkpoint"] + model_format: Literal["checkpoint"] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) variant: ModelVariantType @@ -191,7 +191,7 @@ class StableDiffusion2Model(DiffusersModel): return cls.create_config( path=path, - format=model_format, + model_format=model_format, config=ckpt_config_path, variant=variant, diff --git a/invokeai/backend/model_management/models/textual_inversion.py b/invokeai/backend/model_management/models/textual_inversion.py index 66847f53eb..9a032218f0 100644 --- a/invokeai/backend/model_management/models/textual_inversion.py +++ b/invokeai/backend/model_management/models/textual_inversion.py @@ -16,7 +16,7 @@ class TextualInversionModel(ModelBase): #model_size: int class Config(ModelConfigBase): - format: None + model_format: None def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.TextualInversion diff --git a/invokeai/backend/model_management/models/vae.py b/invokeai/backend/model_management/models/vae.py index b78617869a..e86bc00ecd 100644 --- a/invokeai/backend/model_management/models/vae.py +++ b/invokeai/backend/model_management/models/vae.py @@ -24,7 +24,7 @@ class VaeModel(ModelBase): #model_size: int class Config(ModelConfigBase): - format: Union[Literal["checkpoint"], Literal["diffusers"]] + model_format: Union[Literal["checkpoint"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.Vae From da566b59e88b5862ece7f4a5a3ecddc94bc44441 Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Tue, 20 Jun 2023 03:30:09 +0300 Subject: [PATCH 92/99] Update model format field to use enums --- .../backend/model_management/models/base.py | 34 ++++++++----------- .../model_management/models/controlnet.py | 13 ++++--- .../backend/model_management/models/lora.py | 13 ++++--- .../models/stable_diffusion.py | 31 ++++++++++------- .../backend/model_management/models/vae.py | 13 ++++--- 5 files changed, 60 insertions(+), 44 deletions(-) diff --git a/invokeai/backend/model_management/models/base.py b/invokeai/backend/model_management/models/base.py index 06cc3db50f..ef354ecc07 100644 --- a/invokeai/backend/model_management/models/base.py +++ b/invokeai/backend/model_management/models/base.py @@ -125,30 +125,24 @@ class ModelBase(metaclass=ABCMeta): continue fields = inspect.get_annotations(value) - if "model_format" not in fields: - raise Exception("Invalid config definition - model_format field not found") + try: + field = fields["model_format"] + except: + raise Exception(f"Invalid config definition - format field not found({cls.__qualname__})") - format_type = typing.get_origin(fields["model_format"]) - if format_type not in {None, Literal, Union}: - raise Exception(f"Invalid config definition - unknown format type: {fields['model_format']}") + if isinstance(field, type) and issubclass(field, str) and issubclass(field, Enum): + for model_format in field: + configs[model_format.value] = value - if format_type is Union and not all(typing.get_origin(v) in {None, Literal} for v in fields["model_format"].__args__): - raise Exception(f"Invalid config definition - unknown format type: {fields['model_format']}") + elif typing.get_origin(field) is Literal and all(isinstance(arg, str) and isinstance(arg, Enum) for arg in field.__args__): + for model_format in field.__args__: + configs[model_format.value] = value + + elif field is None: + configs[None] = value - if format_type == Union: - f_fields = fields["model_format"].__args__ else: - f_fields = (fields["model_format"],) - - - for field in f_fields: - if field is None: - format_name = None - else: - format_name = field.__args__[0] - - configs[format_name] = value # TODO: error when override(multiple)? - + raise Exception(f"Unsupported format definition in {cls.__qualname__}") cls.__configs = configs return cls.__configs diff --git a/invokeai/backend/model_management/models/controlnet.py b/invokeai/backend/model_management/models/controlnet.py index e452621eba..9563f87afd 100644 --- a/invokeai/backend/model_management/models/controlnet.py +++ b/invokeai/backend/model_management/models/controlnet.py @@ -1,5 +1,6 @@ import os import torch +from enum import Enum from pathlib import Path from typing import Optional, Union, Literal from .base import ( @@ -14,12 +15,16 @@ from .base import ( classproperty, ) +class ControlNetModelFormat(str, Enum): + Checkpoint = "checkpoint" + Diffusers = "diffusers" + class ControlNetModel(ModelBase): #model_class: Type #model_size: int class Config(ModelConfigBase): - model_format: Union[Literal["checkpoint"], Literal["diffusers"]] + model_format: ControlNetModelFormat def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.ControlNet @@ -69,9 +74,9 @@ class ControlNetModel(ModelBase): @classmethod def detect_format(cls, path: str): if os.path.isdir(path): - return "diffusers" + return ControlNetModelFormat.Diffusers else: - return "checkpoint" + return ControlNetModelFormat.Checkpoint @classmethod def convert_if_required( @@ -81,7 +86,7 @@ class ControlNetModel(ModelBase): config: ModelConfigBase, # empty config or config of parent model base_model: BaseModelType, ) -> str: - if cls.detect_format(model_path) != "diffusers": + if cls.detect_format(model_path) != ControlNetModelFormat.Diffusers: raise NotImplementedError("Checkpoint controlnet models currently unsupported") else: return model_path diff --git a/invokeai/backend/model_management/models/lora.py b/invokeai/backend/model_management/models/lora.py index 2e4309a161..59feacde06 100644 --- a/invokeai/backend/model_management/models/lora.py +++ b/invokeai/backend/model_management/models/lora.py @@ -1,5 +1,6 @@ import os import torch +from enum import Enum from typing import Optional, Union, Literal from .base import ( ModelBase, @@ -12,11 +13,15 @@ from .base import ( # TODO: naming from ..lora import LoRAModel as LoRAModelRaw +class LoRAModelFormat(str, Enum): + LyCORIS = "lycoris" + Diffusers = "diffusers" + class LoRAModel(ModelBase): #model_size: int class Config(ModelConfigBase): - model_format: Union[Literal["lycoris"], Literal["diffusers"]] + model_format: LoRAModelFormat # TODO: def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.Lora @@ -52,9 +57,9 @@ class LoRAModel(ModelBase): @classmethod def detect_format(cls, path: str): if os.path.isdir(path): - return "diffusers" + return LoRAModelFormat.Diffusers else: - return "lycoris" + return LoRAModelFormat.LyCORIS @classmethod def convert_if_required( @@ -64,7 +69,7 @@ class LoRAModel(ModelBase): config: ModelConfigBase, base_model: BaseModelType, ) -> str: - if cls.detect_format(model_path) == "diffusers": + if cls.detect_format(model_path) == LoRAModelFormat.Diffusers: # TODO: add diffusers lora when it stabilizes a bit raise NotImplementedError("Diffusers lora not supported") else: diff --git a/invokeai/backend/model_management/models/stable_diffusion.py b/invokeai/backend/model_management/models/stable_diffusion.py index 50089b3338..f169326571 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -1,5 +1,6 @@ import os import json +from enum import Enum from pydantic import Field from pathlib import Path from typing import Literal, Optional, Union @@ -19,16 +20,19 @@ from .base import ( from invokeai.app.services.config import InvokeAIAppConfig from omegaconf import OmegaConf +class StableDiffusion1ModelFormat(str, Enum): + Checkpoint = "checkpoint" + Diffusers = "diffusers" class StableDiffusion1Model(DiffusersModel): class DiffusersConfig(ModelConfigBase): - model_format: Literal["diffusers"] + model_format: Literal[StableDiffusion1ModelFormat.Diffusers] vae: Optional[str] = Field(None) variant: ModelVariantType class CheckpointConfig(ModelConfigBase): - model_format: Literal["checkpoint"] + model_format: Literal[StableDiffusion1ModelFormat.Checkpoint] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) variant: ModelVariantType @@ -47,7 +51,7 @@ class StableDiffusion1Model(DiffusersModel): def probe_config(cls, path: str, **kwargs): model_format = cls.detect_format(path) ckpt_config_path = kwargs.get("config", None) - if model_format == "checkpoint": + if model_format == StableDiffusion1ModelFormat.Checkpoint: if ckpt_config_path: ckpt_config = OmegaConf.load(ckpt_config_path) ckpt_config["model"]["params"]["unet_config"]["params"]["in_channels"] @@ -57,7 +61,7 @@ class StableDiffusion1Model(DiffusersModel): checkpoint = checkpoint.get('state_dict', checkpoint) in_channels = checkpoint["model.diffusion_model.input_blocks.0.0.weight"].shape[1] - elif model_format == "diffusers": + elif model_format == StableDiffusion1ModelFormat.Diffusers: unet_config_path = os.path.join(path, "unet", "config.json") if os.path.exists(unet_config_path): with open(unet_config_path, "r") as f: @@ -93,9 +97,9 @@ class StableDiffusion1Model(DiffusersModel): @classmethod def detect_format(cls, model_path: str): if os.path.isdir(model_path): - return "diffusers" + return StableDiffusion1ModelFormat.Diffusers else: - return "checkpoint" + return StableDiffusion1ModelFormat.Checkpoint @classmethod def convert_if_required( @@ -116,19 +120,22 @@ class StableDiffusion1Model(DiffusersModel): else: return model_path +class StableDiffusion2ModelFormat(str, Enum): + Checkpoint = "checkpoint" + Diffusers = "diffusers" class StableDiffusion2Model(DiffusersModel): # TODO: check that configs overwriten properly class DiffusersConfig(ModelConfigBase): - model_format: Literal["diffusers"] + model_format: Literal[StableDiffusion2ModelFormat.Diffusers] vae: Optional[str] = Field(None) variant: ModelVariantType prediction_type: SchedulerPredictionType upcast_attention: bool class CheckpointConfig(ModelConfigBase): - model_format: Literal["checkpoint"] + model_format: Literal[StableDiffusion2ModelFormat.Checkpoint] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) variant: ModelVariantType @@ -149,7 +156,7 @@ class StableDiffusion2Model(DiffusersModel): def probe_config(cls, path: str, **kwargs): model_format = cls.detect_format(path) ckpt_config_path = kwargs.get("config", None) - if model_format == "checkpoint": + if model_format == StableDiffusion2ModelFormat.Checkpoint: if ckpt_config_path: ckpt_config = OmegaConf.load(ckpt_config_path) ckpt_config["model"]["params"]["unet_config"]["params"]["in_channels"] @@ -159,7 +166,7 @@ class StableDiffusion2Model(DiffusersModel): checkpoint = checkpoint.get('state_dict', checkpoint) in_channels = checkpoint["model.diffusion_model.input_blocks.0.0.weight"].shape[1] - elif model_format == "diffusers": + elif model_format == StableDiffusion2ModelFormat.Diffusers: unet_config_path = os.path.join(path, "unet", "config.json") if os.path.exists(unet_config_path): with open(unet_config_path, "r") as f: @@ -206,9 +213,9 @@ class StableDiffusion2Model(DiffusersModel): @classmethod def detect_format(cls, model_path: str): if os.path.isdir(model_path): - return "diffusers" + return StableDiffusion2ModelFormat.Diffusers else: - return "checkpoint" + return StableDiffusion2ModelFormat.Checkpoint @classmethod def convert_if_required( diff --git a/invokeai/backend/model_management/models/vae.py b/invokeai/backend/model_management/models/vae.py index e86bc00ecd..76133b074d 100644 --- a/invokeai/backend/model_management/models/vae.py +++ b/invokeai/backend/model_management/models/vae.py @@ -1,6 +1,7 @@ import os import torch import safetensors +from enum import Enum from pathlib import Path from typing import Optional, Union, Literal from .base import ( @@ -19,12 +20,16 @@ from invokeai.app.services.config import InvokeAIAppConfig from diffusers.utils import is_safetensors_available from omegaconf import OmegaConf +class VaeModelFormat(str, Enum): + Checkpoint = "checkpoint" + Diffusers = "diffusers" + class VaeModel(ModelBase): #vae_class: Type #model_size: int class Config(ModelConfigBase): - model_format: Union[Literal["checkpoint"], Literal["diffusers"]] + model_format: VaeModelFormat def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.Vae @@ -71,9 +76,9 @@ class VaeModel(ModelBase): @classmethod def detect_format(cls, path: str): if os.path.isdir(path): - return "diffusers" + return VaeModelFormat.Diffusers else: - return "checkpoint" + return VaeModelFormat.Checkpoint @classmethod def convert_if_required( @@ -83,7 +88,7 @@ class VaeModel(ModelBase): config: ModelConfigBase, # empty config or config of parent model base_model: BaseModelType, ) -> str: - if cls.detect_format(model_path) != "diffusers": + if cls.detect_format(model_path) == VaeModelFormat.Checkpoint: return _convert_vae_ckpt_and_cache( weights_path=model_path, output_path=output_path, From 21245a0fb2de5f865faa8dc14c444fdf2eb026da Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Tue, 20 Jun 2023 03:44:58 +0300 Subject: [PATCH 93/99] Set model type to const value in openapi schema, add model format enums to model schema(as they not not referenced in case of Literal definition) --- invokeai/app/api_app.py | 16 +++++ .../model_management/models/__init__.py | 71 ++++++++++++++----- 2 files changed, 71 insertions(+), 16 deletions(-) diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index 22b4efec74..e14c58bab7 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -120,6 +120,22 @@ def custom_openapi(): invoker_schema["output"] = outputs_ref + from invokeai.backend.model_management.models import get_model_config_enums + for model_config_format_enum in set(get_model_config_enums()): + name = model_config_format_enum.__qualname__ + + if name in openapi_schema["components"]["schemas"]: + # print(f"Config with name {name} already defined") + continue + + # "BaseModelType":{"title":"BaseModelType","description":"An enumeration.","enum":["sd-1","sd-2"],"type":"string"} + openapi_schema["components"]["schemas"][name] = dict( + title=name, + description="An enumeration.", + type="string", + enum=list(v.value for v in model_config_format_enum), + ) + app.openapi_schema = openapi_schema return app.openapi_schema diff --git a/invokeai/backend/model_management/models/__init__.py b/invokeai/backend/model_management/models/__init__.py index b22075991e..6975d45f93 100644 --- a/invokeai/backend/model_management/models/__init__.py +++ b/invokeai/backend/model_management/models/__init__.py @@ -1,4 +1,7 @@ +import inspect +from enum import Enum from pydantic import BaseModel +from typing import Literal, get_origin from .base import BaseModelType, ModelType, SubModelType, ModelBase, ModelConfigBase, ModelVariantType, SchedulerPredictionType, ModelError, SilenceWarnings from .stable_diffusion import StableDiffusion1Model, StableDiffusion2Model from .vae import VaeModel @@ -30,15 +33,7 @@ MODEL_CLASSES = { #}, } -def _get_all_model_configs(): - configs = set() - for models in MODEL_CLASSES.values(): - for _, model in models.items(): - configs.update(model._get_configs().values()) - configs.discard(None) - return list(configs) - -MODEL_CONFIGS = _get_all_model_configs() +MODEL_CONFIGS = list() OPENAPI_MODEL_CONFIGS = list() class OpenAPIModelInfoBase(BaseModel): @@ -46,11 +41,55 @@ class OpenAPIModelInfoBase(BaseModel): base_model: BaseModelType type: ModelType -for cfg in MODEL_CONFIGS: - model_name, cfg_name = cfg.__qualname__.split('.')[-2:] - openapi_cfg_name = model_name + cfg_name - name_wrapper = type(openapi_cfg_name, (cfg, OpenAPIModelInfoBase), {}) - #globals()[name] = value - vars()[openapi_cfg_name] = name_wrapper - OPENAPI_MODEL_CONFIGS.append(name_wrapper) +for base_model, models in MODEL_CLASSES.items(): + for model_type, model_class in models.items(): + model_configs = set(model_class._get_configs().values()) + model_configs.discard(None) + MODEL_CONFIGS.extend(model_configs) + + for cfg in model_configs: + model_name, cfg_name = cfg.__qualname__.split('.')[-2:] + openapi_cfg_name = model_name + cfg_name + if openapi_cfg_name in vars(): + continue + + api_wrapper = type(openapi_cfg_name, (cfg, OpenAPIModelInfoBase), dict( + __annotations__ = dict( + type=Literal[model_type.value], + ), + )) + + #globals()[openapi_cfg_name] = api_wrapper + vars()[openapi_cfg_name] = api_wrapper + OPENAPI_MODEL_CONFIGS.append(api_wrapper) + +def get_model_config_enums(): + enums = list() + + for model_config in MODEL_CONFIGS: + fields = inspect.get_annotations(model_config) + try: + field = fields["model_format"] + except: + raise Exception("format field not found") + + # model_format: None + # model_format: SomeModelFormat + # model_format: Literal[SomeModelFormat.Diffusers] + # model_format: Literal[SomeModelFormat.Diffusers, SomeModelFormat.Checkpoint] + + if isinstance(field, type) and issubclass(field, str) and issubclass(field, Enum): + enums.append(field) + + elif get_origin(field) is Literal and all(isinstance(arg, str) and isinstance(arg, Enum) for arg in field.__args__): + enums.append(type(field.__args__[0])) + + elif field is None: + pass + + else: + raise Exception(f"Unsupported format definition in {model_configs.__qualname__}") + + return enums + From b937b7da011a34d9d0d5fbfc8f3d77f2e85afc73 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:34:12 +1000 Subject: [PATCH 94/99] feat(models): update model manager service & route to return list of models --- invokeai/app/api/routers/models.py | 7 +++---- .../app/services/model_manager_service.py | 19 ++++--------------- .../backend/model_management/model_manager.py | 16 ++++++---------- 3 files changed, 13 insertions(+), 29 deletions(-) diff --git a/invokeai/app/api/routers/models.py b/invokeai/app/api/routers/models.py index 0abcc19dcf..50d645eb57 100644 --- a/invokeai/app/api/routers/models.py +++ b/invokeai/app/api/routers/models.py @@ -62,8 +62,7 @@ class ConvertedModelResponse(BaseModel): info: DiffusersModelInfo = Field(description="The converted model info") class ModelsList(BaseModel): - models: Dict[BaseModelType, Dict[ModelType, Dict[str, MODEL_CONFIGS]]] # TODO: debug/discuss with frontend - #models: dict[SDModelType, dict[str, Annotated[Union[(DiffusersModelInfo,CkptModelInfo,SafetensorsModelInfo)], Field(discriminator="format")]]] + models: list[MODEL_CONFIGS] @models_router.get( @@ -72,10 +71,10 @@ class ModelsList(BaseModel): responses={200: {"model": ModelsList }}, ) async def list_models( - base_model: BaseModelType = Query( + base_model: Optional[BaseModelType] = Query( default=None, description="Base model" ), - model_type: ModelType = Query( + model_type: Optional[ModelType] = Query( default=None, description="The type of model to get" ), ) -> ModelsList: diff --git a/invokeai/app/services/model_manager_service.py b/invokeai/app/services/model_manager_service.py index 8956b55139..8b46b17ad0 100644 --- a/invokeai/app/services/model_manager_service.py +++ b/invokeai/app/services/model_manager_service.py @@ -5,7 +5,7 @@ from __future__ import annotations import torch from abc import ABC, abstractmethod from pathlib import Path -from typing import Union, Callable, List, Tuple, types, TYPE_CHECKING +from typing import Optional, Union, Callable, List, Tuple, types, TYPE_CHECKING from dataclasses import dataclass from invokeai.backend.model_management.model_manager import ( @@ -273,21 +273,10 @@ class ModelManagerService(ModelManagerServiceBase): self, base_model: Optional[BaseModelType] = None, model_type: Optional[ModelType] = None - ) -> dict: + ) -> list[dict]: + # ) -> dict: """ - Return a dict of models in the format: - { model_type1: - { model_name1: {'status': 'active'|'cached'|'not loaded', - 'model_name' : name, - 'model_type' : SDModelType, - 'description': description, - 'format': 'folder'|'safetensors'|'ckpt' - }, - model_name2: { etc } - }, - model_type2: - { model_name_n: etc - } + Return a list of models. """ return self.mgr.list_models(base_model, model_type) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 9a8c7e64c6..f9a66a87dd 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -473,9 +473,9 @@ class ModelManager(object): self, base_model: Optional[BaseModelType] = None, model_type: Optional[ModelType] = None, - ) -> Dict[str, Dict[str, str]]: + ) -> list[dict]: """ - Return a dict of models, in format [base_model][model_type][model_name] + Return a list of models. Please use model_manager.models() to get all the model names, model_manager.model_info('model-name') to get the stanza for the model @@ -483,7 +483,7 @@ class ModelManager(object): object derived from models.yaml """ - models = dict() + models = [] for model_key in sorted(self.models, key=str.casefold): model_config = self.models[model_key] @@ -493,20 +493,16 @@ class ModelManager(object): if model_type is not None and cur_model_type != model_type: continue - if cur_base_model not in models: - models[cur_base_model] = dict() - if cur_model_type not in models[cur_base_model]: - models[cur_base_model][cur_model_type] = dict() - - models[cur_base_model][cur_model_type][cur_model_name] = dict( + model_dict = dict( **model_config.dict(exclude_defaults=True), - # OpenAPIModelInfoBase name=cur_model_name, base_model=cur_base_model, type=cur_model_type, ) + models.append(model_dict) + return models def print_models(self) -> None: From 42a59aa147754294b6aec01b42ae83773709ce5a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:36:05 +1000 Subject: [PATCH 95/99] feat(nodes): add `sd_model_loader` node Loads any pipeline model. Also introduced is `PipelineModelField`, which includes a model name and base model. --- invokeai/app/invocations/model.py | 111 ++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/invokeai/app/invocations/model.py b/invokeai/app/invocations/model.py index 9d77cadf8c..48b15c2e4e 100644 --- a/invokeai/app/invocations/model.py +++ b/invokeai/app/invocations/model.py @@ -43,6 +43,117 @@ class ModelLoaderOutput(BaseInvocationOutput): #fmt: on +class PipelineModelField(BaseModel): + """Pipeline model field""" + + model_name: str = Field(description="Name of the model") + base_model: BaseModelType = Field(description="Base model") + + +class SDModelLoaderInvocation(BaseInvocation): + """Loading submodels of selected model.""" + + type: Literal["sd_model_loader"] = "sd_model_loader" + + model: PipelineModelField = Field(description="The model to load") + # TODO: precision? + + # Schema customisation + class Config(InvocationConfig): + schema_extra = { + "ui": { + "tags": ["model", "loader"], + "type_hints": { + "model": "model" + } + }, + } + + def invoke(self, context: InvocationContext) -> ModelLoaderOutput: + + base_model = self.model.base_model + model_name = self.model.model_name + model_type = ModelType.Pipeline + + # TODO: not found exceptions + if not context.services.model_manager.model_exists( + model_name=model_name, + base_model=base_model, + model_type=model_type, + ): + raise Exception(f"Unknown {base_model} {model_type} model: {model_name}") + + """ + if not context.services.model_manager.model_exists( + model_name=self.model_name, + model_type=SDModelType.Diffusers, + submodel=SDModelType.Tokenizer, + ): + raise Exception( + f"Failed to find tokenizer submodel in {self.model_name}! Check if model corrupted" + ) + + if not context.services.model_manager.model_exists( + model_name=self.model_name, + model_type=SDModelType.Diffusers, + submodel=SDModelType.TextEncoder, + ): + raise Exception( + f"Failed to find text_encoder submodel in {self.model_name}! Check if model corrupted" + ) + + if not context.services.model_manager.model_exists( + model_name=self.model_name, + model_type=SDModelType.Diffusers, + submodel=SDModelType.UNet, + ): + raise Exception( + f"Failed to find unet submodel from {self.model_name}! Check if model corrupted" + ) + """ + + + return ModelLoaderOutput( + unet=UNetField( + unet=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.UNet, + ), + scheduler=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.Scheduler, + ), + loras=[], + ), + clip=ClipField( + tokenizer=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.Tokenizer, + ), + text_encoder=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.TextEncoder, + ), + loras=[], + ), + vae=VaeField( + vae=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.Vae, + ), + ) + ) + class SD1ModelLoaderInvocation(BaseInvocation): """Loading submodels of selected model.""" From 3722cdf5d6521d18f4efe5242d765a6f1a90d994 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:36:20 +1000 Subject: [PATCH 96/99] chore(ui): regen api client --- .../frontend/web/src/services/api/index.ts | 35 +-- .../src/services/api/models/AddInvocation.ts | 1 + .../services/api/models/Body_upload_image.ts | 1 + .../models/CannyImageProcessorInvocation.ts | 1 + .../src/services/api/models/CkptModelInfo.ts | 1 + .../web/src/services/api/models/ClipField.ts | 3 - .../services/api/models/CollectInvocation.ts | 1 + .../api/models/CollectInvocationOutput.ts | 1 + .../web/src/services/api/models/ColorField.ts | 1 + .../services/api/models/CompelInvocation.ts | 1 + .../src/services/api/models/CompelOutput.ts | 1 + .../services/api/models/ConditioningField.ts | 1 + .../ContentShuffleImageProcessorInvocation.ts | 1 + .../src/services/api/models/ControlField.ts | 1 + .../api/models/ControlNetInvocation.ts | 1 + .../api/models/ControlNetModelConfig.ts | 8 +- .../api/models/ControlNetModelFormat.ts | 8 + .../src/services/api/models/ControlOutput.ts | 1 + .../services/api/models/CreateModelRequest.ts | 1 + .../api/models/CvInpaintInvocation.ts | 1 + .../services/api/models/DiffusersModelInfo.ts | 1 + .../services/api/models/DivideInvocation.ts | 1 + .../api/models/DynamicPromptInvocation.ts | 1 + .../web/src/services/api/models/Edge.ts | 1 + .../src/services/api/models/EdgeConnection.ts | 1 + .../api/models/FloatCollectionOutput.ts | 1 + .../api/models/FloatLinearRangeInvocation.ts | 1 + .../src/services/api/models/FloatOutput.ts | 1 + .../web/src/services/api/models/Graph.ts | 8 +- .../api/models/GraphExecutionState.ts | 5 +- .../services/api/models/GraphInvocation.ts | 1 + .../api/models/GraphInvocationOutput.ts | 1 + .../api/models/HTTPValidationError.ts | 1 + .../api/models/HedImageProcessorInvocation.ts | 1 + .../api/models/ImageBlurInvocation.ts | 1 + .../api/models/ImageChannelInvocation.ts | 1 + .../api/models/ImageConvertInvocation.ts | 1 + .../api/models/ImageCropInvocation.ts | 1 + .../web/src/services/api/models/ImageDTO.ts | 1 + .../web/src/services/api/models/ImageField.ts | 1 + .../api/models/ImageInverseLerpInvocation.ts | 1 + .../api/models/ImageLerpInvocation.ts | 1 + .../src/services/api/models/ImageMetadata.ts | 1 + .../api/models/ImageMultiplyInvocation.ts | 1 + .../src/services/api/models/ImageOutput.ts | 1 + .../api/models/ImagePasteInvocation.ts | 1 + .../api/models/ImageProcessorInvocation.ts | 1 + .../services/api/models/ImageRecordChanges.ts | 1 + .../api/models/ImageResizeInvocation.ts | 1 + .../api/models/ImageScaleInvocation.ts | 1 + .../api/models/ImageToImageInvocation.ts | 76 ------ .../api/models/ImageToLatentsInvocation.ts | 1 + .../src/services/api/models/ImageUrlsDTO.ts | 1 + .../api/models/InfillColorInvocation.ts | 1 + .../api/models/InfillPatchMatchInvocation.ts | 1 + .../api/models/InfillTileInvocation.ts | 1 + .../services/api/models/InpaintInvocation.ts | 1 + .../api/models/IntCollectionOutput.ts | 1 + .../web/src/services/api/models/IntOutput.ts | 1 + .../services/api/models/IterateInvocation.ts | 1 + .../api/models/IterateInvocationOutput.ts | 1 + .../src/services/api/models/LatentsField.ts | 1 + .../src/services/api/models/LatentsOutput.ts | 1 + .../api/models/LatentsToImageInvocation.ts | 1 + .../api/models/LatentsToLatentsInvocation.ts | 1 + .../LineartAnimeImageProcessorInvocation.ts | 1 + .../models/LineartImageProcessorInvocation.ts | 1 + ...{LoraModelConfig.ts => LoRAModelConfig.ts} | 8 +- .../services/api/models/LoRAModelFormat.ts | 8 + .../api/models/LoadImageInvocation.ts | 1 + .../web/src/services/api/models/LoraInfo.ts | 3 - .../api/models/LoraLoaderInvocation.ts | 3 - .../services/api/models/LoraLoaderOutput.ts | 3 - .../api/models/MaskFromAlphaInvocation.ts | 1 + .../web/src/services/api/models/MaskOutput.ts | 1 + .../MediapipeFaceProcessorInvocation.ts | 1 + .../MidasDepthImageProcessorInvocation.ts | 1 + .../models/MlsdImageProcessorInvocation.ts | 1 + .../web/src/services/api/models/ModelInfo.ts | 3 - .../services/api/models/ModelLoaderOutput.ts | 3 - .../web/src/services/api/models/ModelsList.ts | 25 +- .../services/api/models/MultiplyInvocation.ts | 1 + .../services/api/models/NoiseInvocation.ts | 1 + .../src/services/api/models/NoiseOutput.ts | 1 + .../NormalbaeImageProcessorInvocation.ts | 1 + .../OffsetPaginatedResults_ImageDTO_.ts | 1 + .../OpenposeImageProcessorInvocation.ts | 1 + .../PaginatedResults_GraphExecutionState_.ts | 1 + .../api/models/ParamFloatInvocation.ts | 1 + .../services/api/models/ParamIntInvocation.ts | 1 + .../models/PidiImageProcessorInvocation.ts | 1 + .../services/api/models/PipelineModelField.ts | 20 ++ .../api/models/PromptCollectionOutput.ts | 1 + .../src/services/api/models/PromptOutput.ts | 1 + .../api/models/RandomIntInvocation.ts | 1 + .../api/models/RandomRangeInvocation.ts | 1 + .../services/api/models/RangeInvocation.ts | 1 + .../api/models/RangeOfSizeInvocation.ts | 1 + .../api/models/ResizeLatentsInvocation.ts | 1 + .../api/models/RestoreFaceInvocation.ts | 1 + .../api/models/SD1ModelLoaderInvocation.ts | 3 - .../api/models/SD2ModelLoaderInvocation.ts | 3 - .../api/models/SDModelLoaderInvocation.ts | 25 ++ .../api/models/ScaleLatentsInvocation.ts | 1 + .../api/models/ShowImageInvocation.ts | 1 + .../StableDiffusion1ModelCheckpointConfig.ts | 7 +- .../StableDiffusion1ModelDiffusersConfig.ts | 7 +- .../api/models/StableDiffusion1ModelFormat.ts | 8 + .../StableDiffusion2ModelCheckpointConfig.ts | 7 +- .../StableDiffusion2ModelDiffusersConfig.ts | 7 +- .../api/models/StableDiffusion2ModelFormat.ts | 8 + .../api/models/StepParamEasingInvocation.ts | 1 + .../services/api/models/SubtractInvocation.ts | 1 + .../api/models/TextToImageInvocation.ts | 64 ----- .../api/models/TextToLatentsInvocation.ts | 1 + .../api/models/TextualInversionModelConfig.ts | 7 +- .../web/src/services/api/models/UNetField.ts | 3 - .../services/api/models/UpscaleInvocation.ts | 1 + .../web/src/services/api/models/VaeField.ts | 3 - .../{VAEModelConfig.ts => VaeModelConfig.ts} | 8 +- .../src/services/api/models/VaeModelFormat.ts | 8 + .../web/src/services/api/models/VaeRepo.ts | 1 + .../services/api/models/ValidationError.ts | 1 + .../ZoeDepthImageProcessorInvocation.ts | 1 + ...ls__controlnet__ControlNetModel__Config.ts | 14 -- ...gement__models__lora__LoRAModel__Config.ts | 14 -- ...StableDiffusion1Model__CheckpointConfig.ts | 18 -- ..._StableDiffusion1Model__DiffusersConfig.ts | 17 -- ...StableDiffusion2Model__CheckpointConfig.ts | 21 -- ..._StableDiffusion2Model__DiffusersConfig.ts | 20 -- ...nversion__TextualInversionModel__Config.ts | 14 -- ...nagement__models__vae__VaeModel__Config.ts | 14 -- .../services/api/services/ImagesService.ts | 156 +++++------- .../services/api/services/ModelsService.ts | 31 +-- .../services/api/services/SessionsService.ts | 224 ++++++++---------- 135 files changed, 387 insertions(+), 636 deletions(-) create mode 100644 invokeai/frontend/web/src/services/api/models/ControlNetModelFormat.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts rename invokeai/frontend/web/src/services/api/models/{LoraModelConfig.ts => LoRAModelConfig.ts} (71%) create mode 100644 invokeai/frontend/web/src/services/api/models/LoRAModelFormat.ts create mode 100644 invokeai/frontend/web/src/services/api/models/PipelineModelField.ts create mode 100644 invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelFormat.ts create mode 100644 invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelFormat.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts rename invokeai/frontend/web/src/services/api/models/{VAEModelConfig.ts => VaeModelConfig.ts} (71%) create mode 100644 invokeai/frontend/web/src/services/api/models/VaeModelFormat.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__controlnet__ControlNetModel__Config.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__lora__LoRAModel__Config.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__vae__VaeModel__Config.ts diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index a738a9aafd..3c143ecf6e 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -8,13 +8,10 @@ export type { OpenAPIConfig } from './core/OpenAPI'; export type { AddInvocation } from './models/AddInvocation'; export type { BaseModelType } from './models/BaseModelType'; -<<<<<<< HEAD export type { BoardChanges } from './models/BoardChanges'; export type { BoardDTO } from './models/BoardDTO'; export type { Body_create_board_image } from './models/Body_create_board_image'; export type { Body_remove_board_image } from './models/Body_remove_board_image'; -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) export type { Body_upload_image } from './models/Body_upload_image'; export type { CannyImageProcessorInvocation } from './models/CannyImageProcessorInvocation'; export type { CkptModelInfo } from './models/CkptModelInfo'; @@ -29,6 +26,7 @@ export type { ContentShuffleImageProcessorInvocation } from './models/ContentShu export type { ControlField } from './models/ControlField'; export type { ControlNetInvocation } from './models/ControlNetInvocation'; export type { ControlNetModelConfig } from './models/ControlNetModelConfig'; +export type { ControlNetModelFormat } from './models/ControlNetModelFormat'; export type { ControlOutput } from './models/ControlOutput'; export type { CreateModelRequest } from './models/CreateModelRequest'; export type { CvInpaintInvocation } from './models/CvInpaintInvocation'; @@ -71,14 +69,6 @@ export type { InfillTileInvocation } from './models/InfillTileInvocation'; export type { InpaintInvocation } from './models/InpaintInvocation'; export type { IntCollectionOutput } from './models/IntCollectionOutput'; export type { IntOutput } from './models/IntOutput'; -export type { invokeai__backend__model_management__models__controlnet__ControlNetModel__Config } from './models/invokeai__backend__model_management__models__controlnet__ControlNetModel__Config'; -export type { invokeai__backend__model_management__models__lora__LoRAModel__Config } from './models/invokeai__backend__model_management__models__lora__LoRAModel__Config'; -export type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig } from './models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig'; -export type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig } from './models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig'; -export type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig } from './models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig'; -export type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig } from './models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig'; -export type { invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config } from './models/invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config'; -export type { invokeai__backend__model_management__models__vae__VaeModel__Config } from './models/invokeai__backend__model_management__models__vae__VaeModel__Config'; export type { IterateInvocation } from './models/IterateInvocation'; export type { IterateInvocationOutput } from './models/IterateInvocationOutput'; export type { LatentsField } from './models/LatentsField'; @@ -91,14 +81,8 @@ export type { LoadImageInvocation } from './models/LoadImageInvocation'; export type { LoraInfo } from './models/LoraInfo'; export type { LoraLoaderInvocation } from './models/LoraLoaderInvocation'; export type { LoraLoaderOutput } from './models/LoraLoaderOutput'; -<<<<<<< HEAD -<<<<<<< HEAD -======= -export type { LoraModelConfig } from './models/LoraModelConfig'; ->>>>>>> 76dd749b1 (chore: Rebuild API) -======= export type { LoRAModelConfig } from './models/LoRAModelConfig'; ->>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) +export type { LoRAModelFormat } from './models/LoRAModelFormat'; export type { MaskFromAlphaInvocation } from './models/MaskFromAlphaInvocation'; export type { MaskOutput } from './models/MaskOutput'; export type { MediapipeFaceProcessorInvocation } from './models/MediapipeFaceProcessorInvocation'; @@ -121,6 +105,7 @@ export type { PaginatedResults_GraphExecutionState_ } from './models/PaginatedRe export type { ParamFloatInvocation } from './models/ParamFloatInvocation'; export type { ParamIntInvocation } from './models/ParamIntInvocation'; export type { PidiImageProcessorInvocation } from './models/PidiImageProcessorInvocation'; +export type { PipelineModelField } from './models/PipelineModelField'; export type { PromptCollectionOutput } from './models/PromptCollectionOutput'; export type { PromptOutput } from './models/PromptOutput'; export type { RandomIntInvocation } from './models/RandomIntInvocation'; @@ -134,30 +119,24 @@ export type { ScaleLatentsInvocation } from './models/ScaleLatentsInvocation'; export type { SchedulerPredictionType } from './models/SchedulerPredictionType'; export type { SD1ModelLoaderInvocation } from './models/SD1ModelLoaderInvocation'; export type { SD2ModelLoaderInvocation } from './models/SD2ModelLoaderInvocation'; +export type { SDModelLoaderInvocation } from './models/SDModelLoaderInvocation'; export type { ShowImageInvocation } from './models/ShowImageInvocation'; export type { StableDiffusion1ModelCheckpointConfig } from './models/StableDiffusion1ModelCheckpointConfig'; export type { StableDiffusion1ModelDiffusersConfig } from './models/StableDiffusion1ModelDiffusersConfig'; +export type { StableDiffusion1ModelFormat } from './models/StableDiffusion1ModelFormat'; export type { StableDiffusion2ModelCheckpointConfig } from './models/StableDiffusion2ModelCheckpointConfig'; export type { StableDiffusion2ModelDiffusersConfig } from './models/StableDiffusion2ModelDiffusersConfig'; +export type { StableDiffusion2ModelFormat } from './models/StableDiffusion2ModelFormat'; export type { StepParamEasingInvocation } from './models/StepParamEasingInvocation'; export type { SubModelType } from './models/SubModelType'; export type { SubtractInvocation } from './models/SubtractInvocation'; export type { TextToLatentsInvocation } from './models/TextToLatentsInvocation'; -<<<<<<< HEAD -export type { UNetField } from './models/UNetField'; -export type { UpscaleInvocation } from './models/UpscaleInvocation'; -export type { VaeField } from './models/VaeField'; -======= export type { TextualInversionModelConfig } from './models/TextualInversionModelConfig'; export type { UNetField } from './models/UNetField'; export type { UpscaleInvocation } from './models/UpscaleInvocation'; export type { VaeField } from './models/VaeField'; -<<<<<<< HEAD -export type { VAEModelConfig } from './models/VAEModelConfig'; ->>>>>>> 76dd749b1 (chore: Rebuild API) -======= export type { VaeModelConfig } from './models/VaeModelConfig'; ->>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) +export type { VaeModelFormat } from './models/VaeModelFormat'; export type { VaeRepo } from './models/VaeRepo'; export type { ValidationError } from './models/ValidationError'; export type { ZoeDepthImageProcessorInvocation } from './models/ZoeDepthImageProcessorInvocation'; diff --git a/invokeai/frontend/web/src/services/api/models/AddInvocation.ts b/invokeai/frontend/web/src/services/api/models/AddInvocation.ts index b7c1c88187..e9671a918f 100644 --- a/invokeai/frontend/web/src/services/api/models/AddInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/AddInvocation.ts @@ -24,3 +24,4 @@ export type AddInvocation = { */ 'b'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts b/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts index fd26ed49e0..b81146d3ab 100644 --- a/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts +++ b/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts @@ -5,3 +5,4 @@ export type Body_upload_image = { file: Blob; }; + diff --git a/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts index 3f1a5b3d46..d5203867ac 100644 --- a/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts @@ -30,3 +30,4 @@ export type CannyImageProcessorInvocation = { */ high_threshold?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts b/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts index 464474736f..cfa4357725 100644 --- a/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts @@ -37,3 +37,4 @@ export type CkptModelInfo = { */ height?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ClipField.ts b/invokeai/frontend/web/src/services/api/models/ClipField.ts index f267fe85d9..f9ef2cc683 100644 --- a/invokeai/frontend/web/src/services/api/models/ClipField.ts +++ b/invokeai/frontend/web/src/services/api/models/ClipField.ts @@ -19,7 +19,4 @@ export type ClipField = { */ loras: Array; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts b/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts index a0fe38613c..f190ab7073 100644 --- a/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts @@ -24,3 +24,4 @@ export type CollectInvocation = { */ collection?: Array; }; + diff --git a/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts index 62c785f374..a5976242ea 100644 --- a/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts @@ -12,3 +12,4 @@ export type CollectInvocationOutput = { */ collection: Array; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ColorField.ts b/invokeai/frontend/web/src/services/api/models/ColorField.ts index 25167433d4..e0a609ec12 100644 --- a/invokeai/frontend/web/src/services/api/models/ColorField.ts +++ b/invokeai/frontend/web/src/services/api/models/ColorField.ts @@ -20,3 +20,4 @@ export type ColorField = { */ 'a': number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts b/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts index 642e11023b..dd381ef22c 100644 --- a/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts @@ -26,3 +26,4 @@ export type CompelInvocation = { */ clip?: ClipField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/CompelOutput.ts b/invokeai/frontend/web/src/services/api/models/CompelOutput.ts index b42ab73c74..94f1fcb282 100644 --- a/invokeai/frontend/web/src/services/api/models/CompelOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/CompelOutput.ts @@ -14,3 +14,4 @@ export type CompelOutput = { */ conditioning?: ConditioningField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ConditioningField.ts b/invokeai/frontend/web/src/services/api/models/ConditioningField.ts index da227dd4f9..7e53a63b42 100644 --- a/invokeai/frontend/web/src/services/api/models/ConditioningField.ts +++ b/invokeai/frontend/web/src/services/api/models/ConditioningField.ts @@ -8,3 +8,4 @@ export type ConditioningField = { */ conditioning_name: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts index 43f6100db8..e3f67ec9be 100644 --- a/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts @@ -42,3 +42,4 @@ export type ContentShuffleImageProcessorInvocation = { */ 'f'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ControlField.ts b/invokeai/frontend/web/src/services/api/models/ControlField.ts index 8de332b82d..0479684d2c 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlField.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlField.ts @@ -26,3 +26,4 @@ export type ControlField = { */ end_step_percent: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts b/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts index 31e22a1bba..42268b8295 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts @@ -38,3 +38,4 @@ export type ControlNetInvocation = { */ end_step_percent?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts b/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts index e4f77ba7bf..60e2958f5c 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts @@ -3,16 +3,16 @@ /* eslint-disable */ import type { BaseModelType } from './BaseModelType'; +import type { ControlNetModelFormat } from './ControlNetModelFormat'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; export type ControlNetModelConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'controlnet'; path: string; description?: string; - format: ('checkpoint' | 'diffusers'); - default?: boolean; + model_format: ControlNetModelFormat; error?: ModelError; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ControlNetModelFormat.ts b/invokeai/frontend/web/src/services/api/models/ControlNetModelFormat.ts new file mode 100644 index 0000000000..500b3e8f8c --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ControlNetModelFormat.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type ControlNetModelFormat = 'checkpoint' | 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/ControlOutput.ts b/invokeai/frontend/web/src/services/api/models/ControlOutput.ts index 625d8f670d..a3cc5530c1 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlOutput.ts @@ -14,3 +14,4 @@ export type ControlOutput = { */ control?: ControlField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts b/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts index 80976f126f..0b0f52b8fe 100644 --- a/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts +++ b/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts @@ -15,3 +15,4 @@ export type CreateModelRequest = { */ info: (CkptModelInfo | DiffusersModelInfo); }; + diff --git a/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts index 4f1c33483e..874df93c30 100644 --- a/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts @@ -26,3 +26,4 @@ export type CvInpaintInvocation = { */ mask?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts b/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts index ea175e351a..4e722ddb80 100644 --- a/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts @@ -31,3 +31,4 @@ export type DiffusersModelInfo = { */ path?: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts b/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts index 5b37dea710..fd5b3475ae 100644 --- a/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts @@ -24,3 +24,4 @@ export type DivideInvocation = { */ 'b'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts b/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts index 79dc958d0f..f7323a489b 100644 --- a/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts @@ -28,3 +28,4 @@ export type DynamicPromptInvocation = { */ combinatorial?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/Edge.ts b/invokeai/frontend/web/src/services/api/models/Edge.ts index e72108f74a..bba275cb26 100644 --- a/invokeai/frontend/web/src/services/api/models/Edge.ts +++ b/invokeai/frontend/web/src/services/api/models/Edge.ts @@ -14,3 +14,4 @@ export type Edge = { */ destination: EdgeConnection; }; + diff --git a/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts b/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts index ab4c6d354b..ecbddccd76 100644 --- a/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts +++ b/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts @@ -12,3 +12,4 @@ export type EdgeConnection = { */ field: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts b/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts index fb9e4164e6..a3f08247a4 100644 --- a/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts @@ -12,3 +12,4 @@ export type FloatCollectionOutput = { */ collection?: Array; }; + diff --git a/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts index a9e67d8135..e0fd4a1caa 100644 --- a/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts @@ -28,3 +28,4 @@ export type FloatLinearRangeInvocation = { */ steps?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/FloatOutput.ts b/invokeai/frontend/web/src/services/api/models/FloatOutput.ts index db2784ed9f..2331936b30 100644 --- a/invokeai/frontend/web/src/services/api/models/FloatOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/FloatOutput.ts @@ -12,3 +12,4 @@ export type FloatOutput = { */ param?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index 3f51247701..7976c08abb 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -58,6 +58,7 @@ import type { RestoreFaceInvocation } from './RestoreFaceInvocation'; import type { ScaleLatentsInvocation } from './ScaleLatentsInvocation'; import type { SD1ModelLoaderInvocation } from './SD1ModelLoaderInvocation'; import type { SD2ModelLoaderInvocation } from './SD2ModelLoaderInvocation'; +import type { SDModelLoaderInvocation } from './SDModelLoaderInvocation'; import type { ShowImageInvocation } from './ShowImageInvocation'; import type { StepParamEasingInvocation } from './StepParamEasingInvocation'; import type { SubtractInvocation } from './SubtractInvocation'; @@ -73,13 +74,10 @@ export type Graph = { /** * The nodes in this graph */ -<<<<<<< HEAD - nodes?: Record; -======= - nodes?: Record; ->>>>>>> 76dd749b1 (chore: Rebuild API) + nodes?: Record; /** * The connections between nodes and their fields in this graph */ edges?: Array; }; + diff --git a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts index 24ec8c9d6d..602e7a2ebc 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts @@ -48,11 +48,7 @@ export type GraphExecutionState = { /** * The results of node executions */ -<<<<<<< HEAD results: Record; -======= - results: Record; ->>>>>>> 76dd749b1 (chore: Rebuild API) /** * Errors raised when executing nodes */ @@ -66,3 +62,4 @@ export type GraphExecutionState = { */ source_prepared_mapping: Record>; }; + diff --git a/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts b/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts index a7e3d6c948..8512faae74 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts @@ -22,3 +22,4 @@ export type GraphInvocation = { */ graph?: Graph; }; + diff --git a/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts index 219a4a675d..af0aae3edb 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts @@ -8,3 +8,4 @@ export type GraphInvocationOutput = { type: 'graph_output'; }; + diff --git a/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts b/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts index 69908c3bba..5e13adc4e5 100644 --- a/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts +++ b/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts @@ -7,3 +7,4 @@ import type { ValidationError } from './ValidationError'; export type HTTPValidationError = { detail?: Array; }; + diff --git a/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts index 387e8c8634..1132012c5a 100644 --- a/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts @@ -34,3 +34,4 @@ export type HedImageProcessorInvocation = { */ scribble?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts index 6466efcd82..3ba86d8fab 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts @@ -30,3 +30,4 @@ export type ImageBlurInvocation = { */ blur_type?: 'gaussian' | 'box'; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts index d6abae5eae..47bfd4110f 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts @@ -26,3 +26,4 @@ export type ImageChannelInvocation = { */ channel?: 'A' | 'R' | 'G' | 'B'; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts index d303c7ce79..4bd59d03b0 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts @@ -26,3 +26,4 @@ export type ImageConvertInvocation = { */ mode?: 'L' | 'RGB' | 'RGBA' | 'CMYK' | 'YCbCr' | 'LAB' | 'HSV' | 'I' | 'F'; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts index e29e177aa7..5207ebbf6d 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts @@ -38,3 +38,4 @@ export type ImageCropInvocation = { */ height?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageDTO.ts b/invokeai/frontend/web/src/services/api/models/ImageDTO.ts index 1e0ea0648f..4e273e8854 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageDTO.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageDTO.ts @@ -71,3 +71,4 @@ export type ImageDTO = { */ board_id?: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageField.ts b/invokeai/frontend/web/src/services/api/models/ImageField.ts index c4c67a0674..baf3bf2b54 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageField.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageField.ts @@ -11,3 +11,4 @@ export type ImageField = { */ image_name: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts index 63220c8047..0347d4dc38 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts @@ -30,3 +30,4 @@ export type ImageInverseLerpInvocation = { */ max?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts index 444c7e6467..388c86061c 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts @@ -30,3 +30,4 @@ export type ImageLerpInvocation = { */ max?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts b/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts index 8aecdddc4f..0b2af78799 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts @@ -78,3 +78,4 @@ export type ImageMetadata = { */ extra?: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts index 724061fce8..751ee49158 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts @@ -26,3 +26,4 @@ export type ImageMultiplyInvocation = { */ image2?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageOutput.ts b/invokeai/frontend/web/src/services/api/models/ImageOutput.ts index c030632926..d7db0c11de 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageOutput.ts @@ -22,3 +22,4 @@ export type ImageOutput = { */ height: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts index 5af28452b6..c883b9a5d8 100644 --- a/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts @@ -38,3 +38,4 @@ export type ImagePasteInvocation = { */ 'y'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts index e0058a78ca..0d995c4e68 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts @@ -22,3 +22,4 @@ export type ImageProcessorInvocation = { */ image?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts b/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts index 209b6d8e88..e597cd907d 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts @@ -26,3 +26,4 @@ export type ImageRecordChanges = { */ is_intermediate?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts index f277516ccd..3b096c83b7 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts @@ -34,3 +34,4 @@ export type ImageResizeInvocation = { */ resample_mode?: 'nearest' | 'box' | 'bilinear' | 'hamming' | 'bicubic' | 'lanczos'; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts index 709e1cc66a..bf4da28a4a 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts @@ -30,3 +30,4 @@ export type ImageScaleInvocation = { */ resample_mode?: 'nearest' | 'box' | 'bilinear' | 'hamming' | 'bicubic' | 'lanczos'; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts deleted file mode 100644 index faa9d164a8..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Generates an image using img2img. - */ -export type ImageToImageInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'img2img'; - /** - * The prompt to generate an image from - */ - prompt?: string; - /** - * The seed to use (omit for random) - */ - seed?: number; - /** - * The number of steps to use to generate the image - */ - steps?: number; - /** - * The width of the resulting image - */ - width?: number; - /** - * The height of the resulting image - */ - height?: number; - /** - * The Classifier-Free Guidance, higher values may result in a result closer to the prompt - */ - cfg_scale?: number; - /** - * The scheduler to use - */ - scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; - /** - * The model to use (currently ignored) - */ - model?: string; - /** - * Whether or not to produce progress images during generation - */ - progress_images?: boolean; - /** - * The control model to use - */ - control_model?: string; - /** - * The processed control image - */ - control_image?: ImageField; - /** - * The input image - */ - image?: ImageField; - /** - * The strength of the original image - */ - strength?: number; - /** - * Whether or not the result should be fit to the aspect ratio of the input image - */ - fit?: boolean; -}; diff --git a/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts index 65df90351a..ace0ed8e3c 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts @@ -31,3 +31,4 @@ export type ImageToLatentsInvocation = { */ tiled?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts b/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts index ad45d2047e..1e0ff322e8 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts @@ -19,3 +19,4 @@ export type ImageUrlsDTO = { */ thumbnail_url: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts index 6d60bbe226..3e637b299c 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts @@ -27,3 +27,4 @@ export type InfillColorInvocation = { */ color?: ColorField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts index bf6e8012b7..325bfe2080 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts @@ -22,3 +22,4 @@ export type InfillPatchMatchInvocation = { */ image?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts index 551e00da16..dfb1cbc61d 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts @@ -30,3 +30,4 @@ export type InfillTileInvocation = { */ seed?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts index 5e7d3f4b92..8fb9ad3d54 100644 --- a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts @@ -118,3 +118,4 @@ export type InpaintInvocation = { */ inpaint_replace?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts b/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts index 1e60ee8009..93a115f980 100644 --- a/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts @@ -12,3 +12,4 @@ export type IntCollectionOutput = { */ collection?: Array; }; + diff --git a/invokeai/frontend/web/src/services/api/models/IntOutput.ts b/invokeai/frontend/web/src/services/api/models/IntOutput.ts index 58655d0858..eeea6c68b4 100644 --- a/invokeai/frontend/web/src/services/api/models/IntOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/IntOutput.ts @@ -12,3 +12,4 @@ export type IntOutput = { */ 'a'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts b/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts index b6a70156c3..15bf92dfea 100644 --- a/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts @@ -24,3 +24,4 @@ export type IterateInvocation = { */ index?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts index 2eeffd05fd..ce8d9f8c4b 100644 --- a/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts @@ -12,3 +12,4 @@ export type IterateInvocationOutput = { */ item: any; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LatentsField.ts b/invokeai/frontend/web/src/services/api/models/LatentsField.ts index e7446e9cb3..bc6a525f7c 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsField.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsField.ts @@ -11,3 +11,4 @@ export type LatentsField = { */ latents_name: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts b/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts index edf388dbf6..3e9c2f60e4 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts @@ -22,3 +22,4 @@ export type LatentsOutput = { */ height: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts index 1235962d8f..865eeff554 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts @@ -31,3 +31,4 @@ export type LatentsToImageInvocation = { */ tiled?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts index 4f0f49d961..4273115963 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts @@ -61,3 +61,4 @@ export type LatentsToLatentsInvocation = { */ strength?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts index 7c655480c7..5d239536d5 100644 --- a/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts @@ -30,3 +30,4 @@ export type LineartAnimeImageProcessorInvocation = { */ image_resolution?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts index af3a527f3a..17720e689b 100644 --- a/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts @@ -34,3 +34,4 @@ export type LineartImageProcessorInvocation = { */ coarse?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts b/invokeai/frontend/web/src/services/api/models/LoRAModelConfig.ts similarity index 71% rename from invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts rename to invokeai/frontend/web/src/services/api/models/LoRAModelConfig.ts index d300e38fd0..184a266169 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/LoRAModelConfig.ts @@ -3,16 +3,16 @@ /* eslint-disable */ import type { BaseModelType } from './BaseModelType'; +import type { LoRAModelFormat } from './LoRAModelFormat'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; export type LoRAModelConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'lora'; path: string; description?: string; - format: ('lycoris' | 'diffusers'); - default?: boolean; + model_format: LoRAModelFormat; error?: ModelError; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LoRAModelFormat.ts b/invokeai/frontend/web/src/services/api/models/LoRAModelFormat.ts new file mode 100644 index 0000000000..829f8a7c57 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/LoRAModelFormat.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type LoRAModelFormat = 'lycoris' | 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts index 469e7200ad..f20d983f9b 100644 --- a/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts @@ -22,3 +22,4 @@ export type LoadImageInvocation = { */ image?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LoraInfo.ts b/invokeai/frontend/web/src/services/api/models/LoraInfo.ts index d4499530ac..1a575d4147 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraInfo.ts @@ -28,7 +28,4 @@ export type LoraInfo = { */ weight: number; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts index b448a7a8ad..b93281c5a7 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts @@ -35,7 +35,4 @@ export type LoraLoaderInvocation = { */ clip?: ClipField; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts b/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts index 54e02d49e5..1fed1ebc58 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts @@ -19,7 +19,4 @@ export type LoraLoaderOutput = { */ clip?: ClipField; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts b/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts index a031c0e05f..e3693f6d98 100644 --- a/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts @@ -26,3 +26,4 @@ export type MaskFromAlphaInvocation = { */ invert?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/MaskOutput.ts b/invokeai/frontend/web/src/services/api/models/MaskOutput.ts index 05d2b36d58..d4594fe6e9 100644 --- a/invokeai/frontend/web/src/services/api/models/MaskOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/MaskOutput.ts @@ -22,3 +22,4 @@ export type MaskOutput = { */ height?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts index 76e89422e9..aa7b966b4b 100644 --- a/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts @@ -30,3 +30,4 @@ export type MediapipeFaceProcessorInvocation = { */ min_confidence?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts index 14cf26f075..bd274228db 100644 --- a/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts @@ -30,3 +30,4 @@ export type MidasDepthImageProcessorInvocation = { */ bg_th?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts index b2a15b5861..0e81c9a4b8 100644 --- a/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts @@ -38,3 +38,4 @@ export type MlsdImageProcessorInvocation = { */ thr_d?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ModelInfo.ts b/invokeai/frontend/web/src/services/api/models/ModelInfo.ts index d11bb523c7..e87799d142 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelInfo.ts @@ -24,7 +24,4 @@ export type ModelInfo = { */ submodel?: SubModelType; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts b/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts index 2599d650cb..5b5b51e71f 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts @@ -24,7 +24,4 @@ export type ModelLoaderOutput = { */ vae?: VaeField; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index 42d0ddd8f6..01575b990c 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -2,19 +2,6 @@ /* tslint:disable */ /* eslint-disable */ -<<<<<<< HEAD -import type { invokeai__backend__model_management__models__controlnet__ControlNetModel__Config } from './invokeai__backend__model_management__models__controlnet__ControlNetModel__Config'; -import type { invokeai__backend__model_management__models__lora__LoRAModel__Config } from './invokeai__backend__model_management__models__lora__LoRAModel__Config'; -import type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig } from './invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig'; -import type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig } from './invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig'; -import type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig } from './invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig'; -import type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig } from './invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig'; -import type { invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config } from './invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config'; -import type { invokeai__backend__model_management__models__vae__VaeModel__Config } from './invokeai__backend__model_management__models__vae__VaeModel__Config'; - -export type ModelsList = { - models: Record>>; -======= import type { ControlNetModelConfig } from './ControlNetModelConfig'; import type { LoRAModelConfig } from './LoRAModelConfig'; import type { StableDiffusion1ModelCheckpointConfig } from './StableDiffusion1ModelCheckpointConfig'; @@ -25,14 +12,6 @@ import type { TextualInversionModelConfig } from './TextualInversionModelConfig' import type { VaeModelConfig } from './VaeModelConfig'; export type ModelsList = { -<<<<<<< HEAD -<<<<<<< HEAD - models: Record>>; ->>>>>>> 76dd749b1 (chore: Rebuild API) -======= - models: Record>>; ->>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) -======= - models: Record>>; ->>>>>>> 24673fd85 (chore: Rebuild API - base_model and type added) + models: Array<(StableDiffusion1ModelDiffusersConfig | StableDiffusion1ModelCheckpointConfig | VaeModelConfig | LoRAModelConfig | ControlNetModelConfig | TextualInversionModelConfig | StableDiffusion2ModelDiffusersConfig | StableDiffusion2ModelCheckpointConfig)>; }; + diff --git a/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts b/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts index 6a3b17feea..9fd716f33d 100644 --- a/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts @@ -24,3 +24,4 @@ export type MultiplyInvocation = { */ 'b'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts b/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts index 22846f5345..239a24bfe5 100644 --- a/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts @@ -28,3 +28,4 @@ export type NoiseInvocation = { */ height?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts b/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts index cb1b13ef25..f1832d7aa2 100644 --- a/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts @@ -22,3 +22,4 @@ export type NoiseOutput = { */ height: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts index 29fcebf567..400068171e 100644 --- a/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts @@ -30,3 +30,4 @@ export type NormalbaeImageProcessorInvocation = { */ image_resolution?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts index 2b22b247b4..3408bea6db 100644 --- a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts +++ b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts @@ -25,3 +25,4 @@ export type OffsetPaginatedResults_ImageDTO_ = { */ total: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts index 80c136546d..982ce8ade7 100644 --- a/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts @@ -34,3 +34,4 @@ export type OpenposeImageProcessorInvocation = { */ image_resolution?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts b/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts index cb5914f677..dd9f50cd4a 100644 --- a/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts +++ b/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts @@ -29,3 +29,4 @@ export type PaginatedResults_GraphExecutionState_ = { */ total: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts b/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts index 4e9087237b..87c01f847f 100644 --- a/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts @@ -20,3 +20,4 @@ export type ParamFloatInvocation = { */ param?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts b/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts index f47c3b8f01..7a45d0a0ac 100644 --- a/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts @@ -20,3 +20,4 @@ export type ParamIntInvocation = { */ 'a'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts index 96433218f7..91c9dc0ce5 100644 --- a/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts @@ -38,3 +38,4 @@ export type PidiImageProcessorInvocation = { */ scribble?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/PipelineModelField.ts b/invokeai/frontend/web/src/services/api/models/PipelineModelField.ts new file mode 100644 index 0000000000..c2f1c07fbf --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/PipelineModelField.ts @@ -0,0 +1,20 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { BaseModelType } from './BaseModelType'; + +/** + * Pipeline model field + */ +export type PipelineModelField = { + /** + * Name of the model + */ + model_name: string; + /** + * Base model + */ + base_model: BaseModelType; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts b/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts index ffab4ca09a..4444ab4d33 100644 --- a/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts @@ -16,3 +16,4 @@ export type PromptCollectionOutput = { */ count: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/PromptOutput.ts b/invokeai/frontend/web/src/services/api/models/PromptOutput.ts index f9dcfd1b08..5bca3f3037 100644 --- a/invokeai/frontend/web/src/services/api/models/PromptOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/PromptOutput.ts @@ -12,3 +12,4 @@ export type PromptOutput = { */ prompt: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts b/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts index c4fa84cc8e..a2f7c2f02a 100644 --- a/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts @@ -24,3 +24,4 @@ export type RandomIntInvocation = { */ high?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts index 5625324a1e..925511578d 100644 --- a/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts @@ -32,3 +32,4 @@ export type RandomRangeInvocation = { */ seed?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts index 5292d32156..3681602a95 100644 --- a/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts @@ -28,3 +28,4 @@ export type RangeInvocation = { */ step?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts index d97826099a..7dfac68d39 100644 --- a/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts @@ -28,3 +28,4 @@ export type RangeOfSizeInvocation = { */ step?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts index 500514f3c9..9a7b6c61e4 100644 --- a/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts @@ -38,3 +38,4 @@ export type ResizeLatentsInvocation = { */ antialias?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts b/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts index 5cfc165e23..0bacb5d805 100644 --- a/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts @@ -26,3 +26,4 @@ export type RestoreFaceInvocation = { */ strength?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts index 0f287be7bb..9a8a23077a 100644 --- a/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts @@ -20,7 +20,4 @@ export type SD1ModelLoaderInvocation = { */ model_name?: string; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts index 5afc63a387..f477c11a8d 100644 --- a/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts @@ -20,7 +20,4 @@ export type SD2ModelLoaderInvocation = { */ model_name?: string; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts new file mode 100644 index 0000000000..3086c59cf0 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts @@ -0,0 +1,25 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { PipelineModelField } from './PipelineModelField'; + +/** + * Loading submodels of selected model. + */ +export type SDModelLoaderInvocation = { + /** + * The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Whether or not this node is an intermediate node. + */ + is_intermediate?: boolean; + type?: 'sd_model_loader'; + /** + * The model to load + */ + model: PipelineModelField; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts index a65308dcba..506b21e540 100644 --- a/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts @@ -34,3 +34,4 @@ export type ScaleLatentsInvocation = { */ antialias?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts index c6bceda651..1b73055584 100644 --- a/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts @@ -22,3 +22,4 @@ export type ShowImageInvocation = { */ image?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts index c9708a0b6f..be7077cc53 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts @@ -4,19 +4,18 @@ import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; export type StableDiffusion1ModelCheckpointConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'pipeline'; path: string; description?: string; - format: 'checkpoint'; - default?: boolean; + model_format: 'checkpoint'; error?: ModelError; vae?: string; config?: string; variant: ModelVariantType; }; + diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts index 4b6f834216..befe014605 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts @@ -4,18 +4,17 @@ import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; export type StableDiffusion1ModelDiffusersConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'pipeline'; path: string; description?: string; - format: 'diffusers'; - default?: boolean; + model_format: 'diffusers'; error?: ModelError; vae?: string; variant: ModelVariantType; }; + diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelFormat.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelFormat.ts new file mode 100644 index 0000000000..01b50c2fc0 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelFormat.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type StableDiffusion1ModelFormat = 'checkpoint' | 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts index 27b6879703..dadd7cac9b 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts @@ -4,18 +4,16 @@ import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; import type { SchedulerPredictionType } from './SchedulerPredictionType'; export type StableDiffusion2ModelCheckpointConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'pipeline'; path: string; description?: string; - format: 'checkpoint'; - default?: boolean; + model_format: 'checkpoint'; error?: ModelError; vae?: string; config?: string; @@ -23,3 +21,4 @@ export type StableDiffusion2ModelCheckpointConfig = { prediction_type: SchedulerPredictionType; upcast_attention: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts index a2b66d7157..1e4a34c5dc 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts @@ -4,21 +4,20 @@ import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; import type { SchedulerPredictionType } from './SchedulerPredictionType'; export type StableDiffusion2ModelDiffusersConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'pipeline'; path: string; description?: string; - format: 'diffusers'; - default?: boolean; + model_format: 'diffusers'; error?: ModelError; vae?: string; variant: ModelVariantType; prediction_type: SchedulerPredictionType; upcast_attention: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelFormat.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelFormat.ts new file mode 100644 index 0000000000..7e7b895231 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelFormat.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type StableDiffusion2ModelFormat = 'checkpoint' | 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts b/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts index dca4fa8e82..2cff38b3e5 100644 --- a/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts @@ -56,3 +56,4 @@ export type StepParamEasingInvocation = { */ show_easing_plot?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts b/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts index a1b8ca5628..23334bd891 100644 --- a/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts @@ -24,3 +24,4 @@ export type SubtractInvocation = { */ 'b'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts deleted file mode 100644 index 26d6117573..0000000000 --- a/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Generates an image using text2img. - */ -export type TextToImageInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'txt2img'; - /** - * The prompt to generate an image from - */ - prompt?: string; - /** - * The seed to use (omit for random) - */ - seed?: number; - /** - * The number of steps to use to generate the image - */ - steps?: number; - /** - * The width of the resulting image - */ - width?: number; - /** - * The height of the resulting image - */ - height?: number; - /** - * The Classifier-Free Guidance, higher values may result in a result closer to the prompt - */ - cfg_scale?: number; - /** - * The scheduler to use - */ - scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; - /** - * The model to use (currently ignored) - */ - model?: string; - /** - * Whether or not to produce progress images during generation - */ - progress_images?: boolean; - /** - * The control model to use - */ - control_model?: string; - /** - * The processed control image - */ - control_image?: ImageField; -}; diff --git a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts index b0b8ec5fc1..cf8229b1f7 100644 --- a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts @@ -53,3 +53,4 @@ export type TextToLatentsInvocation = { */ control?: (ControlField | Array); }; + diff --git a/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts b/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts index 7abfbec081..97d6aa7ffa 100644 --- a/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts @@ -4,15 +4,14 @@ import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; export type TextualInversionModelConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'embedding'; path: string; description?: string; - format: null; - default?: boolean; + model_format: null; error?: ModelError; }; + diff --git a/invokeai/frontend/web/src/services/api/models/UNetField.ts b/invokeai/frontend/web/src/services/api/models/UNetField.ts index f0f247c860..ad3b1ddb5b 100644 --- a/invokeai/frontend/web/src/services/api/models/UNetField.ts +++ b/invokeai/frontend/web/src/services/api/models/UNetField.ts @@ -19,7 +19,4 @@ export type UNetField = { */ loras: Array; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts b/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts index 3b42906e39..d0aca63964 100644 --- a/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts @@ -30,3 +30,4 @@ export type UpscaleInvocation = { */ level?: 2 | 4; }; + diff --git a/invokeai/frontend/web/src/services/api/models/VaeField.ts b/invokeai/frontend/web/src/services/api/models/VaeField.ts index 8d3b6f4e53..bfe2793887 100644 --- a/invokeai/frontend/web/src/services/api/models/VaeField.ts +++ b/invokeai/frontend/web/src/services/api/models/VaeField.ts @@ -10,7 +10,4 @@ export type VaeField = { */ vae: ModelInfo; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts b/invokeai/frontend/web/src/services/api/models/VaeModelConfig.ts similarity index 71% rename from invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts rename to invokeai/frontend/web/src/services/api/models/VaeModelConfig.ts index ad7f70c3d4..a73ee0aa32 100644 --- a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/VaeModelConfig.ts @@ -4,15 +4,15 @@ import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; +import type { VaeModelFormat } from './VaeModelFormat'; export type VaeModelConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'vae'; path: string; description?: string; - format: ('checkpoint' | 'diffusers'); - default?: boolean; + model_format: VaeModelFormat; error?: ModelError; }; + diff --git a/invokeai/frontend/web/src/services/api/models/VaeModelFormat.ts b/invokeai/frontend/web/src/services/api/models/VaeModelFormat.ts new file mode 100644 index 0000000000..497f81d16f --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/VaeModelFormat.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type VaeModelFormat = 'checkpoint' | 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/VaeRepo.ts b/invokeai/frontend/web/src/services/api/models/VaeRepo.ts index cb6e33c199..0e233626c6 100644 --- a/invokeai/frontend/web/src/services/api/models/VaeRepo.ts +++ b/invokeai/frontend/web/src/services/api/models/VaeRepo.ts @@ -16,3 +16,4 @@ export type VaeRepo = { */ subfolder?: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ValidationError.ts b/invokeai/frontend/web/src/services/api/models/ValidationError.ts index 92697e1d74..14e1fdecd0 100644 --- a/invokeai/frontend/web/src/services/api/models/ValidationError.ts +++ b/invokeai/frontend/web/src/services/api/models/ValidationError.ts @@ -7,3 +7,4 @@ export type ValidationError = { msg: string; type: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts index 0dbc99c9e3..6caded8f04 100644 --- a/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts @@ -22,3 +22,4 @@ export type ZoeDepthImageProcessorInvocation = { */ image?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__controlnet__ControlNetModel__Config.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__controlnet__ControlNetModel__Config.ts deleted file mode 100644 index f8decdb341..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__controlnet__ControlNetModel__Config.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; - -export type invokeai__backend__model_management__models__controlnet__ControlNetModel__Config = { - path: string; - description?: string; - format: ('checkpoint' | 'diffusers'); - default?: boolean; - error?: ModelError; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__lora__LoRAModel__Config.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__lora__LoRAModel__Config.ts deleted file mode 100644 index 614749a2c5..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__lora__LoRAModel__Config.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; - -export type invokeai__backend__model_management__models__lora__LoRAModel__Config = { - path: string; - description?: string; - format: ('lycoris' | 'diffusers'); - default?: boolean; - error?: ModelError; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig.ts deleted file mode 100644 index 6bdcb87dd4..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; -import type { ModelVariantType } from './ModelVariantType'; - -export type invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig = { - path: string; - description?: string; - format: 'checkpoint'; - default?: boolean; - error?: ModelError; - vae?: string; - config?: string; - variant: ModelVariantType; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig.ts deleted file mode 100644 index c88e042178..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; -import type { ModelVariantType } from './ModelVariantType'; - -export type invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig = { - path: string; - description?: string; - format: 'diffusers'; - default?: boolean; - error?: ModelError; - vae?: string; - variant: ModelVariantType; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig.ts deleted file mode 100644 index ec2ae4a845..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; -import type { ModelVariantType } from './ModelVariantType'; -import type { SchedulerPredictionType } from './SchedulerPredictionType'; - -export type invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig = { - path: string; - description?: string; - format: 'checkpoint'; - default?: boolean; - error?: ModelError; - vae?: string; - config?: string; - variant: ModelVariantType; - prediction_type: SchedulerPredictionType; - upcast_attention: boolean; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig.ts deleted file mode 100644 index 67b897d9d9..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; -import type { ModelVariantType } from './ModelVariantType'; -import type { SchedulerPredictionType } from './SchedulerPredictionType'; - -export type invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig = { - path: string; - description?: string; - format: 'diffusers'; - default?: boolean; - error?: ModelError; - vae?: string; - variant: ModelVariantType; - prediction_type: SchedulerPredictionType; - upcast_attention: boolean; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config.ts deleted file mode 100644 index f23d5002e3..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; - -export type invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config = { - path: string; - description?: string; - format: null; - default?: boolean; - error?: ModelError; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__vae__VaeModel__Config.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__vae__VaeModel__Config.ts deleted file mode 100644 index d9314a6063..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__vae__VaeModel__Config.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; - -export type invokeai__backend__model_management__models__vae__VaeModel__Config = { - path: string; - description?: string; - format: ('checkpoint' | 'diffusers'); - default?: boolean; - error?: ModelError; -}; - diff --git a/invokeai/frontend/web/src/services/api/services/ImagesService.ts b/invokeai/frontend/web/src/services/api/services/ImagesService.ts index f372d4fa87..bfdef887a0 100644 --- a/invokeai/frontend/web/src/services/api/services/ImagesService.ts +++ b/invokeai/frontend/web/src/services/api/services/ImagesService.ts @@ -22,7 +22,6 @@ export class ImagesService { * @throws ApiError */ public static listImagesWithMetadata({ -<<<<<<< HEAD imageOrigin, categories, isIntermediate, @@ -55,35 +54,6 @@ export class ImagesService { */ limit?: number, }): CancelablePromise { -======= -imageOrigin, -categories, -isIntermediate, -offset, -limit = 10, -}: { -/** - * The origin of images to list - */ -imageOrigin?: ResourceOrigin, -/** - * The categories of image to include - */ -categories?: Array, -/** - * Whether to list intermediate images - */ -isIntermediate?: boolean, -/** - * The page offset - */ -offset?: number, -/** - * The number of images per page - */ -limit?: number, -}): CancelablePromise { ->>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/', @@ -108,25 +78,25 @@ limit?: number, * @throws ApiError */ public static uploadImage({ -imageCategory, -isIntermediate, -formData, -sessionId, -}: { -/** - * The category of the image - */ -imageCategory: ImageCategory, -/** - * Whether this is an intermediate image - */ -isIntermediate: boolean, -formData: Body_upload_image, -/** - * The session ID associated with this upload, if any - */ -sessionId?: string, -}): CancelablePromise { + imageCategory, + isIntermediate, + formData, + sessionId, + }: { + /** + * The category of the image + */ + imageCategory: ImageCategory, + /** + * Whether this is an intermediate image + */ + isIntermediate: boolean, + formData: Body_upload_image, + /** + * The session ID associated with this upload, if any + */ + sessionId?: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/images/', @@ -151,13 +121,13 @@ sessionId?: string, * @throws ApiError */ public static getImageFull({ -imageName, -}: { -/** - * The name of full-resolution image file to get - */ -imageName: string, -}): CancelablePromise { + imageName, + }: { + /** + * The name of full-resolution image file to get + */ + imageName: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}', @@ -178,13 +148,13 @@ imageName: string, * @throws ApiError */ public static deleteImage({ -imageName, -}: { -/** - * The name of the image to delete - */ -imageName: string, -}): CancelablePromise { + imageName, + }: { + /** + * The name of the image to delete + */ + imageName: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/images/{image_name}', @@ -204,15 +174,15 @@ imageName: string, * @throws ApiError */ public static updateImage({ -imageName, -requestBody, -}: { -/** - * The name of the image to update - */ -imageName: string, -requestBody: ImageRecordChanges, -}): CancelablePromise { + imageName, + requestBody, + }: { + /** + * The name of the image to update + */ + imageName: string, + requestBody: ImageRecordChanges, + }): CancelablePromise { return __request(OpenAPI, { method: 'PATCH', url: '/api/v1/images/{image_name}', @@ -234,13 +204,13 @@ requestBody: ImageRecordChanges, * @throws ApiError */ public static getImageMetadata({ -imageName, -}: { -/** - * The name of image to get - */ -imageName: string, -}): CancelablePromise { + imageName, + }: { + /** + * The name of image to get + */ + imageName: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}/metadata', @@ -260,13 +230,13 @@ imageName: string, * @throws ApiError */ public static getImageThumbnail({ -imageName, -}: { -/** - * The name of thumbnail image file to get - */ -imageName: string, -}): CancelablePromise { + imageName, + }: { + /** + * The name of thumbnail image file to get + */ + imageName: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}/thumbnail', @@ -287,13 +257,13 @@ imageName: string, * @throws ApiError */ public static getImageUrls({ -imageName, -}: { -/** - * The name of the image whose URL to get - */ -imageName: string, -}): CancelablePromise { + imageName, + }: { + /** + * The name of the image whose URL to get + */ + imageName: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}/urls', diff --git a/invokeai/frontend/web/src/services/api/services/ModelsService.ts b/invokeai/frontend/web/src/services/api/services/ModelsService.ts index 248f4a352e..54580ce204 100644 --- a/invokeai/frontend/web/src/services/api/services/ModelsService.ts +++ b/invokeai/frontend/web/src/services/api/services/ModelsService.ts @@ -19,7 +19,6 @@ export class ModelsService { * @throws ApiError */ public static listModels({ -<<<<<<< HEAD baseModel, modelType, }: { @@ -32,20 +31,6 @@ export class ModelsService { */ modelType?: ModelType, }): CancelablePromise { -======= -baseModel, -modelType, -}: { -/** - * Base model - */ -baseModel?: BaseModelType, -/** - * The type of model to get - */ -modelType?: ModelType, -}): CancelablePromise { ->>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'GET', url: '/api/v1/models/', @@ -66,10 +51,10 @@ modelType?: ModelType, * @throws ApiError */ public static updateModel({ -requestBody, -}: { -requestBody: CreateModelRequest, -}): CancelablePromise { + requestBody, + }: { + requestBody: CreateModelRequest, + }): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/models/', @@ -88,10 +73,10 @@ requestBody: CreateModelRequest, * @throws ApiError */ public static delModel({ -modelName, -}: { -modelName: string, -}): CancelablePromise { + modelName, + }: { + modelName: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/models/{model_name}', diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index 937cff9c05..2c6ca91319 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -60,6 +60,7 @@ import type { RestoreFaceInvocation } from '../models/RestoreFaceInvocation'; import type { ScaleLatentsInvocation } from '../models/ScaleLatentsInvocation'; import type { SD1ModelLoaderInvocation } from '../models/SD1ModelLoaderInvocation'; import type { SD2ModelLoaderInvocation } from '../models/SD2ModelLoaderInvocation'; +import type { SDModelLoaderInvocation } from '../models/SDModelLoaderInvocation'; import type { ShowImageInvocation } from '../models/ShowImageInvocation'; import type { StepParamEasingInvocation } from '../models/StepParamEasingInvocation'; import type { SubtractInvocation } from '../models/SubtractInvocation'; @@ -80,23 +81,23 @@ export class SessionsService { * @throws ApiError */ public static listSessions({ -page, -perPage = 10, -query = '', -}: { -/** - * The page of results to get - */ -page?: number, -/** - * The number of results per page - */ -perPage?: number, -/** - * The query string to search for - */ -query?: string, -}): CancelablePromise { + page, + perPage = 10, + query = '', + }: { + /** + * The page of results to get + */ + page?: number, + /** + * The number of results per page + */ + perPage?: number, + /** + * The query string to search for + */ + query?: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/sessions/', @@ -118,10 +119,10 @@ query?: string, * @throws ApiError */ public static createSession({ -requestBody, -}: { -requestBody?: Graph, -}): CancelablePromise { + requestBody, + }: { + requestBody?: Graph, + }): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/sessions/', @@ -141,13 +142,13 @@ requestBody?: Graph, * @throws ApiError */ public static getSession({ -sessionId, -}: { -/** - * The id of the session to get - */ -sessionId: string, -}): CancelablePromise { + sessionId, + }: { + /** + * The id of the session to get + */ + sessionId: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/sessions/{session_id}', @@ -168,7 +169,6 @@ sessionId: string, * @throws ApiError */ public static addNode({ -<<<<<<< HEAD sessionId, requestBody, }: { @@ -176,19 +176,8 @@ sessionId: string, * The id of the session */ sessionId: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SDModelLoaderInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { -======= -sessionId, -requestBody, -}: { -/** - * The id of the session - */ -sessionId: string, -requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | CompelInvocation | LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CvInpaintInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | DynamicPromptInvocation | RestoreFaceInvocation | UpscaleInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | ImageToImageInvocation | LatentsToLatentsInvocation | InpaintInvocation), -}): CancelablePromise { ->>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'POST', url: '/api/v1/sessions/{session_id}/nodes', @@ -212,7 +201,6 @@ requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | * @throws ApiError */ public static updateNode({ -<<<<<<< HEAD sessionId, nodePath, requestBody, @@ -225,24 +213,8 @@ requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | * The path to the node in the graph */ nodePath: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SDModelLoaderInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { -======= -sessionId, -nodePath, -requestBody, -}: { -/** - * The id of the session - */ -sessionId: string, -/** - * The path to the node in the graph - */ -nodePath: string, -requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | CompelInvocation | LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CvInpaintInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | DynamicPromptInvocation | RestoreFaceInvocation | UpscaleInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | ImageToImageInvocation | LatentsToLatentsInvocation | InpaintInvocation), -}): CancelablePromise { ->>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'PUT', url: '/api/v1/sessions/{session_id}/nodes/{node_path}', @@ -267,18 +239,18 @@ requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | * @throws ApiError */ public static deleteNode({ -sessionId, -nodePath, -}: { -/** - * The id of the session - */ -sessionId: string, -/** - * The path to the node to delete - */ -nodePath: string, -}): CancelablePromise { + sessionId, + nodePath, + }: { + /** + * The id of the session + */ + sessionId: string, + /** + * The path to the node to delete + */ + nodePath: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/sessions/{session_id}/nodes/{node_path}', @@ -301,15 +273,15 @@ nodePath: string, * @throws ApiError */ public static addEdge({ -sessionId, -requestBody, -}: { -/** - * The id of the session - */ -sessionId: string, -requestBody: Edge, -}): CancelablePromise { + sessionId, + requestBody, + }: { + /** + * The id of the session + */ + sessionId: string, + requestBody: Edge, + }): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/sessions/{session_id}/edges', @@ -333,33 +305,33 @@ requestBody: Edge, * @throws ApiError */ public static deleteEdge({ -sessionId, -fromNodeId, -fromField, -toNodeId, -toField, -}: { -/** - * The id of the session - */ -sessionId: string, -/** - * The id of the node the edge is coming from - */ -fromNodeId: string, -/** - * The field of the node the edge is coming from - */ -fromField: string, -/** - * The id of the node the edge is going to - */ -toNodeId: string, -/** - * The field of the node the edge is going to - */ -toField: string, -}): CancelablePromise { + sessionId, + fromNodeId, + fromField, + toNodeId, + toField, + }: { + /** + * The id of the session + */ + sessionId: string, + /** + * The id of the node the edge is coming from + */ + fromNodeId: string, + /** + * The field of the node the edge is coming from + */ + fromField: string, + /** + * The id of the node the edge is going to + */ + toNodeId: string, + /** + * The field of the node the edge is going to + */ + toField: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/sessions/{session_id}/edges/{from_node_id}/{from_field}/{to_node_id}/{to_field}', @@ -385,18 +357,18 @@ toField: string, * @throws ApiError */ public static invokeSession({ -sessionId, -all = false, -}: { -/** - * The id of the session to invoke - */ -sessionId: string, -/** - * Whether or not to invoke all remaining invocations - */ -all?: boolean, -}): CancelablePromise { + sessionId, + all = false, + }: { + /** + * The id of the session to invoke + */ + sessionId: string, + /** + * Whether or not to invoke all remaining invocations + */ + all?: boolean, + }): CancelablePromise { return __request(OpenAPI, { method: 'PUT', url: '/api/v1/sessions/{session_id}/invoke', @@ -421,13 +393,13 @@ all?: boolean, * @throws ApiError */ public static cancelSessionInvoke({ -sessionId, -}: { -/** - * The id of the session to cancel - */ -sessionId: string, -}): CancelablePromise { + sessionId, + }: { + /** + * The id of the session to cancel + */ + sessionId: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/sessions/{session_id}/invoke', From 1bc170727badcf5d2d1cd74fe7115f80682cb6cc Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:47:58 +1000 Subject: [PATCH 97/99] tidy(nodes): rename `sd_model_loader` to `pipeline_model_loader` this is more accurate bc it can do eg kandinsky also --- invokeai/app/invocations/model.py | 211 +----------------------------- 1 file changed, 3 insertions(+), 208 deletions(-) diff --git a/invokeai/app/invocations/model.py b/invokeai/app/invocations/model.py index 48b15c2e4e..b77aa5dafd 100644 --- a/invokeai/app/invocations/model.py +++ b/invokeai/app/invocations/model.py @@ -50,10 +50,10 @@ class PipelineModelField(BaseModel): base_model: BaseModelType = Field(description="Base model") -class SDModelLoaderInvocation(BaseInvocation): - """Loading submodels of selected model.""" +class PipelineModelLoaderInvocation(BaseInvocation): + """Loads a pipeline model, outputting its submodels.""" - type: Literal["sd_model_loader"] = "sd_model_loader" + type: Literal["pipeline_model_loader"] = "pipeline_model_loader" model: PipelineModelField = Field(description="The model to load") # TODO: precision? @@ -154,211 +154,6 @@ class SDModelLoaderInvocation(BaseInvocation): ) ) -class SD1ModelLoaderInvocation(BaseInvocation): - """Loading submodels of selected model.""" - - type: Literal["sd1_model_loader"] = "sd1_model_loader" - - model_name: str = Field(default="", description="Model to load") - # TODO: precision? - - # Schema customisation - class Config(InvocationConfig): - schema_extra = { - "ui": { - "tags": ["model", "loader"], - "type_hints": { - "model_name": "model" # TODO: rename to model_name? - } - }, - } - - def invoke(self, context: InvocationContext) -> ModelLoaderOutput: - - base_model = BaseModelType.StableDiffusion1 # TODO: - - # TODO: not found exceptions - if not context.services.model_manager.model_exists( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - ): - raise Exception(f"Unkown model name: {self.model_name}!") - - """ - if not context.services.model_manager.model_exists( - model_name=self.model_name, - model_type=SDModelType.Diffusers, - submodel=SDModelType.Tokenizer, - ): - raise Exception( - f"Failed to find tokenizer submodel in {self.model_name}! Check if model corrupted" - ) - - if not context.services.model_manager.model_exists( - model_name=self.model_name, - model_type=SDModelType.Diffusers, - submodel=SDModelType.TextEncoder, - ): - raise Exception( - f"Failed to find text_encoder submodel in {self.model_name}! Check if model corrupted" - ) - - if not context.services.model_manager.model_exists( - model_name=self.model_name, - model_type=SDModelType.Diffusers, - submodel=SDModelType.UNet, - ): - raise Exception( - f"Failed to find unet submodel from {self.model_name}! Check if model corrupted" - ) - """ - - - return ModelLoaderOutput( - unet=UNetField( - unet=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.UNet, - ), - scheduler=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.Scheduler, - ), - loras=[], - ), - clip=ClipField( - tokenizer=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.Tokenizer, - ), - text_encoder=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.TextEncoder, - ), - loras=[], - ), - vae=VaeField( - vae=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.Vae, - ), - ) - ) - -# TODO: optimize(less code copy) -class SD2ModelLoaderInvocation(BaseInvocation): - """Loading submodels of selected model.""" - - type: Literal["sd2_model_loader"] = "sd2_model_loader" - - model_name: str = Field(default="", description="Model to load") - # TODO: precision? - - # Schema customisation - class Config(InvocationConfig): - schema_extra = { - "ui": { - "tags": ["model", "loader"], - "type_hints": { - "model_name": "model" # TODO: rename to model_name? - } - }, - } - - def invoke(self, context: InvocationContext) -> ModelLoaderOutput: - - base_model = BaseModelType.StableDiffusion2 # TODO: - - # TODO: not found exceptions - if not context.services.model_manager.model_exists( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - ): - raise Exception(f"Unkown model name: {self.model_name}!") - - """ - if not context.services.model_manager.model_exists( - model_name=self.model_name, - model_type=SDModelType.Diffusers, - submodel=SDModelType.Tokenizer, - ): - raise Exception( - f"Failed to find tokenizer submodel in {self.model_name}! Check if model corrupted" - ) - - if not context.services.model_manager.model_exists( - model_name=self.model_name, - model_type=SDModelType.Diffusers, - submodel=SDModelType.TextEncoder, - ): - raise Exception( - f"Failed to find text_encoder submodel in {self.model_name}! Check if model corrupted" - ) - - if not context.services.model_manager.model_exists( - model_name=self.model_name, - model_type=SDModelType.Diffusers, - submodel=SDModelType.UNet, - ): - raise Exception( - f"Failed to find unet submodel from {self.model_name}! Check if model corrupted" - ) - """ - - - return ModelLoaderOutput( - unet=UNetField( - unet=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.UNet, - ), - scheduler=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.Scheduler, - ), - loras=[], - ), - clip=ClipField( - tokenizer=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.Tokenizer, - ), - text_encoder=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.TextEncoder, - ), - loras=[], - ), - vae=VaeField( - vae=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.Vae, - ), - ) - ) - class LoraLoaderOutput(BaseInvocationOutput): """Model loader output""" From 2a178f5a25190e2a4026d0ddb314d4de4b4a606a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:48:13 +1000 Subject: [PATCH 98/99] chore(ui): regen api client --- .../frontend/web/src/services/api/index.ts | 4 +--- .../web/src/services/api/models/Graph.ts | 6 ++--- .../web/src/services/api/models/ModelsList.ts | 2 +- ...on.ts => PipelineModelLoaderInvocation.ts} | 6 ++--- .../api/models/SD1ModelLoaderInvocation.ts | 23 ------------------- .../api/models/SD2ModelLoaderInvocation.ts | 23 ------------------- .../services/api/services/SessionsService.ts | 8 +++---- 7 files changed, 10 insertions(+), 62 deletions(-) rename invokeai/frontend/web/src/services/api/models/{SDModelLoaderInvocation.ts => PipelineModelLoaderInvocation.ts} (74%) delete mode 100644 invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index 3c143ecf6e..8ce42494e5 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -106,6 +106,7 @@ export type { ParamFloatInvocation } from './models/ParamFloatInvocation'; export type { ParamIntInvocation } from './models/ParamIntInvocation'; export type { PidiImageProcessorInvocation } from './models/PidiImageProcessorInvocation'; export type { PipelineModelField } from './models/PipelineModelField'; +export type { PipelineModelLoaderInvocation } from './models/PipelineModelLoaderInvocation'; export type { PromptCollectionOutput } from './models/PromptCollectionOutput'; export type { PromptOutput } from './models/PromptOutput'; export type { RandomIntInvocation } from './models/RandomIntInvocation'; @@ -117,9 +118,6 @@ export type { ResourceOrigin } from './models/ResourceOrigin'; export type { RestoreFaceInvocation } from './models/RestoreFaceInvocation'; export type { ScaleLatentsInvocation } from './models/ScaleLatentsInvocation'; export type { SchedulerPredictionType } from './models/SchedulerPredictionType'; -export type { SD1ModelLoaderInvocation } from './models/SD1ModelLoaderInvocation'; -export type { SD2ModelLoaderInvocation } from './models/SD2ModelLoaderInvocation'; -export type { SDModelLoaderInvocation } from './models/SDModelLoaderInvocation'; export type { ShowImageInvocation } from './models/ShowImageInvocation'; export type { StableDiffusion1ModelCheckpointConfig } from './models/StableDiffusion1ModelCheckpointConfig'; export type { StableDiffusion1ModelDiffusersConfig } from './models/StableDiffusion1ModelDiffusersConfig'; diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index 7976c08abb..5fba3d8311 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -49,6 +49,7 @@ import type { OpenposeImageProcessorInvocation } from './OpenposeImageProcessorI import type { ParamFloatInvocation } from './ParamFloatInvocation'; import type { ParamIntInvocation } from './ParamIntInvocation'; import type { PidiImageProcessorInvocation } from './PidiImageProcessorInvocation'; +import type { PipelineModelLoaderInvocation } from './PipelineModelLoaderInvocation'; import type { RandomIntInvocation } from './RandomIntInvocation'; import type { RandomRangeInvocation } from './RandomRangeInvocation'; import type { RangeInvocation } from './RangeInvocation'; @@ -56,9 +57,6 @@ import type { RangeOfSizeInvocation } from './RangeOfSizeInvocation'; import type { ResizeLatentsInvocation } from './ResizeLatentsInvocation'; import type { RestoreFaceInvocation } from './RestoreFaceInvocation'; import type { ScaleLatentsInvocation } from './ScaleLatentsInvocation'; -import type { SD1ModelLoaderInvocation } from './SD1ModelLoaderInvocation'; -import type { SD2ModelLoaderInvocation } from './SD2ModelLoaderInvocation'; -import type { SDModelLoaderInvocation } from './SDModelLoaderInvocation'; import type { ShowImageInvocation } from './ShowImageInvocation'; import type { StepParamEasingInvocation } from './StepParamEasingInvocation'; import type { SubtractInvocation } from './SubtractInvocation'; @@ -74,7 +72,7 @@ export type Graph = { /** * The nodes in this graph */ - nodes?: Record; + nodes?: Record; /** * The connections between nodes and their fields in this graph */ diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index 01575b990c..9186db3e29 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -12,6 +12,6 @@ import type { TextualInversionModelConfig } from './TextualInversionModelConfig' import type { VaeModelConfig } from './VaeModelConfig'; export type ModelsList = { - models: Array<(StableDiffusion1ModelDiffusersConfig | StableDiffusion1ModelCheckpointConfig | VaeModelConfig | LoRAModelConfig | ControlNetModelConfig | TextualInversionModelConfig | StableDiffusion2ModelDiffusersConfig | StableDiffusion2ModelCheckpointConfig)>; + models: Array<(StableDiffusion1ModelCheckpointConfig | StableDiffusion1ModelDiffusersConfig | VaeModelConfig | LoRAModelConfig | ControlNetModelConfig | TextualInversionModelConfig | StableDiffusion2ModelCheckpointConfig | StableDiffusion2ModelDiffusersConfig)>; }; diff --git a/invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/PipelineModelLoaderInvocation.ts similarity index 74% rename from invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts rename to invokeai/frontend/web/src/services/api/models/PipelineModelLoaderInvocation.ts index 3086c59cf0..b8cdb27acf 100644 --- a/invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/PipelineModelLoaderInvocation.ts @@ -5,9 +5,9 @@ import type { PipelineModelField } from './PipelineModelField'; /** - * Loading submodels of selected model. + * Loads a pipeline model, outputting its submodels. */ -export type SDModelLoaderInvocation = { +export type PipelineModelLoaderInvocation = { /** * The id of this node. Must be unique among all nodes. */ @@ -16,7 +16,7 @@ export type SDModelLoaderInvocation = { * Whether or not this node is an intermediate node. */ is_intermediate?: boolean; - type?: 'sd_model_loader'; + type?: 'pipeline_model_loader'; /** * The model to load */ diff --git a/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts deleted file mode 100644 index 9a8a23077a..0000000000 --- a/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Loading submodels of selected model. - */ -export type SD1ModelLoaderInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'sd1_model_loader'; - /** - * Model to load - */ - model_name?: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts deleted file mode 100644 index f477c11a8d..0000000000 --- a/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Loading submodels of selected model. - */ -export type SD2ModelLoaderInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'sd2_model_loader'; - /** - * Model to load - */ - model_name?: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index 2c6ca91319..51a36caad1 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -51,6 +51,7 @@ import type { PaginatedResults_GraphExecutionState_ } from '../models/PaginatedR import type { ParamFloatInvocation } from '../models/ParamFloatInvocation'; import type { ParamIntInvocation } from '../models/ParamIntInvocation'; import type { PidiImageProcessorInvocation } from '../models/PidiImageProcessorInvocation'; +import type { PipelineModelLoaderInvocation } from '../models/PipelineModelLoaderInvocation'; import type { RandomIntInvocation } from '../models/RandomIntInvocation'; import type { RandomRangeInvocation } from '../models/RandomRangeInvocation'; import type { RangeInvocation } from '../models/RangeInvocation'; @@ -58,9 +59,6 @@ import type { RangeOfSizeInvocation } from '../models/RangeOfSizeInvocation'; import type { ResizeLatentsInvocation } from '../models/ResizeLatentsInvocation'; import type { RestoreFaceInvocation } from '../models/RestoreFaceInvocation'; import type { ScaleLatentsInvocation } from '../models/ScaleLatentsInvocation'; -import type { SD1ModelLoaderInvocation } from '../models/SD1ModelLoaderInvocation'; -import type { SD2ModelLoaderInvocation } from '../models/SD2ModelLoaderInvocation'; -import type { SDModelLoaderInvocation } from '../models/SDModelLoaderInvocation'; import type { ShowImageInvocation } from '../models/ShowImageInvocation'; import type { StepParamEasingInvocation } from '../models/StepParamEasingInvocation'; import type { SubtractInvocation } from '../models/SubtractInvocation'; @@ -176,7 +174,7 @@ export class SessionsService { * The id of the session */ sessionId: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SDModelLoaderInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | PipelineModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -213,7 +211,7 @@ export class SessionsService { * The path to the node in the graph */ nodePath: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SDModelLoaderInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | PipelineModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'PUT', From 339e7ce2136c8e2bbe0a707b9e22451e76c05421 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:48:57 +1000 Subject: [PATCH 99/99] feat(ui): initial implementation of model loading - Update model listing code to use `rtk-query` - Update all graph generation to use new `pipeline_model_loader` node --- .../frontend/web/src/app/components/App.tsx | 13 +++ .../enhancers/reduxRemember/serialize.ts | 3 - .../enhancers/reduxRemember/unserialize.ts | 4 - .../listeners/socketio/socketConnected.ts | 12 +-- invokeai/frontend/web/src/app/store/store.ts | 9 +- .../fields/ModelInputFieldComponent.tsx | 98 +++++++++++++++---- .../src/features/nodes/store/nodesSlice.ts | 15 --- .../buildCanvasImageToImageGraph.ts | 9 +- .../graphBuilders/buildCanvasInpaintGraph.ts | 9 +- .../buildCanvasTextToImageGraph.ts | 9 +- .../buildLinearImageToImageGraph.ts | 9 +- .../buildLinearTextToImageGraph.ts | 15 ++- .../util/graphBuilders/buildNodesGraph.ts | 9 +- .../nodes/util/graphBuilders/constants.ts | 2 +- .../nodes/util/modelIdToPipelineModelField.ts | 18 ++++ .../parameters/store/generationSlice.ts | 29 +----- .../parameters/store/parameterZodSchemas.ts | 14 +++ .../system/components/ModelSelect.tsx | 85 +++++++++++----- .../system/hooks/useIsApplicationReady.ts | 11 +-- .../features/system/store/modelSelectors.ts | 59 ----------- .../store/models/sd1PipelineModelSlice.ts | 56 ----------- .../store/models/sd2PipelineModelSlice.ts | 56 ----------- .../system/store/modelsPersistDenylist.ts | 9 -- .../src/features/system/store/systemSlice.ts | 8 -- .../frontend/web/src/services/apiSlice.ts | 48 ++++++++- .../frontend/web/src/services/thunks/model.ts | 58 ----------- 26 files changed, 281 insertions(+), 386 deletions(-) create mode 100644 invokeai/frontend/web/src/features/nodes/util/modelIdToPipelineModelField.ts delete mode 100644 invokeai/frontend/web/src/features/system/store/modelSelectors.ts delete mode 100644 invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts delete mode 100644 invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts delete mode 100644 invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts delete mode 100644 invokeai/frontend/web/src/services/thunks/model.ts diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index a11d8d048c..55fcc97745 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -24,6 +24,7 @@ import Toaster from './Toaster'; import DeleteImageModal from 'features/gallery/components/DeleteImageModal'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal'; +import { useListModelsQuery } from 'services/apiSlice'; const DEFAULT_CONFIG = {}; @@ -46,6 +47,18 @@ const App = ({ const isApplicationReady = useIsApplicationReady(); + const { data: pipelineModels } = useListModelsQuery({ + model_type: 'pipeline', + }); + const { data: controlnetModels } = useListModelsQuery({ + model_type: 'controlnet', + }); + const { data: vaeModels } = useListModelsQuery({ model_type: 'vae' }); + const { data: loraModels } = useListModelsQuery({ model_type: 'lora' }); + const { data: embeddingModels } = useListModelsQuery({ + model_type: 'embedding', + }); + const [loadingOverridden, setLoadingOverridden] = useState(false); const dispatch = useAppDispatch(); diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts index e498ecb749..cb18d48301 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts @@ -5,7 +5,6 @@ import { lightboxPersistDenylist } from 'features/lightbox/store/lightboxPersist import { nodesPersistDenylist } from 'features/nodes/store/nodesPersistDenylist'; import { generationPersistDenylist } from 'features/parameters/store/generationPersistDenylist'; import { postprocessingPersistDenylist } from 'features/parameters/store/postprocessingPersistDenylist'; -import { modelsPersistDenylist } from 'features/system/store/modelsPersistDenylist'; import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist'; import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist'; import { omit } from 'lodash-es'; @@ -18,8 +17,6 @@ const serializationDenylist: { gallery: galleryPersistDenylist, generation: generationPersistDenylist, lightbox: lightboxPersistDenylist, - sd1models: modelsPersistDenylist, - sd2models: modelsPersistDenylist, nodes: nodesPersistDenylist, postprocessing: postprocessingPersistDenylist, system: systemPersistDenylist, diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts index 649b56316d..8f40b0bb59 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts @@ -7,8 +7,6 @@ import { initialNodesState } from 'features/nodes/store/nodesSlice'; import { initialGenerationState } from 'features/parameters/store/generationSlice'; import { initialPostprocessingState } from 'features/parameters/store/postprocessingSlice'; import { initialConfigState } from 'features/system/store/configSlice'; -import { sd1InitialPipelineModelsState } from 'features/system/store/models/sd1PipelineModelSlice'; -import { sd2InitialPipelineModelsState } from 'features/system/store/models/sd2PipelineModelSlice'; import { initialSystemState } from 'features/system/store/systemSlice'; import { initialHotkeysState } from 'features/ui/store/hotkeysSlice'; import { initialUIState } from 'features/ui/store/uiSlice'; @@ -22,8 +20,6 @@ const initialStates: { gallery: initialGalleryState, generation: initialGenerationState, lightbox: initialLightboxState, - sd1PipelineModels: sd1InitialPipelineModelsState, - sd2PipelineModels: sd2InitialPipelineModelsState, nodes: initialNodesState, postprocessing: initialPostprocessingState, system: initialSystemState, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index 0893066f1f..bf54e63836 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -1,7 +1,6 @@ import { log } from 'app/logging/useLogger'; import { appSocketConnected, socketConnected } from 'services/events/actions'; import { receivedPageOfImages } from 'services/thunks/image'; -import { receivedModels } from 'services/thunks/model'; import { receivedOpenAPISchema } from 'services/thunks/schema'; import { startAppListening } from '../..'; @@ -15,8 +14,7 @@ export const addSocketConnectedEventListener = () => { moduleLog.debug({ timestamp }, 'Connected'); - const { sd1pipelinemodels, sd2pipelinemodels, nodes, config, images } = - getState(); + const { nodes, config, images } = getState(); const { disabledTabs } = config; @@ -29,14 +27,6 @@ export const addSocketConnectedEventListener = () => { ); } - if (!sd1pipelinemodels.ids.length) { - dispatch(receivedModels({ baseModel: 'sd-1', modelType: 'pipeline' })); - } - - if (!sd2pipelinemodels.ids.length) { - dispatch(receivedModels({ baseModel: 'sd-2', modelType: 'pipeline' })); - } - if (!nodes.schema && !disabledTabs.includes('nodes')) { dispatch(receivedOpenAPISchema()); } diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 8489de85f0..57a97168a3 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -28,11 +28,6 @@ import { listenerMiddleware } from './middleware/listenerMiddleware'; import { actionSanitizer } from './middleware/devtools/actionSanitizer'; import { actionsDenylist } from './middleware/devtools/actionsDenylist'; import { stateSanitizer } from './middleware/devtools/stateSanitizer'; - -// Model Reducers -import sd1PipelineModelReducer from 'features/system/store/models/sd1PipelineModelSlice'; -import sd2PipelineModelReducer from 'features/system/store/models/sd2PipelineModelSlice'; - import { LOCALSTORAGE_PREFIX } from './constants'; import { serialize } from './enhancers/reduxRemember/serialize'; import { unserialize } from './enhancers/reduxRemember/unserialize'; @@ -43,8 +38,6 @@ const allReducers = { gallery: galleryReducer, generation: generationReducer, lightbox: lightboxReducer, - sd1pipelinemodels: sd1PipelineModelReducer, - sd2pipelinemodels: sd2PipelineModelReducer, nodes: nodesReducer, postprocessing: postprocessingReducer, system: systemReducer, @@ -54,8 +47,8 @@ const allReducers = { images: imagesReducer, controlNet: controlNetReducer, boards: boardsReducer, - [api.reducerPath]: api.reducer, // session: sessionReducer, + [api.reducerPath]: api.reducer, }; const rootReducer = combineReducers(allReducers); diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx index 480c8591bb..c274f23f26 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx @@ -1,14 +1,18 @@ -import { NativeSelect } from '@mantine/core'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { SelectItem } from '@mantine/core'; +import { useAppDispatch } from 'app/store/storeHooks'; import { fieldValueChanged } from 'features/nodes/store/nodesSlice'; import { ModelInputFieldTemplate, ModelInputFieldValue, } from 'features/nodes/types/types'; -import { modelSelector } from 'features/system/store/modelSelectors'; -import { ChangeEvent, memo } from 'react'; +import { memo, useCallback, useEffect, useMemo } from 'react'; import { FieldComponentProps } from './types'; +import { forEach, isString } from 'lodash-es'; +import { MODEL_TYPE_MAP as BASE_MODEL_NAME_MAP } from 'features/system/components/ModelSelect'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; +import { useTranslation } from 'react-i18next'; +import { useListModelsQuery } from 'services/apiSlice'; const ModelInputFieldComponent = ( props: FieldComponentProps @@ -16,26 +20,82 @@ const ModelInputFieldComponent = ( const { nodeId, field } = props; const dispatch = useAppDispatch(); + const { t } = useTranslation(); - const { sd1PipelineModelDropDownData, sd2PipelineModelDropdownData } = - useAppSelector(modelSelector); + const { data: pipelineModels } = useListModelsQuery({ + model_type: 'pipeline', + }); - const handleValueChanged = (e: ChangeEvent) => { - dispatch( - fieldValueChanged({ - nodeId, - fieldName: field.name, - value: e.target.value, - }) - ); - }; + const data = useMemo(() => { + if (!pipelineModels) { + return []; + } + + const data: SelectItem[] = []; + + forEach(pipelineModels.entities, (model, id) => { + if (!model) { + return; + } + + data.push({ + value: id, + label: model.name, + group: BASE_MODEL_NAME_MAP[model.base_model], + }); + }); + + return data; + }, [pipelineModels]); + + const selectedModel = useMemo( + () => pipelineModels?.entities[field.value ?? pipelineModels.ids[0]], + [pipelineModels?.entities, pipelineModels?.ids, field.value] + ); + + const handleValueChanged = useCallback( + (v: string | null) => { + if (!v) { + return; + } + + dispatch( + fieldValueChanged({ + nodeId, + fieldName: field.name, + value: v, + }) + ); + }, + [dispatch, field.name, nodeId] + ); + + useEffect(() => { + if (field.value && pipelineModels?.ids.includes(field.value)) { + return; + } + + const firstModel = pipelineModels?.ids[0]; + + if (!isString(firstModel)) { + return; + } + + handleValueChanged(firstModel); + }, [field.value, handleValueChanged, pipelineModels?.ids]); return ( - + /> ); }; diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts index 5425d1cfd5..341f0c467b 100644 --- a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts +++ b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts @@ -101,21 +101,6 @@ const nodesSlice = createSlice({ builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => { state.schema = action.payload; }); - - builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - const { image_name, image_url, thumbnail_url } = action.payload; - - state.nodes.forEach((node) => { - forEach(node.data.inputs, (input) => { - if (input.type === 'image') { - if (input.value?.image_name === image_name) { - input.value.image_url = image_url; - input.value.thumbnail_url = thumbnail_url; - } - } - }); - }); - }); }, }); diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts index efaeaddff2..ccdc3e0a27 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts @@ -23,6 +23,7 @@ import { } from './constants'; import { set } from 'lodash-es'; import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; const moduleLog = log.child({ namespace: 'nodes' }); @@ -36,7 +37,7 @@ export const buildCanvasImageToImageGraph = ( const { positivePrompt, negativePrompt, - model: model_name, + model: modelId, cfgScale: cfg_scale, scheduler, steps, @@ -49,6 +50,8 @@ export const buildCanvasImageToImageGraph = ( // The bounding box determines width and height, not the width and height params const { width, height } = state.canvas.boundingBoxDimensions; + const model = modelIdToPipelineModelField(modelId); + /** * 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 @@ -85,9 +88,9 @@ export const buildCanvasImageToImageGraph = ( id: NOISE, }, [MODEL_LOADER]: { - type: 'sd1_model_loader', + type: 'pipeline_model_loader', id: MODEL_LOADER, - model_name, + model, }, [LATENTS_TO_IMAGE]: { type: 'l2i', diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts index 785e1d2fdb..9ffe85b3c9 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts @@ -17,6 +17,7 @@ import { INPAINT_GRAPH, INPAINT, } from './constants'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; const moduleLog = log.child({ namespace: 'nodes' }); @@ -31,7 +32,7 @@ export const buildCanvasInpaintGraph = ( const { positivePrompt, negativePrompt, - model: model_name, + model: modelId, cfgScale: cfg_scale, scheduler, steps, @@ -54,6 +55,8 @@ export const buildCanvasInpaintGraph = ( // We may need to set the inpaint width and height to scale the image const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas; + const model = modelIdToPipelineModelField(modelId); + const graph: NonNullableGraph = { id: INPAINT_GRAPH, nodes: { @@ -99,9 +102,9 @@ export const buildCanvasInpaintGraph = ( prompt: negativePrompt, }, [MODEL_LOADER]: { - type: 'sd1_model_loader', + type: 'pipeline_model_loader', id: MODEL_LOADER, - model_name, + model, }, [RANGE_OF_SIZE]: { type: 'range_of_size', diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts index ca0e56e849..920cb5bf02 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts @@ -14,6 +14,7 @@ import { TEXT_TO_LATENTS, } from './constants'; import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; /** * Builds the Canvas tab's Text to Image graph. @@ -24,7 +25,7 @@ export const buildCanvasTextToImageGraph = ( const { positivePrompt, negativePrompt, - model: model_name, + model: modelId, cfgScale: cfg_scale, scheduler, steps, @@ -36,6 +37,8 @@ export const buildCanvasTextToImageGraph = ( // The bounding box determines width and height, not the width and height params const { width, height } = state.canvas.boundingBoxDimensions; + const model = modelIdToPipelineModelField(modelId); + /** * 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 @@ -80,9 +83,9 @@ export const buildCanvasTextToImageGraph = ( steps, }, [MODEL_LOADER]: { - type: 'sd1_model_loader', + type: 'pipeline_model_loader', id: MODEL_LOADER, - model_name, + model, }, [LATENTS_TO_IMAGE]: { type: 'l2i', diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts index 78a6623a16..8425ac043a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts @@ -22,6 +22,7 @@ import { } from './constants'; import { set } from 'lodash-es'; import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; const moduleLog = log.child({ namespace: 'nodes' }); @@ -34,7 +35,7 @@ export const buildLinearImageToImageGraph = ( const { positivePrompt, negativePrompt, - model: model_name, + model: modelId, cfgScale: cfg_scale, scheduler, steps, @@ -62,6 +63,8 @@ export const buildLinearImageToImageGraph = ( throw new Error('No initial image found in state'); } + const model = modelIdToPipelineModelField(modelId); + // copy-pasted graph from node editor, filled in with state values & friendly node ids const graph: NonNullableGraph = { id: IMAGE_TO_IMAGE_GRAPH, @@ -89,9 +92,9 @@ export const buildLinearImageToImageGraph = ( id: NOISE, }, [MODEL_LOADER]: { - type: 'sd1_model_loader', + type: 'pipeline_model_loader', id: MODEL_LOADER, - model_name, + model, }, [LATENTS_TO_IMAGE]: { type: 'l2i', diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts index c179a89504..973acdfb77 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts @@ -1,6 +1,10 @@ import { RootState } from 'app/store/store'; import { NonNullableGraph } from 'features/nodes/types/types'; -import { RandomIntInvocation, RangeOfSizeInvocation } from 'services/api'; +import { + BaseModelType, + RandomIntInvocation, + RangeOfSizeInvocation, +} from 'services/api'; import { ITERATE, LATENTS_TO_IMAGE, @@ -14,6 +18,7 @@ import { TEXT_TO_LATENTS, } from './constants'; import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; type TextToImageGraphOverrides = { width: number; @@ -27,7 +32,7 @@ export const buildLinearTextToImageGraph = ( const { positivePrompt, negativePrompt, - model: model_name, + model: modelId, cfgScale: cfg_scale, scheduler, steps, @@ -38,6 +43,8 @@ export const buildLinearTextToImageGraph = ( shouldRandomizeSeed, } = state.generation; + const model = modelIdToPipelineModelField(modelId); + /** * 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 @@ -82,9 +89,9 @@ export const buildLinearTextToImageGraph = ( steps, }, [MODEL_LOADER]: { - type: 'sd1_model_loader', + type: 'pipeline_model_loader', id: MODEL_LOADER, - model_name, + model, }, [LATENTS_TO_IMAGE]: { type: 'l2i', diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts index 6a700d4813..072b1a53fd 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts @@ -1,9 +1,10 @@ import { Graph } from 'services/api'; import { v4 as uuidv4 } from 'uuid'; -import { cloneDeep, forEach, omit, reduce, values } from 'lodash-es'; +import { cloneDeep, omit, reduce } from 'lodash-es'; import { RootState } from 'app/store/store'; import { InputFieldValue } from 'features/nodes/types/types'; import { AnyInvocation } from 'services/events/types'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; /** * We need to do special handling for some fields @@ -24,6 +25,12 @@ export const parseFieldValue = (field: InputFieldValue) => { } } + if (field.type === 'model') { + if (field.value) { + return modelIdToPipelineModelField(field.value); + } + } + return field.value; }; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts index 39e0080d11..7d4469bc41 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts @@ -7,7 +7,7 @@ export const NOISE = 'noise'; export const RANDOM_INT = 'rand_int'; export const RANGE_OF_SIZE = 'range_of_size'; export const ITERATE = 'iterate'; -export const MODEL_LOADER = 'model_loader'; +export const MODEL_LOADER = 'pipeline_model_loader'; export const IMAGE_TO_LATENTS = 'image_to_latents'; export const LATENTS_TO_LATENTS = 'latents_to_latents'; export const RESIZE = 'resize_image'; diff --git a/invokeai/frontend/web/src/features/nodes/util/modelIdToPipelineModelField.ts b/invokeai/frontend/web/src/features/nodes/util/modelIdToPipelineModelField.ts new file mode 100644 index 0000000000..bbcd8d9bc6 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/modelIdToPipelineModelField.ts @@ -0,0 +1,18 @@ +import { BaseModelType, PipelineModelField } from 'services/api'; + +/** + * Crudely converts a model id to a pipeline model field + * TODO: Make better + */ +export const modelIdToPipelineModelField = ( + modelId: string +): PipelineModelField => { + const [base_model, model_type, model_name] = modelId.split('/'); + + const field: PipelineModelField = { + base_model: base_model as BaseModelType, + model_name, + }; + + return field; +}; diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index e1de166b5c..e7dcbf0d83 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,12 +1,9 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import { DEFAULT_SCHEDULER_NAME, Scheduler } from 'app/constants'; -import { ModelLoaderTypes } from 'features/system/components/ModelSelect'; +import { DEFAULT_SCHEDULER_NAME } from 'app/constants'; import { configChanged } from 'features/system/store/configSlice'; -import { clamp, sortBy } from 'lodash-es'; +import { clamp } from 'lodash-es'; import { ImageDTO } from 'services/api'; -import { imageUrlsReceived } from 'services/thunks/image'; -import { receivedModels } from 'services/thunks/model'; import { CfgScaleParam, HeightParam, @@ -50,7 +47,6 @@ export interface GenerationState { horizontalSymmetrySteps: number; verticalSymmetrySteps: number; model: ModelParam; - currentModelType: ModelLoaderTypes; shouldUseSeamless: boolean; seamlessXAxis: boolean; seamlessYAxis: boolean; @@ -85,7 +81,6 @@ export const initialGenerationState: GenerationState = { horizontalSymmetrySteps: 0, verticalSymmetrySteps: 0, model: '', - currentModelType: 'sd1_model_loader', shouldUseSeamless: false, seamlessXAxis: true, seamlessYAxis: true, @@ -221,33 +216,14 @@ export const generationSlice = createSlice({ modelSelected: (state, action: PayloadAction) => { state.model = action.payload; }, - setCurrentModelType: (state, action: PayloadAction) => { - state.currentModelType = action.payload; - }, }, extraReducers: (builder) => { - builder.addCase(receivedModels.fulfilled, (state, action) => { - if (!state.model) { - const firstModel = sortBy(action.payload, 'name')[0]; - state.model = firstModel.name; - } - }); - builder.addCase(configChanged, (state, action) => { const defaultModel = action.payload.sd?.defaultModel; if (defaultModel && !state.model) { state.model = defaultModel; } }); - - // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - // const { image_name, image_url, thumbnail_url } = action.payload; - - // if (state.initialImage?.image_name === image_name) { - // state.initialImage.image_url = image_url; - // state.initialImage.thumbnail_url = thumbnail_url; - // } - // }); }, }); @@ -284,7 +260,6 @@ export const { setVerticalSymmetrySteps, initialImageChanged, modelSelected, - setCurrentModelType, setShouldUseNoiseSettings, setSeamless, setSeamlessXAxis, diff --git a/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts b/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts index 61567d3fb8..48eb309e7d 100644 --- a/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts +++ b/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts @@ -154,3 +154,17 @@ export type StrengthParam = z.infer; */ export const isValidStrength = (val: unknown): val is StrengthParam => zStrength.safeParse(val).success; + +// /** +// * Zod schema for BaseModelType +// */ +// export const zBaseModelType = z.enum(['sd-1', 'sd-2']); +// /** +// * Type alias for base model type, inferred from its zod schema. Should be identical to the type alias from OpenAPI. +// */ +// export type BaseModelType = z.infer; +// /** +// * Validates/type-guards a value as a base model type +// */ +// export const isValidBaseModelType = (val: unknown): val is BaseModelType => +// zBaseModelType.safeParse(val).success; diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index 813bd9fb70..43de14d507 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -1,39 +1,58 @@ -import { memo, useCallback, useEffect } from 'react'; +import { memo, useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIMantineSelect from 'common/components/IAIMantineSelect'; -import { - modelSelected, - setCurrentModelType, -} from 'features/parameters/store/generationSlice'; +import { modelSelected } from 'features/parameters/store/generationSlice'; -import { modelSelector } from '../store/modelSelectors'; +import { forEach, isString } from 'lodash-es'; +import { SelectItem } from '@mantine/core'; +import { RootState } from 'app/store/store'; +import { useListModelsQuery } from 'services/apiSlice'; -export type ModelLoaderTypes = 'sd1_model_loader' | 'sd2_model_loader'; - -const MODEL_LOADER_MAP = { - 'sd-1': 'sd1_model_loader', - 'sd-2': 'sd2_model_loader', +export const MODEL_TYPE_MAP = { + 'sd-1': 'Stable Diffusion 1.x', + 'sd-2': 'Stable Diffusion 2.x', }; const ModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const { - selectedModel, - sd1PipelineModelDropDownData, - sd2PipelineModelDropdownData, - } = useAppSelector(modelSelector); - useEffect(() => { - if (selectedModel) - dispatch( - setCurrentModelType( - MODEL_LOADER_MAP[selectedModel?.base_model] as ModelLoaderTypes - ) - ); - }, [dispatch, selectedModel]); + const selectedModelId = useAppSelector( + (state: RootState) => state.generation.model + ); + + const { data: pipelineModels } = useListModelsQuery({ + model_type: 'pipeline', + }); + + const data = useMemo(() => { + if (!pipelineModels) { + return []; + } + + const data: SelectItem[] = []; + + forEach(pipelineModels.entities, (model, id) => { + if (!model) { + return; + } + + data.push({ + value: id, + label: model.name, + group: MODEL_TYPE_MAP[model.base_model], + }); + }); + + return data; + }, [pipelineModels]); + + const selectedModel = useMemo( + () => pipelineModels?.entities[selectedModelId], + [pipelineModels?.entities, selectedModelId] + ); const handleChangeModel = useCallback( (v: string | null) => { @@ -45,13 +64,27 @@ const ModelSelect = () => { [dispatch] ); + useEffect(() => { + if (selectedModelId && pipelineModels?.ids.includes(selectedModelId)) { + return; + } + + const firstModel = pipelineModels?.ids[0]; + + if (!isString(firstModel)) { + return; + } + + handleChangeModel(firstModel); + }, [handleChangeModel, pipelineModels?.ids, selectedModelId]); + return ( ); diff --git a/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts b/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts index 193420e29c..8ba5731a5b 100644 --- a/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts +++ b/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts @@ -7,13 +7,12 @@ import { systemSelector } from '../store/systemSelectors'; const isApplicationReadySelector = createSelector( [systemSelector, configSelector], (system, config) => { - const { wereModelsReceived, wasSchemaParsed } = system; + const { wasSchemaParsed } = system; const { disabledTabs } = config; return { disabledTabs, - wereModelsReceived, wasSchemaParsed, }; } @@ -23,21 +22,17 @@ const isApplicationReadySelector = createSelector( * Checks if the application is ready to be used, i.e. if the initial startup process is finished. */ export const useIsApplicationReady = () => { - const { disabledTabs, wereModelsReceived, wasSchemaParsed } = useAppSelector( + const { disabledTabs, wasSchemaParsed } = useAppSelector( isApplicationReadySelector ); const isApplicationReady = useMemo(() => { - if (!wereModelsReceived) { - return false; - } - if (!disabledTabs.includes('nodes') && !wasSchemaParsed) { return false; } return true; - }, [disabledTabs, wereModelsReceived, wasSchemaParsed]); + }, [disabledTabs, wasSchemaParsed]); return isApplicationReady; }; diff --git a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts b/invokeai/frontend/web/src/features/system/store/modelSelectors.ts deleted file mode 100644 index b63c6d256c..0000000000 --- a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { IAISelectDataType } from 'common/components/IAIMantineSelect'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { isEqual } from 'lodash-es'; - -import { - selectAllSD1PipelineModels, - selectByIdSD1PipelineModels, -} from './models/sd1PipelineModelSlice'; - -import { - selectAllSD2PipelineModels, - selectByIdSD2PipelineModels, -} from './models/sd2PipelineModelSlice'; - -export const modelSelector = createSelector( - [(state: RootState) => state, generationSelector], - (state, generation) => { - let selectedModel = selectByIdSD1PipelineModels(state, generation.model); - if (selectedModel === undefined) - selectedModel = selectByIdSD2PipelineModels(state, generation.model); - - const sd1PipelineModels = selectAllSD1PipelineModels(state); - const sd2PipelineModels = selectAllSD2PipelineModels(state); - - const allPipelineModels = sd1PipelineModels.concat(sd2PipelineModels); - - const sd1PipelineModelDropDownData = selectAllSD1PipelineModels(state) - .map((m) => ({ - value: m.name, - label: m.name, - group: '1.x Models', - })) - .sort((a, b) => a.label.localeCompare(b.label)); - - const sd2PipelineModelDropdownData = selectAllSD2PipelineModels(state) - .map((m) => ({ - value: m.name, - label: m.name, - group: '2.x Models', - })) - .sort((a, b) => a.label.localeCompare(b.label)); - - return { - selectedModel, - allPipelineModels, - sd1PipelineModels, - sd2PipelineModels, - sd1PipelineModelDropDownData, - sd2PipelineModelDropdownData, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts deleted file mode 100644 index 99f1514e6c..0000000000 --- a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { - StableDiffusion1ModelCheckpointConfig, - StableDiffusion1ModelDiffusersConfig, -} from 'services/api'; - -import { receivedModels } from 'services/thunks/model'; - -export type SD1PipelineModel = ( - | StableDiffusion1ModelCheckpointConfig - | StableDiffusion1ModelDiffusersConfig -) & { - name: string; -}; - -export const sd1PipelineModelsAdapter = createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), -}); - -export const sd1InitialPipelineModelsState = - sd1PipelineModelsAdapter.getInitialState(); - -export type SD1PipelineModelState = typeof sd1InitialPipelineModelsState; - -export const sd1PipelineModelsSlice = createSlice({ - name: 'sd1PipelineModels', - initialState: sd1InitialPipelineModelsState, - reducers: { - modelAdded: sd1PipelineModelsAdapter.upsertOne, - }, - extraReducers(builder) { - /** - * Received Models - FULFILLED - */ - builder.addCase(receivedModels.fulfilled, (state, action) => { - if (action.meta.arg.baseModel !== 'sd-1') return; - sd1PipelineModelsAdapter.setAll(state, action.payload); - }); - }, -}); - -export const { - selectAll: selectAllSD1PipelineModels, - selectById: selectByIdSD1PipelineModels, - selectEntities: selectEntitiesSD1PipelineModels, - selectIds: selectIdsSD1PipelineModels, - selectTotal: selectTotalSD1PipelineModels, -} = sd1PipelineModelsAdapter.getSelectors( - (state) => state.sd1pipelinemodels -); - -export const { modelAdded } = sd1PipelineModelsSlice.actions; - -export default sd1PipelineModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts deleted file mode 100644 index 69ff772222..0000000000 --- a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { - StableDiffusion2ModelCheckpointConfig, - StableDiffusion2ModelDiffusersConfig, -} from 'services/api'; - -import { receivedModels } from 'services/thunks/model'; - -export type SD2PipelineModel = ( - | StableDiffusion2ModelCheckpointConfig - | StableDiffusion2ModelDiffusersConfig -) & { - name: string; -}; - -export const sd2PipelineModelsAdapater = createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), -}); - -export const sd2InitialPipelineModelsState = - sd2PipelineModelsAdapater.getInitialState(); - -export type SD2PipelineModelState = typeof sd2InitialPipelineModelsState; - -export const sd2PipelineModelsSlice = createSlice({ - name: 'sd2PipelineModels', - initialState: sd2InitialPipelineModelsState, - reducers: { - modelAdded: sd2PipelineModelsAdapater.upsertOne, - }, - extraReducers(builder) { - /** - * Received Models - FULFILLED - */ - builder.addCase(receivedModels.fulfilled, (state, action) => { - if (action.meta.arg.baseModel !== 'sd-2') return; - sd2PipelineModelsAdapater.setAll(state, action.payload); - }); - }, -}); - -export const { - selectAll: selectAllSD2PipelineModels, - selectById: selectByIdSD2PipelineModels, - selectEntities: selectEntitiesSD2PipelineModels, - selectIds: selectIdsSD2PipelineModels, - selectTotal: selectTotalSD2PipelineModels, -} = sd2PipelineModelsAdapater.getSelectors( - (state) => state.sd2pipelinemodels -); - -export const { modelAdded } = sd2PipelineModelsSlice.actions; - -export default sd2PipelineModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts deleted file mode 100644 index 417a399cf2..0000000000 --- a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { SD1PipelineModelState } from './models/sd1PipelineModelSlice'; -import { SD2PipelineModelState } from './models/sd2PipelineModelSlice'; - -/** - * Models slice persist denylist - */ -export const modelsPersistDenylist: - | (keyof SD1PipelineModelState)[] - | (keyof SD2PipelineModelState)[] = ['entities', 'ids']; diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 8a148ca38b..688f69c1f7 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -20,7 +20,6 @@ import { } from 'services/events/actions'; import { ProgressImage } from 'services/events/types'; import { imageUploaded } from 'services/thunks/image'; -import { receivedModels } from 'services/thunks/model'; import { isAnySessionRejected, sessionCanceled } from 'services/thunks/session'; import { makeToast } from '../../../app/components/Toaster'; import { LANGUAGES } from '../components/LanguagePicker'; @@ -377,13 +376,6 @@ export const systemSlice = createSlice({ ); }); - /** - * Received available models from the backend - */ - builder.addCase(receivedModels.fulfilled, (state) => { - state.wereModelsReceived = true; - }); - /** * OpenAPI schema was parsed */ diff --git a/invokeai/frontend/web/src/services/apiSlice.ts b/invokeai/frontend/web/src/services/apiSlice.ts index 2d42931b0b..e2d765dd90 100644 --- a/invokeai/frontend/web/src/services/apiSlice.ts +++ b/invokeai/frontend/web/src/services/apiSlice.ts @@ -13,23 +13,68 @@ import { TagTypesFrom, TagTypesFromApi, } from '@reduxjs/toolkit/dist/query/endpointDefinitions'; +import { EntityState, createEntityAdapter } from '@reduxjs/toolkit'; +import { BaseModelType } from './api/models/BaseModelType'; +import { ModelType } from './api/models/ModelType'; +import { ModelsList } from './api/models/ModelsList'; +import { keyBy } from 'lodash-es'; type ListBoardsArg = { offset: number; limit: number }; type UpdateBoardArg = { board_id: string; changes: BoardChanges }; type AddImageToBoardArg = { board_id: string; image_name: string }; type RemoveImageFromBoardArg = { board_id: string; image_name: string }; type ListBoardImagesArg = { board_id: string; offset: number; limit: number }; +type ListModelsArg = { base_model?: BaseModelType; model_type?: ModelType }; -const tagTypes = ['Board', 'Image']; +type ModelConfig = ModelsList['models'][number]; + +const tagTypes = ['Board', 'Image', 'Model']; type ApiFullTagDescription = FullTagDescription<(typeof tagTypes)[number]>; const LIST = 'LIST'; +const modelsAdapter = createEntityAdapter({ + selectId: (model) => getModelId(model), + sortComparer: (a, b) => a.name.localeCompare(b.name), +}); + +const getModelId = ({ base_model, type, name }: ModelConfig) => + `${base_model}/${type}/${name}`; + export const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5173/api/v1/' }), reducerPath: 'api', tagTypes, endpoints: (build) => ({ + /** + * Models Queries + */ + + listModels: build.query, ListModelsArg>({ + query: (arg) => ({ url: 'models/', params: arg }), + providesTags: (result, error, arg) => { + // any list of boards + const tags: ApiFullTagDescription[] = [{ id: 'Model', type: LIST }]; + + if (result) { + // and individual tags for each board + tags.push( + ...result.ids.map((id) => ({ + type: 'Model' as const, + id, + })) + ); + } + + return tags; + }, + transformResponse: (response: ModelsList, meta, arg) => { + return modelsAdapter.addMany( + modelsAdapter.getInitialState(), + keyBy(response.models, getModelId) + ); + }, + }), /** * Boards Queries */ @@ -174,4 +219,5 @@ export const { useRemoveImageFromBoardMutation, useListBoardImagesQuery, useGetImageDTOQuery, + useListModelsQuery, } = api; diff --git a/invokeai/frontend/web/src/services/thunks/model.ts b/invokeai/frontend/web/src/services/thunks/model.ts deleted file mode 100644 index 619aa4b7b2..0000000000 --- a/invokeai/frontend/web/src/services/thunks/model.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { log } from 'app/logging/useLogger'; -import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { SD1PipelineModel } from 'features/system/store/models/sd1PipelineModelSlice'; -import { SD2PipelineModel } from 'features/system/store/models/sd2PipelineModelSlice'; -import { reduce, size } from 'lodash-es'; -import { BaseModelType, ModelType, ModelsService } from 'services/api'; - -const models = log.child({ namespace: 'model' }); - -export const IMAGES_PER_PAGE = 20; - -type receivedModelsArg = { - baseModel: BaseModelType | undefined; - modelType: ModelType | undefined; -}; - -export const receivedModels = createAppAsyncThunk( - 'models/receivedModels', - async (arg: receivedModelsArg) => { - const response = await ModelsService.listModels(arg); - - let deserializedModels = {}; - - if (arg.baseModel === undefined) return response.models; - if (arg.modelType === undefined) return response.models; - - if (arg.baseModel === 'sd-1') { - deserializedModels = reduce( - response.models[arg.baseModel][arg.modelType], - (modelsAccumulator, model, modelName) => { - modelsAccumulator[modelName] = { ...model, name: modelName }; - return modelsAccumulator; - }, - {} as Record - ); - } - - if (arg.baseModel === 'sd-2') { - deserializedModels = reduce( - response.models[arg.baseModel][arg.modelType], - (modelsAccumulator, model, modelName) => { - modelsAccumulator[modelName] = { ...model, name: modelName }; - return modelsAccumulator; - }, - {} as Record - ); - } - - models.info( - { response }, - `Received ${size(response.models[arg.baseModel][arg.modelType])} ${[ - arg.baseModel, - ]} models` - ); - - return deserializedModels; - } -);