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 9317a794c50..cea4991d129 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -8,8 +8,6 @@ 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 5599d569d5c..8aa61d08aa1 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.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 @@ def initialize(config, event_handler_id: int, logger: Logger = logger): latents = ForwardCacheLatentsStorage( DiskLatentsStorage(f"{output_folder}/latents") ) + boards = SqliteBoardStorage(db_location) images = ImageService( image_record_storage=image_record_storage, @@ -96,6 +99,7 @@ def initialize(config, event_handler_id: int, logger: Logger = logger): 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 00000000000..47e4f74b0af --- /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 fa46762d569..d00d92f763f 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -24,7 +24,7 @@ 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 @@ async def shutdown_event(): 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 00000000000..69b1afa048b --- /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 30b379ed8bc..88d9b549262 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -135,7 +135,7 @@ def __init__(self, filename: str) -> None: 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 @@ def _create_tables(self) -> None: 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 1f910253e58..d69e0b294f3 100644 --- a/invokeai/app/services/invocation_services.py +++ b/invokeai/app/services/invocation_services.py @@ -14,6 +14,7 @@ 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 @@ def __init__( processor: "InvocationProcessorABC", restoration: "RestorationServices", configuration: "InvokeAISettings", + boards: "BoardStorageBase", ): self.model_manager = model_manager self.events = events @@ -58,3 +61,4 @@ def __init__( 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 47e4f74b0af..9ef416e3964 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 88d9b549262..0a35763ff3c 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -260,6 +260,18 @@ def update( """, (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 d971d659168..a4699f74c8c 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 11453d97f19..24bb7166357 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 0a35763ff3c..84abcb6b2e3 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -286,6 +286,7 @@ def get_many( 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 @@ def get_many( 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 9f7188f6073..173268563ae 100644 --- a/invokeai/app/services/images.py +++ b/invokeai/app/services/images.py @@ -322,6 +322,7 @@ def get_many( 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 @@ def get_many( 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 9ef416e3964..eb2f5956ab4 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 69b1afa048b..00d90637fed 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -54,11 +54,6 @@ def __init__(self, message="Board record not deleted"): 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 @@ def save( (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 a4699f74c8c..98f370f337f 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 84abcb6b2e3..3c98ef5f29f 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -82,6 +82,7 @@ def get_many( 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 eb2f5956ab4..c8e877ca59f 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 00d90637fed..3cdadd6c22c 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 @@ def save( """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 @@ def save( 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 3c98ef5f29f..96c6beea12f 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -94,6 +94,11 @@ def delete(self, image_name: str) -> None: """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 @@ def update( 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 | 177 +++++---- 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 | 356 +++++++----------- 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, 1017 insertions(+), 392 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 8aa61d08aa1..8889c706742 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 @@ def initialize(config, event_handler_id: int, logger: Logger = logger): # 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 @@ def initialize(config, event_handler_id: int, logger: Logger = logger): 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 @@ def initialize(config, event_handler_id: int, logger: Logger = logger): 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 @@ def initialize(config, event_handler_id: int, logger: Logger = logger): 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 c8e877ca59f..f3a76e08d33 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 ..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: - 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""" - - 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""" - - 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 +# 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 + +# 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.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.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 + + + +# 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/routers/images.py b/invokeai/app/api/routers/images.py index 24bb7166357..11453d97f19 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 d00d92f763f..50228edf7e2 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -78,7 +78,7 @@ async def shutdown_event(): 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 00000000000..b805087da85 --- /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 00000000000..dd2e1041808 --- /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 00000000000..a954fe7ac49 --- /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 3cdadd6c22c..07d64e655a5 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 - -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) - - -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.""" + +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 + +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 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 - - 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')) - ); - """ - ) +class BoardServiceDependencies: + """Service dependencies for the BoardService.""" - self._cursor.execute( - """--sql - CREATE INDEX IF NOT EXISTS idx_boards_created_at ON boards(created_at); - """ - ) + board_image_records: BoardImageRecordStorageBase + board_records: BoardRecordStorageBase + image_records: ImageRecordStorageBase + urls: UrlServiceBase + logger: Logger - # 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 __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 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( +class BoardService(BoardServiceABC): + _services: BoardServiceDependencies + + def __init__(self, services: BoardServiceDependencies): + self._services = services + + def create( 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,), - ) + ) -> 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 + ) + 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) - result = self._cursor.fetchone() - return result - except sqlite3.Error as e: - self._conn.rollback() - raise BoardRecordSaveException from e - finally: - self._lock.release() - + 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: + self._services.board_records.delete(board_id) 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 + 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)) + + 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 96c6beea12f..2ca9ad66ca5 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -82,7 +82,6 @@ def get_many( 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 @@ def delete(self, image_name: str) -> None: """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 @@ def _create_tables(self) -> None: 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 @@ def update( ) # 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 @@ def update( 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 @@ def get_many( 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 @@ def get_many( 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 @@ def get_many( 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 173268563ae..aa27e38d17c 100644 --- a/invokeai/app/services/images.py +++ b/invokeai/app/services/images.py @@ -49,7 +49,7 @@ def create( 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 @@ def get_dto(self, image_name: str) -> ImageDTO: 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 @@ def get_many( 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 @@ def get_many( 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 d69e0b294f3..10d1d919207 100644 --- a/invokeai/app/services/invocation_services.py +++ b/invokeai/app/services/invocation_services.py @@ -4,7 +4,9 @@ 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 @@ 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 @@ def __init__( 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 98f370f337f..d971d659168 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 | 163 +++++++++--------- 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, 213 insertions(+), 134 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 00000000000..b206ab500d7 --- /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 f3a76e08d33..618e8f990b2 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 + +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.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""" + + 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[BoardDTO]: + """Gets a list of boards""" + + results = ApiDependencies.invoker.services.boards.get_many( + offset, + limit, + ) + return results -# 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: -# 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""" - -# 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""" - -# 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 - - - -# 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 50228edf7e2..22b4efec747 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -24,7 +24,7 @@ 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 @@ async def shutdown_event(): 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 dd2e1041808..df2af4bbcfd 100644 --- a/invokeai/app/services/board_images.py +++ b/invokeai/app/services/board_images.py @@ -2,7 +2,6 @@ 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 @@ 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 a954fe7ac49..8207470e6d5 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -6,41 +6,11 @@ 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 @@ def save( 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 @@ def get_many( ) 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 07d64e655a5..ddfaacf79ca 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -5,8 +5,6 @@ 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 @@ 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 2ca9ad66ca5..677e1d3445b 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -261,17 +261,6 @@ def update( (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 00000000000..35c4cea9b00 --- /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 7481a5daade..60db5eda3a1 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 00000000000..fb2bfa0cd9e --- /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 00000000000..50db9b22eb9 --- /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 00000000000..47f8537eaac --- /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 00000000000..6f5a3652d05 --- /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 00000000000..7a6736dc541 --- /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 00000000000..6533c09f03d --- /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 3049d2c9339..5bb02f60fa8 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 f577b738959..6198a414bf5 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 00000000000..c6762dbe548 --- /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 b13d58e3228..d52ba89d8fa 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 fe8690e3791..a9752d4447d 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 00000000000..4bc98553afd --- /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 00000000000..6b4f1e94318 --- /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 4f250a7c3a6..a8237a711d4 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 9c18380c542..0b2b9f0f580 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 00000000000..4ead87c4d42 --- /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 334c04e6ed5..469b485cf38 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 b805087da85..abebe8c0a09 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -12,7 +12,7 @@ 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 @@ def get_boards_for_image( self, board_id: str, ) -> OffsetPaginatedResults[BoardRecord]: - """Gets images for a board.""" + """Gets boards for an image.""" pass @abstractmethod @@ -93,7 +93,9 @@ def _create_tables(self) -> None: 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 @@ def get_images_for_board( 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 @@ def get_boards_for_image( 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 @@ def get_image_count_for_board(self, board_id: str) -> int: """, (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 618e8f990b2..7935db58f2c 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 @@ 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 60db5eda3a1..ef62245553c 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 50db9b22eb9..1b72f452acd 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 e148954f164..0a724e2724a 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 602e7a2ebc1..156cdc60920 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 7a6736dc541..2e4734f469b 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 6533c09f03d..9108e3fd517 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 2e4a83b25f7..6e9ce83aaf8 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 469b485cf38..7ac0d95e6ad 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 c6762dbe548..dc06fb389bc 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 a9752d4447d..12194095df2 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 6b4f1e94318..58457de5276 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 abebe8c0a09..851e8502e14 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -102,6 +102,20 @@ def _create_tables(self) -> None: """ ) + # 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 8207470e6d5..e7ba477a890 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -128,7 +128,7 @@ def _create_tables(self) -> None: 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 e7ba477a890..8b849a05013 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -121,7 +121,8 @@ def _create_tables(self) -> None: -- 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 | 49 +++++------ .../src/features/gallery/store/boardSlice.ts | 10 ++- 5 files changed, 173 insertions(+), 61 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 00000000000..2f60ea2dacc --- /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 00000000000..064d40b2d35 --- /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 dc06fb389bc..fa1caeac7ab 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 12194095df2..d97d814adf4 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 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 { categories } = images; + + 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 58457de5276..de7ea1e828a 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 2f60ea2dacc..d8828fe7362 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 064d40b2d35..8603f28c9cb 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 fa1caeac7ab..4bb63dbb5e7 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 d97d814adf4..95c37cba61d 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 de7ea1e828a..d2e9a451d3b 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 4ead87c4d42..a536a3fdb05 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 8603f28c9cb..9e7d1ab9603 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 4bc98553afd..3dac2b6e505 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 d2e9a451d3b..fc67b4f26a5 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 4bb63dbb5e7..d368d4ab0b8 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 ddc6dace272..a11d8d048cb 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 0537d1de2a8..141e62652db 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 00000000000..cf541dca013 --- /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 00000000000..8a94764ab15 --- /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 d52ba89d8fa..b21c62785b4 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 a536a3fdb05..4535081e475 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 ddfaacf79ca..148af50103d 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -136,11 +136,12 @@ def get_many( 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 677e1d3445b..bc59c4a27c9 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -109,6 +109,11 @@ def save( """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 @@ def save( 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 8b849a05013..65e256d040e 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -154,12 +154,15 @@ def delete(self, board_id: str) -> None: 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 @@ def save( 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 65e256d040e..20a9683b027 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 @@ def save( (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 @@ def update( self, board_id: str, changes: BoardChanges, - ) -> None: + ) -> BoardRecord: try: self._lock.acquire() @@ -260,6 +248,7 @@ def update( 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 6198a414bf5..4032db3159a 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 8889c706742..60f8c1b09d3 100644 --- a/invokeai/app/api/dependencies.py +++ b/invokeai/app/api/dependencies.py @@ -12,7 +12,7 @@ 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 @@ def initialize(config, event_handler_id: int, logger: Logger = logger): ) 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 851e8502e14..2f1603be82d 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 @@ def get_images_for_board( 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 @@ def add_image_to_board( board_id: str, image_name: str, ) -> None: - """Adds an image to a board.""" try: self._lock.acquire() self._cursor.execute( @@ -156,7 +155,6 @@ def remove_image_from_board( board_id: str, image_name: str, ) -> None: - """Removes an image from a board.""" try: self._lock.acquire() self._cursor.execute( @@ -179,7 +177,6 @@ def get_images_for_board( offset: int = 0, limit: int = 10, ) -> OffsetPaginatedResults[ImageRecord]: - """Gets images for a board.""" try: self._lock.acquire() self._cursor.execute( @@ -211,46 +208,31 @@ def get_images_for_board( 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 df2af4bbcfd..cf16993a7a7 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 @@ def get_images_for_board( 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 @@ def get_images_for_board( 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 @@ def get_images_for_board( 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 aa27e38d17c..59591161610 100644 --- a/invokeai/app/services/images.py +++ b/invokeai/app/services/images.py @@ -10,6 +10,7 @@ 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 @@ def delete(self, image_name: str): 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 @@ def __init__( 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 @@ def __init__( 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 @@ def create( 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 @@ def create( 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 @@ def update( 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 @@ def update( 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 @@ def get_pil_image(self, image_name: str) -> PILImageType: 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 @@ def get_record(self, image_name: str) -> ImageRecord: 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 @@ def get_dto(self, image_name: str) -> ImageDTO: 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 @@ def get_many( 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 @@ def get_many( 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 @@ def get_many( 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 d971d659168..cc02016cf9e 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 8a94764ab15..b5e00d78014 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 cf16993a7a7..072effbfae2 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 @@ def get_board_for_image( 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 148af50103d..93def0cafd0 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -101,15 +101,15 @@ def create( 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 @@ def update( 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 @@ def get_many( 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 35c4cea9b00..16748570c4b 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 cf541dca013..da3dcb22396 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 9e7d1ab9603..1f84d3be0ea 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 d368d4ab0b8..6fd6ac41bec 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 647477ce395..c1c414ad8bc 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 c1c414ad8bc..adb7791afbf 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 c9ab894ddb3..24e8eb312f7 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 390d43d0333..76ab757e5ef 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 b17f497f6c7..f86415cf379 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 5832cb24b13..ed154b9cd87 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 f1eb844f2cc..5b427b16908 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 2c4cba510a3..62b58641855 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 6fd6ac41bec..055fa8f68b0 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 055fa8f68b0..fdde7528cb7 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 2f1603be82d..73fad134061 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -139,9 +139,10 @@ def add_image_to_board( 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 16748570c4b..bf7a33e8b96 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 20a9683b027..ec56122c9f9 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -263,7 +263,7 @@ def get_many( """--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 7935db58f2c..a6f226fef80 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 @@ 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 ec56122c9f9..15ea9cc5a75 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -5,12 +5,14 @@ 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 @@ def get_many( """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 @@ def get_many( 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 93def0cafd0..9361322e6cc 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -61,6 +61,13 @@ def get_many( """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 @@ def create( 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 @@ def update( 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 @@ def get_many( 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 @@ def get_many( 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 bf7a33e8b96..325ddd094e7 100644 --- a/invokeai/app/services/models/board_record.py +++ b/invokeai/app/services/models/board_record.py @@ -3,6 +3,7 @@ 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 da3dcb22396..d29c1c8a48a 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 8c073e81d69..15fd48fbb29 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 00000000000..0f404cab681 --- /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 4c0c0572425..9792137bbed 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 4032db3159a..a9011f93565 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 d8828fe7362..284e6558ac9 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 1f84d3be0ea..be849e625ef 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 fdde7528cb7..7ae864f55b2 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 9136e23e035..edd4d215afa 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 a5eaeb4c716..169a965be01 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 c591206a279..649cae7682b 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 b21c62785b4..86ec3436f02 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 adb7791afbf..48bd2bde745 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 892516a3cc0..e5cb4cf4a8b 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 82e7a0d6239..b1f06ad4333 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 a8237a711d4..b07ab487ae8 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 1b72f452acd..ee3c29a7974 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 9108e3fd517..bda2b70e758 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 00000000000..09eb061e29e --- /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 4535081e475..03c59dba106 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 | 42 +- .../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, 632 insertions(+), 142 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 8263b48114f..50d80dcf285 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 ce1b515b840..7ff9a5118c4 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 24e8eb312f7..680f9c7041d 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 7cb80128485..22182833b09 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 b8757eff0c1..c3132f02852 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 ea04aa95c85..ec1e87cca76 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 b7092bf7e07..3e40c1211da 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; - - 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; - } - }); - }); + // 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.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 ae78287a7b7..9294e10d32d 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 b8d8896dade..a121875f595 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 f1b62cd997f..5a54bdcd74e 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 be849e625ef..5854c3fe7c1 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 649cae7682b..bff32f1d78e 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 dc4590e6caa..c5a3a1970b9 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 e29b46af70e..cc88328729a 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 fa415074e66..d1f473b8338 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 961ea1b8af5..001fc351382 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 09eb061e29e..9a1521ce5a1 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 50d80dcf285..d01298944b7 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 40ed062353e..fc44d206c80 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 22182833b09..b9ddcea4c34 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 efd490c292a..a9c5f854628 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 cc88328729a..6ebd0148762 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 d1f473b8338..e4b3a9191e3 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 001fc351382..2facb65f045 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 60f8c1b09d3..efeb7789221 100644 --- a/invokeai/app/api/dependencies.py +++ b/invokeai/app/api/dependencies.py @@ -29,7 +29,6 @@ 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 a9c5f854628..00000000000 --- 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 1f2c8327e09..78a6623a166 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 9792137bbed..32bfcbba077 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 7ae864f55b2..71e080ff17b 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 5bb02f60fa8..3049d2c9339 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 ef62245553c..acb7a411f79 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 0a724e2724a..e148954f164 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 156cdc60920..602e7a2ebc1 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 a2d88d19674..40c2ebb1b92 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 6e9ce83aaf8..2e4a83b25f7 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 669a68c88a0..e54b4a8872f 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 3d34fbca9e5..03a00d5b1c9 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 a121875f595..217caf94611 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 284e6558ac9..632cebcb336 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 51a76096788..51e95b64c4d 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 5854c3fe7c1..fb68021deed 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 71e080ff17b..a2c07e48708 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} - /> - - - { - handleUpdateBoardName(nextValue); - }} - > - - + )} + {!(board.cover_image_name && coverImage?.image_url) && ( + + )} + - - - {board.image_count} + > + {board.image_count} + + + {isSelected && } + + + {isDropActive && } + + + + { + handleUpdateBoardName(nextValue); + }} + > + + + + )} diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index bff32f1d78e..49376b4807c 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 48bd2bde745..8648962c8ce 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 00000000000..7038b4b64fa --- /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 e4b3a9191e3..fbfa00e2a14 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 fb68021deed..81ee4be7259 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 680f9c7041d..bef0a2ccdb7 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 11453d97f19..a8c84b81b97 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 73fad134061..45d21520f82 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -178,6 +178,7 @@ def get_images_for_board( 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 bc59c4a27c9..8b5a77da2c7 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -82,6 +82,7 @@ def get_many( 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 @@ def get_many( 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 @@ def get_many( 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 59591161610..542f874f1d7 100644 --- a/invokeai/app/services/images.py +++ b/invokeai/app/services/images.py @@ -102,6 +102,7 @@ def get_many( 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 @@ def get_many( 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 @@ def get_many( 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 40c2ebb1b92..a2d88d19674 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 d0eae92d4b6..bfdef887a00 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 8648962c8ce..bd69425525a 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 images = selectImagesAll(state).filter((i) => { + const isInCategory = categories.includes(i.image_category); + const isInSelectedBoard = selectedBoardId + ? i.board_id === selectedBoardId + : true; + return isInCategory && isInSelectedBoard; + }); - const totalImagesInFilter = selectImagesAll(state).filter((i) => - categories.includes(i.image_category) - ).length; + let queryArg: ReceivedImagesArg = {}; - const response = await ImagesService.listImagesWithMetadata({ - categories, - isIntermediate: false, - offset: totalImagesInFilter, - limit: IMAGES_PER_PAGE, - }); + 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 76ab757e5ef..96c1c055e49 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 81ee4be7259..738693a2781 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 96c1c055e49..8fc9bfa4868 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 8b5a77da2c7..c34d2ca5c82 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -273,7 +273,7 @@ def update( raise ImageRecordSaveException from e finally: self._lock.release() - + def get_many( self, offset: int = 0, @@ -287,11 +287,6 @@ def get_many( 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 @@ def get_many( WHERE 1=1 """ - # images_query = """--sql - # SELECT * FROM images WHERE 1=1 - # """ - query_conditions = "" query_params = [] @@ -320,7 +311,7 @@ def get_many( 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 @@ def get_many( 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 @@ def save( 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 @@ def get_most_recent_image_for_board(self, board_id: str) -> Union[ImageRecord, N 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 15fd48fbb29..4ec185bd834 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 00000000000..3889130a9c6 --- /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 32bfcbba077..224aa0d2aa2 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 bd69425525a..866a68f0b63 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 3889130a9c6..30fb696ac31 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 866a68f0b63..c2f8a304097 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 30fb696ac31..8e4667fd14a 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 - we don't need to do anything + // 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 { 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 85d56d39132..8f01b8d7b8c 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 3049d2c9339..9fe554fee16 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 49376b4807c..5426fee3b1a 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 b07ab487ae8..b7fc0809a65 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 0b2b9f0f580..25a3341532b 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 88da7fbcb45..fe198cf6f9a 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 45d21520f82..ae2f1a98958 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -95,6 +95,8 @@ def _create_tables(self) -> None: 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 9a1521ce5a1..2d42931b0b6 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 8e4667fd14a..eab4389ceb7 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 51e95b64c4d..e506c88e2d3 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 4ec185bd834..cb641d00db7 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 00000000000..40847ade3ae --- /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 86ec3436f02..2482e5a1d0e 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 d29c1c8a48a..f5a856d3d84 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 edd4d215afa..b16bddd6b4b 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 bef0a2ccdb7..c204f0bdfbc 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 03c59dba106..00000000000 --- 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 a6f226fef80..55cd7c8ca2c 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 bda2b70e758..236c765cb98 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 ae2f1a98958..7aff41860cd 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -93,10 +93,10 @@ def _create_tables(self) -> None: 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 325ddd094e7..bf5401b2098 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 ee3c29a7974..bbcc6f1dd6d 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 d75c55010ad..687afbffbdd 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 @@ def convert_if_required( 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 bcf3224ece1..60865817b99 100644 --- a/invokeai/backend/model_management/models/lora.py +++ b/invokeai/backend/model_management/models/lora.py @@ -15,7 +15,7 @@ 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 bd519c88c80..9856069ea54 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -22,12 +22,12 @@ 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 @@ def convert_if_required( ) -> 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 @@ def convert_if_required( 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 @@ def convert_if_required( ) -> 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 66847f53ebd..453a8ad671b 100644 --- a/invokeai/backend/model_management/models/textual_inversion.py +++ b/invokeai/backend/model_management/models/textual_inversion.py @@ -15,7 +15,7 @@ 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 1edb57ccc4b..f2856483231 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 acb7a411f79..f3aec17eb67 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 e9671a918ff..b7c1c881872 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 b81146d3ab7..fd26ed49e0f 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 d5203867acf..3f1a5b3d46a 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 cfa43577256..464474736f3 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 f9ef2cc683a..f267fe85d99 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 f190ab70733..a0fe38613cf 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 a5976242ea8..62c785f3748 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 e0a609ec12c..25167433d44 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 dd381ef22c2..642e11023b1 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 94f1fcb2829..b42ab73c748 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 7e53a63b423..da227dd4f91 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 e3f67ec9be4..43f6100db8e 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 0479684d2ca..8de332b82d8 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 42268b8295c..31e22a1bbab 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 00000000000..109fc39b7d5 --- /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 a3cc5530c17..625d8f670df 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 0b0f52b8feb..80976f126f3 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 874df93c30d..4f1c33483e6 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 4e722ddb80a..ea175e351a2 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 fd5b3475aee..5b37dea7105 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 f7323a489bf..79dc958d0fa 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 bba275cb26f..e72108f74a2 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 ecbddccd76f..ab4c6d354b8 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 a3f08247a45..fb9e4164e6f 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 e0fd4a1caaa..a9e67d81354 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 2331936b30b..db2784ed9fa 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 e148954f164..3f51247701b 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 602e7a2ebc1..24ec8c9d6d9 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 8512faae748..a7e3d6c9485 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 af0aae3edb5..219a4a675dc 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 5e13adc4e54..69908c3bbaf 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 1132012c5a2..387e8c8634b 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 3ba86d8faba..6466efcd824 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 47bfd4110fd..d6abae5eae6 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 4bd59d03b0d..d303c7ce794 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 5207ebbf6d9..e29e177aa79 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 4e273e88548..1e0ea0648fe 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 baf3bf2b54e..c4c67a06744 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 0347d4dc38f..63220c8047b 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 388c86061c1..444c7e64673 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 0b2af787993..8aecdddc4f0 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 751ee491586..724061fce8a 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 d7db0c11de7..c0306329269 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 c883b9a5d8e..5af28452b6a 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 0d995c4e689..e0058a78cad 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 e597cd907d5..209b6d8e880 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 3b096c83b76..f277516ccd9 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 bf4da28a4af..709e1cc66a9 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 00000000000..faa9d164a8d --- /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 ace0ed8e3c3..65df90351aa 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 1e0ff322e8d..ad45d2047ed 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 3e637b299c8..6d60bbe2261 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 325bfe2080c..bf6e8012b7a 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 dfb1cbc61d7..551e00da16e 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 8fb9ad3d540..5e7d3f4b921 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 93a115f980a..1e60ee80092 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 eeea6c68b46..58655d08587 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 15bf92dfeac..b6a70156c3c 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 ce8d9f8c4b9..2eeffd05fde 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 bc6a525f7c4..e7446e9cb36 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 3e9c2f60e4b..edf388dbf6d 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 865eeff5549..1235962d8f1 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 4273115963f..4f0f49d9612 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 5d239536d5a..7c655480c78 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 17720e689bd..af3a527f3a4 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 f20d983f9b5..469e7200ad8 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 1a575d41472..d4499530aca 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 b93281c5a74..b448a7a8ad8 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 1fed1ebc587..54e02d49e5a 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 00000000000..d5b3a02eff9 --- /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 e3693f6d984..a031c0e05f5 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 d4594fe6e9a..05d2b36d589 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 aa7b966b4b3..76e89422e9e 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 bd274228db6..14cf26f0759 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 0e81c9a4b81..b2a15b58614 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 e87799d1425..d11bb523c75 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 5b5b51e71f3..2599d650cb3 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 a2d88d19674..bd6e8bf4da2 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 9fd716f33d7..6a3b17feeab 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 239a24bfe51..22846f53459 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 f1832d7aa20..cb1b13ef253 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 400068171ea..29fcebf567b 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 3408bea6dbf..2b22b247b4d 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 982ce8ade77..80c136546da 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 dd9f50cd4a2..cb5914f6777 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 87c01f847f7..4e9087237b2 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 7a45d0a0ac7..f47c3b8f010 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 91c9dc0ce53..96433218f78 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 4444ab4d330..ffab4ca09a0 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 5bca3f30378..f9dcfd1b081 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 a2f7c2f02a4..c4fa84cc8ed 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 925511578d2..5625324a1e1 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 3681602a959..5292d321562 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 7dfac68d392..d97826099a8 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 9a7b6c61e4b..500514f3c96 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 0bacb5d8057..5cfc165e232 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 9a8a23077aa..0f287be7bb3 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 f477c11a8dd..5afc63a387a 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 506b21e5402..a65308dcba1 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 1b730555847..c6bceda6510 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 00000000000..e9ed1bbfc29 --- /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 00000000000..db51f6c7b90 --- /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 00000000000..74a341d861e --- /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 00000000000..1ac441b2a6a --- /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 2cff38b3e5b..dca4fa8e82f 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 23334bd8910..a1b8ca5628a 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 00000000000..26d61175738 --- /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 cf8229b1f7f..b0b8ec5fc14 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 00000000000..34ef4791bc4 --- /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 ad3b1ddb5b2..f0f247c860f 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 d0aca63964e..3b42906e39f 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 00000000000..ffaba2f808a --- /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 bfe2793887e..8d3b6f4e53c 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 0e233626c6f..cb6e33c199f 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 14e1fdecd0c..92697e1d741 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 6caded8f044..0dbc99c9e3f 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 bfdef887a00..f372d4fa873 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 54580ce2048..248f4a352e8 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 2e4a83b25f7..937cff9c05a 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 9856069ea54..0ac88c8a943 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -107,7 +107,7 @@ def convert_if_required( ) -> 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 @@ def convert_if_required( ) -> 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 f510279f183..0abcc19dcfa 100644 --- a/invokeai/app/api/routers/models.py +++ b/invokeai/app/api/routers/models.py @@ -7,8 +7,8 @@ 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 40995498bf4..eff71798a5f 100644 --- a/invokeai/backend/model_management/models/__init__.py +++ b/invokeai/backend/model_management/models/__init__.py @@ -29,10 +29,22 @@ #}, } -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 687afbffbdd..de9926c83e6 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 60865817b99..bcf3224ece1 100644 --- a/invokeai/backend/model_management/models/lora.py +++ b/invokeai/backend/model_management/models/lora.py @@ -15,7 +15,7 @@ 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 0ac88c8a943..20aaae23a60 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -22,12 +22,12 @@ 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 @@ def convert_if_required( ) -> 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 @@ def convert_if_required( 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 @@ def convert_if_required( ) -> 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 453a8ad671b..66847f53ebd 100644 --- a/invokeai/backend/model_management/models/textual_inversion.py +++ b/invokeai/backend/model_management/models/textual_inversion.py @@ -15,7 +15,7 @@ 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 f2856483231..b78617869a7 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 f3aec17eb67..a738a9aafd5 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 d5b3a02eff9..a62d1a3f4b2 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 bd6e8bf4da2..db41b330485 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 e9ed1bbfc29..7b3f90cd0aa 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 db51f6c7b90..ec634b9692b 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 74a341d861e..bcf9801314d 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 1ac441b2a6a..3f3142dae39 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 ffaba2f808a..8d0d2e83044 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 5025ca081ad..e498ecb7498 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 c6af5f36121..93cc19f8329 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 9fe554fee16..b257b470bd4 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 a9011f93565..4ecc9eb9bf0 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 a1ef69de01b..d3d37765f4d 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 2facb65f045..39f38e386cc 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 a38ab150ddf..a65c8501dc0 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 ed384258720..00000000000 --- 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 00000000000..9f62fde2643 --- /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 00000000000..e8e1f5bedfe --- /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 aa9fb057e1c..7b0d78d37e6 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 f86415cf379..fd9b8a0a08b 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 97f2bd80165..4d134439f7b 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 cbb319c6ea6..e6cab04da73 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -530,6 +530,8 @@ def list_models( 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 @@ def commit(self, conf_file: Path=None) -> None: 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 eff71798a5f..b22075991e3 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 3bf0045918f..ac43b938b0e 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 109fc39b7d5..e4f77ba7bf7 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 a62d1a3f4b2..d300e38fd02 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 db41b330485..42d0ddd8f6b 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 7b3f90cd0aa..c9708a0b6f1 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 ec634b9692b..4b6f834216d 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 bcf9801314d..27b68797030 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 3f3142dae39..a2b66d7157d 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 34ef4791bc4..7abfbec0813 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 8d0d2e83044..ad7f70c3d47 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 ac43b938b0e..f32e658aa1b 100644 --- a/invokeai/backend/model_management/models/base.py +++ b/invokeai/backend/model_management/models/base.py @@ -94,6 +94,11 @@ def __init__( 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 39f38e386cc..96a6070ad21 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 a65c8501dc0..bf0775d52e5 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 d3d37765f4d..3842e8da3a7 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 bf0775d52e5..43de1449910 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 f857bc85bcf..6e101da5f5e 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 93cc19f8329..dc1c25c0153 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 b257b470bd4..a88576565ae 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 4ecc9eb9bf0..8489de85f04 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 3842e8da3a7..480c8591bb5 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 43de1449910..813bd9fb705 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 6e101da5f5e..b63c6d256cd 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 sd1PipelineModels = selectAllSD1PipelineModels(state); + const sd2PipelineModels = selectAllSD2PipelineModels(state); - const sd1Models = selectAllSD1Models(state); - const sd2Models = selectAllSD2Models(state); + const allPipelineModels = sd1PipelineModels.concat(sd2PipelineModels); - const sd1ModelDropDownData = selectAllSD1Models(state) + 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 9f62fde2643..00000000000 --- 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 00000000000..5755b14886c --- /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 e8e1f5bedfe..00000000000 --- 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 00000000000..0c307e23cc7 --- /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 7b0d78d37e6..417a399cf22 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 4d134439f7b..039748fa3f5 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 96a6070ad21..d93552809d1 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 2e0b3234c75..a27db43e6b7 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 a88576565ae..0893066f1f5 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 d93552809d1..946e9084d84 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 5755b14886c..8c8fbbd4f28 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 0c307e23cc7..fb94fae9d04 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 fd9b8a0a08b..8a148ca38b8 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 039748fa3f5..05766de7b37 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 dc1c25c0153..649b56316da 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 8c8fbbd4f28..a59c29d87ad 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 fb94fae9d04..8e10767a7c0 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 a59c29d87ad..99f1514e6c6 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 8e10767a7c0..69ff7722221 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 05766de7b37..619aa4b7b23 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 946e9084d84..e1de166b5c3 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 a27db43e6b7..26c11604e10 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 65da89b94d9..5092893eedb 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 ffecbf2ffa0..318aecbc615 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 c39b3bed81c..82db58bd355 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 2a7a05bbd21..2fdbd1a769a 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 adfb8ab2883..952e0a50668 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 c212ff6a726..8956b551394 100644 --- a/invokeai/app/services/model_manager_service.py +++ b/invokeai/app/services/model_manager_service.py @@ -69,19 +69,6 @@ def model_exists( ) -> 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 @@ def model_exists( 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 e6cab04da73..37798aaf6f1 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -445,38 +445,6 @@ def get_model( _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 f32e658aa1b..e57ba81c3c8 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 37798aaf6f1..9a8c7e64c64 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -266,6 +266,8 @@ def __init__( 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 @@ def commit(self, conf_file: Path=None) -> None: 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 e57ba81c3c8..06cc3db50f4 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 @@ def _get_configs(cls): 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 @@ def _get_configs(cls): @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 de9926c83e6..e452621ebaa 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 bcf3224ece1..2e4309a1610 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 20aaae23a60..50089b33385 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -23,12 +23,12 @@ 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 @@ def probe_config(cls, path: str, **kwargs): 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 @@ def probe_config(cls, path: str, **kwargs): 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 66847f53ebd..9a032218f03 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 b78617869a7..e86bc00ecda 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 06cc3db50f4..ef354ecc076 100644 --- a/invokeai/backend/model_management/models/base.py +++ b/invokeai/backend/model_management/models/base.py @@ -125,30 +125,24 @@ def _get_configs(cls): continue fields = inspect.get_annotations(value) - if "model_format" not in fields: - raise Exception("Invalid config definition - model_format field not found") - - 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']}") + try: + field = fields["model_format"] + except: + raise Exception(f"Invalid config definition - format field not found({cls.__qualname__})") - 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 isinstance(field, type) and issubclass(field, str) and issubclass(field, Enum): + for model_format in field: + configs[model_format.value] = value - if format_type == Union: - f_fields = fields["model_format"].__args__ - else: - f_fields = (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 - 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)? + elif field is None: + configs[None] = value + else: + 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 e452621ebaa..9563f87afd2 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 @@ 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 @@ def save_to_config(cls) -> bool: @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 @@ def convert_if_required( 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 2e4309a1610..59feacde065 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 @@ # 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 @@ def save_to_config(cls) -> bool: @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 @@ def convert_if_required( 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 50089b33385..f1693265715 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 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 @@ def __init__(self, model_path: str, base_model: BaseModelType, model_type: Model 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 @@ def probe_config(cls, path: str, **kwargs): 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 @@ def save_to_config(cls) -> bool: @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 @@ def convert_if_required( 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 @@ def __init__(self, model_path: str, base_model: BaseModelType, model_type: Model 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 @@ def probe_config(cls, path: str, **kwargs): 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 @@ def save_to_config(cls) -> bool: @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 e86bc00ecda..76133b074de 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 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 @@ def save_to_config(cls) -> bool: @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 @@ def convert_if_required( 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 22b4efec747..e14c58bab76 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 b22075991e3..6975d45f93b 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 @@ #}, } -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 0abcc19dcfa..50d645eb572 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 8956b551394..8b46b17ad04 100644 --- a/invokeai/app/services/model_manager_service.py +++ b/invokeai/app/services/model_manager_service.py @@ -5,7 +5,7 @@ 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 @@ def list_models( 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 9a8c7e64c64..f9a66a87ddb 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -473,9 +473,9 @@ def list_models( 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 @@ def list_models( 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 @@ def list_models( 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 9d77cadf8c5..48b15c2e4e8 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 a738a9aafd5..3c143ecf6e9 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 b7c1c881872..e9671a918ff 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 fd26ed49e0f..b81146d3ab7 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 3f1a5b3d46a..d5203867acf 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 464474736f3..cfa43577256 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 f267fe85d99..f9ef2cc683a 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 a0fe38613cf..f190ab70733 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 62c785f3748..a5976242ea8 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 25167433d44..e0a609ec12c 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 642e11023b1..dd381ef22c2 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 b42ab73c748..94f1fcb2829 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 da227dd4f91..7e53a63b423 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 43f6100db8e..e3f67ec9be4 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 8de332b82d8..0479684d2ca 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 31e22a1bbab..42268b8295c 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 e4f77ba7bf7..60e2958f5c3 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 00000000000..500b3e8f8c4 --- /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 625d8f670df..a3cc5530c17 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 80976f126f3..0b0f52b8feb 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 4f1c33483e6..874df93c30d 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 ea175e351a2..4e722ddb80a 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 5b37dea7105..fd5b3475aee 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 79dc958d0fa..f7323a489bf 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 e72108f74a2..bba275cb26f 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 ab4c6d354b8..ecbddccd76f 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 fb9e4164e6f..a3f08247a45 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 a9e67d81354..e0fd4a1caaa 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 db2784ed9fa..2331936b30b 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 3f51247701b..7976c08abbf 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 24ec8c9d6d9..602e7a2ebc1 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 a7e3d6c9485..8512faae748 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 219a4a675dc..af0aae3edb5 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 69908c3bbaf..5e13adc4e54 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 387e8c8634b..1132012c5a2 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 6466efcd824..3ba86d8faba 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 d6abae5eae6..47bfd4110fd 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 d303c7ce794..4bd59d03b0d 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 e29e177aa79..5207ebbf6d9 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 1e0ea0648fe..4e273e88548 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 c4c67a06744..baf3bf2b54e 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 63220c8047b..0347d4dc38f 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 444c7e64673..388c86061c1 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 8aecdddc4f0..0b2af787993 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 724061fce8a..751ee491586 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 c0306329269..d7db0c11de7 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 5af28452b6a..c883b9a5d8e 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 e0058a78cad..0d995c4e689 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 209b6d8e880..e597cd907d5 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 f277516ccd9..3b096c83b76 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 709e1cc66a9..bf4da28a4af 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 faa9d164a8d..00000000000 --- 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 65df90351aa..ace0ed8e3c3 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 ad45d2047ed..1e0ff322e8d 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 6d60bbe2261..3e637b299c8 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 bf6e8012b7a..325bfe2080c 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 551e00da16e..dfb1cbc61d7 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 5e7d3f4b921..8fb9ad3d540 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 1e60ee80092..93a115f980a 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 58655d08587..eeea6c68b46 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 b6a70156c3c..15bf92dfeac 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 2eeffd05fde..ce8d9f8c4b9 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 e7446e9cb36..bc6a525f7c4 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 edf388dbf6d..3e9c2f60e4b 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 1235962d8f1..865eeff5549 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 4f0f49d9612..4273115963f 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 7c655480c78..5d239536d5a 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 af3a527f3a4..17720e689bd 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 d300e38fd02..184a2661694 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 00000000000..829f8a7c576 --- /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 469e7200ad8..f20d983f9b5 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 d4499530aca..1a575d41472 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 b448a7a8ad8..b93281c5a74 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 54e02d49e5a..1fed1ebc587 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 a031c0e05f5..e3693f6d984 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 05d2b36d589..d4594fe6e9a 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 76e89422e9e..aa7b966b4b3 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 14cf26f0759..bd274228db6 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 b2a15b58614..0e81c9a4b81 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 d11bb523c75..e87799d1425 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 2599d650cb3..5b5b51e71f3 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 42d0ddd8f6b..01575b990c3 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 6a3b17feeab..9fd716f33d7 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 22846f53459..239a24bfe51 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 cb1b13ef253..f1832d7aa20 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 29fcebf567b..400068171ea 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 2b22b247b4d..3408bea6dbf 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 80c136546da..982ce8ade77 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 cb5914f6777..dd9f50cd4a2 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 4e9087237b2..87c01f847f7 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 f47c3b8f010..7a45d0a0ac7 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 96433218f78..91c9dc0ce53 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 00000000000..c2f1c07fbfc --- /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 ffab4ca09a0..4444ab4d330 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 f9dcfd1b081..5bca3f30378 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 c4fa84cc8ed..a2f7c2f02a4 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 5625324a1e1..925511578d2 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 5292d321562..3681602a959 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 d97826099a8..7dfac68d392 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 500514f3c96..9a7b6c61e4b 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 5cfc165e232..0bacb5d8057 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 0f287be7bb3..9a8a23077aa 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 5afc63a387a..f477c11a8dd 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 00000000000..3086c59cf0f --- /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 a65308dcba1..506b21e5402 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 c6bceda6510..1b730555847 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 c9708a0b6f1..be7077cc535 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 4b6f834216d..befe0146056 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 00000000000..01b50c2fc09 --- /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 27b68797030..dadd7cac9bb 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 a2b66d7157d..1e4a34c5dce 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 00000000000..7e7b8952310 --- /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 dca4fa8e82f..2cff38b3e5b 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 a1b8ca5628a..23334bd8910 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 26d61175738..00000000000 --- 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 b0b8ec5fc14..cf8229b1f7f 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 7abfbec0813..97d6aa7ffad 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 f0f247c860f..ad3b1ddb5b2 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 3b42906e39f..d0aca63964e 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 8d3b6f4e53c..bfe2793887e 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 ad7f70c3d47..a73ee0aa32a 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 00000000000..497f81d16f9 --- /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 cb6e33c199f..0e233626c6f 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 92697e1d741..14e1fdecd0c 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 0dbc99c9e3f..6caded8f044 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 f8decdb3417..00000000000 --- 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 614749a2c54..00000000000 --- 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 6bdcb87dd49..00000000000 --- 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 c88e0421783..00000000000 --- 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 ec2ae4a8459..00000000000 --- 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 67b897d9d97..00000000000 --- 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 f23d5002e3a..00000000000 --- 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 d9314a6063f..00000000000 --- 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 f372d4fa873..bfdef887a00 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 248f4a352e8..54580ce2048 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 937cff9c05a..2c6ca913191 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 48b15c2e4e8..b77aa5dafd4 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 @@ def invoke(self, context: InvocationContext) -> ModelLoaderOutput: ) ) -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 3c143ecf6e9..8ce42494e53 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 7976c08abbf..5fba3d83115 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 01575b990c3..9186db3e295 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 3086c59cf0f..b8cdb27acfe 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 9a8a23077aa..00000000000 --- 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 f477c11a8dd..00000000000 --- 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 2c6ca913191..51a36caad1d 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 a11d8d048cb..55fcc977459 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 e498ecb7498..cb18d483014 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 649b56316da..8f40b0bb59a 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 0893066f1f5..bf54e638364 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 8489de85f04..57a97168a38 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 480c8591bb5..c274f23f260 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 5425d1cfd52..341f0c467b6 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 efaeaddff20..ccdc3e0a277 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 785e1d2fdbf..9ffe85b3c91 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 ca0e56e849f..920cb5bf025 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 78a6623a166..8425ac043a0 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 c179a89504a..973acdfb77e 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 6a700d48132..072b1a53fd3 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 39e0080d112..7d4469bc411 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 00000000000..bbcd8d9bc6d --- /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 e1de166b5c3..e7dcbf0d839 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 61567d3fb8f..48eb309e7d6 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 813bd9fb705..43de14d507c 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 193420e29cd..8ba5731a5b2 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 b63c6d256cd..00000000000 --- 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 99f1514e6c6..00000000000 --- 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 69ff7722221..00000000000 --- 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 417a399cf22..00000000000 --- 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 8a148ca38b8..688f69c1f71 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 2d42931b0b6..e2d765dd90e 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 619aa4b7b23..00000000000 --- 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; - } -);