From 9d4b84ef687cc10335ffe74f7b541583e431f4f7 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 16 Jun 2023 23:57:57 +1200 Subject: [PATCH 001/110] feat: Upgrade to Diffusers 0.17.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 70a87359a4..8707c6c911 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ dependencies = [ "controlnet-aux>=0.0.4", "timm==0.6.13", # needed to override timm latest in controlnet_aux, see https://github.com/isl-org/ZoeDepth/issues/26 "datasets", - "diffusers[torch]~=0.17.0", + "diffusers[torch]~=0.17.1", "dnspython==2.2.1", "dynamicprompts", "easing-functions", From 469dae8c88a9bb770ea1d2611d2845849eb0731c Mon Sep 17 00:00:00 2001 From: Stephan Koglin-Fischer <81704844+skf-funzt@users.noreply.github.com> Date: Fri, 16 Jun 2023 15:18:23 +0200 Subject: [PATCH 002/110] fix(linux): installer script prints maximum python version usable --- installer/install.sh.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer/install.sh.in b/installer/install.sh.in index c1014b2496..74aa092873 100755 --- a/installer/install.sh.in +++ b/installer/install.sh.in @@ -25,7 +25,7 @@ done if [ -z "$PYTHON" ]; then echo "A suitable Python interpreter could not be found" - echo "Please install Python 3.9 or higher before running this script. See instructions at $INSTRUCTIONS for help." + echo "Please install Python $MINIMUM_PYTHON_VERSION or higher (maximum $MAXIMUM_PYTHON_VERSION) before running this script. See instructions at $INSTRUCTIONS for help." read -p "Press any key to exit" exit -1 fi From f3d9797ebeb924069827ad5b607c672d551f36b5 Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Sun, 18 Jun 2023 23:38:15 +0300 Subject: [PATCH 003/110] Add dpmpp_sde and dpmpp_2m_sde schedulers(with karras) --- invokeai/app/invocations/latent.py | 13 +++++++++++-- invokeai/backend/install/legacy_arg_parsing.py | 4 ++++ .../stable_diffusion/schedulers/schedulers.py | 6 +++++- invokeai/backend/web/modules/parameters.py | 4 ++++ invokeai/frontend/web/src/app/constants.ts | 8 ++++++++ .../src/services/api/models/InpaintInvocation.ts | 2 +- .../api/models/LatentsToLatentsInvocation.ts | 2 +- .../services/api/models/TextToLatentsInvocation.ts | 2 +- 8 files changed, 35 insertions(+), 6 deletions(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index cf216e6c54..1a448edca8 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -7,7 +7,7 @@ import einops from pydantic import BaseModel, Field, validator import torch -from diffusers import ControlNetModel +from diffusers import ControlNetModel, DPMSolverMultistepScheduler from diffusers.image_processor import VaeImageProcessor from diffusers.schedulers import SchedulerMixin as Scheduler @@ -222,6 +222,15 @@ class TextToLatentsInvocation(BaseInvocation): c, extra_conditioning_info = context.services.latents.get(self.positive_conditioning.conditioning_name) uc, _ = context.services.latents.get(self.negative_conditioning.conditioning_name) + custom_args = dict( + eta=0.0, #ddim_eta + ) + + if type(scheduler) is DPMSolverMultistepScheduler and scheduler.config.algorithm_type in ["sde-dpmsolver", "sde-dpmsolver++"]: + custom_args.update( + generator=torch.Generator(device=uc.device).manual_seed(0), + ) + conditioning_data = ConditioningData( unconditioned_embeddings=uc, text_embeddings=c, @@ -233,7 +242,7 @@ class TextToLatentsInvocation(BaseInvocation): h_symmetry_time_pct=None,#h_symmetry_time_pct, v_symmetry_time_pct=None#v_symmetry_time_pct, ), - ).add_scheduler_args_if_applicable(scheduler, eta=0.0)#ddim_eta) + ).add_scheduler_args_if_applicable(scheduler, **custom_args) return conditioning_data def create_pipeline(self, unet, scheduler) -> StableDiffusionGeneratorPipeline: diff --git a/invokeai/backend/install/legacy_arg_parsing.py b/invokeai/backend/install/legacy_arg_parsing.py index b4f3ab1186..4a58ff8336 100644 --- a/invokeai/backend/install/legacy_arg_parsing.py +++ b/invokeai/backend/install/legacy_arg_parsing.py @@ -22,6 +22,10 @@ SAMPLER_CHOICES = [ "dpmpp_2s_k", "dpmpp_2m", "dpmpp_2m_k", + "dpmpp_2m_sde", + "dpmpp_2m_sde_k", + "dpmpp_sde", + "dpmpp_sde_k", "unipc", ] diff --git a/invokeai/backend/stable_diffusion/schedulers/schedulers.py b/invokeai/backend/stable_diffusion/schedulers/schedulers.py index d8da143962..77c45d5eb8 100644 --- a/invokeai/backend/stable_diffusion/schedulers/schedulers.py +++ b/invokeai/backend/stable_diffusion/schedulers/schedulers.py @@ -1,7 +1,7 @@ from diffusers import DDIMScheduler, DPMSolverMultistepScheduler, KDPM2DiscreteScheduler, \ KDPM2AncestralDiscreteScheduler, EulerDiscreteScheduler, EulerAncestralDiscreteScheduler, \ HeunDiscreteScheduler, LMSDiscreteScheduler, PNDMScheduler, UniPCMultistepScheduler, \ - DPMSolverSinglestepScheduler, DEISMultistepScheduler, DDPMScheduler + DPMSolverSinglestepScheduler, DEISMultistepScheduler, DDPMScheduler, DPMSolverSDEScheduler SCHEDULER_MAP = dict( ddim=(DDIMScheduler, dict()), @@ -21,5 +21,9 @@ SCHEDULER_MAP = dict( dpmpp_2s_k=(DPMSolverSinglestepScheduler, dict(use_karras_sigmas=True)), dpmpp_2m=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=False)), dpmpp_2m_k=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=True)), + dpmpp_2m_sde=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=False, algorithm_type='sde-dpmsolver++')), + dpmpp_2m_sde_k=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=True, algorithm_type='sde-dpmsolver++')), + dpmpp_sde=(DPMSolverSDEScheduler, dict(use_karras_sigmas=False, noise_sampler_seed=0)), + dpmpp_sde_k=(DPMSolverSDEScheduler, dict(use_karras_sigmas=True, noise_sampler_seed=0)), unipc=(UniPCMultistepScheduler, dict(cpu_only=True)) ) diff --git a/invokeai/backend/web/modules/parameters.py b/invokeai/backend/web/modules/parameters.py index 9b00093a44..440f21a947 100644 --- a/invokeai/backend/web/modules/parameters.py +++ b/invokeai/backend/web/modules/parameters.py @@ -20,6 +20,10 @@ SAMPLER_CHOICES = [ "dpmpp_2s_k", "dpmpp_2m", "dpmpp_2m_k", + "dpmpp_2m_sde", + "dpmpp_2m_sde_k", + "dpmpp_sde", + "dpmpp_sde_k", "unipc", ] diff --git a/invokeai/frontend/web/src/app/constants.ts b/invokeai/frontend/web/src/app/constants.ts index db5fea4a66..5fd413d915 100644 --- a/invokeai/frontend/web/src/app/constants.ts +++ b/invokeai/frontend/web/src/app/constants.ts @@ -9,6 +9,8 @@ export const SCHEDULER_NAMES_AS_CONST = [ 'ddpm', 'dpmpp_2s', 'dpmpp_2m', + 'dpmpp_2m_sde', + 'dpmpp_sde', 'heun', 'kdpm_2', 'lms', @@ -17,6 +19,8 @@ export const SCHEDULER_NAMES_AS_CONST = [ 'euler_k', 'dpmpp_2s_k', 'dpmpp_2m_k', + 'dpmpp_2m_sde_k', + 'dpmpp_sde_k', 'heun_k', 'lms_k', 'euler_a', @@ -32,16 +36,20 @@ export const SCHEDULER_LABEL_MAP: Record = { deis: 'DEIS', ddim: 'DDIM', ddpm: 'DDPM', + dpmpp_sde: 'DPM++ SDE', dpmpp_2s: 'DPM++ 2S', dpmpp_2m: 'DPM++ 2M', + dpmpp_2m_sde: 'DPM++ 2M SDE', heun: 'Heun', kdpm_2: 'KDPM 2', lms: 'LMS', pndm: 'PNDM', unipc: 'UniPC', euler_k: 'Euler Karras', + dpmpp_sde_k: 'DPM++ SDE Karras', dpmpp_2s_k: 'DPM++ 2S Karras', dpmpp_2m_k: 'DPM++ 2M Karras', + dpmpp_2m_sde_k: 'DPM++ 2M SDE Karras', heun_k: 'Heun Karras', lms_k: 'LMS Karras', euler_a: 'Euler Ancestral', diff --git a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts index 7eb0039c87..6527508237 100644 --- a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts @@ -45,7 +45,7 @@ export type InpaintInvocation = { /** * The scheduler to use */ - scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'lms_k' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2s_k' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; + scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'lms_k' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2s_k' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'dpmpp_2m_sde' | 'dpmpp_2m_sde_k' | 'dpmpp_sde' | 'dpmpp_sde_k' | 'unipc'; /** * The model to use (currently ignored) */ diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts index 174d368178..d5f2915bf2 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts @@ -42,7 +42,7 @@ export type LatentsToLatentsInvocation = { /** * The scheduler to use */ - scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'lms_k' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2s_k' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; + scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'lms_k' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2s_k' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'dpmpp_2m_sde' | 'dpmpp_2m_sde_k' | 'dpmpp_sde' | 'dpmpp_sde_k' | 'unipc'; /** * The model to use (currently ignored) */ diff --git a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts index 117533f106..513acff08d 100644 --- a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts @@ -42,7 +42,7 @@ export type TextToLatentsInvocation = { /** * The scheduler to use */ - scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'lms_k' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2s_k' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; + scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'lms_k' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2s_k' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'dpmpp_2m_sde' | 'dpmpp_2m_sde_k' | 'dpmpp_sde' | 'dpmpp_sde_k' | 'unipc'; /** * The model to use (currently ignored) */ From 9b324077442f53f3ff646865cd6e657576bcac67 Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Mon, 19 Jun 2023 00:34:01 +0300 Subject: [PATCH 004/110] Provide generator to all schedulers step function to make both ancestral and sde schedulers reproducible --- invokeai/app/invocations/latent.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 1a448edca8..63db3d925c 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -222,15 +222,6 @@ class TextToLatentsInvocation(BaseInvocation): c, extra_conditioning_info = context.services.latents.get(self.positive_conditioning.conditioning_name) uc, _ = context.services.latents.get(self.negative_conditioning.conditioning_name) - custom_args = dict( - eta=0.0, #ddim_eta - ) - - if type(scheduler) is DPMSolverMultistepScheduler and scheduler.config.algorithm_type in ["sde-dpmsolver", "sde-dpmsolver++"]: - custom_args.update( - generator=torch.Generator(device=uc.device).manual_seed(0), - ) - conditioning_data = ConditioningData( unconditioned_embeddings=uc, text_embeddings=c, @@ -242,7 +233,17 @@ class TextToLatentsInvocation(BaseInvocation): h_symmetry_time_pct=None,#h_symmetry_time_pct, v_symmetry_time_pct=None#v_symmetry_time_pct, ), - ).add_scheduler_args_if_applicable(scheduler, **custom_args) + ) + + conditioning_data = conditioning_data.add_scheduler_args_if_applicable( + scheduler, + + # for ddim scheduler + eta=0.0, #ddim_eta + + # for ancestral and sde schedulers + generator=torch.Generator(device=uc.device).manual_seed(0), + ) return conditioning_data def create_pipeline(self, unet, scheduler) -> StableDiffusionGeneratorPipeline: From c26e1a9271f800b5f332066f29d9867ee6aabcdd Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Fri, 16 Jun 2023 23:17:18 +0300 Subject: [PATCH 005/110] Rewrite inpaint node to new model manager, remove TextToImage and ImageToImage nodes --- invokeai/app/invocations/generate.py | 305 ++++++++---------- .../stable_diffusion/diffusers_pipeline.py | 3 +- 2 files changed, 133 insertions(+), 175 deletions(-) diff --git a/invokeai/app/invocations/generate.py b/invokeai/app/invocations/generate.py index 21574c7323..83220d89ef 100644 --- a/invokeai/app/invocations/generate.py +++ b/invokeai/app/invocations/generate.py @@ -18,6 +18,12 @@ from ..util.step_callback import stable_diffusion_step_callback from .baseinvocation import BaseInvocation, InvocationConfig, InvocationContext from .image import ImageOutput +import re +from ...backend.model_management.lora import ModelPatcher +from ...backend.stable_diffusion.diffusers_pipeline import StableDiffusionGeneratorPipeline +from .model import UNetField, ClipField, VaeField +from contextlib import contextmanager, ExitStack, ContextDecorator + SAMPLER_NAME_VALUES = Literal[tuple(InvokeAIGenerator.schedulers())] INFILL_METHODS = Literal[tuple(infill_methods())] DEFAULT_INFILL_METHOD = ( @@ -25,30 +31,38 @@ DEFAULT_INFILL_METHOD = ( ) -class SDImageInvocation(BaseModel): - """Helper class to provide all Stable Diffusion raster image invocations with additional config""" +from .latent import get_scheduler - # Schema customisation - class Config(InvocationConfig): - schema_extra = { - "ui": { - "tags": ["stable-diffusion", "image"], - "type_hints": { - "model": "model", - }, - }, - } +class OldModelContext(ContextDecorator): + model: StableDiffusionGeneratorPipeline + + def __init__(self, model): + self.model = model + + def __enter__(self): + return self.model + + def __exit__(self, *exc): + return False + +class OldModelInfo: + name: str + hash: str + context: OldModelContext + + def __init__(self, name: str, hash: str, model: StableDiffusionGeneratorPipeline): + self.name = name + self.hash = hash + self.context = OldModelContext( + model=model, + ) -# Text to image -class TextToImageInvocation(BaseInvocation, SDImageInvocation): - """Generates an image using text2img.""" +class InpaintInvocation(BaseInvocation): + """Generates an image using inpaint.""" - type: Literal["txt2img"] = "txt2img" + type: Literal["inpaint"] = "inpaint" - # Inputs - # TODO: consider making prompt optional to enable providing prompt through a link - # fmt: off prompt: Optional[str] = Field(description="The prompt to generate an image from") seed: int = Field(ge=0, le=SEED_MAX, description="The seed to use (omit for random)", default_factory=get_random_seed) steps: int = Field(default=30, gt=0, description="The number of steps to use to generate the image") @@ -56,83 +70,13 @@ class TextToImageInvocation(BaseInvocation, SDImageInvocation): height: int = Field(default=512, multiple_of=8, gt=0, description="The height of the resulting image", ) cfg_scale: float = Field(default=7.5, ge=1, description="The Classifier-Free Guidance, higher values may result in a result closer to the prompt", ) scheduler: SAMPLER_NAME_VALUES = Field(default="euler", description="The scheduler to use" ) - model: str = Field(default="", description="The model to use (currently ignored)") - progress_images: bool = Field(default=False, description="Whether or not to produce progress images during generation", ) - control_model: Optional[str] = Field(default=None, description="The control model to use") - control_image: Optional[ImageField] = Field(default=None, description="The processed control image") - # fmt: on - - # TODO: pass this an emitter method or something? or a session for dispatching? - def dispatch_progress( - self, - context: InvocationContext, - source_node_id: str, - intermediate_state: PipelineIntermediateState, - ) -> None: - stable_diffusion_step_callback( - context=context, - intermediate_state=intermediate_state, - node=self.dict(), - source_node_id=source_node_id, - ) - - def invoke(self, context: InvocationContext) -> ImageOutput: - # Handle invalid model parameter - model = context.services.model_manager.get_model(self.model,node=self,context=context) - - # loading controlnet image (currently requires pre-processed image) - control_image = ( - None if self.control_image is None - else context.services.images.get_pil_image(self.control_image.image_name) - ) - # loading controlnet model - if (self.control_model is None or self.control_model==''): - control_model = None - else: - # FIXME: change this to dropdown menu? - # FIXME: generalize so don't have to hardcode torch_dtype and device - control_model = ControlNetModel.from_pretrained(self.control_model, - torch_dtype=torch.float16).to("cuda") - - # Get the source node id (we are invoking the prepared node) - graph_execution_state = context.services.graph_execution_manager.get( - context.graph_execution_state_id - ) - source_node_id = graph_execution_state.prepared_source_mapping[self.id] - - txt2img = Txt2Img(model, control_model=control_model) - outputs = txt2img.generate( - prompt=self.prompt, - step_callback=partial(self.dispatch_progress, context, source_node_id), - control_image=control_image, - **self.dict( - exclude={"prompt", "control_image" } - ), # Shorthand for passing all of the parameters above manually - ) - # Outputs is an infinite iterator that will return a new InvokeAIGeneratorOutput object - # each time it is called. We only need the first one. - generate_output = next(outputs) - - image_dto = context.services.images.create( - image=generate_output.image, - image_origin=ResourceOrigin.INTERNAL, - image_category=ImageCategory.GENERAL, - session_id=context.graph_execution_state_id, - node_id=self.id, - is_intermediate=self.is_intermediate, - ) - - return ImageOutput( - image=ImageField(image_name=image_dto.image_name), - width=image_dto.width, - height=image_dto.height, - ) - - -class ImageToImageInvocation(TextToImageInvocation): - """Generates an image using img2img.""" - - type: Literal["img2img"] = "img2img" + #model: str = Field(default="", description="The model to use (currently ignored)") + #progress_images: bool = Field(default=False, description="Whether or not to produce progress images during generation", ) + #control_model: Optional[str] = Field(default=None, description="The control model to use") + #control_image: Optional[ImageField] = Field(default=None, description="The processed control image") + unet: UNetField = Field(default=None, description="UNet model") + clip: ClipField = Field(default=None, description="Clip model") + vae: VaeField = Field(default=None, description="Vae model") # Inputs image: Union[ImageField, None] = Field(description="The input image") @@ -144,72 +88,6 @@ class ImageToImageInvocation(TextToImageInvocation): description="Whether or not the result should be fit to the aspect ratio of the input image", ) - def dispatch_progress( - self, - context: InvocationContext, - source_node_id: str, - intermediate_state: PipelineIntermediateState, - ) -> None: - stable_diffusion_step_callback( - context=context, - intermediate_state=intermediate_state, - node=self.dict(), - source_node_id=source_node_id, - ) - - def invoke(self, context: InvocationContext) -> ImageOutput: - image = ( - None - if self.image is None - else context.services.images.get_pil_image(self.image.image_name) - ) - - if self.fit: - image = image.resize((self.width, self.height)) - - # Handle invalid model parameter - model = context.services.model_manager.get_model(self.model,node=self,context=context) - - # Get the source node id (we are invoking the prepared node) - graph_execution_state = context.services.graph_execution_manager.get( - context.graph_execution_state_id - ) - source_node_id = graph_execution_state.prepared_source_mapping[self.id] - - outputs = Img2Img(model).generate( - prompt=self.prompt, - init_image=image, - step_callback=partial(self.dispatch_progress, context, source_node_id), - **self.dict( - exclude={"prompt", "image", "mask"} - ), # Shorthand for passing all of the parameters above manually - ) - - # Outputs is an infinite iterator that will return a new InvokeAIGeneratorOutput object - # each time it is called. We only need the first one. - generator_output = next(outputs) - - image_dto = context.services.images.create( - image=generator_output.image, - image_origin=ResourceOrigin.INTERNAL, - image_category=ImageCategory.GENERAL, - session_id=context.graph_execution_state_id, - node_id=self.id, - is_intermediate=self.is_intermediate, - ) - - return ImageOutput( - image=ImageField(image_name=image_dto.image_name), - width=image_dto.width, - height=image_dto.height, - ) - - -class InpaintInvocation(ImageToImageInvocation): - """Generates an image using inpaint.""" - - type: Literal["inpaint"] = "inpaint" - # Inputs mask: Union[ImageField, None] = Field(description="The mask") seam_size: int = Field(default=96, ge=1, description="The seam inpaint size (px)") @@ -252,6 +130,14 @@ class InpaintInvocation(ImageToImageInvocation): description="The amount by which to replace masked areas with latent noise", ) + # Schema customisation + class Config(InvocationConfig): + schema_extra = { + "ui": { + "tags": ["stable-diffusion", "image"], + }, + } + def dispatch_progress( self, context: InvocationContext, @@ -265,6 +151,79 @@ class InpaintInvocation(ImageToImageInvocation): source_node_id=source_node_id, ) + @contextmanager + def load_model_old_way(self, context): + with ExitStack() as stack: + unet_info = context.services.model_manager.get_model(**self.unet.unet.dict()) + tokenizer_info = context.services.model_manager.get_model(**self.clip.tokenizer.dict()) + text_encoder_info = context.services.model_manager.get_model(**self.clip.text_encoder.dict()) + vae_info = context.services.model_manager.get_model(**self.vae.vae.dict()) + + #unet = stack.enter_context(unet_info) + #tokenizer = stack.enter_context(tokenizer_info) + #text_encoder = stack.enter_context(text_encoder_info) + #vae = stack.enter_context(vae_info) + with vae_info as vae: + device = vae.device + dtype = vae.dtype + + # not load models to gpu as it should be handled by pipeline + unet = unet_info.context.model + tokenizer = tokenizer_info.context.model + text_encoder = text_encoder_info.context.model + vae = vae_info.context.model + + scheduler = get_scheduler( + context=context, + scheduler_info=self.unet.scheduler, + scheduler_name=self.scheduler, + ) + + loras = [(stack.enter_context(context.services.model_manager.get_model(**lora.dict(exclude={"weight"}))), lora.weight) for lora in self.unet.loras] + ti_list = [] + for trigger in re.findall(r"<[a-zA-Z0-9., _-]+>", self.prompt): + name = trigger[1:-1] + try: + ti_list.append( + stack.enter_context( + context.services.model_manager.get_model( + model_name=name, + base_model=self.clip.text_encoder.base_model, + model_type=ModelType.TextualInversion, + ) + ) + ) + except Exception: + #print(e) + #import traceback + #print(traceback.format_exc()) + print(f"Warn: trigger: \"{trigger}\" not found") + + + with ModelPatcher.apply_lora_unet(unet, loras),\ + ModelPatcher.apply_lora_text_encoder(text_encoder, loras),\ + ModelPatcher.apply_ti(tokenizer, text_encoder, ti_list) as (ti_tokenizer, ti_manager): + + pipeline = StableDiffusionGeneratorPipeline( + # TODO: ti_manager + vae=vae, + text_encoder=text_encoder, + tokenizer=ti_tokenizer, + unet=unet, + scheduler=scheduler, + safety_checker=None, + feature_extractor=None, + requires_safety_checker=False, + precision="float16" if dtype == torch.float16 else "float32", + execution_device=device, + ) + + yield OldModelInfo( + name=self.unet.unet.model_name, + hash="", + model=pipeline, + ) + def invoke(self, context: InvocationContext) -> ImageOutput: image = ( None @@ -277,24 +236,22 @@ class InpaintInvocation(ImageToImageInvocation): else context.services.images.get_pil_image(self.mask.image_name) ) - # Handle invalid model parameter - model = context.services.model_manager.get_model(self.model,node=self,context=context) - # Get the source node id (we are invoking the prepared node) graph_execution_state = context.services.graph_execution_manager.get( context.graph_execution_state_id ) source_node_id = graph_execution_state.prepared_source_mapping[self.id] - outputs = Inpaint(model).generate( - prompt=self.prompt, - init_image=image, - mask_image=mask, - step_callback=partial(self.dispatch_progress, context, source_node_id), - **self.dict( - exclude={"prompt", "image", "mask"} - ), # Shorthand for passing all of the parameters above manually - ) + with self.load_model_old_way(context) as model: + outputs = Inpaint(model).generate( + prompt=self.prompt, + init_image=image, + mask_image=mask, + step_callback=partial(self.dispatch_progress, context, source_node_id), + **self.dict( + exclude={"prompt", "image", "mask"} + ), # Shorthand for passing all of the parameters above manually + ) # Outputs is an infinite iterator that will return a new InvokeAIGeneratorOutput object # each time it is called. We only need the first one. diff --git a/invokeai/backend/stable_diffusion/diffusers_pipeline.py b/invokeai/backend/stable_diffusion/diffusers_pipeline.py index f4afd880d3..2922238af9 100644 --- a/invokeai/backend/stable_diffusion/diffusers_pipeline.py +++ b/invokeai/backend/stable_diffusion/diffusers_pipeline.py @@ -317,6 +317,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): requires_safety_checker: bool = False, precision: str = "float32", control_model: ControlNetModel = None, + execution_device: Optional[torch.device] = None, ): super().__init__( vae, @@ -356,7 +357,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): textual_inversion_manager=self.textual_inversion_manager, ) - self._model_group = FullyLoadedModelGroup(self.unet.device) + self._model_group = FullyLoadedModelGroup(execution_device or self.unet.device) self._model_group.install(*self._submodels) self.control_model = control_model From 7b35162b9e1599a136541ac3e37ef73dbfaf9c57 Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Sat, 17 Jun 2023 19:20:24 +0300 Subject: [PATCH 006/110] Remove old logic except for inpaint, add support for lora and ti to inpaint node --- invokeai/app/invocations/generate.py | 95 ++- invokeai/backend/generator/base.py | 54 +- invokeai/backend/generator/embiggen.py | 559 ------------------ invokeai/backend/generator/img2img.py | 1 - invokeai/backend/generator/inpaint.py | 11 - invokeai/backend/generator/txt2img.py | 125 ---- invokeai/backend/generator/txt2img2img.py | 209 ------- invokeai/backend/model_management/lora.py | 16 +- .../models/textual_inversion.py | 1 + invokeai/backend/prompting/__init__.py | 9 - invokeai/backend/prompting/conditioning.py | 297 ---------- invokeai/backend/stable_diffusion/__init__.py | 1 - .../stable_diffusion/diffusers_pipeline.py | 77 --- .../textual_inversion_manager.py | 429 -------------- 14 files changed, 62 insertions(+), 1822 deletions(-) delete mode 100644 invokeai/backend/generator/embiggen.py delete mode 100644 invokeai/backend/generator/txt2img.py delete mode 100644 invokeai/backend/generator/txt2img2img.py delete mode 100644 invokeai/backend/prompting/__init__.py delete mode 100644 invokeai/backend/prompting/conditioning.py delete mode 100644 invokeai/backend/stable_diffusion/textual_inversion_manager.py diff --git a/invokeai/app/invocations/generate.py b/invokeai/app/invocations/generate.py index 83220d89ef..fc3aca4a19 100644 --- a/invokeai/app/invocations/generate.py +++ b/invokeai/app/invocations/generate.py @@ -21,7 +21,8 @@ from .image import ImageOutput import re from ...backend.model_management.lora import ModelPatcher from ...backend.stable_diffusion.diffusers_pipeline import StableDiffusionGeneratorPipeline -from .model import UNetField, ClipField, VaeField +from .model import UNetField, VaeField +from .compel import ConditioningField from contextlib import contextmanager, ExitStack, ContextDecorator SAMPLER_NAME_VALUES = Literal[tuple(InvokeAIGenerator.schedulers())] @@ -63,19 +64,15 @@ class InpaintInvocation(BaseInvocation): type: Literal["inpaint"] = "inpaint" - prompt: Optional[str] = Field(description="The prompt to generate an image from") + positive_conditioning: Optional[ConditioningField] = Field(description="Positive conditioning for generation") + negative_conditioning: Optional[ConditioningField] = Field(description="Negative conditioning for generation") seed: int = Field(ge=0, le=SEED_MAX, description="The seed to use (omit for random)", default_factory=get_random_seed) steps: int = Field(default=30, gt=0, description="The number of steps to use to generate the image") width: int = Field(default=512, multiple_of=8, gt=0, description="The width of the resulting image", ) height: int = Field(default=512, multiple_of=8, gt=0, description="The height of the resulting image", ) cfg_scale: float = Field(default=7.5, ge=1, description="The Classifier-Free Guidance, higher values may result in a result closer to the prompt", ) scheduler: SAMPLER_NAME_VALUES = Field(default="euler", description="The scheduler to use" ) - #model: str = Field(default="", description="The model to use (currently ignored)") - #progress_images: bool = Field(default=False, description="Whether or not to produce progress images during generation", ) - #control_model: Optional[str] = Field(default=None, description="The control model to use") - #control_image: Optional[ImageField] = Field(default=None, description="The processed control image") unet: UNetField = Field(default=None, description="UNet model") - clip: ClipField = Field(default=None, description="Clip model") vae: VaeField = Field(default=None, description="Vae model") # Inputs @@ -151,64 +148,34 @@ class InpaintInvocation(BaseInvocation): source_node_id=source_node_id, ) + def get_conditioning(self, context): + c, extra_conditioning_info = context.services.latents.get(self.positive_conditioning.conditioning_name) + uc, _ = context.services.latents.get(self.negative_conditioning.conditioning_name) + + return (uc, c, extra_conditioning_info) + @contextmanager - def load_model_old_way(self, context): + def load_model_old_way(self, context, scheduler): + unet_info = context.services.model_manager.get_model(**self.unet.unet.dict()) + vae_info = context.services.model_manager.get_model(**self.vae.vae.dict()) + + #unet = unet_info.context.model + #vae = vae_info.context.model + with ExitStack() as stack: - unet_info = context.services.model_manager.get_model(**self.unet.unet.dict()) - tokenizer_info = context.services.model_manager.get_model(**self.clip.tokenizer.dict()) - text_encoder_info = context.services.model_manager.get_model(**self.clip.text_encoder.dict()) - vae_info = context.services.model_manager.get_model(**self.vae.vae.dict()) - - #unet = stack.enter_context(unet_info) - #tokenizer = stack.enter_context(tokenizer_info) - #text_encoder = stack.enter_context(text_encoder_info) - #vae = stack.enter_context(vae_info) - with vae_info as vae: - device = vae.device - dtype = vae.dtype - - # not load models to gpu as it should be handled by pipeline - unet = unet_info.context.model - tokenizer = tokenizer_info.context.model - text_encoder = text_encoder_info.context.model - vae = vae_info.context.model - - scheduler = get_scheduler( - context=context, - scheduler_info=self.unet.scheduler, - scheduler_name=self.scheduler, - ) - loras = [(stack.enter_context(context.services.model_manager.get_model(**lora.dict(exclude={"weight"}))), lora.weight) for lora in self.unet.loras] - ti_list = [] - for trigger in re.findall(r"<[a-zA-Z0-9., _-]+>", self.prompt): - name = trigger[1:-1] - try: - ti_list.append( - stack.enter_context( - context.services.model_manager.get_model( - model_name=name, - base_model=self.clip.text_encoder.base_model, - model_type=ModelType.TextualInversion, - ) - ) - ) - except Exception: - #print(e) - #import traceback - #print(traceback.format_exc()) - print(f"Warn: trigger: \"{trigger}\" not found") + with vae_info as vae,\ + unet_info as unet,\ + ModelPatcher.apply_lora_unet(unet, loras): - with ModelPatcher.apply_lora_unet(unet, loras),\ - ModelPatcher.apply_lora_text_encoder(text_encoder, loras),\ - ModelPatcher.apply_ti(tokenizer, text_encoder, ti_list) as (ti_tokenizer, ti_manager): + device = context.services.model_manager.mgr.cache.execution_device + dtype = context.services.model_manager.mgr.cache.precision pipeline = StableDiffusionGeneratorPipeline( - # TODO: ti_manager vae=vae, - text_encoder=text_encoder, - tokenizer=ti_tokenizer, + text_encoder=None, + tokenizer=None, unet=unet, scheduler=scheduler, safety_checker=None, @@ -242,14 +209,22 @@ class InpaintInvocation(BaseInvocation): ) source_node_id = graph_execution_state.prepared_source_mapping[self.id] - with self.load_model_old_way(context) as model: + conditioning = self.get_conditioning(context) + scheduler = get_scheduler( + context=context, + scheduler_info=self.unet.scheduler, + scheduler_name=self.scheduler, + ) + + with self.load_model_old_way(context, scheduler) as model: outputs = Inpaint(model).generate( - prompt=self.prompt, + conditioning=conditioning, + scheduler=scheduler, init_image=image, mask_image=mask, step_callback=partial(self.dispatch_progress, context, source_node_id), **self.dict( - exclude={"prompt", "image", "mask"} + exclude={"positive_conditioning", "negative_conditioning", "scheduler", "image", "mask"} ), # Shorthand for passing all of the parameters above manually ) diff --git a/invokeai/backend/generator/base.py b/invokeai/backend/generator/base.py index fb293ab5b2..a379cf6350 100644 --- a/invokeai/backend/generator/base.py +++ b/invokeai/backend/generator/base.py @@ -29,7 +29,6 @@ import invokeai.backend.util.logging as logger from ..image_util import configure_model_padding from ..util.util import rand_perlin_2d from ..safety_checker import SafetyChecker -from ..prompting.conditioning import get_uc_and_c_and_ec from ..stable_diffusion.diffusers_pipeline import StableDiffusionGeneratorPipeline from ..stable_diffusion.schedulers import SCHEDULER_MAP @@ -81,13 +80,15 @@ class InvokeAIGenerator(metaclass=ABCMeta): self.params=params self.kwargs = kwargs - def generate(self, - prompt: str='', - callback: Optional[Callable]=None, - step_callback: Optional[Callable]=None, - iterations: int=1, - **keyword_args, - )->Iterator[InvokeAIGeneratorOutput]: + def generate( + self, + conditioning: tuple, + scheduler, + callback: Optional[Callable]=None, + step_callback: Optional[Callable]=None, + iterations: int=1, + **keyword_args, + )->Iterator[InvokeAIGeneratorOutput]: ''' Return an iterator across the indicated number of generations. Each time the iterator is called it will return an InvokeAIGeneratorOutput @@ -116,11 +117,6 @@ class InvokeAIGenerator(metaclass=ABCMeta): model_name = model_info.name model_hash = model_info.hash with model_info.context as model: - scheduler: Scheduler = self.get_scheduler( - model=model, - scheduler_name=generator_args.get('scheduler') - ) - uc, c, extra_conditioning_info = get_uc_and_c_and_ec(prompt,model=model) gen_class = self._generator_class() generator = gen_class(model, self.params.precision, **self.kwargs) if self.params.variation_amount > 0: @@ -143,12 +139,12 @@ class InvokeAIGenerator(metaclass=ABCMeta): iteration_count = range(iterations) if iterations else itertools.count(start=0, step=1) for i in iteration_count: - results = generator.generate(prompt, - conditioning=(uc, c, extra_conditioning_info), - step_callback=step_callback, - sampler=scheduler, - **generator_args, - ) + results = generator.generate( + conditioning=conditioning, + step_callback=step_callback, + sampler=scheduler, + **generator_args, + ) output = InvokeAIGeneratorOutput( image=results[0][0], seed=results[0][1], @@ -170,20 +166,6 @@ class InvokeAIGenerator(metaclass=ABCMeta): def load_generator(self, model: StableDiffusionGeneratorPipeline, generator_class: Type[Generator]): return generator_class(model, self.params.precision) - def get_scheduler(self, scheduler_name:str, model: StableDiffusionGeneratorPipeline)->Scheduler: - scheduler_class, scheduler_extra_config = SCHEDULER_MAP.get(scheduler_name, SCHEDULER_MAP['ddim']) - - scheduler_config = model.scheduler.config - if "_backup" in scheduler_config: - scheduler_config = scheduler_config["_backup"] - scheduler_config = {**scheduler_config, **scheduler_extra_config, "_backup": scheduler_config} - scheduler = scheduler_class.from_config(scheduler_config) - - # hack copied over from generate.py - if not hasattr(scheduler, 'uses_inpainting_model'): - scheduler.uses_inpainting_model = lambda: False - return scheduler - @classmethod def _generator_class(cls)->Type[Generator]: ''' @@ -281,7 +263,7 @@ class Generator: self.model = model self.precision = precision self.seed = None - self.latent_channels = model.channels + self.latent_channels = model.unet.config.in_channels self.downsampling_factor = downsampling # BUG: should come from model or config self.safety_checker = None self.perlin = 0.0 @@ -292,7 +274,7 @@ class Generator: self.free_gpu_mem = None # this is going to be overridden in img2img.py, txt2img.py and inpaint.py - def get_make_image(self, prompt, **kwargs): + def get_make_image(self, **kwargs): """ Returns a function returning an image derived from the prompt and the initial image Return value depends on the seed at the time you call it @@ -308,7 +290,6 @@ class Generator: def generate( self, - prompt, width, height, sampler, @@ -333,7 +314,6 @@ class Generator: saver.get_stacked_maps_image() ) make_image = self.get_make_image( - prompt, sampler=sampler, init_image=init_image, width=width, diff --git a/invokeai/backend/generator/embiggen.py b/invokeai/backend/generator/embiggen.py deleted file mode 100644 index 6eae5732b0..0000000000 --- a/invokeai/backend/generator/embiggen.py +++ /dev/null @@ -1,559 +0,0 @@ -""" -invokeai.backend.generator.embiggen descends from .generator -and generates with .generator.img2img -""" - -import numpy as np -import torch -from PIL import Image -from tqdm import trange - -import invokeai.backend.util.logging as logger - -from .base import Generator -from .img2img import Img2Img - -class Embiggen(Generator): - def __init__(self, model, precision): - super().__init__(model, precision) - self.init_latent = None - - # Replace generate because Embiggen doesn't need/use most of what it does normallly - def generate( - self, - prompt, - iterations=1, - seed=None, - image_callback=None, - step_callback=None, - **kwargs, - ): - make_image = self.get_make_image(prompt, step_callback=step_callback, **kwargs) - results = [] - seed = seed if seed else self.new_seed() - - # Noise will be generated by the Img2Img generator when called - for _ in trange(iterations, desc="Generating"): - # make_image will call Img2Img which will do the equivalent of get_noise itself - image = make_image() - results.append([image, seed]) - if image_callback is not None: - image_callback(image, seed, prompt_in=prompt) - seed = self.new_seed() - return results - - @torch.no_grad() - def get_make_image( - self, - prompt, - sampler, - steps, - cfg_scale, - ddim_eta, - conditioning, - init_img, - strength, - width, - height, - embiggen, - embiggen_tiles, - step_callback=None, - **kwargs, - ): - """ - Returns a function returning an image derived from the prompt and multi-stage twice-baked potato layering over the img2img on the initial image - Return value depends on the seed at the time you call it - """ - assert ( - not sampler.uses_inpainting_model() - ), "--embiggen is not supported by inpainting models" - - # Construct embiggen arg array, and sanity check arguments - if embiggen == None: # embiggen can also be called with just embiggen_tiles - embiggen = [1.0] # If not specified, assume no scaling - elif embiggen[0] < 0: - embiggen[0] = 1.0 - logger.warning( - "Embiggen scaling factor cannot be negative, fell back to the default of 1.0 !" - ) - if len(embiggen) < 2: - embiggen.append(0.75) - elif embiggen[1] > 1.0 or embiggen[1] < 0: - embiggen[1] = 0.75 - logger.warning( - "Embiggen upscaling strength for ESRGAN must be between 0 and 1, fell back to the default of 0.75 !" - ) - if len(embiggen) < 3: - embiggen.append(0.25) - elif embiggen[2] < 0: - embiggen[2] = 0.25 - logger.warning( - "Overlap size for Embiggen must be a positive ratio between 0 and 1 OR a number of pixels, fell back to the default of 0.25 !" - ) - - # Convert tiles from their user-freindly count-from-one to count-from-zero, because we need to do modulo math - # and then sort them, because... people. - if embiggen_tiles: - embiggen_tiles = list(map(lambda n: n - 1, embiggen_tiles)) - embiggen_tiles.sort() - - if strength >= 0.5: - logger.warning( - f"Embiggen may produce mirror motifs if the strength (-f) is too high (currently {strength}). Try values between 0.35-0.45." - ) - - # Prep img2img generator, since we wrap over it - gen_img2img = Img2Img(self.model, self.precision) - - # Open original init image (not a tensor) to manipulate - initsuperimage = Image.open(init_img) - - with Image.open(init_img) as img: - initsuperimage = img.convert("RGB") - - # Size of the target super init image in pixels - initsuperwidth, initsuperheight = initsuperimage.size - - # Increase by scaling factor if not already resized, using ESRGAN as able - if embiggen[0] != 1.0: - initsuperwidth = round(initsuperwidth * embiggen[0]) - initsuperheight = round(initsuperheight * embiggen[0]) - if embiggen[1] > 0: # No point in ESRGAN upscaling if strength is set zero - from ..restoration.realesrgan import ESRGAN - - esrgan = ESRGAN() - logger.info( - f"ESRGAN upscaling init image prior to cutting with Embiggen with strength {embiggen[1]}" - ) - if embiggen[0] > 2: - initsuperimage = esrgan.process( - initsuperimage, - embiggen[1], # upscale strength - self.seed, - 4, # upscale scale - ) - else: - initsuperimage = esrgan.process( - initsuperimage, - embiggen[1], # upscale strength - self.seed, - 2, # upscale scale - ) - # We could keep recursively re-running ESRGAN for a requested embiggen[0] larger than 4x - # but from personal experiance it doesn't greatly improve anything after 4x - # Resize to target scaling factor resolution - initsuperimage = initsuperimage.resize( - (initsuperwidth, initsuperheight), Image.Resampling.LANCZOS - ) - - # Use width and height as tile widths and height - # Determine buffer size in pixels - if embiggen[2] < 1: - if embiggen[2] < 0: - embiggen[2] = 0 - overlap_size_x = round(embiggen[2] * width) - overlap_size_y = round(embiggen[2] * height) - else: - overlap_size_x = round(embiggen[2]) - overlap_size_y = round(embiggen[2]) - - # With overall image width and height known, determine how many tiles we need - def ceildiv(a, b): - return -1 * (-a // b) - - # X and Y needs to be determined independantly (we may have savings on one based on the buffer pixel count) - # (initsuperwidth - width) is the area remaining to the right that we need to layers tiles to fill - # (width - overlap_size_x) is how much new we can fill with a single tile - emb_tiles_x = 1 - emb_tiles_y = 1 - if (initsuperwidth - width) > 0: - emb_tiles_x = ceildiv(initsuperwidth - width, width - overlap_size_x) + 1 - if (initsuperheight - height) > 0: - emb_tiles_y = ceildiv(initsuperheight - height, height - overlap_size_y) + 1 - # Sanity - assert ( - emb_tiles_x > 1 or emb_tiles_y > 1 - ), f"ERROR: Based on the requested dimensions of {initsuperwidth}x{initsuperheight} and tiles of {width}x{height} you don't need to Embiggen! Check your arguments." - - # Prep alpha layers -------------- - # https://stackoverflow.com/questions/69321734/how-to-create-different-transparency-like-gradient-with-python-pil - # agradientL is Left-side transparent - agradientL = ( - Image.linear_gradient("L").rotate(90).resize((overlap_size_x, height)) - ) - # agradientT is Top-side transparent - agradientT = Image.linear_gradient("L").resize((width, overlap_size_y)) - # radial corner is the left-top corner, made full circle then cut to just the left-top quadrant - agradientC = Image.new("L", (256, 256)) - for y in range(256): - for x in range(256): - # Find distance to lower right corner (numpy takes arrays) - distanceToLR = np.sqrt([(255 - x) ** 2 + (255 - y) ** 2])[0] - # Clamp values to max 255 - if distanceToLR > 255: - distanceToLR = 255 - # Place the pixel as invert of distance - agradientC.putpixel((x, y), round(255 - distanceToLR)) - - # Create alternative asymmetric diagonal corner to use on "tailing" intersections to prevent hard edges - # Fits for a left-fading gradient on the bottom side and full opacity on the right side. - agradientAsymC = Image.new("L", (256, 256)) - for y in range(256): - for x in range(256): - value = round(max(0, x - (255 - y)) * (255 / max(1, y))) - # Clamp values - value = max(0, value) - value = min(255, value) - agradientAsymC.putpixel((x, y), value) - - # Create alpha layers default fully white - alphaLayerL = Image.new("L", (width, height), 255) - alphaLayerT = Image.new("L", (width, height), 255) - alphaLayerLTC = Image.new("L", (width, height), 255) - # Paste gradients into alpha layers - alphaLayerL.paste(agradientL, (0, 0)) - alphaLayerT.paste(agradientT, (0, 0)) - alphaLayerLTC.paste(agradientL, (0, 0)) - alphaLayerLTC.paste(agradientT, (0, 0)) - alphaLayerLTC.paste(agradientC.resize((overlap_size_x, overlap_size_y)), (0, 0)) - # make masks with an asymmetric upper-right corner so when the curved transparent corner of the next tile - # to its right is placed it doesn't reveal a hard trailing semi-transparent edge in the overlapping space - alphaLayerTaC = alphaLayerT.copy() - alphaLayerTaC.paste( - agradientAsymC.rotate(270).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, 0), - ) - alphaLayerLTaC = alphaLayerLTC.copy() - alphaLayerLTaC.paste( - agradientAsymC.rotate(270).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, 0), - ) - - if embiggen_tiles: - # Individual unconnected sides - alphaLayerR = Image.new("L", (width, height), 255) - alphaLayerR.paste(agradientL.rotate(180), (width - overlap_size_x, 0)) - alphaLayerB = Image.new("L", (width, height), 255) - alphaLayerB.paste(agradientT.rotate(180), (0, height - overlap_size_y)) - alphaLayerTB = Image.new("L", (width, height), 255) - alphaLayerTB.paste(agradientT, (0, 0)) - alphaLayerTB.paste(agradientT.rotate(180), (0, height - overlap_size_y)) - alphaLayerLR = Image.new("L", (width, height), 255) - alphaLayerLR.paste(agradientL, (0, 0)) - alphaLayerLR.paste(agradientL.rotate(180), (width - overlap_size_x, 0)) - - # Sides and corner Layers - alphaLayerRBC = Image.new("L", (width, height), 255) - alphaLayerRBC.paste(agradientL.rotate(180), (width - overlap_size_x, 0)) - alphaLayerRBC.paste(agradientT.rotate(180), (0, height - overlap_size_y)) - alphaLayerRBC.paste( - agradientC.rotate(180).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, height - overlap_size_y), - ) - alphaLayerLBC = Image.new("L", (width, height), 255) - alphaLayerLBC.paste(agradientL, (0, 0)) - alphaLayerLBC.paste(agradientT.rotate(180), (0, height - overlap_size_y)) - alphaLayerLBC.paste( - agradientC.rotate(90).resize((overlap_size_x, overlap_size_y)), - (0, height - overlap_size_y), - ) - alphaLayerRTC = Image.new("L", (width, height), 255) - alphaLayerRTC.paste(agradientL.rotate(180), (width - overlap_size_x, 0)) - alphaLayerRTC.paste(agradientT, (0, 0)) - alphaLayerRTC.paste( - agradientC.rotate(270).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, 0), - ) - - # All but X layers - alphaLayerABT = Image.new("L", (width, height), 255) - alphaLayerABT.paste(alphaLayerLBC, (0, 0)) - alphaLayerABT.paste(agradientL.rotate(180), (width - overlap_size_x, 0)) - alphaLayerABT.paste( - agradientC.rotate(180).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, height - overlap_size_y), - ) - alphaLayerABL = Image.new("L", (width, height), 255) - alphaLayerABL.paste(alphaLayerRTC, (0, 0)) - alphaLayerABL.paste(agradientT.rotate(180), (0, height - overlap_size_y)) - alphaLayerABL.paste( - agradientC.rotate(180).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, height - overlap_size_y), - ) - alphaLayerABR = Image.new("L", (width, height), 255) - alphaLayerABR.paste(alphaLayerLBC, (0, 0)) - alphaLayerABR.paste(agradientT, (0, 0)) - alphaLayerABR.paste( - agradientC.resize((overlap_size_x, overlap_size_y)), (0, 0) - ) - alphaLayerABB = Image.new("L", (width, height), 255) - alphaLayerABB.paste(alphaLayerRTC, (0, 0)) - alphaLayerABB.paste(agradientL, (0, 0)) - alphaLayerABB.paste( - agradientC.resize((overlap_size_x, overlap_size_y)), (0, 0) - ) - - # All-around layer - alphaLayerAA = Image.new("L", (width, height), 255) - alphaLayerAA.paste(alphaLayerABT, (0, 0)) - alphaLayerAA.paste(agradientT, (0, 0)) - alphaLayerAA.paste( - agradientC.resize((overlap_size_x, overlap_size_y)), (0, 0) - ) - alphaLayerAA.paste( - agradientC.rotate(270).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, 0), - ) - - # Clean up temporary gradients - del agradientL - del agradientT - del agradientC - - def make_image(): - # Make main tiles ------------------------------------------------- - if embiggen_tiles: - logger.info(f"Making {len(embiggen_tiles)} Embiggen tiles...") - else: - logger.info( - f"Making {(emb_tiles_x * emb_tiles_y)} Embiggen tiles ({emb_tiles_x}x{emb_tiles_y})..." - ) - - emb_tile_store = [] - # Although we could use the same seed for every tile for determinism, at higher strengths this may - # produce duplicated structures for each tile and make the tiling effect more obvious - # instead track and iterate a local seed we pass to Img2Img - seed = self.seed - seedintlimit = ( - np.iinfo(np.uint32).max - 1 - ) # only retreive this one from numpy - - for tile in range(emb_tiles_x * emb_tiles_y): - # Don't iterate on first tile - if tile != 0: - if seed < seedintlimit: - seed += 1 - else: - seed = 0 - - # Determine if this is a re-run and replace - if embiggen_tiles and not tile in embiggen_tiles: - continue - # Get row and column entries - emb_row_i = tile // emb_tiles_x - emb_column_i = tile % emb_tiles_x - # Determine bounds to cut up the init image - # Determine upper-left point - if emb_column_i + 1 == emb_tiles_x: - left = initsuperwidth - width - else: - left = round(emb_column_i * (width - overlap_size_x)) - if emb_row_i + 1 == emb_tiles_y: - top = initsuperheight - height - else: - top = round(emb_row_i * (height - overlap_size_y)) - right = left + width - bottom = top + height - - # Cropped image of above dimension (does not modify the original) - newinitimage = initsuperimage.crop((left, top, right, bottom)) - # DEBUG: - # newinitimagepath = init_img[0:-4] + f'_emb_Ti{tile}.png' - # newinitimage.save(newinitimagepath) - - if embiggen_tiles: - logger.debug( - f"Making tile #{tile + 1} ({embiggen_tiles.index(tile) + 1} of {len(embiggen_tiles)} requested)" - ) - else: - logger.debug(f"Starting {tile + 1} of {(emb_tiles_x * emb_tiles_y)} tiles") - - # create a torch tensor from an Image - newinitimage = np.array(newinitimage).astype(np.float32) / 255.0 - newinitimage = newinitimage[None].transpose(0, 3, 1, 2) - newinitimage = torch.from_numpy(newinitimage) - newinitimage = 2.0 * newinitimage - 1.0 - newinitimage = newinitimage.to(self.model.device) - clear_cuda_cache = ( - kwargs["clear_cuda_cache"] if "clear_cuda_cache" in kwargs else None - ) - - tile_results = gen_img2img.generate( - prompt, - iterations=1, - seed=seed, - sampler=sampler, - steps=steps, - cfg_scale=cfg_scale, - conditioning=conditioning, - ddim_eta=ddim_eta, - image_callback=None, # called only after the final image is generated - step_callback=step_callback, # called after each intermediate image is generated - width=width, - height=height, - init_image=newinitimage, # notice that init_image is different from init_img - mask_image=None, - strength=strength, - clear_cuda_cache=clear_cuda_cache, - ) - - emb_tile_store.append(tile_results[0][0]) - # DEBUG (but, also has other uses), worth saving if you want tiles without a transparency overlap to manually composite - # emb_tile_store[-1].save(init_img[0:-4] + f'_emb_To{tile}.png') - del newinitimage - - # Sanity check we have them all - if len(emb_tile_store) == (emb_tiles_x * emb_tiles_y) or ( - embiggen_tiles != [] and len(emb_tile_store) == len(embiggen_tiles) - ): - outputsuperimage = Image.new("RGBA", (initsuperwidth, initsuperheight)) - if embiggen_tiles: - outputsuperimage.alpha_composite( - initsuperimage.convert("RGBA"), (0, 0) - ) - for tile in range(emb_tiles_x * emb_tiles_y): - if embiggen_tiles: - if tile in embiggen_tiles: - intileimage = emb_tile_store.pop(0) - else: - continue - else: - intileimage = emb_tile_store[tile] - intileimage = intileimage.convert("RGBA") - # Get row and column entries - emb_row_i = tile // emb_tiles_x - emb_column_i = tile % emb_tiles_x - if emb_row_i == 0 and emb_column_i == 0 and not embiggen_tiles: - left = 0 - top = 0 - else: - # Determine upper-left point - if emb_column_i + 1 == emb_tiles_x: - left = initsuperwidth - width - else: - left = round(emb_column_i * (width - overlap_size_x)) - if emb_row_i + 1 == emb_tiles_y: - top = initsuperheight - height - else: - top = round(emb_row_i * (height - overlap_size_y)) - # Handle gradients for various conditions - # Handle emb_rerun case - if embiggen_tiles: - # top of image - if emb_row_i == 0: - if emb_column_i == 0: - if (tile + 1) in embiggen_tiles: # Look-ahead right - if ( - tile + emb_tiles_x - ) not in embiggen_tiles: # Look-ahead down - intileimage.putalpha(alphaLayerB) - # Otherwise do nothing on this tile - elif ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down only - intileimage.putalpha(alphaLayerR) - else: - intileimage.putalpha(alphaLayerRBC) - elif emb_column_i == emb_tiles_x - 1: - if ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down - intileimage.putalpha(alphaLayerL) - else: - intileimage.putalpha(alphaLayerLBC) - else: - if (tile + 1) in embiggen_tiles: # Look-ahead right - if ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down - intileimage.putalpha(alphaLayerL) - else: - intileimage.putalpha(alphaLayerLBC) - elif ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down only - intileimage.putalpha(alphaLayerLR) - else: - intileimage.putalpha(alphaLayerABT) - # bottom of image - elif emb_row_i == emb_tiles_y - 1: - if emb_column_i == 0: - if (tile + 1) in embiggen_tiles: # Look-ahead right - intileimage.putalpha(alphaLayerTaC) - else: - intileimage.putalpha(alphaLayerRTC) - elif emb_column_i == emb_tiles_x - 1: - # No tiles to look ahead to - intileimage.putalpha(alphaLayerLTC) - else: - if (tile + 1) in embiggen_tiles: # Look-ahead right - intileimage.putalpha(alphaLayerLTaC) - else: - intileimage.putalpha(alphaLayerABB) - # vertical middle of image - else: - if emb_column_i == 0: - if (tile + 1) in embiggen_tiles: # Look-ahead right - if ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down - intileimage.putalpha(alphaLayerTaC) - else: - intileimage.putalpha(alphaLayerTB) - elif ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down only - intileimage.putalpha(alphaLayerRTC) - else: - intileimage.putalpha(alphaLayerABL) - elif emb_column_i == emb_tiles_x - 1: - if ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down - intileimage.putalpha(alphaLayerLTC) - else: - intileimage.putalpha(alphaLayerABR) - else: - if (tile + 1) in embiggen_tiles: # Look-ahead right - if ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down - intileimage.putalpha(alphaLayerLTaC) - else: - intileimage.putalpha(alphaLayerABR) - elif ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down only - intileimage.putalpha(alphaLayerABB) - else: - intileimage.putalpha(alphaLayerAA) - # Handle normal tiling case (much simpler - since we tile left to right, top to bottom) - else: - if emb_row_i == 0 and emb_column_i >= 1: - intileimage.putalpha(alphaLayerL) - elif emb_row_i >= 1 and emb_column_i == 0: - if ( - emb_column_i + 1 == emb_tiles_x - ): # If we don't have anything that can be placed to the right - intileimage.putalpha(alphaLayerT) - else: - intileimage.putalpha(alphaLayerTaC) - else: - if ( - emb_column_i + 1 == emb_tiles_x - ): # If we don't have anything that can be placed to the right - intileimage.putalpha(alphaLayerLTC) - else: - intileimage.putalpha(alphaLayerLTaC) - # Layer tile onto final image - outputsuperimage.alpha_composite(intileimage, (left, top)) - else: - logger.error( - "Could not find all Embiggen output tiles in memory? Something must have gone wrong with img2img generation." - ) - - # after internal loops and patching up return Embiggen image - return outputsuperimage - - # end of function declaration - return make_image diff --git a/invokeai/backend/generator/img2img.py b/invokeai/backend/generator/img2img.py index 2c62bec4d6..1cfbeb66c0 100644 --- a/invokeai/backend/generator/img2img.py +++ b/invokeai/backend/generator/img2img.py @@ -22,7 +22,6 @@ class Img2Img(Generator): def get_make_image( self, - prompt, sampler, steps, cfg_scale, diff --git a/invokeai/backend/generator/inpaint.py b/invokeai/backend/generator/inpaint.py index a7fec83eb7..eaf4047109 100644 --- a/invokeai/backend/generator/inpaint.py +++ b/invokeai/backend/generator/inpaint.py @@ -161,9 +161,7 @@ class Inpaint(Img2Img): im: Image.Image, seam_size: int, seam_blur: int, - prompt, seed, - sampler, steps, cfg_scale, ddim_eta, @@ -177,8 +175,6 @@ class Inpaint(Img2Img): mask = self.mask_edge(hard_mask, seam_size, seam_blur) make_image = self.get_make_image( - prompt, - sampler, steps, cfg_scale, ddim_eta, @@ -203,8 +199,6 @@ class Inpaint(Img2Img): @torch.no_grad() def get_make_image( self, - prompt, - sampler, steps, cfg_scale, ddim_eta, @@ -306,7 +300,6 @@ class Inpaint(Img2Img): # noinspection PyTypeChecker pipeline: StableDiffusionGeneratorPipeline = self.model - pipeline.scheduler = sampler # todo: support cross-attention control uc, c, _ = conditioning @@ -345,9 +338,7 @@ class Inpaint(Img2Img): result, seam_size, seam_blur, - prompt, seed, - sampler, seam_steps, cfg_scale, ddim_eta, @@ -360,8 +351,6 @@ class Inpaint(Img2Img): # Restore original settings self.get_make_image( - prompt, - sampler, steps, cfg_scale, ddim_eta, diff --git a/invokeai/backend/generator/txt2img.py b/invokeai/backend/generator/txt2img.py deleted file mode 100644 index 9ea19bd896..0000000000 --- a/invokeai/backend/generator/txt2img.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -invokeai.backend.generator.txt2img inherits from invokeai.backend.generator -""" -import PIL.Image -import torch - -from typing import Any, Callable, Dict, List, Optional, Tuple, Union -from diffusers.models.controlnet import ControlNetModel, ControlNetOutput -from diffusers.pipelines.controlnet import MultiControlNetModel - -from ..stable_diffusion import ( - ConditioningData, - PostprocessingSettings, - StableDiffusionGeneratorPipeline, -) -from .base import Generator - - -class Txt2Img(Generator): - def __init__(self, model, precision, - control_model: Optional[Union[ControlNetModel, List[ControlNetModel]]] = None, - **kwargs): - self.control_model = control_model - if isinstance(self.control_model, list): - self.control_model = MultiControlNetModel(self.control_model) - super().__init__(model, precision, **kwargs) - - @torch.no_grad() - def get_make_image( - self, - prompt, - sampler, - steps, - cfg_scale, - ddim_eta, - conditioning, - width, - height, - step_callback=None, - threshold=0.0, - warmup=0.2, - perlin=0.0, - h_symmetry_time_pct=None, - v_symmetry_time_pct=None, - attention_maps_callback=None, - **kwargs, - ): - """ - Returns a function returning an image derived from the prompt and the initial image - Return value depends on the seed at the time you call it - kwargs are 'width' and 'height' - """ - self.perlin = perlin - control_image = kwargs.get("control_image", None) - do_classifier_free_guidance = cfg_scale > 1.0 - - # noinspection PyTypeChecker - pipeline: StableDiffusionGeneratorPipeline = self.model - pipeline.control_model = self.control_model - pipeline.scheduler = sampler - - uc, c, extra_conditioning_info = conditioning - conditioning_data = ConditioningData( - uc, - c, - cfg_scale, - extra_conditioning_info, - postprocessing_settings=PostprocessingSettings( - threshold=threshold, - warmup=warmup, - h_symmetry_time_pct=h_symmetry_time_pct, - v_symmetry_time_pct=v_symmetry_time_pct, - ), - ).add_scheduler_args_if_applicable(pipeline.scheduler, eta=ddim_eta) - - # FIXME: still need to test with different widths, heights, devices, dtypes - # and add in batch_size, num_images_per_prompt? - if control_image is not None: - if isinstance(self.control_model, ControlNetModel): - control_image = pipeline.prepare_control_image( - image=control_image, - do_classifier_free_guidance=do_classifier_free_guidance, - width=width, - height=height, - # batch_size=batch_size * num_images_per_prompt, - # num_images_per_prompt=num_images_per_prompt, - device=self.control_model.device, - dtype=self.control_model.dtype, - ) - elif isinstance(self.control_model, MultiControlNetModel): - images = [] - for image_ in control_image: - image_ = pipeline.prepare_control_image( - image=image_, - do_classifier_free_guidance=do_classifier_free_guidance, - width=width, - height=height, - # batch_size=batch_size * num_images_per_prompt, - # num_images_per_prompt=num_images_per_prompt, - device=self.control_model.device, - dtype=self.control_model.dtype, - ) - images.append(image_) - control_image = images - kwargs["control_image"] = control_image - - def make_image(x_T: torch.Tensor, _: int) -> PIL.Image.Image: - pipeline_output = pipeline.image_from_embeddings( - latents=torch.zeros_like(x_T, dtype=self.torch_dtype()), - noise=x_T, - num_inference_steps=steps, - conditioning_data=conditioning_data, - callback=step_callback, - **kwargs, - ) - - if ( - pipeline_output.attention_map_saver is not None - and attention_maps_callback is not None - ): - attention_maps_callback(pipeline_output.attention_map_saver) - - return pipeline.numpy_to_pil(pipeline_output.images)[0] - - return make_image diff --git a/invokeai/backend/generator/txt2img2img.py b/invokeai/backend/generator/txt2img2img.py deleted file mode 100644 index 1257a44fb1..0000000000 --- a/invokeai/backend/generator/txt2img2img.py +++ /dev/null @@ -1,209 +0,0 @@ -""" -invokeai.backend.generator.txt2img inherits from invokeai.backend.generator -""" - -import math -from typing import Callable, Optional - -import torch -from diffusers.utils.logging import get_verbosity, set_verbosity, set_verbosity_error - -from ..stable_diffusion import PostprocessingSettings -from .base import Generator -from ..stable_diffusion.diffusers_pipeline import StableDiffusionGeneratorPipeline -from ..stable_diffusion.diffusers_pipeline import ConditioningData -from ..stable_diffusion.diffusers_pipeline import trim_to_multiple_of - -import invokeai.backend.util.logging as logger - -class Txt2Img2Img(Generator): - def __init__(self, model, precision): - super().__init__(model, precision) - self.init_latent = None # for get_noise() - - def get_make_image( - self, - prompt: str, - sampler, - steps: int, - cfg_scale: float, - ddim_eta, - conditioning, - width: int, - height: int, - strength: float, - step_callback: Optional[Callable] = None, - threshold=0.0, - warmup=0.2, - perlin=0.0, - h_symmetry_time_pct=None, - v_symmetry_time_pct=None, - attention_maps_callback=None, - **kwargs, - ): - """ - Returns a function returning an image derived from the prompt and the initial image - Return value depends on the seed at the time you call it - kwargs are 'width' and 'height' - """ - self.perlin = perlin - - # noinspection PyTypeChecker - pipeline: StableDiffusionGeneratorPipeline = self.model - pipeline.scheduler = sampler - - uc, c, extra_conditioning_info = conditioning - conditioning_data = ConditioningData( - uc, - c, - cfg_scale, - extra_conditioning_info, - postprocessing_settings=PostprocessingSettings( - threshold=threshold, - warmup=0.2, - h_symmetry_time_pct=h_symmetry_time_pct, - v_symmetry_time_pct=v_symmetry_time_pct, - ), - ).add_scheduler_args_if_applicable(pipeline.scheduler, eta=ddim_eta) - - def make_image(x_T: torch.Tensor, _: int): - first_pass_latent_output, _ = pipeline.latents_from_embeddings( - latents=torch.zeros_like(x_T), - num_inference_steps=steps, - conditioning_data=conditioning_data, - noise=x_T, - callback=step_callback, - ) - - # Get our initial generation width and height directly from the latent output so - # the message below is accurate. - init_width = first_pass_latent_output.size()[3] * self.downsampling_factor - init_height = first_pass_latent_output.size()[2] * self.downsampling_factor - logger.info( - f"Interpolating from {init_width}x{init_height} to {width}x{height} using DDIM sampling" - ) - - # resizing - resized_latents = torch.nn.functional.interpolate( - first_pass_latent_output, - size=( - height // self.downsampling_factor, - width // self.downsampling_factor, - ), - mode="bilinear", - ) - - # Free up memory from the last generation. - clear_cuda_cache = kwargs["clear_cuda_cache"] or None - if clear_cuda_cache is not None: - clear_cuda_cache() - - second_pass_noise = self.get_noise_like( - resized_latents, override_perlin=True - ) - - # Clear symmetry for the second pass - from dataclasses import replace - - new_postprocessing_settings = replace( - conditioning_data.postprocessing_settings, h_symmetry_time_pct=None - ) - new_postprocessing_settings = replace( - new_postprocessing_settings, v_symmetry_time_pct=None - ) - new_conditioning_data = replace( - conditioning_data, postprocessing_settings=new_postprocessing_settings - ) - - verbosity = get_verbosity() - set_verbosity_error() - pipeline_output = pipeline.img2img_from_latents_and_embeddings( - resized_latents, - num_inference_steps=steps, - conditioning_data=new_conditioning_data, - strength=strength, - noise=second_pass_noise, - callback=step_callback, - ) - set_verbosity(verbosity) - - if ( - pipeline_output.attention_map_saver is not None - and attention_maps_callback is not None - ): - attention_maps_callback(pipeline_output.attention_map_saver) - - return pipeline.numpy_to_pil(pipeline_output.images)[0] - - # FIXME: do we really need something entirely different for the inpainting model? - - # in the case of the inpainting model being loaded, the trick of - # providing an interpolated latent doesn't work, so we transiently - # create a 512x512 PIL image, upscale it, and run the inpainting - # over it in img2img mode. Because the inpaing model is so conservative - # it doesn't change the image (much) - - return make_image - - def get_noise_like(self, like: torch.Tensor, override_perlin: bool = False): - device = like.device - if device.type == "mps": - x = torch.randn_like(like, device="cpu", dtype=self.torch_dtype()).to( - device - ) - else: - x = torch.randn_like(like, device=device, dtype=self.torch_dtype()) - if self.perlin > 0.0 and override_perlin == False: - shape = like.shape - x = (1 - self.perlin) * x + self.perlin * self.get_perlin_noise( - shape[3], shape[2] - ) - return x - - # returns a tensor filled with random numbers from a normal distribution - def get_noise(self, width, height, scale=True): - # print(f"Get noise: {width}x{height}") - if scale: - # Scale the input width and height for the initial generation - # Make their area equivalent to the model's resolution area (e.g. 512*512 = 262144), - # while keeping the minimum dimension at least 0.5 * resolution (e.g. 512*0.5 = 256) - - aspect = width / height - dimension = self.model.unet.config.sample_size * self.model.vae_scale_factor - min_dimension = math.floor(dimension * 0.5) - model_area = ( - dimension * dimension - ) # hardcoded for now since all models are trained on square images - - if aspect > 1.0: - init_height = max(min_dimension, math.sqrt(model_area / aspect)) - init_width = init_height * aspect - else: - init_width = max(min_dimension, math.sqrt(model_area * aspect)) - init_height = init_width / aspect - - scaled_width, scaled_height = trim_to_multiple_of( - math.floor(init_width), math.floor(init_height) - ) - - else: - scaled_width = width - scaled_height = height - - device = self.model.device - channels = self.latent_channels - if channels == 9: - channels = 4 # we don't really want noise for all the mask channels - shape = ( - 1, - channels, - scaled_height // self.downsampling_factor, - scaled_width // self.downsampling_factor, - ) - if self.use_mps_noise or device.type == "mps": - tensor = torch.empty(size=shape, device="cpu") - tensor = self.get_noise_like(like=tensor).to(device) - else: - tensor = torch.empty(size=shape, device=device) - tensor = self.get_noise_like(like=tensor) - return tensor diff --git a/invokeai/backend/model_management/lora.py b/invokeai/backend/model_management/lora.py index 46638878aa..c351a76590 100644 --- a/invokeai/backend/model_management/lora.py +++ b/invokeai/backend/model_management/lora.py @@ -556,8 +556,8 @@ class ModelPatcher: new_tokens_added = None try: - ti_manager = TextualInversionManager() ti_tokenizer = copy.deepcopy(tokenizer) + ti_manager = TextualInversionManager(ti_tokenizer) init_tokens_count = text_encoder.resize_token_embeddings(None).num_embeddings def _get_trigger(ti, index): @@ -650,22 +650,24 @@ class TextualInversionModel: class TextualInversionManager(BaseTextualInversionManager): pad_tokens: Dict[int, List[int]] + tokenizer: CLIPTokenizer - def __init__(self): + def __init__(self, tokenizer: CLIPTokenizer): self.pad_tokens = dict() + self.tokenizer = tokenizer def expand_textual_inversion_token_ids_if_necessary( self, token_ids: list[int] ) -> list[int]: - #if token_ids[0] == self.tokenizer.bos_token_id: - # raise ValueError("token_ids must not start with bos_token_id") - #if token_ids[-1] == self.tokenizer.eos_token_id: - # raise ValueError("token_ids must not end with eos_token_id") - if len(self.pad_tokens) == 0: return token_ids + if token_ids[0] == self.tokenizer.bos_token_id: + raise ValueError("token_ids must not start with bos_token_id") + if token_ids[-1] == self.tokenizer.eos_token_id: + raise ValueError("token_ids must not end with eos_token_id") + new_token_ids = [] for token_id in token_ids: new_token_ids.append(token_id) diff --git a/invokeai/backend/model_management/models/textual_inversion.py b/invokeai/backend/model_management/models/textual_inversion.py index e8c96ff31e..66847f53eb 100644 --- a/invokeai/backend/model_management/models/textual_inversion.py +++ b/invokeai/backend/model_management/models/textual_inversion.py @@ -1,3 +1,4 @@ +import os import torch from typing import Optional from .base import ( diff --git a/invokeai/backend/prompting/__init__.py b/invokeai/backend/prompting/__init__.py deleted file mode 100644 index b52206dd94..0000000000 --- a/invokeai/backend/prompting/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Initialization file for invokeai.backend.prompting -""" -from .conditioning import ( - get_prompt_structure, - get_tokens_for_prompt_object, - get_uc_and_c_and_ec, - split_weighted_subprompts, -) diff --git a/invokeai/backend/prompting/conditioning.py b/invokeai/backend/prompting/conditioning.py deleted file mode 100644 index d070342794..0000000000 --- a/invokeai/backend/prompting/conditioning.py +++ /dev/null @@ -1,297 +0,0 @@ -""" -This module handles the generation of the conditioning tensors. - -Useful function exports: - -get_uc_and_c_and_ec() get the conditioned and unconditioned latent, and edited conditioning if we're doing cross-attention control - -""" -import re -import torch -from typing import Optional, Union - -from compel import Compel -from compel.prompt_parser import ( - Blend, - CrossAttentionControlSubstitute, - FlattenedPrompt, - Fragment, - PromptParser, - Conjunction, -) - -import invokeai.backend.util.logging as logger - -from invokeai.app.services.config import InvokeAIAppConfig -from ..stable_diffusion import InvokeAIDiffuserComponent -from ..util import torch_dtype - -config = InvokeAIAppConfig.get_config() - -def get_uc_and_c_and_ec(prompt_string, - model: InvokeAIDiffuserComponent, - log_tokens=False, skip_normalize_legacy_blend=False): - # lazy-load any deferred textual inversions. - # this might take a couple of seconds the first time a textual inversion is used. - model.textual_inversion_manager.create_deferred_token_ids_for_any_trigger_terms(prompt_string) - - compel = Compel(tokenizer=model.tokenizer, - text_encoder=model.text_encoder, - textual_inversion_manager=model.textual_inversion_manager, - dtype_for_device_getter=torch_dtype, - truncate_long_prompts=False, - ) - - # get rid of any newline characters - prompt_string = prompt_string.replace("\n", " ") - positive_prompt_string, negative_prompt_string = split_prompt_to_positive_and_negative(prompt_string) - - legacy_blend = try_parse_legacy_blend(positive_prompt_string, skip_normalize_legacy_blend) - positive_conjunction: Conjunction - if legacy_blend is not None: - positive_conjunction = legacy_blend - else: - positive_conjunction = Compel.parse_prompt_string(positive_prompt_string) - positive_prompt = positive_conjunction.prompts[0] - - negative_conjunction = Compel.parse_prompt_string(negative_prompt_string) - negative_prompt: FlattenedPrompt | Blend = negative_conjunction.prompts[0] - - tokens_count = get_max_token_count(model.tokenizer, positive_prompt) - if log_tokens or config.log_tokenization: - log_tokenization(positive_prompt, negative_prompt, tokenizer=model.tokenizer) - - c, options = compel.build_conditioning_tensor_for_prompt_object(positive_prompt) - uc, _ = compel.build_conditioning_tensor_for_prompt_object(negative_prompt) - [c, uc] = compel.pad_conditioning_tensors_to_same_length([c, uc]) - - ec = InvokeAIDiffuserComponent.ExtraConditioningInfo(tokens_count_including_eos_bos=tokens_count, - cross_attention_control_args=options.get( - 'cross_attention_control', None)) - return uc, c, ec - -def get_prompt_structure( - prompt_string, skip_normalize_legacy_blend: bool = False -) -> (Union[FlattenedPrompt, Blend], FlattenedPrompt): - ( - positive_prompt_string, - negative_prompt_string, - ) = split_prompt_to_positive_and_negative(prompt_string) - legacy_blend = try_parse_legacy_blend( - positive_prompt_string, skip_normalize_legacy_blend - ) - positive_prompt: Conjunction - if legacy_blend is not None: - positive_conjunction = legacy_blend - else: - positive_conjunction = Compel.parse_prompt_string(positive_prompt_string) - positive_prompt = positive_conjunction.prompts[0] - negative_conjunction = Compel.parse_prompt_string(negative_prompt_string) - negative_prompt: FlattenedPrompt|Blend = negative_conjunction.prompts[0] - - return positive_prompt, negative_prompt - -def get_max_token_count( - tokenizer, prompt: Union[FlattenedPrompt, Blend], truncate_if_too_long=False -) -> int: - if type(prompt) is Blend: - blend: Blend = prompt - return max( - [ - get_max_token_count(tokenizer, c, truncate_if_too_long) - for c in blend.prompts - ] - ) - else: - return len( - get_tokens_for_prompt_object(tokenizer, prompt, truncate_if_too_long) - ) - - -def get_tokens_for_prompt_object( - tokenizer, parsed_prompt: FlattenedPrompt, truncate_if_too_long=True -) -> [str]: - if type(parsed_prompt) is Blend: - raise ValueError( - "Blend is not supported here - you need to get tokens for each of its .children" - ) - - text_fragments = [ - x.text - if type(x) is Fragment - else ( - " ".join([f.text for f in x.original]) - if type(x) is CrossAttentionControlSubstitute - else str(x) - ) - for x in parsed_prompt.children - ] - text = " ".join(text_fragments) - tokens = tokenizer.tokenize(text) - if truncate_if_too_long: - max_tokens_length = tokenizer.model_max_length - 2 # typically 75 - tokens = tokens[0:max_tokens_length] - return tokens - - -def split_prompt_to_positive_and_negative(prompt_string_uncleaned: str): - unconditioned_words = "" - unconditional_regex = r"\[(.*?)\]" - unconditionals = re.findall(unconditional_regex, prompt_string_uncleaned) - if len(unconditionals) > 0: - unconditioned_words = " ".join(unconditionals) - - # Remove Unconditioned Words From Prompt - unconditional_regex_compile = re.compile(unconditional_regex) - clean_prompt = unconditional_regex_compile.sub(" ", prompt_string_uncleaned) - prompt_string_cleaned = re.sub(" +", " ", clean_prompt) - else: - prompt_string_cleaned = prompt_string_uncleaned - return prompt_string_cleaned, unconditioned_words - - -def log_tokenization( - positive_prompt: Union[Blend, FlattenedPrompt], - negative_prompt: Union[Blend, FlattenedPrompt], - tokenizer, -): - logger.info(f"[TOKENLOG] Parsed Prompt: {positive_prompt}") - logger.info(f"[TOKENLOG] Parsed Negative Prompt: {negative_prompt}") - - log_tokenization_for_prompt_object(positive_prompt, tokenizer) - log_tokenization_for_prompt_object( - negative_prompt, tokenizer, display_label_prefix="(negative prompt)" - ) - - -def log_tokenization_for_prompt_object( - p: Union[Blend, FlattenedPrompt], tokenizer, display_label_prefix=None -): - display_label_prefix = display_label_prefix or "" - if type(p) is Blend: - blend: Blend = p - for i, c in enumerate(blend.prompts): - log_tokenization_for_prompt_object( - c, - tokenizer, - display_label_prefix=f"{display_label_prefix}(blend part {i + 1}, weight={blend.weights[i]})", - ) - elif type(p) is FlattenedPrompt: - flattened_prompt: FlattenedPrompt = p - if flattened_prompt.wants_cross_attention_control: - original_fragments = [] - edited_fragments = [] - for f in flattened_prompt.children: - if type(f) is CrossAttentionControlSubstitute: - original_fragments += f.original - edited_fragments += f.edited - else: - original_fragments.append(f) - edited_fragments.append(f) - - original_text = " ".join([x.text for x in original_fragments]) - log_tokenization_for_text( - original_text, - tokenizer, - display_label=f"{display_label_prefix}(.swap originals)", - ) - edited_text = " ".join([x.text for x in edited_fragments]) - log_tokenization_for_text( - edited_text, - tokenizer, - display_label=f"{display_label_prefix}(.swap replacements)", - ) - else: - text = " ".join([x.text for x in flattened_prompt.children]) - log_tokenization_for_text( - text, tokenizer, display_label=display_label_prefix - ) - - -def log_tokenization_for_text(text, tokenizer, display_label=None, truncate_if_too_long=False): - """shows how the prompt is tokenized - # usually tokens have '' to indicate end-of-word, - # but for readability it has been replaced with ' ' - """ - tokens = tokenizer.tokenize(text) - tokenized = "" - discarded = "" - usedTokens = 0 - totalTokens = len(tokens) - - for i in range(0, totalTokens): - token = tokens[i].replace("", " ") - # alternate color - s = (usedTokens % 6) + 1 - if truncate_if_too_long and i >= tokenizer.model_max_length: - discarded = discarded + f"\x1b[0;3{s};40m{token}" - else: - tokenized = tokenized + f"\x1b[0;3{s};40m{token}" - usedTokens += 1 - - if usedTokens > 0: - logger.info(f'[TOKENLOG] Tokens {display_label or ""} ({usedTokens}):') - logger.debug(f"{tokenized}\x1b[0m") - - if discarded != "": - logger.info(f"[TOKENLOG] Tokens Discarded ({totalTokens - usedTokens}):") - logger.debug(f"{discarded}\x1b[0m") - -def try_parse_legacy_blend(text: str, skip_normalize: bool = False) -> Optional[Conjunction]: - weighted_subprompts = split_weighted_subprompts(text, skip_normalize=skip_normalize) - if len(weighted_subprompts) <= 1: - return None - strings = [x[0] for x in weighted_subprompts] - - pp = PromptParser() - parsed_conjunctions = [pp.parse_conjunction(x) for x in strings] - flattened_prompts = [] - weights = [] - for i, x in enumerate(parsed_conjunctions): - if len(x.prompts)>0: - flattened_prompts.append(x.prompts[0]) - weights.append(weighted_subprompts[i][1]) - return Conjunction([Blend(prompts=flattened_prompts, weights=weights, normalize_weights=not skip_normalize)]) - -def split_weighted_subprompts(text, skip_normalize=False) -> list: - """ - Legacy blend parsing. - - grabs all text up to the first occurrence of ':' - uses the grabbed text as a sub-prompt, and takes the value following ':' as weight - if ':' has no value defined, defaults to 1.0 - repeats until no text remaining - """ - prompt_parser = re.compile( - """ - (?P # capture group for 'prompt' - (?:\\\:|[^:])+ # match one or more non ':' characters or escaped colons '\:' - ) # end 'prompt' - (?: # non-capture group - :+ # match one or more ':' characters - (?P # capture group for 'weight' - -?\d+(?:\.\d+)? # match positive or negative integer or decimal number - )? # end weight capture group, make optional - \s* # strip spaces after weight - | # OR - $ # else, if no ':' then match end of line - ) # end non-capture group - """, - re.VERBOSE, - ) - parsed_prompts = [ - (match.group("prompt").replace("\\:", ":"), float(match.group("weight") or 1)) - for match in re.finditer(prompt_parser, text) - ] - if len(parsed_prompts) == 0: - return [] - if skip_normalize: - return parsed_prompts - weight_sum = sum(map(lambda x: x[1], parsed_prompts)) - if weight_sum == 0: - logger.warning( - "Subprompt weights add up to zero. Discarding and using even weights instead." - ) - equal_weight = 1 / max(len(parsed_prompts), 1) - return [(x[0], equal_weight) for x in parsed_prompts] - return [(x[0], x[1] / weight_sum) for x in parsed_prompts] diff --git a/invokeai/backend/stable_diffusion/__init__.py b/invokeai/backend/stable_diffusion/__init__.py index 55333d3589..ff47bd5f65 100644 --- a/invokeai/backend/stable_diffusion/__init__.py +++ b/invokeai/backend/stable_diffusion/__init__.py @@ -10,4 +10,3 @@ from .diffusers_pipeline import ( from .diffusion import InvokeAIDiffuserComponent from .diffusion.cross_attention_map_saving import AttentionMapSaver from .diffusion.shared_invokeai_diffusion import PostprocessingSettings -from .textual_inversion_manager import TextualInversionManager diff --git a/invokeai/backend/stable_diffusion/diffusers_pipeline.py b/invokeai/backend/stable_diffusion/diffusers_pipeline.py index 2922238af9..798f398ed6 100644 --- a/invokeai/backend/stable_diffusion/diffusers_pipeline.py +++ b/invokeai/backend/stable_diffusion/diffusers_pipeline.py @@ -16,7 +16,6 @@ from accelerate.utils import set_seed import psutil import torch import torchvision.transforms as T -from compel import EmbeddingsProvider from diffusers.models import AutoencoderKL, UNet2DConditionModel from diffusers.models.controlnet import ControlNetModel, ControlNetOutput from diffusers.pipelines.stable_diffusion import StableDiffusionPipelineOutput @@ -48,7 +47,6 @@ from .diffusion import ( PostprocessingSettings, ) from .offloading import FullyLoadedModelGroup, LazilyLoadedModelGroup, ModelGroup -from .textual_inversion_manager import TextualInversionManager @dataclass class PipelineIntermediateState: @@ -344,18 +342,6 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): self.invokeai_diffuser = InvokeAIDiffuserComponent( self.unet, self._unet_forward, is_running_diffusers=True ) - use_full_precision = precision == "float32" or precision == "autocast" - self.textual_inversion_manager = TextualInversionManager( - tokenizer=self.tokenizer, - text_encoder=self.text_encoder, - full_precision=use_full_precision, - ) - # InvokeAI's interface for text embeddings and whatnot - self.embeddings_provider = EmbeddingsProvider( - tokenizer=self.tokenizer, - text_encoder=self.text_encoder, - textual_inversion_manager=self.textual_inversion_manager, - ) self._model_group = FullyLoadedModelGroup(execution_device or self.unet.device) self._model_group.install(*self._submodels) @@ -405,50 +391,6 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): else: self.disable_attention_slicing() - def enable_offload_submodels(self, device: torch.device): - """ - Offload each submodel when it's not in use. - - Useful for low-vRAM situations where the size of the model in memory is a big chunk of - the total available resource, and you want to free up as much for inference as possible. - - This requires more moving parts and may add some delay as the U-Net is swapped out for the - VAE and vice-versa. - """ - models = self._submodels - if self._model_group is not None: - self._model_group.uninstall(*models) - group = LazilyLoadedModelGroup(device) - group.install(*models) - self._model_group = group - - def disable_offload_submodels(self): - """ - Leave all submodels loaded. - - Appropriate for cases where the size of the model in memory is small compared to the memory - required for inference. Avoids the delay and complexity of shuffling the submodels to and - from the GPU. - """ - models = self._submodels - if self._model_group is not None: - self._model_group.uninstall(*models) - group = FullyLoadedModelGroup(self._model_group.execution_device) - group.install(*models) - self._model_group = group - - def offload_all(self): - """Offload all this pipeline's models to CPU.""" - self._model_group.offload_current() - - def ready(self): - """ - Ready this pipeline's models. - - i.e. preload them to the GPU if appropriate. - """ - self._model_group.ready() - def to(self, torch_device: Optional[Union[str, torch.device]] = None, silence_dtype_warnings=False): # overridden method; types match the superclass. if torch_device is None: @@ -992,25 +934,6 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): device = self._model_group.device_for(self.safety_checker) return super().run_safety_checker(image, device, dtype) - @torch.inference_mode() - def get_learned_conditioning( - self, c: List[List[str]], *, return_tokens=True, fragment_weights=None - ): - """ - Compatibility function for invokeai.models.diffusion.ddpm.LatentDiffusion. - """ - return self.embeddings_provider.get_embeddings_for_weighted_prompt_fragments( - text_batch=c, - fragment_weights_batch=fragment_weights, - should_return_tokens=return_tokens, - device=self._model_group.device_for(self.unet), - ) - - @property - def channels(self) -> int: - """Compatible with DiffusionWrapper""" - return self.unet.config.in_channels - def decode_latents(self, latents): # Explicit call to get the vae loaded, since `decode` isn't the forward method. self._model_group.load(self.vae) diff --git a/invokeai/backend/stable_diffusion/textual_inversion_manager.py b/invokeai/backend/stable_diffusion/textual_inversion_manager.py deleted file mode 100644 index 9476c12dc5..0000000000 --- a/invokeai/backend/stable_diffusion/textual_inversion_manager.py +++ /dev/null @@ -1,429 +0,0 @@ -import traceback -from dataclasses import dataclass -from pathlib import Path -from typing import Optional, Union, List - -import safetensors.torch -import torch - -from compel.embeddings_provider import BaseTextualInversionManager -from picklescan.scanner import scan_file_path -from transformers import CLIPTextModel, CLIPTokenizer - -import invokeai.backend.util.logging as logger -from .concepts_lib import HuggingFaceConceptsLibrary - -@dataclass -class EmbeddingInfo: - name: str - embedding: torch.Tensor - num_vectors_per_token: int - token_dim: int - trained_steps: int = None - trained_model_name: str = None - trained_model_checksum: str = None - -@dataclass -class TextualInversion: - trigger_string: str - embedding: torch.Tensor - trigger_token_id: Optional[int] = None - pad_token_ids: Optional[list[int]] = None - - @property - def embedding_vector_length(self) -> int: - return self.embedding.shape[0] - - -class TextualInversionManager(BaseTextualInversionManager): - def __init__( - self, - tokenizer: CLIPTokenizer, - text_encoder: CLIPTextModel, - full_precision: bool = True, - ): - self.tokenizer = tokenizer - self.text_encoder = text_encoder - self.full_precision = full_precision - self.hf_concepts_library = HuggingFaceConceptsLibrary() - self.trigger_to_sourcefile = dict() - default_textual_inversions: list[TextualInversion] = [] - self.textual_inversions = default_textual_inversions - - def load_huggingface_concepts(self, concepts: list[str]): - for concept_name in concepts: - if concept_name in self.hf_concepts_library.concepts_loaded: - continue - trigger = self.hf_concepts_library.concept_to_trigger(concept_name) - if ( - self.has_textual_inversion_for_trigger_string(trigger) - or self.has_textual_inversion_for_trigger_string(concept_name) - or self.has_textual_inversion_for_trigger_string(f"<{concept_name}>") - ): # in case a token with literal angle brackets encountered - logger.info(f"Loaded local embedding for trigger {concept_name}") - continue - bin_file = self.hf_concepts_library.get_concept_model_path(concept_name) - if not bin_file: - continue - logger.info(f"Loaded remote embedding for trigger {concept_name}") - self.load_textual_inversion(bin_file) - self.hf_concepts_library.concepts_loaded[concept_name] = True - - def get_all_trigger_strings(self) -> list[str]: - return [ti.trigger_string for ti in self.textual_inversions] - - def load_textual_inversion( - self, ckpt_path: Union[str, Path], defer_injecting_tokens: bool = False - ): - ckpt_path = Path(ckpt_path) - - if not ckpt_path.is_file(): - return - - if str(ckpt_path).endswith(".DS_Store"): - return - - embedding_list = self._parse_embedding(str(ckpt_path)) - for embedding_info in embedding_list: - if (self.text_encoder.get_input_embeddings().weight.data[0].shape[0] != embedding_info.token_dim): - logger.warning( - f"Notice: {ckpt_path.parents[0].name}/{ckpt_path.name} was trained on a model with an incompatible token dimension: {self.text_encoder.get_input_embeddings().weight.data[0].shape[0]} vs {embedding_info.token_dim}." - ) - continue - - # Resolve the situation in which an earlier embedding has claimed the same - # trigger string. We replace the trigger with '', as we used to. - trigger_str = embedding_info.name - sourcefile = ( - f"{ckpt_path.parent.name}/{ckpt_path.name}" - if ckpt_path.name == "learned_embeds.bin" - else ckpt_path.name - ) - - if trigger_str in self.trigger_to_sourcefile: - replacement_trigger_str = ( - f"<{ckpt_path.parent.name}>" - if ckpt_path.name == "learned_embeds.bin" - else f"<{ckpt_path.stem}>" - ) - logger.info( - f"{sourcefile}: Trigger token '{trigger_str}' is already claimed by '{self.trigger_to_sourcefile[trigger_str]}'. Trigger this concept with {replacement_trigger_str}" - ) - trigger_str = replacement_trigger_str - - try: - self._add_textual_inversion( - trigger_str, - embedding_info.embedding, - defer_injecting_tokens=defer_injecting_tokens, - ) - # remember which source file claims this trigger - self.trigger_to_sourcefile[trigger_str] = sourcefile - - except ValueError as e: - logger.debug(f'Ignoring incompatible embedding {embedding_info["name"]}') - logger.debug(f"The error was {str(e)}") - - def _add_textual_inversion( - self, trigger_str, embedding, defer_injecting_tokens=False - ) -> Optional[TextualInversion]: - """ - Add a textual inversion to be recognised. - :param trigger_str: The trigger text in the prompt that activates this textual inversion. If unknown to the embedder's tokenizer, will be added. - :param embedding: The actual embedding data that will be inserted into the conditioning at the point where the token_str appears. - :return: The token id for the added embedding, either existing or newly-added. - """ - if trigger_str in [ti.trigger_string for ti in self.textual_inversions]: - logger.warning( - f"TextualInversionManager refusing to overwrite already-loaded token '{trigger_str}'" - ) - return - if not self.full_precision: - embedding = embedding.half() - if len(embedding.shape) == 1: - embedding = embedding.unsqueeze(0) - elif len(embedding.shape) > 2: - raise ValueError( - f"** TextualInversionManager cannot add {trigger_str} because the embedding shape {embedding.shape} is incorrect. The embedding must have shape [token_dim] or [V, token_dim] where V is vector length and token_dim is 768 for SD1 or 1280 for SD2." - ) - - try: - ti = TextualInversion(trigger_string=trigger_str, embedding=embedding) - if not defer_injecting_tokens: - self._inject_tokens_and_assign_embeddings(ti) - self.textual_inversions.append(ti) - return ti - - except ValueError as e: - if str(e).startswith("Warning"): - logger.warning(f"{str(e)}") - else: - traceback.print_exc() - logger.error( - f"TextualInversionManager was unable to add a textual inversion with trigger string {trigger_str}." - ) - raise - - def _inject_tokens_and_assign_embeddings(self, ti: TextualInversion) -> int: - if ti.trigger_token_id is not None: - raise ValueError( - f"Tokens already injected for textual inversion with trigger '{ti.trigger_string}'" - ) - - trigger_token_id = self._get_or_create_token_id_and_assign_embedding( - ti.trigger_string, ti.embedding[0] - ) - - if ti.embedding_vector_length > 1: - # for embeddings with vector length > 1 - pad_token_strings = [ - ti.trigger_string + "-!pad-" + str(pad_index) - for pad_index in range(1, ti.embedding_vector_length) - ] - # todo: batched UI for faster loading when vector length >2 - pad_token_ids = [ - self._get_or_create_token_id_and_assign_embedding( - pad_token_str, ti.embedding[1 + i] - ) - for (i, pad_token_str) in enumerate(pad_token_strings) - ] - else: - pad_token_ids = [] - - ti.trigger_token_id = trigger_token_id - ti.pad_token_ids = pad_token_ids - return ti.trigger_token_id - - def has_textual_inversion_for_trigger_string(self, trigger_string: str) -> bool: - try: - ti = self.get_textual_inversion_for_trigger_string(trigger_string) - return ti is not None - except StopIteration: - return False - - def get_textual_inversion_for_trigger_string( - self, trigger_string: str - ) -> TextualInversion: - return next( - ti for ti in self.textual_inversions if ti.trigger_string == trigger_string - ) - - def get_textual_inversion_for_token_id(self, token_id: int) -> TextualInversion: - return next( - ti for ti in self.textual_inversions if ti.trigger_token_id == token_id - ) - - def create_deferred_token_ids_for_any_trigger_terms( - self, prompt_string: str - ) -> list[int]: - injected_token_ids = [] - for ti in self.textual_inversions: - if ti.trigger_token_id is None and ti.trigger_string in prompt_string: - if ti.embedding_vector_length > 1: - logger.info( - f"Preparing tokens for textual inversion {ti.trigger_string}..." - ) - try: - self._inject_tokens_and_assign_embeddings(ti) - except ValueError as e: - logger.debug( - f"Ignoring incompatible embedding trigger {ti.trigger_string}" - ) - logger.debug(f"The error was {str(e)}") - continue - injected_token_ids.append(ti.trigger_token_id) - injected_token_ids.extend(ti.pad_token_ids) - return injected_token_ids - - def expand_textual_inversion_token_ids_if_necessary( - self, prompt_token_ids: list[int] - ) -> list[int]: - """ - Insert padding tokens as necessary into the passed-in list of token ids to match any textual inversions it includes. - - :param prompt_token_ids: The prompt as a list of token ids (`int`s). Should not include bos and eos markers. - :return: The prompt token ids with any necessary padding to account for textual inversions inserted. May be too - long - caller is responsible for prepending/appending eos and bos token ids, and truncating if necessary. - """ - if len(prompt_token_ids) == 0: - return prompt_token_ids - - if prompt_token_ids[0] == self.tokenizer.bos_token_id: - raise ValueError("prompt_token_ids must not start with bos_token_id") - if prompt_token_ids[-1] == self.tokenizer.eos_token_id: - raise ValueError("prompt_token_ids must not end with eos_token_id") - textual_inversion_trigger_token_ids = [ - ti.trigger_token_id for ti in self.textual_inversions - ] - prompt_token_ids = prompt_token_ids.copy() - for i, token_id in reversed(list(enumerate(prompt_token_ids))): - if token_id in textual_inversion_trigger_token_ids: - textual_inversion = next( - ti - for ti in self.textual_inversions - if ti.trigger_token_id == token_id - ) - for pad_idx in range(0, textual_inversion.embedding_vector_length - 1): - prompt_token_ids.insert( - i + pad_idx + 1, textual_inversion.pad_token_ids[pad_idx] - ) - - return prompt_token_ids - - def _get_or_create_token_id_and_assign_embedding( - self, token_str: str, embedding: torch.Tensor - ) -> int: - if len(embedding.shape) != 1: - raise ValueError( - "Embedding has incorrect shape - must be [token_dim] where token_dim is 768 for SD1 or 1280 for SD2" - ) - existing_token_id = self.tokenizer.convert_tokens_to_ids(token_str) - if existing_token_id == self.tokenizer.unk_token_id: - num_tokens_added = self.tokenizer.add_tokens(token_str) - current_embeddings = self.text_encoder.resize_token_embeddings(None) - current_token_count = current_embeddings.num_embeddings - new_token_count = current_token_count + num_tokens_added - # the following call is slow - todo make batched for better performance with vector length >1 - self.text_encoder.resize_token_embeddings(new_token_count) - - token_id = self.tokenizer.convert_tokens_to_ids(token_str) - if token_id == self.tokenizer.unk_token_id: - raise RuntimeError(f"Unable to find token id for token '{token_str}'") - if ( - self.text_encoder.get_input_embeddings().weight.data[token_id].shape - != embedding.shape - ): - raise ValueError( - f"Warning. Cannot load embedding for {token_str}. It was trained on a model with token dimension {embedding.shape[0]}, but the current model has token dimension {self.text_encoder.get_input_embeddings().weight.data[token_id].shape[0]}." - ) - self.text_encoder.get_input_embeddings().weight.data[token_id] = embedding - - return token_id - - - def _parse_embedding(self, embedding_file: str)->List[EmbeddingInfo]: - suffix = Path(embedding_file).suffix - try: - if suffix in [".pt",".ckpt",".bin"]: - scan_result = scan_file_path(embedding_file) - if scan_result.infected_files > 0: - logger.critical( - f"Security Issues Found in Model: {scan_result.issues_count}" - ) - logger.critical("For your safety, InvokeAI will not load this embed.") - return list() - ckpt = torch.load(embedding_file,map_location="cpu") - else: - ckpt = safetensors.torch.load_file(embedding_file) - except Exception as e: - logger.warning(f"Notice: unrecognized embedding file format: {embedding_file}: {e}") - return list() - - # try to figure out what kind of embedding file it is and parse accordingly - keys = list(ckpt.keys()) - if all(x in keys for x in ['string_to_token','string_to_param','name','step']): - return self._parse_embedding_v1(ckpt, embedding_file) # example rem_rezero.pt - - elif all(x in keys for x in ['string_to_token','string_to_param']): - return self._parse_embedding_v2(ckpt, embedding_file) # example midj-strong.pt - - elif 'emb_params' in keys: - return self._parse_embedding_v3(ckpt, embedding_file) # example easynegative.safetensors - - else: - return self._parse_embedding_v4(ckpt, embedding_file) # usually a '.bin' file - - def _parse_embedding_v1(self, embedding_ckpt: dict, file_path: str)->List[EmbeddingInfo]: - basename = Path(file_path).stem - logger.debug(f'Loading v1 embedding file: {basename}') - - embeddings = list() - token_counter = -1 - for token,embedding in embedding_ckpt["string_to_param"].items(): - if token_counter < 0: - trigger = embedding_ckpt["name"] - elif token_counter == 0: - trigger = '' - else: - trigger = f'<{basename}-{int(token_counter:=token_counter)}>' - token_counter += 1 - embedding_info = EmbeddingInfo( - name = trigger, - embedding = embedding, - num_vectors_per_token = embedding.size()[0], - token_dim = embedding.size()[1], - trained_steps = embedding_ckpt["step"], - trained_model_name = embedding_ckpt["sd_checkpoint_name"], - trained_model_checksum = embedding_ckpt["sd_checkpoint"] - ) - embeddings.append(embedding_info) - return embeddings - - def _parse_embedding_v2 ( - self, embedding_ckpt: dict, file_path: str - ) -> List[EmbeddingInfo]: - """ - This handles embedding .pt file variant #2. - """ - basename = Path(file_path).stem - logger.debug(f'Loading v2 embedding file: {basename}') - embeddings = list() - - if isinstance( - list(embedding_ckpt["string_to_token"].values())[0], torch.Tensor - ): - token_counter = 0 - for token,embedding in embedding_ckpt["string_to_param"].items(): - trigger = token if token != '*' \ - else f'<{basename}>' if token_counter == 0 \ - else f'<{basename}-{int(token_counter:=token_counter+1)}>' - embedding_info = EmbeddingInfo( - name = trigger, - embedding = embedding, - num_vectors_per_token = embedding.size()[0], - token_dim = embedding.size()[1], - ) - embeddings.append(embedding_info) - else: - logger.warning(f"{basename}: Unrecognized embedding format") - - return embeddings - - def _parse_embedding_v3(self, embedding_ckpt: dict, file_path: str)->List[EmbeddingInfo]: - """ - Parse 'version 3' of the .pt textual inversion embedding files. - """ - basename = Path(file_path).stem - logger.debug(f'Loading v3 embedding file: {basename}') - embedding = embedding_ckpt['emb_params'] - embedding_info = EmbeddingInfo( - name = f'<{basename}>', - embedding = embedding, - num_vectors_per_token = embedding.size()[0], - token_dim = embedding.size()[1], - ) - return [embedding_info] - - def _parse_embedding_v4(self, embedding_ckpt: dict, filepath: str)->List[EmbeddingInfo]: - """ - Parse 'version 4' of the textual inversion embedding files. This one - is usually associated with .bin files trained by HuggingFace diffusers. - """ - basename = Path(filepath).stem - short_path = Path(filepath).parents[0].name+'/'+Path(filepath).name - - logger.debug(f'Loading v4 embedding file: {short_path}') - - embeddings = list() - if list(embedding_ckpt.keys()) == 0: - logger.warning(f"Invalid embeddings file: {short_path}") - else: - for token,embedding in embedding_ckpt.items(): - embedding_info = EmbeddingInfo( - name = token or f"<{basename}>", - embedding = embedding, - num_vectors_per_token = 1, # All Concepts seem to default to 1 - token_dim = embedding.size()[0], - ) - embeddings.append(embedding_info) - return embeddings From a01998d095585ed3a29630eed46c4c5a692d318b Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Sat, 17 Jun 2023 19:39:51 +0300 Subject: [PATCH 007/110] Remove more old logic --- invokeai/app/invocations/generate.py | 2 +- invokeai/backend/__init__.py | 1 - invokeai/backend/generator/__init__.py | 1 - invokeai/backend/generator/base.py | 25 -- invokeai/backend/stable_diffusion/__init__.py | 1 - .../backend/stable_diffusion/concepts_lib.py | 275 ------------------ .../stable_diffusion/diffusers_pipeline.py | 2 +- .../diffusion/shared_invokeai_diffusion.py | 135 +-------- 8 files changed, 5 insertions(+), 437 deletions(-) delete mode 100644 invokeai/backend/stable_diffusion/concepts_lib.py diff --git a/invokeai/app/invocations/generate.py b/invokeai/app/invocations/generate.py index fc3aca4a19..7135e66a02 100644 --- a/invokeai/app/invocations/generate.py +++ b/invokeai/app/invocations/generate.py @@ -12,7 +12,7 @@ from invokeai.app.models.image import (ColorField, ImageCategory, ImageField, from invokeai.app.util.misc import SEED_MAX, get_random_seed from invokeai.backend.generator.inpaint import infill_methods -from ...backend.generator import Img2Img, Inpaint, InvokeAIGenerator, Txt2Img +from ...backend.generator import Inpaint, InvokeAIGenerator from ...backend.stable_diffusion import PipelineIntermediateState from ..util.step_callback import stable_diffusion_step_callback from .baseinvocation import BaseInvocation, InvocationConfig, InvocationContext diff --git a/invokeai/backend/__init__.py b/invokeai/backend/__init__.py index 55782bc445..ff8b4bc8c5 100644 --- a/invokeai/backend/__init__.py +++ b/invokeai/backend/__init__.py @@ -5,7 +5,6 @@ from .generator import ( InvokeAIGeneratorBasicParams, InvokeAIGenerator, InvokeAIGeneratorOutput, - Txt2Img, Img2Img, Inpaint ) diff --git a/invokeai/backend/generator/__init__.py b/invokeai/backend/generator/__init__.py index 9d6263453a..8a7f1c9167 100644 --- a/invokeai/backend/generator/__init__.py +++ b/invokeai/backend/generator/__init__.py @@ -5,7 +5,6 @@ from .base import ( InvokeAIGenerator, InvokeAIGeneratorBasicParams, InvokeAIGeneratorOutput, - Txt2Img, Img2Img, Inpaint, Generator, diff --git a/invokeai/backend/generator/base.py b/invokeai/backend/generator/base.py index a379cf6350..462b1a4f4b 100644 --- a/invokeai/backend/generator/base.py +++ b/invokeai/backend/generator/base.py @@ -175,13 +175,6 @@ class InvokeAIGenerator(metaclass=ABCMeta): ''' return Generator -# ------------------------------------ -class Txt2Img(InvokeAIGenerator): - @classmethod - def _generator_class(cls): - from .txt2img import Txt2Img - return Txt2Img - # ------------------------------------ class Img2Img(InvokeAIGenerator): def generate(self, @@ -235,24 +228,6 @@ class Inpaint(Img2Img): from .inpaint import Inpaint return Inpaint -# ------------------------------------ -class Embiggen(Txt2Img): - def generate( - self, - embiggen: list=None, - embiggen_tiles: list = None, - strength: float=0.75, - **kwargs)->Iterator[InvokeAIGeneratorOutput]: - return super().generate(embiggen=embiggen, - embiggen_tiles=embiggen_tiles, - strength=strength, - **kwargs) - - @classmethod - def _generator_class(cls): - from .embiggen import Embiggen - return Embiggen - class Generator: downsampling_factor: int latent_channels: int diff --git a/invokeai/backend/stable_diffusion/__init__.py b/invokeai/backend/stable_diffusion/__init__.py index ff47bd5f65..37024ccace 100644 --- a/invokeai/backend/stable_diffusion/__init__.py +++ b/invokeai/backend/stable_diffusion/__init__.py @@ -1,7 +1,6 @@ """ Initialization file for the invokeai.backend.stable_diffusion package """ -from .concepts_lib import HuggingFaceConceptsLibrary from .diffusers_pipeline import ( ConditioningData, PipelineIntermediateState, diff --git a/invokeai/backend/stable_diffusion/concepts_lib.py b/invokeai/backend/stable_diffusion/concepts_lib.py deleted file mode 100644 index 5294150783..0000000000 --- a/invokeai/backend/stable_diffusion/concepts_lib.py +++ /dev/null @@ -1,275 +0,0 @@ -""" -Query and install embeddings from the HuggingFace SD Concepts Library -at https://huggingface.co/sd-concepts-library. - -The interface is through the Concepts() object. -""" -import os -import re -from typing import Callable -from urllib import error as ul_error -from urllib import request - -from huggingface_hub import ( - HfApi, - HfFolder, - ModelFilter, - hf_hub_url, -) - -from invokeai.backend.util.logging import InvokeAILogger -from invokeai.app.services.config import InvokeAIAppConfig -logger = InvokeAILogger.getLogger() - -class HuggingFaceConceptsLibrary(object): - def __init__(self, root=None): - """ - Initialize the Concepts object. May optionally pass a root directory. - """ - self.config = InvokeAIAppConfig.get_config() - self.root = root or self.config.root - self.hf_api = HfApi() - self.local_concepts = dict() - self.concept_list = None - self.concepts_loaded = dict() - self.triggers = dict() # concept name to trigger phrase - self.concept_names = dict() # trigger phrase to concept name - self.match_trigger = re.compile( - "(<[\w\- >]+>)" - ) # trigger is slightly less restrictive than HF concept name - self.match_concept = re.compile( - "<([\w\-]+)>" - ) # HF concept name can only contain A-Za-z0-9_- - - def list_concepts(self) -> list: - """ - Return a list of all the concepts by name, without the 'sd-concepts-library' part. - Also adds local concepts in invokeai/embeddings folder. - """ - local_concepts_now = self.get_local_concepts( - os.path.join(self.root, "embeddings") - ) - local_concepts_to_add = set(local_concepts_now).difference( - set(self.local_concepts) - ) - self.local_concepts.update(local_concepts_now) - - if self.concept_list is not None: - if local_concepts_to_add: - self.concept_list.extend(list(local_concepts_to_add)) - return self.concept_list - return self.concept_list - elif self.config.internet_available is True: - try: - models = self.hf_api.list_models( - filter=ModelFilter(model_name="sd-concepts-library/") - ) - self.concept_list = [a.id.split("/")[1] for a in models] - # when init, add all in dir. when not init, add only concepts added between init and now - self.concept_list.extend(list(local_concepts_to_add)) - except Exception as e: - logger.warning( - f"Hugging Face textual inversion concepts libraries could not be loaded. The error was {str(e)}." - ) - logger.warning( - "You may load .bin and .pt file(s) manually using the --embedding_directory argument." - ) - return self.concept_list - else: - return self.concept_list - - def get_concept_model_path(self, concept_name: str) -> str: - """ - Returns the path to the 'learned_embeds.bin' file in - the named concept. Returns None if invalid or cannot - be downloaded. - """ - if not concept_name in self.list_concepts(): - logger.warning( - f"{concept_name} is not a local embedding trigger, nor is it a HuggingFace concept. Generation will continue without the concept." - ) - return None - return self.get_concept_file(concept_name.lower(), "learned_embeds.bin") - - def concept_to_trigger(self, concept_name: str) -> str: - """ - Given a concept name returns its trigger by looking in the - "token_identifier.txt" file. - """ - if concept_name in self.triggers: - return self.triggers[concept_name] - elif self.concept_is_local(concept_name): - trigger = f"<{concept_name}>" - self.triggers[concept_name] = trigger - self.concept_names[trigger] = concept_name - return trigger - - file = self.get_concept_file( - concept_name, "token_identifier.txt", local_only=True - ) - if not file: - return None - with open(file, "r") as f: - trigger = f.readline() - trigger = trigger.strip() - self.triggers[concept_name] = trigger - self.concept_names[trigger] = concept_name - return trigger - - def trigger_to_concept(self, trigger: str) -> str: - """ - Given a trigger phrase, maps it to the concept library name. - Only works if concept_to_trigger() has previously been called - on this library. There needs to be a persistent database for - this. - """ - concept = self.concept_names.get(trigger, None) - return f"<{concept}>" if concept else f"{trigger}" - - def replace_triggers_with_concepts(self, prompt: str) -> str: - """ - Given a prompt string that contains tags, replace these - tags with the concept name. The reason for this is so that the - concept names get stored in the prompt metadata. There is no - controlling of colliding triggers in the SD library, so it is - better to store the concept name (unique) than the concept trigger - (not necessarily unique!) - """ - if not prompt: - return prompt - triggers = self.match_trigger.findall(prompt) - if not triggers: - return prompt - - def do_replace(match) -> str: - return self.trigger_to_concept(match.group(1)) or f"<{match.group(1)}>" - - return self.match_trigger.sub(do_replace, prompt) - - def replace_concepts_with_triggers( - self, - prompt: str, - load_concepts_callback: Callable[[list], any], - excluded_tokens: list[str], - ) -> str: - """ - Given a prompt string that contains `` tags, replace - these tags with the appropriate trigger. - - If any `` tags are found, `load_concepts_callback()` is called with a list - of `concepts_name` strings. - - `excluded_tokens` are any tokens that should not be replaced, typically because they - are trigger tokens from a locally-loaded embedding. - """ - concepts = self.match_concept.findall(prompt) - if not concepts: - return prompt - load_concepts_callback(concepts) - - def do_replace(match) -> str: - if excluded_tokens and f"<{match.group(1)}>" in excluded_tokens: - return f"<{match.group(1)}>" - return self.concept_to_trigger(match.group(1)) or f"<{match.group(1)}>" - - return self.match_concept.sub(do_replace, prompt) - - def get_concept_file( - self, - concept_name: str, - file_name: str = "learned_embeds.bin", - local_only: bool = False, - ) -> str: - if not ( - self.concept_is_downloaded(concept_name) - or self.concept_is_local(concept_name) - or local_only - ): - self.download_concept(concept_name) - - # get local path in invokeai/embeddings if local concept - if self.concept_is_local(concept_name): - concept_path = self._concept_local_path(concept_name) - path = concept_path - else: - concept_path = self._concept_path(concept_name) - path = os.path.join(concept_path, file_name) - return path if os.path.exists(path) else None - - def concept_is_local(self, concept_name) -> bool: - return concept_name in self.local_concepts - - def concept_is_downloaded(self, concept_name) -> bool: - concept_directory = self._concept_path(concept_name) - return os.path.exists(concept_directory) - - def download_concept(self, concept_name) -> bool: - repo_id = self._concept_id(concept_name) - dest = self._concept_path(concept_name) - - access_token = HfFolder.get_token() - header = [("Authorization", f"Bearer {access_token}")] if access_token else [] - opener = request.build_opener() - opener.addheaders = header - request.install_opener(opener) - - os.makedirs(dest, exist_ok=True) - succeeded = True - - bytes = 0 - - def tally_download_size(chunk, size, total): - nonlocal bytes - if chunk == 0: - bytes += total - - logger.info(f"Downloading {repo_id}...", end="") - try: - for file in ( - "README.md", - "learned_embeds.bin", - "token_identifier.txt", - "type_of_concept.txt", - ): - url = hf_hub_url(repo_id, file) - request.urlretrieve( - url, os.path.join(dest, file), reporthook=tally_download_size - ) - except ul_error.HTTPError as e: - if e.code == 404: - logger.warning( - f"Concept {concept_name} is not known to the Hugging Face library. Generation will continue without the concept." - ) - else: - logger.warning( - f"Failed to download {concept_name}/{file} ({str(e)}. Generation will continue without the concept.)" - ) - os.rmdir(dest) - return False - except ul_error.URLError as e: - logger.error( - f"an error occurred while downloading {concept_name}: {str(e)}. This may reflect a network issue. Generation will continue without the concept." - ) - os.rmdir(dest) - return False - logger.info("...{:.2f}Kb".format(bytes / 1024)) - return succeeded - - def _concept_id(self, concept_name: str) -> str: - return f"sd-concepts-library/{concept_name}" - - def _concept_path(self, concept_name: str) -> str: - return os.path.join(self.root, "models", "sd-concepts-library", concept_name) - - def _concept_local_path(self, concept_name: str) -> str: - filename = self.local_concepts[concept_name] - return os.path.join(self.root, "embeddings", filename) - - def get_local_concepts(self, loc_dir: str): - locs_dic = dict() - if os.path.isdir(loc_dir): - for file in os.listdir(loc_dir): - f = os.path.splitext(file) - if f[1] == ".bin" or f[1] == ".pt": - locs_dic[f[0]] = file - return locs_dic diff --git a/invokeai/backend/stable_diffusion/diffusers_pipeline.py b/invokeai/backend/stable_diffusion/diffusers_pipeline.py index 798f398ed6..0010f33a0d 100644 --- a/invokeai/backend/stable_diffusion/diffusers_pipeline.py +++ b/invokeai/backend/stable_diffusion/diffusers_pipeline.py @@ -340,7 +340,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): # control_model=control_model, ) self.invokeai_diffuser = InvokeAIDiffuserComponent( - self.unet, self._unet_forward, is_running_diffusers=True + self.unet, self._unet_forward ) self._model_group = FullyLoadedModelGroup(execution_device or self.unet.device) diff --git a/invokeai/backend/stable_diffusion/diffusion/shared_invokeai_diffusion.py b/invokeai/backend/stable_diffusion/diffusion/shared_invokeai_diffusion.py index eec8097857..f3b09f6a9f 100644 --- a/invokeai/backend/stable_diffusion/diffusion/shared_invokeai_diffusion.py +++ b/invokeai/backend/stable_diffusion/diffusion/shared_invokeai_diffusion.py @@ -18,7 +18,6 @@ from .cross_attention_control import ( CrossAttentionType, SwapCrossAttnContext, get_cross_attention_modules, - restore_default_cross_attention, setup_cross_attention_control_attention_processors, ) from .cross_attention_map_saving import AttentionMapSaver @@ -66,7 +65,6 @@ class InvokeAIDiffuserComponent: self, model, model_forward_callback: ModelForwardCallback, - is_running_diffusers: bool = False, ): """ :param model: the unet model to pass through to cross attention control @@ -75,7 +73,6 @@ class InvokeAIDiffuserComponent: config = InvokeAIAppConfig.get_config() self.conditioning = None self.model = model - self.is_running_diffusers = is_running_diffusers self.model_forward_callback = model_forward_callback self.cross_attention_control_context = None self.sequential_guidance = config.sequential_guidance @@ -112,37 +109,6 @@ class InvokeAIDiffuserComponent: # TODO resuscitate attention map saving # self.remove_attention_map_saving() - # apparently unused code - # TODO: delete - # def override_cross_attention( - # self, conditioning: ExtraConditioningInfo, step_count: int - # ) -> Dict[str, AttentionProcessor]: - # """ - # setup cross attention .swap control. for diffusers this replaces the attention processor, so - # the previous attention processor is returned so that the caller can restore it later. - # """ - # self.conditioning = conditioning - # self.cross_attention_control_context = Context( - # arguments=self.conditioning.cross_attention_control_args, - # step_count=step_count, - # ) - # return override_cross_attention( - # self.model, - # self.cross_attention_control_context, - # is_running_diffusers=self.is_running_diffusers, - # ) - - def restore_default_cross_attention( - self, restore_attention_processor: Optional["AttentionProcessor"] = None - ): - self.conditioning = None - self.cross_attention_control_context = None - restore_default_cross_attention( - self.model, - is_running_diffusers=self.is_running_diffusers, - restore_attention_processor=restore_attention_processor, - ) - def setup_attention_map_saving(self, saver: AttentionMapSaver): def callback(slice, dim, offset, slice_size, key): if dim is not None: @@ -204,9 +170,7 @@ class InvokeAIDiffuserComponent: cross_attention_control_types_to_do = [] context: Context = self.cross_attention_control_context if self.cross_attention_control_context is not None: - percent_through = self.calculate_percent_through( - sigma, step_index, total_step_count - ) + percent_through = step_index / total_step_count cross_attention_control_types_to_do = ( context.get_active_cross_attention_control_types_for_step( percent_through @@ -264,9 +228,7 @@ class InvokeAIDiffuserComponent: total_step_count, ) -> torch.Tensor: if postprocessing_settings is not None: - percent_through = self.calculate_percent_through( - sigma, step_index, total_step_count - ) + percent_through = step_index / total_step_count latents = self.apply_threshold( postprocessing_settings, latents, percent_through ) @@ -275,22 +237,6 @@ class InvokeAIDiffuserComponent: ) return latents - def calculate_percent_through(self, sigma, step_index, total_step_count): - if step_index is not None and total_step_count is not None: - # 🧨diffusers codepath - percent_through = ( - step_index / total_step_count - ) # will never reach 1.0 - this is deliberate - else: - # legacy compvis codepath - # TODO remove when compvis codepath support is dropped - if step_index is None and sigma is None: - raise ValueError( - "Either step_index or sigma is required when doing cross attention control, but both are None." - ) - percent_through = self.estimate_percent_through(step_index, sigma) - return percent_through - # methods below are called from do_diffusion_step and should be considered private to this class. def _apply_standard_conditioning(self, x, sigma, unconditioning, conditioning, **kwargs): @@ -323,6 +269,7 @@ class InvokeAIDiffuserComponent: conditioned_next_x = conditioned_next_x.clone() return unconditioned_next_x, conditioned_next_x + # TODO: looks unused def _apply_hybrid_conditioning(self, x, sigma, unconditioning, conditioning, **kwargs): assert isinstance(conditioning, dict) assert isinstance(unconditioning, dict) @@ -350,34 +297,6 @@ class InvokeAIDiffuserComponent: conditioning, cross_attention_control_types_to_do, **kwargs, - ): - if self.is_running_diffusers: - return self._apply_cross_attention_controlled_conditioning__diffusers( - x, - sigma, - unconditioning, - conditioning, - cross_attention_control_types_to_do, - **kwargs, - ) - else: - return self._apply_cross_attention_controlled_conditioning__compvis( - x, - sigma, - unconditioning, - conditioning, - cross_attention_control_types_to_do, - **kwargs, - ) - - def _apply_cross_attention_controlled_conditioning__diffusers( - self, - x: torch.Tensor, - sigma, - unconditioning, - conditioning, - cross_attention_control_types_to_do, - **kwargs, ): context: Context = self.cross_attention_control_context @@ -409,54 +328,6 @@ class InvokeAIDiffuserComponent: ) return unconditioned_next_x, conditioned_next_x - def _apply_cross_attention_controlled_conditioning__compvis( - self, - x: torch.Tensor, - sigma, - unconditioning, - conditioning, - cross_attention_control_types_to_do, - **kwargs, - ): - # print('pct', percent_through, ': doing cross attention control on', cross_attention_control_types_to_do) - # slower non-batched path (20% slower on mac MPS) - # We are only interested in using attention maps for conditioned_next_x, but batching them with generation of - # unconditioned_next_x causes attention maps to *also* be saved for the unconditioned_next_x. - # This messes app their application later, due to mismatched shape of dim 0 (seems to be 16 for batched vs. 8) - # (For the batched invocation the `wrangler` function gets attention tensor with shape[0]=16, - # representing batched uncond + cond, but then when it comes to applying the saved attention, the - # wrangler gets an attention tensor which only has shape[0]=8, representing just self.edited_conditionings.) - # todo: give CrossAttentionControl's `wrangler` function more info so it can work with a batched call as well. - context: Context = self.cross_attention_control_context - - try: - unconditioned_next_x = self.model_forward_callback(x, sigma, unconditioning, **kwargs) - - # process x using the original prompt, saving the attention maps - # print("saving attention maps for", cross_attention_control_types_to_do) - for ca_type in cross_attention_control_types_to_do: - context.request_save_attention_maps(ca_type) - _ = self.model_forward_callback(x, sigma, conditioning, **kwargs,) - context.clear_requests(cleanup=False) - - # process x again, using the saved attention maps to control where self.edited_conditioning will be applied - # print("applying saved attention maps for", cross_attention_control_types_to_do) - for ca_type in cross_attention_control_types_to_do: - context.request_apply_saved_attention_maps(ca_type) - edited_conditioning = ( - self.conditioning.cross_attention_control_args.edited_conditioning - ) - conditioned_next_x = self.model_forward_callback( - x, sigma, edited_conditioning, **kwargs, - ) - context.clear_requests(cleanup=True) - - except: - context.clear_requests(cleanup=True) - raise - - return unconditioned_next_x, conditioned_next_x - def _combine(self, unconditioned_next_x, conditioned_next_x, guidance_scale): # to scale how much effect conditioning has, calculate the changes it does and then scale that scaled_delta = (conditioned_next_x - unconditioned_next_x) * guidance_scale From 3c60616b4d39fb04aaa09323ca7c7eb666374bd2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:49:59 +1000 Subject: [PATCH 008/110] feat(ui): simplify linear graph creation logic Instead of manually creating every node and edge, we can simply copy/paste the base graph from node editor, then sub in parameters. This is a much more intelligible process. We still need to handle seed, img2img fit and controlnet separately. --- .../listeners/userInvokedImageToImage.ts | 2 +- .../listeners/userInvokedTextToImage.ts | 2 +- .../nodes/util/addControlNetToLinearGraph.ts | 5 +- .../graphBuilders/buildImageToImageGraph.ts | 498 ++++++++---------- .../graphBuilders/buildTextToImageGraph.ts | 446 +++++++--------- .../nodes/util/graphBuilders/constants.ts | 17 + 6 files changed, 406 insertions(+), 564 deletions(-) create mode 100644 invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts index 7dcbe8a41d..dc67ebf073 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts @@ -1,10 +1,10 @@ import { startAppListening } from '..'; -import { buildImageToImageGraph } from 'features/nodes/util/graphBuilders/buildImageToImageGraph'; import { sessionCreated } from 'services/thunks/session'; import { log } from 'app/logging/useLogger'; import { imageToImageGraphBuilt } from 'features/nodes/store/actions'; import { userInvoked } from 'app/store/actions'; import { sessionReadyToInvoke } from 'features/system/store/actions'; +import { buildImageToImageGraph } from 'features/nodes/util/graphBuilders/buildImageToImageGraph'; const moduleLog = log.child({ namespace: 'invoke' }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts index 6042d86cb7..0538022d39 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts @@ -1,10 +1,10 @@ import { startAppListening } from '..'; -import { buildTextToImageGraph } from 'features/nodes/util/graphBuilders/buildTextToImageGraph'; import { sessionCreated } from 'services/thunks/session'; import { log } from 'app/logging/useLogger'; import { textToImageGraphBuilt } from 'features/nodes/store/actions'; import { userInvoked } from 'app/store/actions'; import { sessionReadyToInvoke } from 'features/system/store/actions'; +import { buildTextToImageGraph } from 'features/nodes/util/graphBuilders/buildTextToImageGraph'; const moduleLog = log.child({ namespace: 'invoke' }); diff --git a/invokeai/frontend/web/src/features/nodes/util/addControlNetToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/addControlNetToLinearGraph.ts index 1fd7eb2dba..dd5a97e2f1 100644 --- a/invokeai/frontend/web/src/features/nodes/util/addControlNetToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/addControlNetToLinearGraph.ts @@ -2,8 +2,7 @@ import { RootState } from 'app/store/store'; import { filter, forEach, size } from 'lodash-es'; import { CollectInvocation, ControlNetInvocation } from 'services/api'; import { NonNullableGraph } from '../types/types'; - -const CONTROL_NET_COLLECT = 'control_net_collect'; +import { CONTROL_NET_COLLECT } from './graphBuilders/constants'; export const addControlNetToLinearGraph = ( graph: NonNullableGraph, @@ -37,7 +36,7 @@ export const addControlNetToLinearGraph = ( }); } - forEach(controlNets, (controlNet, index) => { + forEach(controlNets, (controlNet) => { const { controlNetId, isEnabled, 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 4986d86713..a46af7199f 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts @@ -1,34 +1,30 @@ 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 { + ITERATE, + LATENTS_TO_IMAGE, + MODEL_LOADER, + NEGATIVE_CONDITIONING, + NOISE, + POSITIVE_CONDITIONING, + RANDOM_INT, + RANGE_OF_SIZE, + IMAGE_TO_IMAGE_GRAPH, + IMAGE_TO_LATENTS, + LATENTS_TO_LATENTS, + RESIZE, +} from './constants'; 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. */ @@ -36,7 +32,7 @@ export const buildImageToImageGraph = (state: RootState): Graph => { const { positivePrompt, negativePrompt, - model, + model: model_name, cfgScale: cfg_scale, scheduler, steps, @@ -50,298 +46,221 @@ export const buildImageToImageGraph = (state: RootState): Graph => { shouldRandomizeSeed, } = state.generation; + /** + * 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 + * ids. + * + * The only thing we need extra logic for is handling randomized seed, control net, and for img2img, + * the `fit` param. These are added to the graph at the end. + */ + if (!initialImage) { moduleLog.error('No initial image found in state'); throw new Error('No initial image found in state'); } + // copy-pasted graph from node editor, filled in with state values & friendly node ids 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', + id: IMAGE_TO_IMAGE_GRAPH, + nodes: { + [POSITIVE_CONDITIONING]: { + type: 'compel', + id: POSITIVE_CONDITIONING, + prompt: positivePrompt, }, - }); - } + [NEGATIVE_CONDITIONING]: { + type: 'compel', + id: NEGATIVE_CONDITIONING, + prompt: negativePrompt, + }, + [RANGE_OF_SIZE]: { + type: 'range_of_size', + id: RANGE_OF_SIZE, + // seed - must be connected manually + // start: 0, + size: iterations, + step: 1, + }, + [NOISE]: { + type: 'noise', + id: NOISE, + }, + [MODEL_LOADER]: { + type: 'sd1_model_loader', + id: MODEL_LOADER, + model_name, + }, + [LATENTS_TO_IMAGE]: { + type: 'l2i', + id: LATENTS_TO_IMAGE, + }, + [ITERATE]: { + type: 'iterate', + id: ITERATE, + }, + [LATENTS_TO_LATENTS]: { + type: 'l2l', + id: LATENTS_TO_LATENTS, + cfg_scale, + scheduler, + steps, + strength, + }, + [IMAGE_TO_LATENTS]: { + type: 'i2l', + id: IMAGE_TO_LATENTS, + // must be set manually later, bc `fit` parameter may require a resize node inserted + // image: { + // image_name: initialImage.image_name, + // }, + }, + }, + edges: [ + { + source: { + node_id: MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'vae', + }, + }, + { + source: { + node_id: RANGE_OF_SIZE, + field: 'collection', + }, + destination: { + node_id: ITERATE, + field: 'collection', + }, + }, + { + source: { + node_id: ITERATE, + field: 'item', + }, + destination: { + node_id: NOISE, + field: 'seed', + }, + }, + { + source: { + node_id: LATENTS_TO_LATENTS, + field: 'latents', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'latents', + }, + }, + { + source: { + node_id: IMAGE_TO_LATENTS, + field: 'latents', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'latents', + }, + }, + { + source: { + node_id: NOISE, + field: 'noise', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'noise', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: IMAGE_TO_LATENTS, + field: 'vae', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'unet', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'unet', + }, + }, + { + source: { + node_id: NEGATIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'negative_conditioning', + }, + }, + { + source: { + node_id: POSITIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'positive_conditioning', + }, + }, + ], + }; - // Single iteration, random seed - if (shouldRandomizeSeed && iterations === 1) { - // Random int node to generate the seed + // handle seed + if (shouldRandomizeSeed) { + // Random int node to generate the starting 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', - }, - }); + } else { + // User specified seed, so set the start of the range of size to the seed + (graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed; } + // handle `fit` if ( shouldFitToWidthHeight && (initialImage.width !== width || initialImage.height !== height) @@ -410,6 +329,7 @@ export const buildImageToImageGraph = (state: RootState): Graph => { }); } + // add controlnet addControlNetToLinearGraph(graph, LATENTS_TO_LATENTS, state); return graph; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts index ae71f569b6..945f96a9e3 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts @@ -1,34 +1,29 @@ import { RootState } from 'app/store/store'; +import { NonNullableGraph } from 'features/nodes/types/types'; import { - CompelInvocation, Graph, - IterateInvocation, - LatentsToImageInvocation, - NoiseInvocation, RandomIntInvocation, RangeOfSizeInvocation, - TextToLatentsInvocation, } from 'services/api'; -import { NonNullableGraph } from 'features/nodes/types/types'; +import { + ITERATE, + LATENTS_TO_IMAGE, + MODEL_LOADER, + NEGATIVE_CONDITIONING, + NOISE, + POSITIVE_CONDITIONING, + RANDOM_INT, + RANGE_OF_SIZE, + TEXT_TO_IMAGE_GRAPH, + TEXT_TO_LATENTS, +} from './constants'; import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; -const POSITIVE_CONDITIONING = 'positive_conditioning'; -const NEGATIVE_CONDITIONING = 'negative_conditioning'; -const TEXT_TO_LATENTS = 'text_to_latents'; -const LATENTS_TO_IMAGE = 'latents_to_image'; -const NOISE = 'noise'; -const RANDOM_INT = 'rand_int'; -const RANGE_OF_SIZE = 'range_of_size'; -const ITERATE = 'iterate'; - -/** - * Builds the Text to Image tab graph. - */ export const buildTextToImageGraph = (state: RootState): Graph => { const { positivePrompt, negativePrompt, - model, + model: model_name, cfgScale: cfg_scale, scheduler, steps, @@ -39,277 +34,188 @@ export const buildTextToImageGraph = (state: RootState): Graph => { shouldRandomizeSeed, } = state.generation; - const graph: NonNullableGraph = { - nodes: {}, - edges: [], - }; - - // Create the conditioning, t2l and l2i nodes - const positiveConditioningNode: CompelInvocation = { - id: POSITIVE_CONDITIONING, - type: 'compel', - prompt: positivePrompt, - model, - }; - - const negativeConditioningNode: CompelInvocation = { - id: NEGATIVE_CONDITIONING, - type: 'compel', - prompt: negativePrompt, - model, - }; - - const textToLatentsNode: TextToLatentsInvocation = { - id: TEXT_TO_LATENTS, - type: 't2l', - cfg_scale, - model, - scheduler, - steps, - }; - - const latentsToImageNode: LatentsToImageInvocation = { - id: LATENTS_TO_IMAGE, - type: 'l2i', - model, - }; - - // Add to the graph - graph.nodes[POSITIVE_CONDITIONING] = positiveConditioningNode; - graph.nodes[NEGATIVE_CONDITIONING] = negativeConditioningNode; - graph.nodes[TEXT_TO_LATENTS] = textToLatentsNode; - graph.nodes[LATENTS_TO_IMAGE] = latentsToImageNode; - - // Connect them - graph.edges.push({ - source: { node_id: POSITIVE_CONDITIONING, field: 'conditioning' }, - destination: { - node_id: TEXT_TO_LATENTS, - field: 'positive_conditioning', - }, - }); - - graph.edges.push({ - source: { node_id: NEGATIVE_CONDITIONING, field: 'conditioning' }, - destination: { - node_id: TEXT_TO_LATENTS, - field: 'negative_conditioning', - }, - }); - - graph.edges.push({ - source: { node_id: TEXT_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 + * 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 + * ids. * - * They all have different graphs and connections. + * The only thing we need extra logic for is handling randomized seed, control net, and for img2img, + * the `fit` param. These are added to the graph at the end. */ - // Single iteration, explicit seed - if (!shouldRandomizeSeed && iterations === 1) { - // Noise node using the explicit seed - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - seed: seed, - width, - height, - }; - - graph.nodes[NOISE] = noiseNode; - - // Connect noise to l2l - graph.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: TEXT_TO_LATENTS, - field: 'noise', + // copy-pasted graph from node editor, filled in with state values & friendly node ids + const graph: NonNullableGraph = { + id: TEXT_TO_IMAGE_GRAPH, + nodes: { + [POSITIVE_CONDITIONING]: { + type: 'compel', + id: POSITIVE_CONDITIONING, + prompt: positivePrompt, }, - }); - } + [NEGATIVE_CONDITIONING]: { + type: 'compel', + id: NEGATIVE_CONDITIONING, + prompt: negativePrompt, + }, + [RANGE_OF_SIZE]: { + type: 'range_of_size', + id: RANGE_OF_SIZE, + // start: 0, // seed - must be connected manually + size: iterations, + step: 1, + }, + [NOISE]: { + type: 'noise', + id: NOISE, + width, + height, + }, + [TEXT_TO_LATENTS]: { + type: 't2l', + id: TEXT_TO_LATENTS, + cfg_scale, + scheduler, + steps, + }, + [MODEL_LOADER]: { + type: 'sd1_model_loader', + id: MODEL_LOADER, + model_name, + }, + [LATENTS_TO_IMAGE]: { + type: 'l2i', + id: LATENTS_TO_IMAGE, + }, + [ITERATE]: { + type: 'iterate', + id: ITERATE, + }, + }, + edges: [ + { + source: { + node_id: NEGATIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'negative_conditioning', + }, + }, + { + source: { + node_id: POSITIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'positive_conditioning', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'unet', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'unet', + }, + }, + { + source: { + node_id: TEXT_TO_LATENTS, + field: 'latents', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'latents', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'vae', + }, + }, + { + source: { + node_id: RANGE_OF_SIZE, + field: 'collection', + }, + destination: { + node_id: ITERATE, + field: 'collection', + }, + }, + { + source: { + node_id: ITERATE, + field: 'item', + }, + destination: { + node_id: NOISE, + field: 'seed', + }, + }, + { + source: { + node_id: NOISE, + field: 'noise', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'noise', + }, + }, + ], + }; - // Single iteration, random seed - if (shouldRandomizeSeed && iterations === 1) { - // Random int node to generate the seed + // handle seed + if (shouldRandomizeSeed) { + // Random int node to generate the starting seed const randomIntNode: RandomIntInvocation = { id: RANDOM_INT, type: 'rand_int', }; - // Noise node without any seed - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - width, - height, - }; - 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 t2l - graph.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: TEXT_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', - width, - height, - }; - - // 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 t2l - graph.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: TEXT_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 t2l - graph.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: TEXT_TO_LATENTS, - field: 'noise', - }, - }); + } else { + // User specified seed, so set the start of the range of size to the seed + (graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed; } + // add controlnet addControlNetToLinearGraph(graph, TEXT_TO_LATENTS, state); return graph; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts new file mode 100644 index 0000000000..a65830e47f --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts @@ -0,0 +1,17 @@ +export const POSITIVE_CONDITIONING = 'positive_conditioning'; +export const NEGATIVE_CONDITIONING = 'negative_conditioning'; +export const TEXT_TO_LATENTS = 'text_to_latents'; +export const LATENTS_TO_IMAGE = 'latents_to_image'; +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 IMAGE_TO_LATENTS = 'image_to_latents'; +export const LATENTS_TO_LATENTS = 'latents_to_latents'; +export const RESIZE = 'resize_image'; + +export const TEXT_TO_IMAGE_GRAPH = 'text_to_image_graph'; +export const IMAGE_TO_IMAGE_GRAPH = 'image_to_image_graph'; + +export const CONTROL_NET_COLLECT = 'control_net_collect'; From 223a679ac1f19c189306eabf1abbb638e50a48a8 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 19 Jun 2023 15:49:14 +1000 Subject: [PATCH 009/110] chore(ui): regen api client --- .../frontend/web/src/services/api/index.ts | 26 ++++++- .../src/services/api/models/BaseModelType.ts | 8 ++ .../src/services/api/models/CkptModelInfo.ts | 8 ++ .../web/src/services/api/models/ClipField.ts | 22 ++++++ .../services/api/models/CompelInvocation.ts | 6 +- .../services/api/models/DiffusersModelInfo.ts | 10 ++- .../web/src/services/api/models/Graph.ts | 7 +- .../api/models/GraphExecutionState.ts | 4 +- .../api/models/ImageToImageInvocation.ts | 77 ------------------- .../api/models/ImageToLatentsInvocation.ts | 9 ++- .../services/api/models/InpaintInvocation.ts | 27 ++++--- .../api/models/LatentsToImageInvocation.ts | 9 ++- .../api/models/LatentsToLatentsInvocation.ts | 5 +- .../web/src/services/api/models/LoraInfo.ts | 31 ++++++++ .../api/models/LoraLoaderInvocation.ts | 38 +++++++++ .../services/api/models/LoraLoaderOutput.ts | 22 ++++++ .../web/src/services/api/models/ModelError.ts | 8 ++ .../web/src/services/api/models/ModelInfo.ts | 27 +++++++ .../services/api/models/ModelLoaderOutput.ts | 27 +++++++ .../web/src/services/api/models/ModelType.ts | 8 ++ .../services/api/models/ModelVariantType.ts | 8 ++ .../web/src/services/api/models/ModelsList.ts | 12 ++- .../api/models/SD1ModelLoaderInvocation.ts | 23 ++++++ .../api/models/SD2ModelLoaderInvocation.ts | 23 ++++++ .../api/models/SchedulerPredictionType.ts | 8 ++ .../src/services/api/models/SubModelType.ts | 8 ++ .../api/models/TextToImageInvocation.ts | 65 ---------------- .../api/models/TextToLatentsInvocation.ts | 5 +- .../web/src/services/api/models/UNetField.ts | 22 ++++++ .../web/src/services/api/models/VaeField.ts | 13 ++++ ...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/ModelsService.ts | 23 +++++- .../services/api/services/SessionsService.ts | 9 ++- 40 files changed, 549 insertions(+), 181 deletions(-) create mode 100644 invokeai/frontend/web/src/services/api/models/BaseModelType.ts create mode 100644 invokeai/frontend/web/src/services/api/models/ClipField.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/LoraInfo.ts create mode 100644 invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts create mode 100644 invokeai/frontend/web/src/services/api/models/ModelError.ts create mode 100644 invokeai/frontend/web/src/services/api/models/ModelInfo.ts create mode 100644 invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts create mode 100644 invokeai/frontend/web/src/services/api/models/ModelType.ts create mode 100644 invokeai/frontend/web/src/services/api/models/ModelVariantType.ts create mode 100644 invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/SchedulerPredictionType.ts create mode 100644 invokeai/frontend/web/src/services/api/models/SubModelType.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/UNetField.ts create mode 100644 invokeai/frontend/web/src/services/api/models/VaeField.ts create mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__controlnet__ControlNetModel__Config.ts create mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__lora__LoRAModel__Config.ts create mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config.ts create 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 cd83555f15..7481a5daad 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -7,9 +7,11 @@ 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 { Body_upload_image } from './models/Body_upload_image'; export type { CannyImageProcessorInvocation } from './models/CannyImageProcessorInvocation'; export type { CkptModelInfo } from './models/CkptModelInfo'; +export type { ClipField } from './models/ClipField'; export type { CollectInvocation } from './models/CollectInvocation'; export type { CollectInvocationOutput } from './models/CollectInvocationOutput'; export type { ColorField } from './models/ColorField'; @@ -53,7 +55,6 @@ export type { ImageProcessorInvocation } from './models/ImageProcessorInvocation export type { ImageRecordChanges } from './models/ImageRecordChanges'; export type { ImageResizeInvocation } from './models/ImageResizeInvocation'; export type { ImageScaleInvocation } from './models/ImageScaleInvocation'; -export type { ImageToImageInvocation } from './models/ImageToImageInvocation'; export type { ImageToLatentsInvocation } from './models/ImageToLatentsInvocation'; export type { ImageUrlsDTO } from './models/ImageUrlsDTO'; export type { InfillColorInvocation } from './models/InfillColorInvocation'; @@ -62,6 +63,14 @@ 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'; @@ -71,12 +80,20 @@ export type { LatentsToLatentsInvocation } from './models/LatentsToLatentsInvoca export type { LineartAnimeImageProcessorInvocation } from './models/LineartAnimeImageProcessorInvocation'; export type { LineartImageProcessorInvocation } from './models/LineartImageProcessorInvocation'; export type { LoadImageInvocation } from './models/LoadImageInvocation'; +export type { LoraInfo } from './models/LoraInfo'; +export type { LoraLoaderInvocation } from './models/LoraLoaderInvocation'; +export type { LoraLoaderOutput } from './models/LoraLoaderOutput'; export type { MaskFromAlphaInvocation } from './models/MaskFromAlphaInvocation'; export type { MaskOutput } from './models/MaskOutput'; export type { MediapipeFaceProcessorInvocation } from './models/MediapipeFaceProcessorInvocation'; export type { MidasDepthImageProcessorInvocation } from './models/MidasDepthImageProcessorInvocation'; export type { MlsdImageProcessorInvocation } from './models/MlsdImageProcessorInvocation'; +export type { ModelError } from './models/ModelError'; +export type { ModelInfo } from './models/ModelInfo'; +export type { ModelLoaderOutput } from './models/ModelLoaderOutput'; export type { ModelsList } from './models/ModelsList'; +export type { ModelType } from './models/ModelType'; +export type { ModelVariantType } from './models/ModelVariantType'; export type { MultiplyInvocation } from './models/MultiplyInvocation'; export type { NoiseInvocation } from './models/NoiseInvocation'; export type { NoiseOutput } from './models/NoiseOutput'; @@ -97,12 +114,17 @@ export type { ResizeLatentsInvocation } from './models/ResizeLatentsInvocation'; 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 { ShowImageInvocation } from './models/ShowImageInvocation'; export type { StepParamEasingInvocation } from './models/StepParamEasingInvocation'; +export type { SubModelType } from './models/SubModelType'; export type { SubtractInvocation } from './models/SubtractInvocation'; -export type { TextToImageInvocation } from './models/TextToImageInvocation'; export type { TextToLatentsInvocation } from './models/TextToLatentsInvocation'; +export type { UNetField } from './models/UNetField'; export type { UpscaleInvocation } from './models/UpscaleInvocation'; +export type { VaeField } from './models/VaeField'; 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/BaseModelType.ts b/invokeai/frontend/web/src/services/api/models/BaseModelType.ts new file mode 100644 index 0000000000..3f72e68fa4 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/BaseModelType.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type BaseModelType = 'sd-1' | 'sd-2'; diff --git a/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts b/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts index 2ae7c09674..cfa4357725 100644 --- a/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts @@ -7,6 +7,14 @@ export type CkptModelInfo = { * A description of the model */ description?: string; + /** + * The name of the model + */ + model_name: string; + /** + * The type of the model + */ + model_type: string; format?: 'ckpt'; /** * The path to the model config diff --git a/invokeai/frontend/web/src/services/api/models/ClipField.ts b/invokeai/frontend/web/src/services/api/models/ClipField.ts new file mode 100644 index 0000000000..f9ef2cc683 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ClipField.ts @@ -0,0 +1,22 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { LoraInfo } from './LoraInfo'; +import type { ModelInfo } from './ModelInfo'; + +export type ClipField = { + /** + * Info to load tokenizer submodel + */ + tokenizer: ModelInfo; + /** + * Info to load text_encoder submodel + */ + text_encoder: ModelInfo; + /** + * Loras to apply on model loading + */ + loras: Array; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts b/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts index 1dc390c1be..dd381ef22c 100644 --- a/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts @@ -2,6 +2,8 @@ /* tslint:disable */ /* eslint-disable */ +import type { ClipField } from './ClipField'; + /** * Parse prompt using compel package to conditioning. */ @@ -20,8 +22,8 @@ export type CompelInvocation = { */ prompt?: string; /** - * Model to use + * Clip to use */ - model?: string; + clip?: ClipField; }; diff --git a/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts b/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts index 5be4801cdd..4e722ddb80 100644 --- a/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts @@ -9,7 +9,15 @@ export type DiffusersModelInfo = { * A description of the model */ description?: string; - format?: 'diffusers'; + /** + * The name of the model + */ + model_name: string; + /** + * The type of the model + */ + model_type: string; + format?: 'folder'; /** * The VAE repo to use for this model */ diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index efac5dabcc..e148954f16 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -26,7 +26,6 @@ import type { ImagePasteInvocation } from './ImagePasteInvocation'; import type { ImageProcessorInvocation } from './ImageProcessorInvocation'; import type { ImageResizeInvocation } from './ImageResizeInvocation'; import type { ImageScaleInvocation } from './ImageScaleInvocation'; -import type { ImageToImageInvocation } from './ImageToImageInvocation'; import type { ImageToLatentsInvocation } from './ImageToLatentsInvocation'; import type { InfillColorInvocation } from './InfillColorInvocation'; import type { InfillPatchMatchInvocation } from './InfillPatchMatchInvocation'; @@ -38,6 +37,7 @@ import type { LatentsToLatentsInvocation } from './LatentsToLatentsInvocation'; import type { LineartAnimeImageProcessorInvocation } from './LineartAnimeImageProcessorInvocation'; import type { LineartImageProcessorInvocation } from './LineartImageProcessorInvocation'; import type { LoadImageInvocation } from './LoadImageInvocation'; +import type { LoraLoaderInvocation } from './LoraLoaderInvocation'; import type { MaskFromAlphaInvocation } from './MaskFromAlphaInvocation'; import type { MediapipeFaceProcessorInvocation } from './MediapipeFaceProcessorInvocation'; import type { MidasDepthImageProcessorInvocation } from './MidasDepthImageProcessorInvocation'; @@ -56,10 +56,11 @@ 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 { ShowImageInvocation } from './ShowImageInvocation'; import type { StepParamEasingInvocation } from './StepParamEasingInvocation'; import type { SubtractInvocation } from './SubtractInvocation'; -import type { TextToImageInvocation } from './TextToImageInvocation'; import type { TextToLatentsInvocation } from './TextToLatentsInvocation'; import type { UpscaleInvocation } from './UpscaleInvocation'; import type { ZoeDepthImageProcessorInvocation } from './ZoeDepthImageProcessorInvocation'; @@ -72,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 ccd5d6f499..602e7a2ebc 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts @@ -14,7 +14,9 @@ import type { IntCollectionOutput } from './IntCollectionOutput'; import type { IntOutput } from './IntOutput'; import type { IterateInvocationOutput } from './IterateInvocationOutput'; import type { LatentsOutput } from './LatentsOutput'; +import type { LoraLoaderOutput } from './LoraLoaderOutput'; import type { MaskOutput } from './MaskOutput'; +import type { ModelLoaderOutput } from './ModelLoaderOutput'; import type { NoiseOutput } from './NoiseOutput'; import type { PromptCollectionOutput } from './PromptCollectionOutput'; import type { PromptOutput } from './PromptOutput'; @@ -46,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/ImageToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts deleted file mode 100644 index e63ec93ada..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts +++ /dev/null @@ -1,77 +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 5569c2fa86..ace0ed8e3c 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts @@ -3,6 +3,7 @@ /* eslint-disable */ import type { ImageField } from './ImageField'; +import type { VaeField } from './VaeField'; /** * Encodes an image into latents. @@ -22,8 +23,12 @@ export type ImageToLatentsInvocation = { */ image?: ImageField; /** - * The model to use + * Vae submodel */ - model?: string; + vae?: VaeField; + /** + * Encode latents by overlaping tiles(less memory consumption) + */ + tiled?: boolean; }; diff --git a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts index 7eb0039c87..8489497c9d 100644 --- a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts @@ -3,7 +3,10 @@ /* eslint-disable */ import type { ColorField } from './ColorField'; +import type { ConditioningField } from './ConditioningField'; import type { ImageField } from './ImageField'; +import type { UNetField } from './UNetField'; +import type { VaeField } from './VaeField'; /** * Generates an image using inpaint. @@ -19,9 +22,13 @@ export type InpaintInvocation = { is_intermediate?: boolean; type?: 'inpaint'; /** - * The prompt to generate an image from + * Positive conditioning for generation */ - prompt?: string; + positive_conditioning?: ConditioningField; + /** + * Negative conditioning for generation + */ + negative_conditioning?: ConditioningField; /** * The seed to use (omit for random) */ @@ -47,21 +54,13 @@ export type InpaintInvocation = { */ scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'lms_k' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2s_k' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; /** - * The model to use (currently ignored) + * UNet model */ - model?: string; + unet?: UNetField; /** - * Whether or not to produce progress images during generation + * Vae model */ - progress_images?: boolean; - /** - * The control model to use - */ - control_model?: string; - /** - * The processed control image - */ - control_image?: ImageField; + vae?: VaeField; /** * The input image */ diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts index fcaa37d7e8..865eeff554 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts @@ -3,6 +3,7 @@ /* eslint-disable */ import type { LatentsField } from './LatentsField'; +import type { VaeField } from './VaeField'; /** * Generates an image from latents. @@ -22,8 +23,12 @@ export type LatentsToImageInvocation = { */ latents?: LatentsField; /** - * The model to use + * Vae submodel */ - model?: string; + vae?: VaeField; + /** + * Decode latents by overlaping tiles(less memory consumption) + */ + 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 174d368178..44c1e296c4 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts @@ -5,6 +5,7 @@ import type { ConditioningField } from './ConditioningField'; import type { ControlField } from './ControlField'; import type { LatentsField } from './LatentsField'; +import type { UNetField } from './UNetField'; /** * Generates latents using latents as base image. @@ -44,9 +45,9 @@ export type LatentsToLatentsInvocation = { */ scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'lms_k' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2s_k' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; /** - * The model to use (currently ignored) + * UNet submodel */ - model?: string; + unet?: UNetField; /** * The control to use */ diff --git a/invokeai/frontend/web/src/services/api/models/LoraInfo.ts b/invokeai/frontend/web/src/services/api/models/LoraInfo.ts new file mode 100644 index 0000000000..1a575d4147 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/LoraInfo.ts @@ -0,0 +1,31 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { BaseModelType } from './BaseModelType'; +import type { ModelType } from './ModelType'; +import type { SubModelType } from './SubModelType'; + +export type LoraInfo = { + /** + * Info to load submodel + */ + model_name: string; + /** + * Base model + */ + base_model: BaseModelType; + /** + * Info to load submodel + */ + model_type: ModelType; + /** + * Info to load submodel + */ + submodel?: SubModelType; + /** + * Lora's weight which to use when apply to model + */ + weight: number; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts new file mode 100644 index 0000000000..b93281c5a7 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts @@ -0,0 +1,38 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ClipField } from './ClipField'; +import type { UNetField } from './UNetField'; + +/** + * Apply selected lora to unet and text_encoder. + */ +export type LoraLoaderInvocation = { + /** + * 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?: 'lora_loader'; + /** + * Lora model name + */ + lora_name: string; + /** + * With what weight to apply lora + */ + weight?: number; + /** + * UNet model for applying lora + */ + unet?: UNetField; + /** + * Clip model for applying lora + */ + clip?: ClipField; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts b/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts new file mode 100644 index 0000000000..1fed1ebc58 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts @@ -0,0 +1,22 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ClipField } from './ClipField'; +import type { UNetField } from './UNetField'; + +/** + * Model loader output + */ +export type LoraLoaderOutput = { + type?: 'lora_loader_output'; + /** + * UNet submodel + */ + unet?: UNetField; + /** + * Tokenizer and text_encoder submodels + */ + clip?: ClipField; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/ModelError.ts b/invokeai/frontend/web/src/services/api/models/ModelError.ts new file mode 100644 index 0000000000..3151a764d6 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ModelError.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type ModelError = 'not_found'; diff --git a/invokeai/frontend/web/src/services/api/models/ModelInfo.ts b/invokeai/frontend/web/src/services/api/models/ModelInfo.ts new file mode 100644 index 0000000000..e87799d142 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ModelInfo.ts @@ -0,0 +1,27 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { BaseModelType } from './BaseModelType'; +import type { ModelType } from './ModelType'; +import type { SubModelType } from './SubModelType'; + +export type ModelInfo = { + /** + * Info to load submodel + */ + model_name: string; + /** + * Base model + */ + base_model: BaseModelType; + /** + * Info to load submodel + */ + model_type: ModelType; + /** + * Info to load submodel + */ + submodel?: SubModelType; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts b/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts new file mode 100644 index 0000000000..5b5b51e71f --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts @@ -0,0 +1,27 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ClipField } from './ClipField'; +import type { UNetField } from './UNetField'; +import type { VaeField } from './VaeField'; + +/** + * Model loader output + */ +export type ModelLoaderOutput = { + type?: 'model_loader_output'; + /** + * UNet submodel + */ + unet?: UNetField; + /** + * Tokenizer and text_encoder submodels + */ + clip?: ClipField; + /** + * Vae submodel + */ + vae?: VaeField; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/ModelType.ts b/invokeai/frontend/web/src/services/api/models/ModelType.ts new file mode 100644 index 0000000000..7d7abcafae --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ModelType.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type ModelType = 'pipeline' | 'vae' | 'lora' | 'controlnet' | 'embedding'; diff --git a/invokeai/frontend/web/src/services/api/models/ModelVariantType.ts b/invokeai/frontend/web/src/services/api/models/ModelVariantType.ts new file mode 100644 index 0000000000..0527c40bcf --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ModelVariantType.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type ModelVariantType = 'normal' | 'inpaint' | 'depth'; diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index 7a7449542d..a2d88d1967 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -2,10 +2,16 @@ /* tslint:disable */ /* eslint-disable */ -import type { CkptModelInfo } from './CkptModelInfo'; -import type { DiffusersModelInfo } from './DiffusersModelInfo'; +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; + models: Record>>; }; diff --git a/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts new file mode 100644 index 0000000000..9a8a23077a --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts @@ -0,0 +1,23 @@ +/* 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 new file mode 100644 index 0000000000..f477c11a8d --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts @@ -0,0 +1,23 @@ +/* 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/models/SchedulerPredictionType.ts b/invokeai/frontend/web/src/services/api/models/SchedulerPredictionType.ts new file mode 100644 index 0000000000..fa24aab5a1 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/SchedulerPredictionType.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type SchedulerPredictionType = 'epsilon' | 'v_prediction' | 'sample'; diff --git a/invokeai/frontend/web/src/services/api/models/SubModelType.ts b/invokeai/frontend/web/src/services/api/models/SubModelType.ts new file mode 100644 index 0000000000..12b055994c --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/SubModelType.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type SubModelType = 'unet' | 'text_encoder' | 'tokenizer' | 'vae' | 'scheduler' | 'safety_checker'; 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 7128ea8440..0000000000 --- a/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts +++ /dev/null @@ -1,65 +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 117533f106..4c194ab1c1 100644 --- a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts @@ -5,6 +5,7 @@ import type { ConditioningField } from './ConditioningField'; import type { ControlField } from './ControlField'; import type { LatentsField } from './LatentsField'; +import type { UNetField } from './UNetField'; /** * Generates latents from conditionings. @@ -44,9 +45,9 @@ export type TextToLatentsInvocation = { */ scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'lms_k' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2s_k' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; /** - * The model to use (currently ignored) + * UNet submodel */ - model?: string; + unet?: UNetField; /** * The control to use */ diff --git a/invokeai/frontend/web/src/services/api/models/UNetField.ts b/invokeai/frontend/web/src/services/api/models/UNetField.ts new file mode 100644 index 0000000000..ad3b1ddb5b --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/UNetField.ts @@ -0,0 +1,22 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { LoraInfo } from './LoraInfo'; +import type { ModelInfo } from './ModelInfo'; + +export type UNetField = { + /** + * Info to load unet submodel + */ + unet: ModelInfo; + /** + * Info to load scheduler submodel + */ + scheduler: ModelInfo; + /** + * Loras to apply on model loading + */ + loras: Array; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/VaeField.ts b/invokeai/frontend/web/src/services/api/models/VaeField.ts new file mode 100644 index 0000000000..bfe2793887 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/VaeField.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelInfo } from './ModelInfo'; + +export type VaeField = { + /** + * Info to load vae submodel + */ + vae: ModelInfo; +}; + 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 new file mode 100644 index 0000000000..f8decdb341 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__controlnet__ControlNetModel__Config.ts @@ -0,0 +1,14 @@ +/* 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 new file mode 100644 index 0000000000..614749a2c5 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__lora__LoRAModel__Config.ts @@ -0,0 +1,14 @@ +/* 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 new file mode 100644 index 0000000000..6bdcb87dd4 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig.ts @@ -0,0 +1,18 @@ +/* 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 new file mode 100644 index 0000000000..c88e042178 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig.ts @@ -0,0 +1,17 @@ +/* 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 new file mode 100644 index 0000000000..ec2ae4a845 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig.ts @@ -0,0 +1,21 @@ +/* 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 new file mode 100644 index 0000000000..67b897d9d9 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig.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 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 new file mode 100644 index 0000000000..f23d5002e3 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config.ts @@ -0,0 +1,14 @@ +/* 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 new file mode 100644 index 0000000000..d9314a6063 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__vae__VaeModel__Config.ts @@ -0,0 +1,14 @@ +/* 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/ModelsService.ts b/invokeai/frontend/web/src/services/api/services/ModelsService.ts index 3f8ae6bf7b..54580ce204 100644 --- a/invokeai/frontend/web/src/services/api/services/ModelsService.ts +++ b/invokeai/frontend/web/src/services/api/services/ModelsService.ts @@ -1,8 +1,10 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from '../models/BaseModelType'; import type { CreateModelRequest } from '../models/CreateModelRequest'; import type { ModelsList } from '../models/ModelsList'; +import type { ModelType } from '../models/ModelType'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; @@ -16,10 +18,29 @@ export class ModelsService { * @returns ModelsList Successful Response * @throws ApiError */ - public static listModels(): CancelablePromise { + public static listModels({ + baseModel, + modelType, + }: { + /** + * Base model + */ + baseModel?: BaseModelType, + /** + * The type of model to get + */ + modelType?: ModelType, + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/models/', + query: { + 'base_model': baseModel, + 'model_type': modelType, + }, + errors: { + 422: `Validation Error`, + }, }); } diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index d850a1ed38..2e4a83b25f 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -27,7 +27,6 @@ import type { ImagePasteInvocation } from '../models/ImagePasteInvocation'; import type { ImageProcessorInvocation } from '../models/ImageProcessorInvocation'; import type { ImageResizeInvocation } from '../models/ImageResizeInvocation'; import type { ImageScaleInvocation } from '../models/ImageScaleInvocation'; -import type { ImageToImageInvocation } from '../models/ImageToImageInvocation'; import type { ImageToLatentsInvocation } from '../models/ImageToLatentsInvocation'; import type { InfillColorInvocation } from '../models/InfillColorInvocation'; import type { InfillPatchMatchInvocation } from '../models/InfillPatchMatchInvocation'; @@ -39,6 +38,7 @@ import type { LatentsToLatentsInvocation } from '../models/LatentsToLatentsInvoc import type { LineartAnimeImageProcessorInvocation } from '../models/LineartAnimeImageProcessorInvocation'; import type { LineartImageProcessorInvocation } from '../models/LineartImageProcessorInvocation'; import type { LoadImageInvocation } from '../models/LoadImageInvocation'; +import type { LoraLoaderInvocation } from '../models/LoraLoaderInvocation'; import type { MaskFromAlphaInvocation } from '../models/MaskFromAlphaInvocation'; import type { MediapipeFaceProcessorInvocation } from '../models/MediapipeFaceProcessorInvocation'; import type { MidasDepthImageProcessorInvocation } from '../models/MidasDepthImageProcessorInvocation'; @@ -58,10 +58,11 @@ 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 { ShowImageInvocation } from '../models/ShowImageInvocation'; import type { StepParamEasingInvocation } from '../models/StepParamEasingInvocation'; import type { SubtractInvocation } from '../models/SubtractInvocation'; -import type { TextToImageInvocation } from '../models/TextToImageInvocation'; import type { TextToLatentsInvocation } from '../models/TextToLatentsInvocation'; import type { UpscaleInvocation } from '../models/UpscaleInvocation'; import type { ZoeDepthImageProcessorInvocation } from '../models/ZoeDepthImageProcessorInvocation'; @@ -174,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 | DynamicPromptInvocation | 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', @@ -211,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 | DynamicPromptInvocation | 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 41442eb7f658c76bbf9971654f47011a8aa9d9b6 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 19 Jun 2023 15:55:53 +1000 Subject: [PATCH 010/110] feat(ui): convert canvas txt2img & img2img to latents - Add graph builders for canvas txt2img & img2img - they are mostly copy and paste from the linear graph builders but different in a few ways that are very tricky to work around. Just made totally new functions for them. - Canvas txt2img and img2img support ControlNet (not inpaint/outpaint). There's no way to determine in real-time which mode the canvas is in just yet, so we cannot disable the ControlNet UI when the mode will be inpaint/outpaint - it will always display. It's possible to determine this in near-real-time, will add this at some point. - Canvas inpaint/outpaint migrated to use model loader, though inpaint/outpaint are still using the non-latents nodes. --- .../listeners/userInvokedCanvas.ts | 117 +++---- .../listeners/userInvokedImageToImage.ts | 4 +- .../listeners/userInvokedTextToImage.ts | 4 +- .../util/graphBuilders/buildCanvasGraph.ts | 133 ++----- .../buildCanvasImageToImageGraph.ts | 331 ++++++++++++++++++ .../graphBuilders/buildCanvasInpaintGraph.ts | 224 ++++++++++++ .../buildCanvasTextToImageGraph.ts | 224 ++++++++++++ ...aph.ts => buildLinearImageToImageGraph.ts} | 8 +- ...raph.ts => buildLinearTextToImageGraph.ts} | 20 +- .../nodes/util/graphBuilders/constants.ts | 7 +- .../UnifiedCanvas/UnifiedCanvasParameters.tsx | 3 +- 11 files changed, 890 insertions(+), 185 deletions(-) create mode 100644 invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts create mode 100644 invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts create mode 100644 invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts rename invokeai/frontend/web/src/features/nodes/util/graphBuilders/{buildImageToImageGraph.ts => buildLinearImageToImageGraph.ts} (98%) rename invokeai/frontend/web/src/features/nodes/util/graphBuilders/{buildTextToImageGraph.ts => buildLinearTextToImageGraph.ts} (93%) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts index 4d8177d7f3..a26d872d50 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts @@ -1,11 +1,10 @@ import { startAppListening } from '..'; import { sessionCreated } from 'services/thunks/session'; -import { buildCanvasGraphComponents } from 'features/nodes/util/graphBuilders/buildCanvasGraph'; +import { buildCanvasGraph } from 'features/nodes/util/graphBuilders/buildCanvasGraph'; import { log } from 'app/logging/useLogger'; import { canvasGraphBuilt } from 'features/nodes/store/actions'; import { imageUpdated, imageUploaded } from 'services/thunks/image'; -import { v4 as uuidv4 } from 'uuid'; -import { Graph } from 'services/api'; +import { ImageDTO } from 'services/api'; import { canvasSessionIdChanged, stagingAreaInitialized, @@ -67,112 +66,106 @@ export const addUserInvokedCanvasListener = () => { moduleLog.debug(`Generation mode: ${generationMode}`); - // Build the canvas graph - const graphComponents = await buildCanvasGraphComponents( - state, - generationMode - ); + // Temp placeholders for the init and mask images + let canvasInitImage: ImageDTO | undefined; + let canvasMaskImage: ImageDTO | undefined; - if (!graphComponents) { - moduleLog.error('Problem building graph'); - return; - } - - const { rangeNode, iterateNode, baseNode, edges } = graphComponents; - - // Assemble! Note that this graph *does not have the init or mask image set yet!* - const nodes: Graph['nodes'] = { - [rangeNode.id]: rangeNode, - [iterateNode.id]: iterateNode, - [baseNode.id]: baseNode, - }; - - const graph = { nodes, edges }; - - dispatch(canvasGraphBuilt(graph)); - - moduleLog.debug({ data: graph }, 'Canvas graph built'); - - // If we are generating img2img or inpaint, we need to upload the init images - if (baseNode.type === 'img2img' || baseNode.type === 'inpaint') { - const baseFilename = `${uuidv4()}.png`; - dispatch( + // For img2img and inpaint/outpaint, we need to upload the init images + if (['img2img', 'inpaint', 'outpaint'].includes(generationMode)) { + // upload the image, saving the request id + const { requestId: initImageUploadedRequestId } = dispatch( imageUploaded({ formData: { - file: new File([baseBlob], baseFilename, { type: 'image/png' }), + file: new File([baseBlob], 'canvasInitImage.png', { + type: 'image/png', + }), }, imageCategory: 'general', isIntermediate: true, }) ); - // Wait for the image to be uploaded - const [{ payload: baseImageDTO }] = await take( + // Wait for the image to be uploaded, matching by request id + const [{ payload }] = await take( (action): action is ReturnType => imageUploaded.fulfilled.match(action) && - action.meta.arg.formData.file.name === baseFilename + action.meta.requestId === initImageUploadedRequestId ); - // Update the base node with the image name and type - baseNode.image = { - image_name: baseImageDTO.image_name, - }; + canvasInitImage = payload; } - // For inpaint, we also need to upload the mask layer - if (baseNode.type === 'inpaint') { - const maskFilename = `${uuidv4()}.png`; - dispatch( + // For inpaint/outpaint, we also need to upload the mask layer + if (['inpaint', 'outpaint'].includes(generationMode)) { + // upload the image, saving the request id + const { requestId: maskImageUploadedRequestId } = dispatch( imageUploaded({ formData: { - file: new File([maskBlob], maskFilename, { type: 'image/png' }), + file: new File([maskBlob], 'canvasMaskImage.png', { + type: 'image/png', + }), }, imageCategory: 'mask', isIntermediate: true, }) ); - // Wait for the mask to be uploaded - const [{ payload: maskImageDTO }] = await take( + // Wait for the image to be uploaded, matching by request id + const [{ payload }] = await take( (action): action is ReturnType => imageUploaded.fulfilled.match(action) && - action.meta.arg.formData.file.name === maskFilename + action.meta.requestId === maskImageUploadedRequestId ); - // Update the base node with the image name and type - baseNode.mask = { - image_name: maskImageDTO.image_name, - }; + canvasMaskImage = payload; } - // Create the session and wait for response - dispatch(sessionCreated({ graph })); - const [sessionCreatedAction] = await take(sessionCreated.fulfilled.match); + const graph = buildCanvasGraph( + state, + generationMode, + canvasInitImage, + canvasMaskImage + ); + + moduleLog.debug({ graph }, `Canvas graph built`); + + // currently this action is just listened to for logging + dispatch(canvasGraphBuilt(graph)); + + // Create the session, store the request id + const { requestId: sessionCreatedRequestId } = dispatch( + sessionCreated({ graph }) + ); + + // Take the session created action, matching by its request id + const [sessionCreatedAction] = await take( + (action): action is ReturnType => + sessionCreated.fulfilled.match(action) && + action.meta.requestId === sessionCreatedRequestId + ); const sessionId = sessionCreatedAction.payload.id; // Associate the init image with the session, now that we have the session ID - if ( - (baseNode.type === 'img2img' || baseNode.type === 'inpaint') && - baseNode.image - ) { + if (['img2img', 'inpaint'].includes(generationMode) && canvasInitImage) { dispatch( imageUpdated({ - imageName: baseNode.image.image_name, + imageName: canvasInitImage.image_name, requestBody: { session_id: sessionId }, }) ); } // Associate the mask image with the session, now that we have the session ID - if (baseNode.type === 'inpaint' && baseNode.mask) { + if (['inpaint'].includes(generationMode) && canvasMaskImage) { dispatch( imageUpdated({ - imageName: baseNode.mask.image_name, + imageName: canvasMaskImage.image_name, requestBody: { session_id: sessionId }, }) ); } + // Prep the canvas staging area if it is not yet initialized if (!state.canvas.layerState.stagingArea.boundingBox) { dispatch( stagingAreaInitialized({ diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts index dc67ebf073..368d97a10f 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts @@ -4,7 +4,7 @@ import { log } from 'app/logging/useLogger'; import { imageToImageGraphBuilt } from 'features/nodes/store/actions'; import { userInvoked } from 'app/store/actions'; import { sessionReadyToInvoke } from 'features/system/store/actions'; -import { buildImageToImageGraph } from 'features/nodes/util/graphBuilders/buildImageToImageGraph'; +import { buildLinearImageToImageGraph } from 'features/nodes/util/graphBuilders/buildLinearImageToImageGraph'; const moduleLog = log.child({ namespace: 'invoke' }); @@ -15,7 +15,7 @@ export const addUserInvokedImageToImageListener = () => { effect: async (action, { getState, dispatch, take }) => { const state = getState(); - const graph = buildImageToImageGraph(state); + const graph = buildLinearImageToImageGraph(state); dispatch(imageToImageGraphBuilt(graph)); moduleLog.debug({ data: graph }, 'Image to Image graph built'); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts index 0538022d39..c76e0dfd4f 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts @@ -4,7 +4,7 @@ import { log } from 'app/logging/useLogger'; import { textToImageGraphBuilt } from 'features/nodes/store/actions'; import { userInvoked } from 'app/store/actions'; import { sessionReadyToInvoke } from 'features/system/store/actions'; -import { buildTextToImageGraph } from 'features/nodes/util/graphBuilders/buildTextToImageGraph'; +import { buildLinearTextToImageGraph } from 'features/nodes/util/graphBuilders/buildLinearTextToImageGraph'; const moduleLog = log.child({ namespace: 'invoke' }); @@ -15,7 +15,7 @@ export const addUserInvokedTextToImageListener = () => { effect: async (action, { getState, dispatch, take }) => { const state = getState(); - const graph = buildTextToImageGraph(state); + const graph = buildLinearTextToImageGraph(state); dispatch(textToImageGraphBuilt(graph)); diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts index 2d23b882ea..3ea513fe7e 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts @@ -1,116 +1,39 @@ import { RootState } from 'app/store/store'; -import { - Edge, - ImageToImageInvocation, - InpaintInvocation, - IterateInvocation, - RandomRangeInvocation, - RangeInvocation, - TextToImageInvocation, -} from 'services/api'; -import { buildImg2ImgNode } from '../nodeBuilders/buildImageToImageNode'; -import { buildTxt2ImgNode } from '../nodeBuilders/buildTextToImageNode'; -import { buildRangeNode } from '../nodeBuilders/buildRangeNode'; -import { buildIterateNode } from '../nodeBuilders/buildIterateNode'; -import { buildEdges } from '../edgeBuilders/buildEdges'; +import { ImageDTO } from 'services/api'; import { log } from 'app/logging/useLogger'; -import { buildInpaintNode } from '../nodeBuilders/buildInpaintNode'; +import { forEach } from 'lodash-es'; +import { buildCanvasInpaintGraph } from './buildCanvasInpaintGraph'; +import { NonNullableGraph } from 'features/nodes/types/types'; +import { buildCanvasImageToImageGraph } from './buildCanvasImageToImageGraph'; +import { buildCanvasTextToImageGraph } from './buildCanvasTextToImageGraph'; const moduleLog = log.child({ namespace: 'nodes' }); -const buildBaseNode = ( - nodeType: 'txt2img' | 'img2img' | 'inpaint' | 'outpaint', - state: RootState -): - | TextToImageInvocation - | ImageToImageInvocation - | InpaintInvocation - | undefined => { - const overrides = { - ...state.canvas.boundingBoxDimensions, - is_intermediate: true, - }; - - if (nodeType === 'txt2img') { - return buildTxt2ImgNode(state, overrides); - } - - if (nodeType === 'img2img') { - return buildImg2ImgNode(state, overrides); - } - - if (nodeType === 'inpaint' || nodeType === 'outpaint') { - return buildInpaintNode(state, overrides); - } -}; - -/** - * Builds the Canvas workflow graph and image blobs. - */ -export const buildCanvasGraphComponents = async ( +export const buildCanvasGraph = ( state: RootState, - generationMode: 'txt2img' | 'img2img' | 'inpaint' | 'outpaint' -): Promise< - | { - rangeNode: RangeInvocation | RandomRangeInvocation; - iterateNode: IterateInvocation; - baseNode: - | TextToImageInvocation - | ImageToImageInvocation - | InpaintInvocation; - edges: Edge[]; - } - | undefined -> => { - // The base node is a txt2img, img2img or inpaint node - const baseNode = buildBaseNode(generationMode, state); + generationMode: 'txt2img' | 'img2img' | 'inpaint' | 'outpaint', + canvasInitImage: ImageDTO | undefined, + canvasMaskImage: ImageDTO | undefined +) => { + let graph: NonNullableGraph; - if (!baseNode) { - moduleLog.error('Problem building base node'); - return; + if (generationMode === 'txt2img') { + graph = buildCanvasTextToImageGraph(state); + } else if (generationMode === 'img2img') { + if (!canvasInitImage) { + throw new Error('Missing canvas init image'); + } + graph = buildCanvasImageToImageGraph(state, canvasInitImage); + } else { + if (!canvasInitImage || !canvasMaskImage) { + throw new Error('Missing canvas init and mask images'); + } + graph = buildCanvasInpaintGraph(state, canvasInitImage, canvasMaskImage); } - if (baseNode.type === 'inpaint') { - const { - seamSize, - seamBlur, - seamSteps, - seamStrength, - tileSize, - infillMethod, - } = state.generation; + forEach(graph.nodes, (node) => { + graph.nodes[node.id].is_intermediate = true; + }); - const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = - state.canvas; - - if (boundingBoxScaleMethod !== 'none') { - baseNode.inpaint_width = scaledBoundingBoxDimensions.width; - baseNode.inpaint_height = scaledBoundingBoxDimensions.height; - } - - baseNode.seam_size = seamSize; - baseNode.seam_blur = seamBlur; - baseNode.seam_strength = seamStrength; - baseNode.seam_steps = seamSteps; - baseNode.infill_method = infillMethod as InpaintInvocation['infill_method']; - - if (infillMethod === 'tile') { - baseNode.tile_size = tileSize; - } - } - - // We always range and iterate nodes, no matter the iteration count - // This is required to provide the correct seeds to the backend engine - const rangeNode = buildRangeNode(state); - const iterateNode = buildIterateNode(); - - // Build the edges for the nodes selected. - const edges = buildEdges(baseNode, rangeNode, iterateNode); - - return { - rangeNode, - iterateNode, - baseNode, - edges, - }; + return graph; }; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts new file mode 100644 index 0000000000..efaeaddff2 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts @@ -0,0 +1,331 @@ +import { RootState } from 'app/store/store'; +import { + ImageDTO, + ImageResizeInvocation, + RandomIntInvocation, + RangeOfSizeInvocation, +} from 'services/api'; +import { NonNullableGraph } from 'features/nodes/types/types'; +import { log } from 'app/logging/useLogger'; +import { + ITERATE, + LATENTS_TO_IMAGE, + MODEL_LOADER, + NEGATIVE_CONDITIONING, + NOISE, + POSITIVE_CONDITIONING, + RANDOM_INT, + RANGE_OF_SIZE, + IMAGE_TO_IMAGE_GRAPH, + IMAGE_TO_LATENTS, + LATENTS_TO_LATENTS, + RESIZE, +} from './constants'; +import { set } from 'lodash-es'; +import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; + +const moduleLog = log.child({ namespace: 'nodes' }); + +/** + * Builds the Canvas tab's Image to Image graph. + */ +export const buildCanvasImageToImageGraph = ( + state: RootState, + initialImage: ImageDTO +): NonNullableGraph => { + const { + positivePrompt, + negativePrompt, + model: model_name, + cfgScale: cfg_scale, + scheduler, + steps, + img2imgStrength: strength, + iterations, + seed, + shouldRandomizeSeed, + } = state.generation; + + // The bounding box determines width and height, not the width and height params + const { width, height } = state.canvas.boundingBoxDimensions; + + /** + * 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 + * ids. + * + * The only thing we need extra logic for is handling randomized seed, control net, and for img2img, + * the `fit` param. These are added to the graph at the end. + */ + + // copy-pasted graph from node editor, filled in with state values & friendly node ids + const graph: NonNullableGraph = { + id: IMAGE_TO_IMAGE_GRAPH, + nodes: { + [POSITIVE_CONDITIONING]: { + type: 'compel', + id: POSITIVE_CONDITIONING, + prompt: positivePrompt, + }, + [NEGATIVE_CONDITIONING]: { + type: 'compel', + id: NEGATIVE_CONDITIONING, + prompt: negativePrompt, + }, + [RANGE_OF_SIZE]: { + type: 'range_of_size', + id: RANGE_OF_SIZE, + // seed - must be connected manually + // start: 0, + size: iterations, + step: 1, + }, + [NOISE]: { + type: 'noise', + id: NOISE, + }, + [MODEL_LOADER]: { + type: 'sd1_model_loader', + id: MODEL_LOADER, + model_name, + }, + [LATENTS_TO_IMAGE]: { + type: 'l2i', + id: LATENTS_TO_IMAGE, + }, + [ITERATE]: { + type: 'iterate', + id: ITERATE, + }, + [LATENTS_TO_LATENTS]: { + type: 'l2l', + id: LATENTS_TO_LATENTS, + cfg_scale, + scheduler, + steps, + strength, + }, + [IMAGE_TO_LATENTS]: { + type: 'i2l', + id: IMAGE_TO_LATENTS, + // must be set manually later, bc `fit` parameter may require a resize node inserted + // image: { + // image_name: initialImage.image_name, + // }, + }, + }, + edges: [ + { + source: { + node_id: MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'vae', + }, + }, + { + source: { + node_id: RANGE_OF_SIZE, + field: 'collection', + }, + destination: { + node_id: ITERATE, + field: 'collection', + }, + }, + { + source: { + node_id: ITERATE, + field: 'item', + }, + destination: { + node_id: NOISE, + field: 'seed', + }, + }, + { + source: { + node_id: LATENTS_TO_LATENTS, + field: 'latents', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'latents', + }, + }, + { + source: { + node_id: IMAGE_TO_LATENTS, + field: 'latents', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'latents', + }, + }, + { + source: { + node_id: NOISE, + field: 'noise', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'noise', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: IMAGE_TO_LATENTS, + field: 'vae', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'unet', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'unet', + }, + }, + { + source: { + node_id: NEGATIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'negative_conditioning', + }, + }, + { + source: { + node_id: POSITIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'positive_conditioning', + }, + }, + ], + }; + + // handle seed + if (shouldRandomizeSeed) { + // Random int node to generate the starting seed + const randomIntNode: RandomIntInvocation = { + id: RANDOM_INT, + type: 'rand_int', + }; + + graph.nodes[RANDOM_INT] = randomIntNode; + + // 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' }, + }); + } else { + // User specified seed, so set the start of the range of size to the seed + (graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed; + } + + // handle `fit` + if (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.image_name, + }, + is_intermediate: true, + width, + height, + }; + + 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.image_name, + }); + + // 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', + }, + }); + } + + // add controlnet + addControlNetToLinearGraph(graph, LATENTS_TO_LATENTS, state); + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts new file mode 100644 index 0000000000..785e1d2fdb --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts @@ -0,0 +1,224 @@ +import { RootState } from 'app/store/store'; +import { + ImageDTO, + InpaintInvocation, + RandomIntInvocation, + RangeOfSizeInvocation, +} from 'services/api'; +import { NonNullableGraph } from 'features/nodes/types/types'; +import { log } from 'app/logging/useLogger'; +import { + ITERATE, + MODEL_LOADER, + NEGATIVE_CONDITIONING, + POSITIVE_CONDITIONING, + RANDOM_INT, + RANGE_OF_SIZE, + INPAINT_GRAPH, + INPAINT, +} from './constants'; + +const moduleLog = log.child({ namespace: 'nodes' }); + +/** + * Builds the Canvas tab's Inpaint graph. + */ +export const buildCanvasInpaintGraph = ( + state: RootState, + canvasInitImage: ImageDTO, + canvasMaskImage: ImageDTO +): NonNullableGraph => { + const { + positivePrompt, + negativePrompt, + model: model_name, + cfgScale: cfg_scale, + scheduler, + steps, + img2imgStrength: strength, + shouldFitToWidthHeight, + iterations, + seed, + shouldRandomizeSeed, + seamSize, + seamBlur, + seamSteps, + seamStrength, + tileSize, + infillMethod, + } = state.generation; + + // The bounding box determines width and height, not the width and height params + const { width, height } = state.canvas.boundingBoxDimensions; + + // We may need to set the inpaint width and height to scale the image + const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas; + + const graph: NonNullableGraph = { + id: INPAINT_GRAPH, + nodes: { + [INPAINT]: { + type: 'inpaint', + id: INPAINT, + steps, + width, + height, + cfg_scale, + scheduler, + image: { + image_name: canvasInitImage.image_name, + }, + strength, + fit: shouldFitToWidthHeight, + mask: { + image_name: canvasMaskImage.image_name, + }, + seam_size: seamSize, + seam_blur: seamBlur, + seam_strength: seamStrength, + seam_steps: seamSteps, + tile_size: infillMethod === 'tile' ? tileSize : undefined, + infill_method: infillMethod as InpaintInvocation['infill_method'], + inpaint_width: + boundingBoxScaleMethod !== 'none' + ? scaledBoundingBoxDimensions.width + : undefined, + inpaint_height: + boundingBoxScaleMethod !== 'none' + ? scaledBoundingBoxDimensions.height + : undefined, + }, + [POSITIVE_CONDITIONING]: { + type: 'compel', + id: POSITIVE_CONDITIONING, + prompt: positivePrompt, + }, + [NEGATIVE_CONDITIONING]: { + type: 'compel', + id: NEGATIVE_CONDITIONING, + prompt: negativePrompt, + }, + [MODEL_LOADER]: { + type: 'sd1_model_loader', + id: MODEL_LOADER, + model_name, + }, + [RANGE_OF_SIZE]: { + type: 'range_of_size', + id: RANGE_OF_SIZE, + // seed - must be connected manually + // start: 0, + size: iterations, + step: 1, + }, + [ITERATE]: { + type: 'iterate', + id: ITERATE, + }, + }, + edges: [ + { + source: { + node_id: NEGATIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: INPAINT, + field: 'negative_conditioning', + }, + }, + { + source: { + node_id: POSITIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: INPAINT, + field: 'positive_conditioning', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'unet', + }, + destination: { + node_id: INPAINT, + field: 'unet', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: INPAINT, + field: 'vae', + }, + }, + { + source: { + node_id: RANGE_OF_SIZE, + field: 'collection', + }, + destination: { + node_id: ITERATE, + field: 'collection', + }, + }, + { + source: { + node_id: ITERATE, + field: 'item', + }, + destination: { + node_id: INPAINT, + field: 'seed', + }, + }, + ], + }; + + // handle seed + if (shouldRandomizeSeed) { + // Random int node to generate the starting seed + const randomIntNode: RandomIntInvocation = { + id: RANDOM_INT, + type: 'rand_int', + }; + + graph.nodes[RANDOM_INT] = randomIntNode; + + // 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' }, + }); + } else { + // User specified seed, so set the start of the range of size to the seed + (graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed; + } + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts new file mode 100644 index 0000000000..ca0e56e849 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts @@ -0,0 +1,224 @@ +import { RootState } from 'app/store/store'; +import { NonNullableGraph } from 'features/nodes/types/types'; +import { RandomIntInvocation, RangeOfSizeInvocation } from 'services/api'; +import { + ITERATE, + LATENTS_TO_IMAGE, + MODEL_LOADER, + NEGATIVE_CONDITIONING, + NOISE, + POSITIVE_CONDITIONING, + RANDOM_INT, + RANGE_OF_SIZE, + TEXT_TO_IMAGE_GRAPH, + TEXT_TO_LATENTS, +} from './constants'; +import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; + +/** + * Builds the Canvas tab's Text to Image graph. + */ +export const buildCanvasTextToImageGraph = ( + state: RootState +): NonNullableGraph => { + const { + positivePrompt, + negativePrompt, + model: model_name, + cfgScale: cfg_scale, + scheduler, + steps, + iterations, + seed, + shouldRandomizeSeed, + } = state.generation; + + // The bounding box determines width and height, not the width and height params + const { width, height } = state.canvas.boundingBoxDimensions; + + /** + * 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 + * ids. + * + * The only thing we need extra logic for is handling randomized seed, control net, and for img2img, + * the `fit` param. These are added to the graph at the end. + */ + + // copy-pasted graph from node editor, filled in with state values & friendly node ids + const graph: NonNullableGraph = { + id: TEXT_TO_IMAGE_GRAPH, + nodes: { + [POSITIVE_CONDITIONING]: { + type: 'compel', + id: POSITIVE_CONDITIONING, + prompt: positivePrompt, + }, + [NEGATIVE_CONDITIONING]: { + type: 'compel', + id: NEGATIVE_CONDITIONING, + prompt: negativePrompt, + }, + [RANGE_OF_SIZE]: { + type: 'range_of_size', + id: RANGE_OF_SIZE, + // start: 0, // seed - must be connected manually + size: iterations, + step: 1, + }, + [NOISE]: { + type: 'noise', + id: NOISE, + width, + height, + }, + [TEXT_TO_LATENTS]: { + type: 't2l', + id: TEXT_TO_LATENTS, + cfg_scale, + scheduler, + steps, + }, + [MODEL_LOADER]: { + type: 'sd1_model_loader', + id: MODEL_LOADER, + model_name, + }, + [LATENTS_TO_IMAGE]: { + type: 'l2i', + id: LATENTS_TO_IMAGE, + }, + [ITERATE]: { + type: 'iterate', + id: ITERATE, + }, + }, + edges: [ + { + source: { + node_id: NEGATIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'negative_conditioning', + }, + }, + { + source: { + node_id: POSITIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'positive_conditioning', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'unet', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'unet', + }, + }, + { + source: { + node_id: TEXT_TO_LATENTS, + field: 'latents', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'latents', + }, + }, + { + source: { + node_id: MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'vae', + }, + }, + { + source: { + node_id: RANGE_OF_SIZE, + field: 'collection', + }, + destination: { + node_id: ITERATE, + field: 'collection', + }, + }, + { + source: { + node_id: ITERATE, + field: 'item', + }, + destination: { + node_id: NOISE, + field: 'seed', + }, + }, + { + source: { + node_id: NOISE, + field: 'noise', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'noise', + }, + }, + ], + }; + + // handle seed + if (shouldRandomizeSeed) { + // Random int node to generate the starting seed + const randomIntNode: RandomIntInvocation = { + id: RANDOM_INT, + type: 'rand_int', + }; + + graph.nodes[RANDOM_INT] = randomIntNode; + + // 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' }, + }); + } else { + // User specified seed, so set the start of the range of size to the seed + (graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed; + } + + // add controlnet + addControlNetToLinearGraph(graph, TEXT_TO_LATENTS, state); + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts similarity index 98% rename from invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts rename to invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts index a46af7199f..1f2c8327e0 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts @@ -1,6 +1,5 @@ import { RootState } from 'app/store/store'; import { - Graph, ImageResizeInvocation, RandomIntInvocation, RangeOfSizeInvocation, @@ -23,12 +22,15 @@ import { } from './constants'; import { set } from 'lodash-es'; import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; + const moduleLog = log.child({ namespace: 'nodes' }); /** * Builds the Image to Image tab graph. */ -export const buildImageToImageGraph = (state: RootState): Graph => { +export const buildLinearImageToImageGraph = ( + state: RootState +): NonNullableGraph => { const { positivePrompt, negativePrompt, @@ -275,8 +277,8 @@ export const buildImageToImageGraph = (state: RootState): Graph => { image_name: initialImage.image_name, }, is_intermediate: true, - height, width, + height, }; graph.nodes[RESIZE] = resizeNode; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts similarity index 93% rename from invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts rename to invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts index 945f96a9e3..c179a89504 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts @@ -1,10 +1,6 @@ import { RootState } from 'app/store/store'; import { NonNullableGraph } from 'features/nodes/types/types'; -import { - Graph, - RandomIntInvocation, - RangeOfSizeInvocation, -} from 'services/api'; +import { RandomIntInvocation, RangeOfSizeInvocation } from 'services/api'; import { ITERATE, LATENTS_TO_IMAGE, @@ -19,7 +15,15 @@ import { } from './constants'; import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; -export const buildTextToImageGraph = (state: RootState): Graph => { +type TextToImageGraphOverrides = { + width: number; + height: number; +}; + +export const buildLinearTextToImageGraph = ( + state: RootState, + overrides?: TextToImageGraphOverrides +): NonNullableGraph => { const { positivePrompt, negativePrompt, @@ -67,8 +71,8 @@ export const buildTextToImageGraph = (state: RootState): Graph => { [NOISE]: { type: 'noise', id: NOISE, - width, - height, + width: overrides?.width || width, + height: overrides?.height || height, }, [TEXT_TO_LATENTS]: { type: 't2l', 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 a65830e47f..39e0080d11 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts @@ -1,3 +1,4 @@ +// friendly node ids export const POSITIVE_CONDITIONING = 'positive_conditioning'; export const NEGATIVE_CONDITIONING = 'negative_conditioning'; export const TEXT_TO_LATENTS = 'text_to_latents'; @@ -10,8 +11,10 @@ export const MODEL_LOADER = 'model_loader'; export const IMAGE_TO_LATENTS = 'image_to_latents'; export const LATENTS_TO_LATENTS = 'latents_to_latents'; export const RESIZE = 'resize_image'; +export const INPAINT = 'inpaint'; +export const CONTROL_NET_COLLECT = 'control_net_collect'; +// friendly graph ids export const TEXT_TO_IMAGE_GRAPH = 'text_to_image_graph'; export const IMAGE_TO_IMAGE_GRAPH = 'image_to_image_graph'; - -export const CONTROL_NET_COLLECT = 'control_net_collect'; +export const INPAINT_GRAPH = 'inpaint_graph'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx index 19ef7fd6fa..8e17ff066c 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx @@ -1,5 +1,4 @@ import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; -import ParamSeedCollapse from 'features/parameters/components/Parameters/Seed/ParamSeedCollapse'; import ParamVariationCollapse from 'features/parameters/components/Parameters/Variations/ParamVariationCollapse'; import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; import ParamInfillAndScalingCollapse from 'features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse'; @@ -8,6 +7,7 @@ import UnifiedCanvasCoreParameters from './UnifiedCanvasCoreParameters'; import { memo } from 'react'; import ParamPositiveConditioning from 'features/parameters/components/Parameters/Core/ParamPositiveConditioning'; import ParamNegativeConditioning from 'features/parameters/components/Parameters/Core/ParamNegativeConditioning'; +import ParamControlNetCollapse from 'features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse'; const UnifiedCanvasParameters = () => { return ( @@ -16,6 +16,7 @@ const UnifiedCanvasParameters = () => { + From 0ae632535360f13564b6398e94627e3ba66a8c0f Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 19 Jun 2023 23:20:53 +1200 Subject: [PATCH 011/110] chore: Add torchsde as a dependency for the SDE schedulers --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 8707c6c911..03396312ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,7 @@ dependencies = [ "torch~=2.0.0", "torchvision>=0.14.1", "torchmetrics", + "torchsde==0.2.5", "transformers~=4.30", "uvicorn[standard]==0.21.1", "windows-curses; sys_platform=='win32'", From 257e9725999c8ecfad851e9458b63144c38db862 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Tue, 20 Jun 2023 13:22:39 -0400 Subject: [PATCH 012/110] fix failing pytest for config module --- tests/test_config.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 9317a794c5..cea4991d12 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -8,8 +8,6 @@ from pathlib import Path os.environ['INVOKEAI_ROOT']='/tmp' from invokeai.app.services.config import InvokeAIAppConfig -from invokeai.app.invocations.generate import TextToImageInvocation - init1 = OmegaConf.create( ''' @@ -37,13 +35,13 @@ def test_use_init(): # sys.argv respectively. conf1 = InvokeAIAppConfig.get_config() assert conf1 - conf1.parse_args(conf=init1) + conf1.parse_args(conf=init1,argv=[]) assert conf1.max_loaded_models==5 assert not conf1.nsfw_checker conf2 = InvokeAIAppConfig.get_config() assert conf2 - conf2.parse_args(conf=init2) + conf2.parse_args(conf=init2,argv=[]) assert conf2.nsfw_checker assert conf2.max_loaded_models==2 assert not hasattr(conf2,'invalid_attribute') @@ -67,7 +65,7 @@ def test_env_override(): # environment variables should be case insensitive os.environ['InvokeAI_Max_Loaded_Models'] = '15' conf = InvokeAIAppConfig() - conf.parse_args(conf=init1) + conf.parse_args(conf=init1,argv=[]) assert conf.max_loaded_models == 15 conf = InvokeAIAppConfig() From a1671519d5576a2e28237b52c560146acb1c4883 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 11:51:20 -0700 Subject: [PATCH 013/110] board CRUD --- invokeai/app/api/dependencies.py | 4 + invokeai/app/api/routers/boards.py | 77 ++++++++ invokeai/app/api_app.py | 4 +- invokeai/app/services/boards.py | 172 ++++++++++++++++++ invokeai/app/services/image_record_storage.py | 3 +- invokeai/app/services/invocation_services.py | 4 + 6 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 invokeai/app/api/routers/boards.py create mode 100644 invokeai/app/services/boards.py diff --git a/invokeai/app/api/dependencies.py b/invokeai/app/api/dependencies.py index 5599d569d5..8aa61d08aa 100644 --- a/invokeai/app/api/dependencies.py +++ b/invokeai/app/api/dependencies.py @@ -2,6 +2,7 @@ from logging import Logger import os +from invokeai.app.services import boards from invokeai.app.services.image_record_storage import SqliteImageRecordStorage from invokeai.app.services.images import ImageService from invokeai.app.services.metadata import CoreMetadataService @@ -20,6 +21,7 @@ from ..services.invoker import Invoker from ..services.processor import DefaultInvocationProcessor from ..services.sqlite import SqliteItemStorage from ..services.model_manager_service import ModelManagerService +from ..services.boards import SqliteBoardStorage from .events import FastAPIEventService @@ -71,6 +73,7 @@ class ApiDependencies: latents = ForwardCacheLatentsStorage( DiskLatentsStorage(f"{output_folder}/latents") ) + boards = SqliteBoardStorage(db_location) images = ImageService( image_record_storage=image_record_storage, @@ -96,6 +99,7 @@ class ApiDependencies: restoration=RestorationServices(config, logger), configuration=config, logger=logger, + boards=boards ) create_system_graphs(services.graph_library) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py new file mode 100644 index 0000000000..47e4f74b0a --- /dev/null +++ b/invokeai/app/api/routers/boards.py @@ -0,0 +1,77 @@ +from fastapi import Body, HTTPException, Path, Query +from fastapi.routing import APIRouter +from invokeai.app.services.boards import BoardRecord, BoardRecordChanges +from invokeai.app.services.image_record_storage import OffsetPaginatedResults + +from ..dependencies import ApiDependencies + +boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) + + +@boards_router.post( + "/", + operation_id="create_board", + responses={ + 201: {"description": "The board was created successfully"}, + }, + status_code=201, +) +async def create_board( + board_name: str = Body(description="The name of the board to create"), +): + """Creates a board""" + try: + ApiDependencies.invoker.services.boards.save(board_name=board_name) + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to create board") + + +@boards_router.delete("/{board_id}", operation_id="delete_board") +async def delete_board( + board_id: str = Path(description="The id of board to delete"), +) -> None: + """Deletes a board""" + + try: + ApiDependencies.invoker.services.boards.delete(board_id=board_id) + except Exception as e: + # TODO: Does this need any exception handling at all? + pass + + +@boards_router.patch( + "/{board_id}", + operation_id="update_baord" +) +async def update_baord( + id: str = Path(description="The id of the board to update"), + board_changes: BoardRecordChanges = Body( + description="The changes to apply to the board" + ), +): + """Updates a board""" + + try: + return ApiDependencies.invoker.services.boards.update( + id, board_changes + ) + except Exception as e: + raise HTTPException(status_code=400, detail="Failed to update board") + +@boards_router.get( + "/", + operation_id="list_boards", + response_model=OffsetPaginatedResults[BoardRecord], +) +async def list_boards( + offset: int = Query(default=0, description="The page offset"), + limit: int = Query(default=10, description="The number of boards per page"), +) -> OffsetPaginatedResults[BoardRecord]: + """Gets a list of boards""" + + boards = ApiDependencies.invoker.services.boards.get_many( + offset, + limit, + ) + + return boards diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index fa46762d56..d00d92f763 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -24,7 +24,7 @@ logger = InvokeAILogger.getLogger(config=app_config) import invokeai.frontend.web as web_dir from .api.dependencies import ApiDependencies -from .api.routers import sessions, models, images +from .api.routers import sessions, models, images, boards from .api.sockets import SocketIO from .invocations.baseinvocation import BaseInvocation @@ -78,6 +78,8 @@ app.include_router(models.models_router, prefix="/api") app.include_router(images.images_router, prefix="/api") +app.include_router(boards.boards_router, prefix="/api") + # Build a custom OpenAPI to include all outputs # TODO: can outputs be included on metadata of invocation schemas somehow? def custom_openapi(): diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py new file mode 100644 index 0000000000..69b1afa048 --- /dev/null +++ b/invokeai/app/services/boards.py @@ -0,0 +1,172 @@ +from abc import ABC, abstractmethod +from datetime import datetime +from typing import Generic, Optional, TypeVar, cast +import sqlite3 +import threading +from typing import Optional, Union +import uuid +from invokeai.app.services.image_record_storage import OffsetPaginatedResults + +from pydantic import BaseModel, Field, Extra +from pydantic.generics import GenericModel + +T = TypeVar("T", bound=BaseModel) + +class BoardRecord(BaseModel): + """Deserialized board record.""" + + id: str = Field(description="The unique ID of the board.") + name: str = Field(description="The name of the board.") + """The name of the board.""" + created_at: Union[datetime, str] = Field( + description="The created timestamp of the board." + ) + """The created timestamp of the image.""" + updated_at: Union[datetime, str] = Field( + description="The updated timestamp of the board." + ) + +class BoardRecordChanges(BaseModel, extra=Extra.forbid): + name: Optional[str] = Field( + description="The board's new name." + ) + +class BoardRecordNotFoundException(Exception): + """Raised when an board record is not found.""" + + def __init__(self, message="Board record not found"): + super().__init__(message) + + +class BoardRecordSaveException(Exception): + """Raised when an board record cannot be saved.""" + + def __init__(self, message="Board record not saved"): + super().__init__(message) + + +class BoardRecordDeleteException(Exception): + """Raised when an board record cannot be deleted.""" + + def __init__(self, message="Board record not deleted"): + super().__init__(message) + +class BoardStorageBase(ABC): + """Low-level service responsible for interfacing with the board record store.""" + + @abstractmethod + def get(self, board_id: str) -> BoardRecord: + """Gets an board record.""" + pass + + @abstractmethod + def delete(self, board_id: str) -> None: + """Deletes a board record.""" + pass + + @abstractmethod + def save( + self, + board_name: str, + ): + """Saves a board record.""" + pass + + +class SqliteBoardStorage(BoardStorageBase): + _filename: str + _conn: sqlite3.Connection + _cursor: sqlite3.Cursor + _lock: threading.Lock + + def __init__(self, filename: str) -> None: + super().__init__() + self._filename = filename + self._conn = sqlite3.connect(filename, check_same_thread=False) + # Enable row factory to get rows as dictionaries (must be done before making the cursor!) + self._conn.row_factory = sqlite3.Row + self._cursor = self._conn.cursor() + self._lock = threading.Lock() + + try: + self._lock.acquire() + # Enable foreign keys + self._conn.execute("PRAGMA foreign_keys = ON;") + self._create_tables() + self._conn.commit() + finally: + self._lock.release() + + def _create_tables(self) -> None: + """Creates the `board` table.""" + + # Create the `images` table. + self._cursor.execute( + """--sql + CREATE TABLE IF NOT EXISTS boards ( + id TEXT NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + -- Updated via trigger + updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) + ); + """ + ) + + self._cursor.execute( + """--sql + CREATE INDEX IF NOT EXISTS idx_boards_created_at ON boards(created_at); + """ + ) + + # Add trigger for `updated_at`. + self._cursor.execute( + """--sql + CREATE TRIGGER IF NOT EXISTS tg_boards_updated_at + AFTER UPDATE + ON boards FOR EACH ROW + BEGIN + UPDATE boards SET updated_at = current_timestamp + WHERE board_name = old.board_name; + END; + """ + ) + + + def delete(self, board_id: str) -> None: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + DELETE FROM boards + WHERE id = ?; + """, + (board_id), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordDeleteException from e + finally: + self._lock.release() + + def save( + self, + board_name: str, + ): + try: + board_id = str(uuid.uuid4()) + self._lock.acquire() + self._cursor.execute( + """--sql + INSERT OR IGNORE INTO boards (id, name) + VALUES (?, ?); + """, + (board_id, board_name), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordSaveException from e + finally: + self._lock.release() diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 30b379ed8b..88d9b54926 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -135,7 +135,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): self._lock.release() def _create_tables(self) -> None: - """Creates the tables for the `images` database.""" + """Creates the `images` table.""" # Create the `images` table. self._cursor.execute( @@ -152,6 +152,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): node_id TEXT, metadata TEXT, is_intermediate BOOLEAN DEFAULT FALSE, + board_id TEXT, created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- Updated via trigger updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), diff --git a/invokeai/app/services/invocation_services.py b/invokeai/app/services/invocation_services.py index 1f910253e5..d69e0b294f 100644 --- a/invokeai/app/services/invocation_services.py +++ b/invokeai/app/services/invocation_services.py @@ -14,6 +14,7 @@ if TYPE_CHECKING: from invokeai.app.services.config import InvokeAISettings from invokeai.app.services.graph import GraphExecutionState, LibraryGraph from invokeai.app.services.invoker import InvocationProcessorABC + from invokeai.app.services.boards import BoardStorageBase class InvocationServices: @@ -27,6 +28,7 @@ class InvocationServices: restoration: "RestorationServices" configuration: "InvokeAISettings" images: "ImageService" + boards: "BoardStorageBase" # NOTE: we must forward-declare any types that include invocations, since invocations can use services graph_library: "ItemStorageABC"["LibraryGraph"] @@ -46,6 +48,7 @@ class InvocationServices: processor: "InvocationProcessorABC", restoration: "RestorationServices", configuration: "InvokeAISettings", + boards: "BoardStorageBase", ): self.model_manager = model_manager self.events = events @@ -58,3 +61,4 @@ class InvocationServices: self.processor = processor self.restoration = restoration self.configuration = configuration + self.boards = boards From 207602f42551287f296019bf26e7d3db3933b18d Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 11:53:31 -0700 Subject: [PATCH 014/110] remove unused --- invokeai/app/api/routers/boards.py | 38 ------------------------------ 1 file changed, 38 deletions(-) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index 47e4f74b0a..9ef416e396 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -1,7 +1,5 @@ from fastapi import Body, HTTPException, Path, Query from fastapi.routing import APIRouter -from invokeai.app.services.boards import BoardRecord, BoardRecordChanges -from invokeai.app.services.image_record_storage import OffsetPaginatedResults from ..dependencies import ApiDependencies @@ -39,39 +37,3 @@ async def delete_board( pass -@boards_router.patch( - "/{board_id}", - operation_id="update_baord" -) -async def update_baord( - id: str = Path(description="The id of the board to update"), - board_changes: BoardRecordChanges = Body( - description="The changes to apply to the board" - ), -): - """Updates a board""" - - try: - return ApiDependencies.invoker.services.boards.update( - id, board_changes - ) - except Exception as e: - raise HTTPException(status_code=400, detail="Failed to update board") - -@boards_router.get( - "/", - operation_id="list_boards", - response_model=OffsetPaginatedResults[BoardRecord], -) -async def list_boards( - offset: int = Query(default=0, description="The page offset"), - limit: int = Query(default=10, description="The number of boards per page"), -) -> OffsetPaginatedResults[BoardRecord]: - """Gets a list of boards""" - - boards = ApiDependencies.invoker.services.boards.get_many( - offset, - limit, - ) - - return boards From a121e6b3a05663eb7277f8d232c54cca2ceb9096 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 11:55:38 -0700 Subject: [PATCH 015/110] add board_id association to image --- invokeai/app/services/image_record_storage.py | 12 ++++++++++++ invokeai/app/services/models/image_record.py | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 88d9b54926..0a35763ff3 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -260,6 +260,18 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): """, (changes.is_intermediate, image_name), ) + + # Change the image's `is_intermediate`` flag + if changes.board_id is not None: + self._cursor.execute( + f"""--sql + UPDATE images + SET board_id = ? + WHERE image_name = ?; + """, + (changes.board_id, image_name), + ) + self._conn.commit() except sqlite3.Error as e: self._conn.rollback() diff --git a/invokeai/app/services/models/image_record.py b/invokeai/app/services/models/image_record.py index d971d65916..a4699f74c8 100644 --- a/invokeai/app/services/models/image_record.py +++ b/invokeai/app/services/models/image_record.py @@ -72,6 +72,10 @@ class ImageRecordChanges(BaseModel, extra=Extra.forbid): default=None, description="The image's new `is_intermediate` flag." ) """The image's new `is_intermediate` flag.""" + board_id: Optional[StrictStr] = Field( + default=None, description="The image's new board ID." + ) + """The image's new board ID.""" class ImageUrlsDTO(BaseModel): From 6ca5ad9075b5b5a915f6315ce3ef870781735622 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 12:07:09 -0700 Subject: [PATCH 016/110] filter images by board_id --- invokeai/app/api/routers/images.py | 4 ++++ invokeai/app/services/image_record_storage.py | 5 +++++ invokeai/app/services/images.py | 2 ++ 3 files changed, 11 insertions(+) diff --git a/invokeai/app/api/routers/images.py b/invokeai/app/api/routers/images.py index 11453d97f1..24bb716635 100644 --- a/invokeai/app/api/routers/images.py +++ b/invokeai/app/api/routers/images.py @@ -221,6 +221,9 @@ async def list_images_with_metadata( is_intermediate: Optional[bool] = Query( default=None, description="Whether to list intermediate images" ), + board_id: Optional[str] = Query( + default=None, description="The board of images to include" + ), offset: int = Query(default=0, description="The page offset"), limit: int = Query(default=10, description="The number of images per page"), ) -> OffsetPaginatedResults[ImageDTO]: @@ -232,6 +235,7 @@ async def list_images_with_metadata( image_origin, categories, is_intermediate, + board_id ) return image_dtos diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 0a35763ff3..84abcb6b2e 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -286,6 +286,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageRecord]: try: self._lock.acquire() @@ -317,6 +318,10 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): query_conditions += f"""AND is_intermediate = ?\n""" query_params.append(is_intermediate) + if board_id is not None: + query_conditions += f"""AND board_id = ?\n""" + query_params.append(board_id) + query_pagination = f"""ORDER BY created_at DESC LIMIT ? OFFSET ?\n""" # Final images query with pagination diff --git a/invokeai/app/services/images.py b/invokeai/app/services/images.py index 9f7188f607..173268563a 100644 --- a/invokeai/app/services/images.py +++ b/invokeai/app/services/images.py @@ -322,6 +322,7 @@ class ImageService(ImageServiceABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageDTO]: try: results = self._services.records.get_many( @@ -330,6 +331,7 @@ class ImageService(ImageServiceABC): image_origin, categories, is_intermediate, + board_id ) image_dtos = list( From 499a1748324b399eb0435bf0cf9cee37e72cc82e Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 12:56:09 -0700 Subject: [PATCH 017/110] some more --- invokeai/app/api/routers/boards.py | 3 ++- invokeai/app/services/boards.py | 17 ++++++++++++----- invokeai/app/services/models/image_record.py | 7 +++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index 9ef416e396..eb2f5956ab 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -19,7 +19,8 @@ async def create_board( ): """Creates a board""" try: - ApiDependencies.invoker.services.boards.save(board_name=board_name) + result = ApiDependencies.invoker.services.boards.save(board_name=board_name) + return result except Exception as e: raise HTTPException(status_code=500, detail="Failed to create board") diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index 69b1afa048..00d90637fe 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -54,11 +54,6 @@ class BoardRecordDeleteException(Exception): class BoardStorageBase(ABC): """Low-level service responsible for interfacing with the board record store.""" - @abstractmethod - def get(self, board_id: str) -> BoardRecord: - """Gets an board record.""" - pass - @abstractmethod def delete(self, board_id: str) -> None: """Deletes a board record.""" @@ -165,6 +160,18 @@ class SqliteBoardStorage(BoardStorageBase): (board_id, board_name), ) self._conn.commit() + + self._cursor.execute( + """--sql + SELECT * + FROM boards + WHERE id = ?; + """, + (board_id,), + ) + + result = self._cursor.fetchone() + return result except sqlite3.Error as e: self._conn.rollback() raise BoardRecordSaveException from e diff --git a/invokeai/app/services/models/image_record.py b/invokeai/app/services/models/image_record.py index a4699f74c8..98f370f337 100644 --- a/invokeai/app/services/models/image_record.py +++ b/invokeai/app/services/models/image_record.py @@ -48,6 +48,11 @@ class ImageRecord(BaseModel): description="A limited subset of the image's generation metadata. Retrieve the image's session for full metadata.", ) """A limited subset of the image's generation metadata. Retrieve the image's session for full metadata.""" + board_id: Optional[str] = Field( + default=None, + description="The board ID that this image belongs to.", + ) + """The board ID that this image belongs to.""" class ImageRecordChanges(BaseModel, extra=Extra.forbid): @@ -126,6 +131,7 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord: updated_at = image_dict.get("updated_at", get_iso_timestamp()) deleted_at = image_dict.get("deleted_at", get_iso_timestamp()) is_intermediate = image_dict.get("is_intermediate", False) + board_id = image_dict.get("board_id", None) raw_metadata = image_dict.get("metadata") @@ -147,4 +153,5 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord: updated_at=updated_at, deleted_at=deleted_at, is_intermediate=is_intermediate, + board_id=board_id, ) From 4bfaae66179be25faefba4ad8dd969803f5e10bf Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 12:59:31 -0700 Subject: [PATCH 018/110] fix type --- invokeai/app/services/image_record_storage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 84abcb6b2e..3c98ef5f29 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -82,6 +82,7 @@ class ImageRecordStorageBase(ABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageRecord]: """Gets a page of image records.""" pass From 3833304f57cfccf4fc43afcbe63de3b91659c4e4 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Tue, 13 Jun 2023 14:08:04 -0700 Subject: [PATCH 019/110] [WIP] board list endpoint w cover photos --- invokeai/app/api/routers/boards.py | 51 +++++++++++++ invokeai/app/services/boards.py | 74 +++++++++++++++++++ invokeai/app/services/image_record_storage.py | 31 ++++++++ 3 files changed, 156 insertions(+) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index eb2f5956ab..c8e877ca59 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -1,5 +1,7 @@ from fastapi import Body, HTTPException, Path, Query from fastapi.routing import APIRouter +from invokeai.app.services.boards import BoardRecord, BoardRecordChanges +from invokeai.app.services.image_record_storage import OffsetPaginatedResults from ..dependencies import ApiDependencies @@ -38,3 +40,52 @@ async def delete_board( pass +@boards_router.get( + "/", + operation_id="list_boards", + response_model=OffsetPaginatedResults[BoardRecord], +) +async def list_boards( + offset: int = Query(default=0, description="The page offset"), + limit: int = Query(default=10, description="The number of boards per page"), +) -> OffsetPaginatedResults[BoardRecord]: + """Gets a list of boards""" + + results = ApiDependencies.invoker.services.boards.get_many( + offset, + limit, + ) + + boards = list( + map( + lambda r: board_record_to_dto( + r, + generate_cover_photo_url(r.id) + ), + results.boards, + ) + ) + + return boards + + +class BoardDTO(BaseModel): + """A DTO for an image""" + id: str + name: str + cover_image_url: str + +def board_record_to_dto( + board_record: BoardRecord, cover_image_url: str +) -> BoardDTO: + """Converts an image record to an image DTO.""" + return BoardDTO( + **board_record.dict(), + cover_image_url=cover_image_url, + ) + +def generate_cover_photo_url(board_id: str) -> str | None: + cover_photo = ApiDependencies.invoker.services.images._services.records.get_board_cover_photo(board_id) + if cover_photo is not None: + url = ApiDependencies.invoker.services.images._services.urls.get_image_url(cover_photo.image_origin, cover_photo.image_name) + return url diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index 00d90637fe..3cdadd6c22 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -26,6 +26,23 @@ class BoardRecord(BaseModel): description="The updated timestamp of the board." ) +class BoardRecordInList(BaseModel): + """Deserialized board record in a list.""" + + id: str = Field(description="The unique ID of the board.") + name: str = Field(description="The name of the board.") + most_recent_image_url: Optional[str] = Field( + description="The URL of the most recent image in the board." + ) + """The name of the board.""" + created_at: Union[datetime, str] = Field( + description="The created timestamp of the board." + ) + """The created timestamp of the image.""" + updated_at: Union[datetime, str] = Field( + description="The updated timestamp of the board." + ) + class BoardRecordChanges(BaseModel, extra=Extra.forbid): name: Optional[str] = Field( description="The board's new name." @@ -67,6 +84,18 @@ class BoardStorageBase(ABC): """Saves a board record.""" pass + def get_cover_photo(self, board_id: str) -> Optional[str]: + """Gets the cover photo for a board.""" + pass + + def get_many( + self, + offset: int, + limit: int, + ): + """Gets many board records.""" + pass + class SqliteBoardStorage(BoardStorageBase): _filename: str @@ -177,3 +206,48 @@ class SqliteBoardStorage(BoardStorageBase): raise BoardRecordSaveException from e finally: self._lock.release() + + + def get_many( + self, + offset: int, + limit: int, + ) -> OffsetPaginatedResults[BoardRecord]: + try: + + self._lock.acquire() + + count_query = f"""SELECT COUNT(*) FROM images WHERE 1=1\n""" + images_query = f"""SELECT * FROM images WHERE 1=1\n""" + + query_conditions = "" + query_params = [] + + query_pagination = f"""ORDER BY created_at DESC LIMIT ? OFFSET ?\n""" + + # Final images query with pagination + images_query += query_conditions + query_pagination + ";" + # Add all the parameters + images_params = query_params.copy() + images_params.append(limit) + images_params.append(offset) + # Build the list of images, deserializing each row + self._cursor.execute(images_query, images_params) + result = cast(list[sqlite3.Row], self._cursor.fetchall()) + boards = [BoardRecord(**dict(row)) for row in result] + + # Set up and execute the count query, without pagination + count_query += query_conditions + ";" + count_params = query_params.copy() + self._cursor.execute(count_query, count_params) + count = self._cursor.fetchone()[0] + + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordSaveException from e + finally: + self._lock.release() + + return OffsetPaginatedResults( + items=boards, offset=offset, limit=limit, total=count + ) \ No newline at end of file diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 3c98ef5f29..96c6beea12 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -94,6 +94,11 @@ class ImageRecordStorageBase(ABC): """Deletes an image record.""" pass + @abstractmethod + def get_board_cover_photo(self, board_id: str) -> Optional[ImageRecord]: + """Gets the cover photo for a board.""" + pass + @abstractmethod def save( self, @@ -280,6 +285,32 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): finally: self._lock.release() + def get_board_cover_photo(self, board_id: str) -> ImageRecord | None: + try: + self._lock.acquire() + self._cursor.execute( + """ + SELECT * + FROM images + WHERE board_id = ? + ORDER BY created_at DESC + LIMIT 1 + """, + (board_id), + ) + self._conn.commit() + result = cast(Union[sqlite3.Row, None], self._cursor.fetchone()) + except sqlite3.Error as e: + self._conn.rollback() + raise ImageRecordNotFoundException from e + finally: + self._lock.release() + + if not result: + raise ImageRecordNotFoundException + + return deserialize_image_record(dict(result)) + def get_many( self, offset: int = 0, From 72e9ced88997bdfd84c06ea4127250284178b9bc Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 00:07:20 +1000 Subject: [PATCH 020/110] feat(nodes): add boards and board_images services --- invokeai/app/api/dependencies.py | 39 +- invokeai/app/api/routers/boards.py | 145 ++++---- invokeai/app/api/routers/images.py | 4 - invokeai/app/api_app.py | 2 +- .../services/board_image_record_storage.py | 253 +++++++++++++ invokeai/app/services/board_images.py | 166 +++++++++ invokeai/app/services/board_record_storage.py | 331 +++++++++++++++++ invokeai/app/services/boards.py | 340 +++++++----------- invokeai/app/services/image_record_storage.py | 45 +-- invokeai/app/services/images.py | 6 +- invokeai/app/services/invocation_services.py | 19 +- invokeai/app/services/models/image_record.py | 11 - 12 files changed, 993 insertions(+), 368 deletions(-) create mode 100644 invokeai/app/services/board_image_record_storage.py create mode 100644 invokeai/app/services/board_images.py create mode 100644 invokeai/app/services/board_record_storage.py diff --git a/invokeai/app/api/dependencies.py b/invokeai/app/api/dependencies.py index 8aa61d08aa..8889c70674 100644 --- a/invokeai/app/api/dependencies.py +++ b/invokeai/app/api/dependencies.py @@ -2,7 +2,15 @@ from logging import Logger import os -from invokeai.app.services import boards +from invokeai.app.services.board_image_record_storage import ( + SqliteBoardImageRecordStorage, +) +from invokeai.app.services.board_images import ( + BoardImagesService, + BoardImagesServiceDependencies, +) +from invokeai.app.services.board_record_storage import SqliteBoardRecordStorage +from invokeai.app.services.boards import BoardService, BoardServiceDependencies from invokeai.app.services.image_record_storage import SqliteImageRecordStorage from invokeai.app.services.images import ImageService from invokeai.app.services.metadata import CoreMetadataService @@ -59,7 +67,7 @@ class ApiDependencies: # TODO: build a file/path manager? db_location = config.db_path - db_location.parent.mkdir(parents=True,exist_ok=True) + db_location.parent.mkdir(parents=True, exist_ok=True) graph_execution_manager = SqliteItemStorage[GraphExecutionState]( filename=db_location, table_name="graph_executions" @@ -73,7 +81,29 @@ class ApiDependencies: latents = ForwardCacheLatentsStorage( DiskLatentsStorage(f"{output_folder}/latents") ) - boards = SqliteBoardStorage(db_location) + + board_record_storage = SqliteBoardRecordStorage(db_location) + board_image_record_storage = SqliteBoardImageRecordStorage(db_location) + + boards = BoardService( + services=BoardServiceDependencies( + board_image_record_storage=board_image_record_storage, + board_record_storage=board_record_storage, + image_record_storage=image_record_storage, + url=urls, + logger=logger, + ) + ) + + board_images = BoardImagesService( + services=BoardImagesServiceDependencies( + board_image_record_storage=board_image_record_storage, + board_record_storage=board_record_storage, + image_record_storage=image_record_storage, + url=urls, + logger=logger, + ) + ) images = ImageService( image_record_storage=image_record_storage, @@ -90,6 +120,8 @@ class ApiDependencies: events=events, latents=latents, images=images, + boards=boards, + board_images=board_images, queue=MemoryInvocationQueue(), graph_library=SqliteItemStorage[LibraryGraph]( filename=db_location, table_name="graphs" @@ -99,7 +131,6 @@ class ApiDependencies: restoration=RestorationServices(config, logger), configuration=config, logger=logger, - boards=boards ) create_system_graphs(services.graph_library) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index c8e877ca59..f3a76e08d3 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -1,91 +1,86 @@ -from fastapi import Body, HTTPException, Path, Query -from fastapi.routing import APIRouter -from invokeai.app.services.boards import BoardRecord, BoardRecordChanges -from invokeai.app.services.image_record_storage import OffsetPaginatedResults +# from fastapi import Body, HTTPException, Path, Query +# from fastapi.routing import APIRouter +# from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges +# from invokeai.app.services.image_record_storage import OffsetPaginatedResults -from ..dependencies import ApiDependencies +# from ..dependencies import ApiDependencies -boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) +# boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) -@boards_router.post( - "/", - operation_id="create_board", - responses={ - 201: {"description": "The board was created successfully"}, - }, - status_code=201, -) -async def create_board( - board_name: str = Body(description="The name of the board to create"), -): - """Creates a board""" - try: - result = ApiDependencies.invoker.services.boards.save(board_name=board_name) - return result - except Exception as e: - raise HTTPException(status_code=500, detail="Failed to create board") +# @boards_router.post( +# "/", +# operation_id="create_board", +# responses={ +# 201: {"description": "The board was created successfully"}, +# }, +# status_code=201, +# ) +# async def create_board( +# board_name: str = Body(description="The name of the board to create"), +# ): +# """Creates a board""" +# try: +# result = ApiDependencies.invoker.services.boards.save(board_name=board_name) +# return result +# except Exception as e: +# raise HTTPException(status_code=500, detail="Failed to create board") -@boards_router.delete("/{board_id}", operation_id="delete_board") -async def delete_board( - board_id: str = Path(description="The id of board to delete"), -) -> None: - """Deletes a board""" +# @boards_router.delete("/{board_id}", operation_id="delete_board") +# async def delete_board( +# board_id: str = Path(description="The id of board to delete"), +# ) -> None: +# """Deletes a board""" - try: - ApiDependencies.invoker.services.boards.delete(board_id=board_id) - except Exception as e: - # TODO: Does this need any exception handling at all? - pass +# try: +# ApiDependencies.invoker.services.boards.delete(board_id=board_id) +# except Exception as e: +# # TODO: Does this need any exception handling at all? +# pass -@boards_router.get( - "/", - operation_id="list_boards", - response_model=OffsetPaginatedResults[BoardRecord], -) -async def list_boards( - offset: int = Query(default=0, description="The page offset"), - limit: int = Query(default=10, description="The number of boards per page"), -) -> OffsetPaginatedResults[BoardRecord]: - """Gets a list of boards""" +# @boards_router.get( +# "/", +# operation_id="list_boards", +# response_model=OffsetPaginatedResults[BoardRecord], +# ) +# async def list_boards( +# offset: int = Query(default=0, description="The page offset"), +# limit: int = Query(default=10, description="The number of boards per page"), +# ) -> OffsetPaginatedResults[BoardRecord]: +# """Gets a list of boards""" - results = ApiDependencies.invoker.services.boards.get_many( - offset, - limit, - ) +# results = ApiDependencies.invoker.services.boards.get_many( +# offset, +# limit, +# ) - boards = list( - map( - lambda r: board_record_to_dto( - r, - generate_cover_photo_url(r.id) - ), - results.boards, - ) - ) +# boards = list( +# map( +# lambda r: board_record_to_dto( +# r, +# generate_cover_photo_url(r.id) +# ), +# results.boards, +# ) +# ) - return boards +# return boards -class BoardDTO(BaseModel): - """A DTO for an image""" - id: str - name: str - cover_image_url: str -def board_record_to_dto( - board_record: BoardRecord, cover_image_url: str -) -> BoardDTO: - """Converts an image record to an image DTO.""" - return BoardDTO( - **board_record.dict(), - cover_image_url=cover_image_url, - ) +# def board_record_to_dto( +# board_record: BoardRecord, cover_image_url: str +# ) -> BoardDTO: +# """Converts an image record to an image DTO.""" +# return BoardDTO( +# **board_record.dict(), +# cover_image_url=cover_image_url, +# ) -def generate_cover_photo_url(board_id: str) -> str | None: - cover_photo = ApiDependencies.invoker.services.images._services.records.get_board_cover_photo(board_id) - if cover_photo is not None: - url = ApiDependencies.invoker.services.images._services.urls.get_image_url(cover_photo.image_origin, cover_photo.image_name) - return url +# def generate_cover_photo_url(board_id: str) -> str | None: +# cover_photo = ApiDependencies.invoker.services.images._services.records.get_board_cover_photo(board_id) +# if cover_photo is not None: +# url = ApiDependencies.invoker.services.images._services.urls.get_image_url(cover_photo.image_origin, cover_photo.image_name) +# return url diff --git a/invokeai/app/api/routers/images.py b/invokeai/app/api/routers/images.py index 24bb716635..11453d97f1 100644 --- a/invokeai/app/api/routers/images.py +++ b/invokeai/app/api/routers/images.py @@ -221,9 +221,6 @@ async def list_images_with_metadata( is_intermediate: Optional[bool] = Query( default=None, description="Whether to list intermediate images" ), - board_id: Optional[str] = Query( - default=None, description="The board of images to include" - ), offset: int = Query(default=0, description="The page offset"), limit: int = Query(default=10, description="The number of images per page"), ) -> OffsetPaginatedResults[ImageDTO]: @@ -235,7 +232,6 @@ async def list_images_with_metadata( image_origin, categories, is_intermediate, - board_id ) return image_dtos diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index d00d92f763..50228edf7e 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -78,7 +78,7 @@ app.include_router(models.models_router, prefix="/api") app.include_router(images.images_router, prefix="/api") -app.include_router(boards.boards_router, prefix="/api") +# app.include_router(boards.boards_router, prefix="/api") # Build a custom OpenAPI to include all outputs # TODO: can outputs be included on metadata of invocation schemas somehow? diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py new file mode 100644 index 0000000000..b805087da8 --- /dev/null +++ b/invokeai/app/services/board_image_record_storage.py @@ -0,0 +1,253 @@ +from abc import ABC, abstractmethod +import sqlite3 +import threading +from typing import cast +from invokeai.app.services.board_record_storage import BoardRecord + +from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.image_record import ( + ImageRecord, + deserialize_image_record, +) + + +class BoardImageRecordStorageBase(ABC): + """Abstract base class for board-image relationship record storage.""" + + @abstractmethod + def add_image_to_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Adds an image to a board.""" + pass + + @abstractmethod + def remove_image_from_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Removes an image from a board.""" + pass + + @abstractmethod + def get_images_for_board( + self, + board_id: str, + ) -> OffsetPaginatedResults[ImageRecord]: + """Gets images for a board.""" + pass + + @abstractmethod + def get_boards_for_image( + self, + board_id: str, + ) -> OffsetPaginatedResults[BoardRecord]: + """Gets images for a board.""" + pass + + @abstractmethod + def get_image_count_for_board( + self, + board_id: str, + ) -> int: + """Gets the number of images for a board.""" + pass + + +class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): + _filename: str + _conn: sqlite3.Connection + _cursor: sqlite3.Cursor + _lock: threading.Lock + + def __init__(self, filename: str) -> None: + super().__init__() + self._filename = filename + self._conn = sqlite3.connect(filename, check_same_thread=False) + # Enable row factory to get rows as dictionaries (must be done before making the cursor!) + self._conn.row_factory = sqlite3.Row + self._cursor = self._conn.cursor() + self._lock = threading.Lock() + + try: + self._lock.acquire() + # Enable foreign keys + self._conn.execute("PRAGMA foreign_keys = ON;") + self._create_tables() + self._conn.commit() + finally: + self._lock.release() + + def _create_tables(self) -> None: + """Creates the `board_images` junction table.""" + + # Create the `board_images` junction table. + self._cursor.execute( + """--sql + CREATE TABLE IF NOT EXISTS board_images ( + board_id TEXT NOT NULL, + image_name TEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + -- updated via trigger + updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + PRIMARY KEY (board_id, image_name), + FOREIGN KEY (board_id) REFERENCES boards (board_id) ON DELETE CASCADE, + FOREIGN KEY (image_name) REFERENCES images (image_name) ON DELETE CASCADE + ); + """ + ) + + # Add trigger for `updated_at`. + self._cursor.execute( + """--sql + CREATE TRIGGER IF NOT EXISTS tg_board_images_updated_at + AFTER UPDATE + ON board_images FOR EACH ROW + BEGIN + UPDATE board_images SET updated_at = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') + WHERE board_id = old.board_id AND image_name = old.image_name; + END; + """ + ) + + def add_image_to_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Adds an image to a board.""" + try: + self._lock.acquire() + self._cursor.execute( + """--sql + INSERT INTO board_images (board_id, image_name) + VALUES (?, ?); + """, + (board_id, image_name), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + + def remove_image_from_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Removes an image from a board.""" + try: + self._lock.acquire() + self._cursor.execute( + """--sql + DELETE FROM board_images + WHERE board_id = ? AND image_name = ?; + """, + (board_id, image_name), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + + def get_images_for_board( + self, + board_id: str, + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[ImageRecord]: + """Gets images for a board.""" + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT images.* + FROM board_images + INNER JOIN images ON board_images.image_name = images.image_name + WHERE board_images.board_id = ? + ORDER BY board_images.updated_at DESC; + """, + (board_id,), + ) + result = cast(list[sqlite3.Row], self._cursor.fetchall()) + images = list(map(lambda r: deserialize_image_record(dict(r)), result)) + + self._cursor.execute( + """--sql + SELECT COUNT(*) FROM images WHERE 1=1; + """ + ) + count = self._cursor.fetchone()[0] + + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + return OffsetPaginatedResults( + items=images, offset=offset, limit=limit, total=count + ) + + def get_boards_for_image( + self, + board_id: str, + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[BoardRecord]: + """Gets boards for an image.""" + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT boards.* + FROM board_images + INNER JOIN boards ON board_images.board_id = boards.board_id + WHERE board_images.image_name = ? + ORDER BY board_images.updated_at DESC; + """, + (board_id,), + ) + result = cast(list[sqlite3.Row], self._cursor.fetchall()) + boards = list(map(lambda r: BoardRecord(**r), result)) + + self._cursor.execute( + """--sql + SELECT COUNT(*) FROM boards WHERE 1=1; + """ + ) + count = self._cursor.fetchone()[0] + + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + return OffsetPaginatedResults( + items=boards, offset=offset, limit=limit, total=count + ) + + def get_image_count_for_board(self, board_id: str) -> int: + """Gets the number of images for a board.""" + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT COUNT(*) FROM board_images WHERE board_id = ?; + """, + (board_id,), + ) + count = self._cursor.fetchone()[0] + + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + return count diff --git a/invokeai/app/services/board_images.py b/invokeai/app/services/board_images.py new file mode 100644 index 0000000000..dd2e104180 --- /dev/null +++ b/invokeai/app/services/board_images.py @@ -0,0 +1,166 @@ +from abc import ABC, abstractmethod +from logging import Logger +from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase +from invokeai.app.services.board_record_storage import ( + BoardDTO, + BoardRecord, + BoardRecordStorageBase, +) + +from invokeai.app.services.image_record_storage import ( + ImageRecordStorageBase, + OffsetPaginatedResults, +) +from invokeai.app.services.models.image_record import ImageDTO, image_record_to_dto +from invokeai.app.services.urls import UrlServiceBase + + +class BoardImagesServiceABC(ABC): + """High-level service for board-image relationship management.""" + + @abstractmethod + def add_image_to_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Adds an image to a board.""" + pass + + @abstractmethod + def remove_image_from_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Removes an image from a board.""" + pass + + @abstractmethod + def get_images_for_board( + self, + board_id: str, + ) -> OffsetPaginatedResults[ImageDTO]: + """Gets images for a board.""" + pass + + @abstractmethod + def get_boards_for_image( + self, + image_name: str, + ) -> OffsetPaginatedResults[BoardDTO]: + """Gets boards for an image.""" + pass + + +class BoardImagesServiceDependencies: + """Service dependencies for the BoardImagesService.""" + + board_image_records: BoardImageRecordStorageBase + board_records: BoardRecordStorageBase + image_records: ImageRecordStorageBase + urls: UrlServiceBase + logger: Logger + + def __init__( + self, + board_image_record_storage: BoardImageRecordStorageBase, + image_record_storage: ImageRecordStorageBase, + board_record_storage: BoardRecordStorageBase, + url: UrlServiceBase, + logger: Logger, + ): + self.board_image_records = board_image_record_storage + self.image_records = image_record_storage + self.board_records = board_record_storage + self.urls = url + self.logger = logger + + +class BoardImagesService(BoardImagesServiceABC): + _services: BoardImagesServiceDependencies + + def __init__(self, services: BoardImagesServiceDependencies): + self._services = services + + def add_image_to_board( + self, + board_id: str, + image_name: str, + ) -> None: + self._services.board_image_records.add_image_to_board(board_id, image_name) + + def remove_image_from_board( + self, + board_id: str, + image_name: str, + ) -> None: + self._services.board_image_records.remove_image_from_board(board_id, image_name) + + def get_images_for_board( + self, + board_id: str, + ) -> OffsetPaginatedResults[ImageDTO]: + image_records = self._services.board_image_records.get_images_for_board( + board_id + ) + image_dtos = list( + map( + lambda r: image_record_to_dto( + r, + self._services.urls.get_image_url(r.image_name), + self._services.urls.get_image_url(r.image_name, True), + ), + image_records.items, + ) + ) + return OffsetPaginatedResults[ImageDTO]( + items=image_dtos, + offset=image_records.offset, + limit=image_records.limit, + total=image_records.total, + ) + + def get_boards_for_image( + self, + image_name: str, + ) -> OffsetPaginatedResults[BoardDTO]: + board_records = self._services.board_image_records.get_boards_for_image( + image_name + ) + board_dtos = [] + + for r in board_records.items: + cover_image_url = ( + self._services.urls.get_image_url(r.cover_image_name, True) + if r.cover_image_name + else None + ) + image_count = self._services.board_image_records.get_image_count_for_board( + r.board_id + ) + board_dtos.append( + board_record_to_dto( + r, + cover_image_url, + image_count, + ) + ) + + return OffsetPaginatedResults[BoardDTO]( + items=board_dtos, + offset=board_records.offset, + limit=board_records.limit, + total=board_records.total, + ) + + +def board_record_to_dto( + board_record: BoardRecord, cover_image_url: str | None, image_count: int +) -> BoardDTO: + """Converts a board record to a board DTO.""" + return BoardDTO( + **board_record.dict(), + cover_image_url=cover_image_url, + image_count=image_count, + ) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py new file mode 100644 index 0000000000..a954fe7ac4 --- /dev/null +++ b/invokeai/app/services/board_record_storage.py @@ -0,0 +1,331 @@ +from abc import ABC, abstractmethod +from datetime import datetime +from typing import Optional, cast +import sqlite3 +import threading +from typing import Optional, Union +import uuid +from invokeai.app.services.image_record_storage import OffsetPaginatedResults + +from pydantic import BaseModel, Field, Extra + + +class BoardRecord(BaseModel): + """Deserialized board record.""" + + board_id: str = Field(description="The unique ID of the board.") + """The unique ID of the board.""" + board_name: str = Field(description="The name of the board.") + """The name of the board.""" + created_at: Union[datetime, str] = Field( + description="The created timestamp of the board." + ) + """The created timestamp of the image.""" + updated_at: Union[datetime, str] = Field( + description="The updated timestamp of the board." + ) + """The updated timestamp of the image.""" + cover_image_name: Optional[str] = Field( + description="The name of the cover image of the board." + ) + """The name of the cover image of the board.""" + + +class BoardDTO(BoardRecord): + """Deserialized board record with cover image URL and image count.""" + + cover_image_url: Optional[str] = Field( + description="The URL of the thumbnail of the board's cover image." + ) + """The URL of the thumbnail of the most recent image in the board.""" + image_count: int = Field(description="The number of images in the board.") + """The number of images in the board.""" + + +class BoardChanges(BaseModel, extra=Extra.forbid): + board_name: Optional[str] = Field(description="The board's new name.") + cover_image_name: Optional[str] = Field( + description="The name of the board's new cover image." + ) + + +class BoardRecordNotFoundException(Exception): + """Raised when an board record is not found.""" + + def __init__(self, message="Board record not found"): + super().__init__(message) + + +class BoardRecordSaveException(Exception): + """Raised when an board record cannot be saved.""" + + def __init__(self, message="Board record not saved"): + super().__init__(message) + + +class BoardRecordDeleteException(Exception): + """Raised when an board record cannot be deleted.""" + + def __init__(self, message="Board record not deleted"): + super().__init__(message) + + +class BoardRecordStorageBase(ABC): + """Low-level service responsible for interfacing with the board record store.""" + + @abstractmethod + def delete(self, board_id: str) -> None: + """Deletes a board record.""" + pass + + @abstractmethod + def save( + self, + board_name: str, + ) -> BoardRecord: + """Saves a board record.""" + pass + + @abstractmethod + def get( + self, + board_id: str, + ) -> BoardRecord: + """Gets a board record.""" + pass + + @abstractmethod + def update( + self, + board_id: str, + changes: BoardChanges, + ) -> BoardRecord: + """Updates a board record.""" + pass + + @abstractmethod + def get_many( + self, + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[BoardRecord]: + """Gets many board records.""" + pass + + +class SqliteBoardRecordStorage(BoardRecordStorageBase): + _filename: str + _conn: sqlite3.Connection + _cursor: sqlite3.Cursor + _lock: threading.Lock + + def __init__(self, filename: str) -> None: + super().__init__() + self._filename = filename + self._conn = sqlite3.connect(filename, check_same_thread=False) + # Enable row factory to get rows as dictionaries (must be done before making the cursor!) + self._conn.row_factory = sqlite3.Row + self._cursor = self._conn.cursor() + self._lock = threading.Lock() + + try: + self._lock.acquire() + # Enable foreign keys + self._conn.execute("PRAGMA foreign_keys = ON;") + self._create_tables() + self._conn.commit() + finally: + self._lock.release() + + def _create_tables(self) -> None: + """Creates the `boards` table and `board_images` junction table.""" + + # Create the `boards` table. + self._cursor.execute( + """--sql + CREATE TABLE IF NOT EXISTS boards ( + board_id TEXT NOT NULL PRIMARY KEY, + board_name TEXT NOT NULL, + cover_image_name TEXT, + created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + -- Updated via trigger + updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + -- Soft delete, currently unused + deleted_at DATETIME + ); + """ + ) + + self._cursor.execute( + """--sql + CREATE INDEX IF NOT EXISTS idx_boards_created_at ON boards(created_at); + """ + ) + + # Add trigger for `updated_at`. + self._cursor.execute( + """--sql + CREATE TRIGGER IF NOT EXISTS tg_boards_updated_at + AFTER UPDATE + ON boards FOR EACH ROW + BEGIN + UPDATE boards SET updated_at = current_timestamp + WHERE board_id = old.board_id; + END; + """ + ) + + def delete(self, board_id: str) -> None: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + DELETE FROM boards + WHERE board_id = ?; + """, + (board_id), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordDeleteException from e + finally: + self._lock.release() + + def save( + self, + board_name: str, + ) -> BoardRecord: + try: + board_id = str(uuid.uuid4()) + self._lock.acquire() + self._cursor.execute( + """--sql + INSERT OR IGNORE INTO boards (board_id, board_name) + VALUES (?, ?); + """, + (board_id, board_name), + ) + self._conn.commit() + + self._cursor.execute( + """--sql + SELECT * + FROM boards + WHERE board_id = ?; + """, + (board_id,), + ) + + result = self._cursor.fetchone() + return BoardRecord(**result) + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordSaveException from e + finally: + self._lock.release() + + def get( + self, + board_id: str, + ) -> BoardRecord: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT * + FROM boards + WHERE board_id = ?; + """, + (board_id,), + ) + + result = cast(Union[sqlite3.Row, None], self._cursor.fetchone()) + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordNotFoundException from e + finally: + self._lock.release() + if result is None: + raise BoardRecordNotFoundException + return BoardRecord(**dict(result)) + + def update( + self, + board_id: str, + changes: BoardChanges, + ) -> None: + try: + self._lock.acquire() + + # Change the name of a board + if changes.board_name is not None: + self._cursor.execute( + f"""--sql + UPDATE boards + SET board_name = ? + WHERE board_id = ?; + """, + (changes.board_name, board_id), + ) + + # Change the cover image of a board + if changes.cover_image_name is not None: + self._cursor.execute( + f"""--sql + UPDATE boards + SET cover_image_name = ? + WHERE board_id = ?; + """, + (changes.cover_image_name, board_id), + ) + + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordSaveException from e + finally: + self._lock.release() + + def get_many( + self, + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[BoardRecord]: + try: + self._lock.acquire() + + # Get all the boards + self._cursor.execute( + """--sql + SELECT * + FROM boards + ORDER BY updated_at DESC + LIMIT ? OFFSET ?; + """, + (limit, offset), + ) + + result = cast(list[sqlite3.Row], self._cursor.fetchall()) + boards = [BoardRecord(**dict(row)) for row in result] + + # Get the total number of boards + self._cursor.execute( + """--sql + SELECT COUNT(*) + FROM boards + WHERE 1=1; + """ + ) + + count = cast(int, self._cursor.fetchone()[0]) + + return OffsetPaginatedResults[BoardRecord]( + items=boards, offset=offset, limit=limit, total=count + ) + + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index 3cdadd6c22..07d64e655a 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -1,253 +1,153 @@ from abc import ABC, abstractmethod -from datetime import datetime -from typing import Generic, Optional, TypeVar, cast -import sqlite3 -import threading -from typing import Optional, Union -import uuid -from invokeai.app.services.image_record_storage import OffsetPaginatedResults -from pydantic import BaseModel, Field, Extra -from pydantic.generics import GenericModel +from logging import Logger +from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase +from invokeai.app.services.board_images import board_record_to_dto -T = TypeVar("T", bound=BaseModel) - -class BoardRecord(BaseModel): - """Deserialized board record.""" - - id: str = Field(description="The unique ID of the board.") - name: str = Field(description="The name of the board.") - """The name of the board.""" - created_at: Union[datetime, str] = Field( - description="The created timestamp of the board." - ) - """The created timestamp of the image.""" - updated_at: Union[datetime, str] = Field( - description="The updated timestamp of the board." - ) - -class BoardRecordInList(BaseModel): - """Deserialized board record in a list.""" - - id: str = Field(description="The unique ID of the board.") - name: str = Field(description="The name of the board.") - most_recent_image_url: Optional[str] = Field( - description="The URL of the most recent image in the board." - ) - """The name of the board.""" - created_at: Union[datetime, str] = Field( - description="The created timestamp of the board." - ) - """The created timestamp of the image.""" - updated_at: Union[datetime, str] = Field( - description="The updated timestamp of the board." - ) - -class BoardRecordChanges(BaseModel, extra=Extra.forbid): - name: Optional[str] = Field( - description="The board's new name." - ) - -class BoardRecordNotFoundException(Exception): - """Raised when an board record is not found.""" - - def __init__(self, message="Board record not found"): - super().__init__(message) +from invokeai.app.services.board_record_storage import ( + BoardDTO, + BoardRecord, + BoardChanges, + BoardRecordStorageBase, +) +from invokeai.app.services.image_record_storage import ( + ImageRecordStorageBase, + OffsetPaginatedResults, +) +from invokeai.app.services.models.image_record import ImageDTO +from invokeai.app.services.urls import UrlServiceBase -class BoardRecordSaveException(Exception): - """Raised when an board record cannot be saved.""" - - def __init__(self, message="Board record not saved"): - super().__init__(message) - - -class BoardRecordDeleteException(Exception): - """Raised when an board record cannot be deleted.""" - - def __init__(self, message="Board record not deleted"): - super().__init__(message) - -class BoardStorageBase(ABC): - """Low-level service responsible for interfacing with the board record store.""" +class BoardServiceABC(ABC): + """High-level service for board management.""" @abstractmethod - def delete(self, board_id: str) -> None: - """Deletes a board record.""" + def create( + self, + board_name: str, + ) -> BoardDTO: + """Creates a board.""" pass @abstractmethod - def save( + def get_dto( self, - board_name: str, - ): - """Saves a board record.""" + board_id: str, + ) -> BoardDTO: + """Gets a board.""" pass - def get_cover_photo(self, board_id: str) -> Optional[str]: - """Gets the cover photo for a board.""" + @abstractmethod + def update( + self, + board_id: str, + changes: BoardChanges, + ) -> BoardDTO: + """Updates a board.""" pass + @abstractmethod + def delete( + self, + board_id: str, + ) -> None: + """Deletes a board.""" + pass + + @abstractmethod def get_many( self, - offset: int, - limit: int, - ): - """Gets many board records.""" + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[BoardDTO]: + """Gets many boards.""" pass -class SqliteBoardStorage(BoardStorageBase): - _filename: str - _conn: sqlite3.Connection - _cursor: sqlite3.Cursor - _lock: threading.Lock +class BoardServiceDependencies: + """Service dependencies for the BoardService.""" - def __init__(self, filename: str) -> None: - super().__init__() - self._filename = filename - self._conn = sqlite3.connect(filename, check_same_thread=False) - # Enable row factory to get rows as dictionaries (must be done before making the cursor!) - self._conn.row_factory = sqlite3.Row - self._cursor = self._conn.cursor() - self._lock = threading.Lock() + board_image_records: BoardImageRecordStorageBase + board_records: BoardRecordStorageBase + image_records: ImageRecordStorageBase + urls: UrlServiceBase + logger: Logger - try: - self._lock.acquire() - # Enable foreign keys - self._conn.execute("PRAGMA foreign_keys = ON;") - self._create_tables() - self._conn.commit() - finally: - self._lock.release() + def __init__( + self, + board_image_record_storage: BoardImageRecordStorageBase, + image_record_storage: ImageRecordStorageBase, + board_record_storage: BoardRecordStorageBase, + url: UrlServiceBase, + logger: Logger, + ): + self.board_image_records = board_image_record_storage + self.image_records = image_record_storage + self.board_records = board_record_storage + self.urls = url + self.logger = logger - def _create_tables(self) -> None: - """Creates the `board` table.""" - # Create the `images` table. - self._cursor.execute( - """--sql - CREATE TABLE IF NOT EXISTS boards ( - id TEXT NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), - -- Updated via trigger - updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) - ); - """ +class BoardService(BoardServiceABC): + _services: BoardServiceDependencies + + def __init__(self, services: BoardServiceDependencies): + self._services = services + + def create( + self, + board_name: str, + ) -> BoardDTO: + board_record = self._services.board_records.save(board_name) + return board_record_to_dto(board_record, None, 0) + + def get_dto(self, board_id: str) -> BoardDTO: + board_record = self._services.board_records.get(board_id) + cover_image_url = ( + self._services.urls.get_image_url(board_record.cover_image_name, True) + if board_record.cover_image_name + else None ) - - self._cursor.execute( - """--sql - CREATE INDEX IF NOT EXISTS idx_boards_created_at ON boards(created_at); - """ + image_count = self._services.board_image_records.get_image_count_for_board( + board_id ) + return board_record_to_dto(board_record, cover_image_url, image_count) - # Add trigger for `updated_at`. - self._cursor.execute( - """--sql - CREATE TRIGGER IF NOT EXISTS tg_boards_updated_at - AFTER UPDATE - ON boards FOR EACH ROW - BEGIN - UPDATE boards SET updated_at = current_timestamp - WHERE board_name = old.board_name; - END; - """ + def update( + self, + board_id: str, + changes: BoardChanges, + ) -> BoardDTO: + board_record = self._services.board_records.update(board_id, changes) + cover_image_url = ( + self._services.urls.get_image_url(board_record.cover_image_name, True) + if board_record.cover_image_name + else None ) - + image_count = self._services.board_image_records.get_image_count_for_board( + board_id + ) + return board_record_to_dto(board_record, cover_image_url, image_count) def delete(self, board_id: str) -> None: - try: - self._lock.acquire() - self._cursor.execute( - """--sql - DELETE FROM boards - WHERE id = ?; - """, - (board_id), - ) - self._conn.commit() - except sqlite3.Error as e: - self._conn.rollback() - raise BoardRecordDeleteException from e - finally: - self._lock.release() - - def save( - self, - board_name: str, - ): - try: - board_id = str(uuid.uuid4()) - self._lock.acquire() - self._cursor.execute( - """--sql - INSERT OR IGNORE INTO boards (id, name) - VALUES (?, ?); - """, - (board_id, board_name), - ) - self._conn.commit() - - self._cursor.execute( - """--sql - SELECT * - FROM boards - WHERE id = ?; - """, - (board_id,), - ) - - result = self._cursor.fetchone() - return result - except sqlite3.Error as e: - self._conn.rollback() - raise BoardRecordSaveException from e - finally: - self._lock.release() - + self._services.board_records.delete(board_id) def get_many( - self, - offset: int, - limit: int, - ) -> OffsetPaginatedResults[BoardRecord]: - try: + self, offset: int = 0, limit: int = 10 + ) -> OffsetPaginatedResults[BoardDTO]: + board_records = self._services.board_records.get_many(offset, limit) + board_dtos = [] + for r in board_records.items: + cover_image_url = ( + self._services.urls.get_image_url(r.cover_image_name, True) + if r.cover_image_name + else None + ) + image_count = self._services.board_image_records.get_image_count_for_board( + r.board_id + ) + board_dtos.append(board_record_to_dto(r, cover_image_url, image_count)) - self._lock.acquire() - - count_query = f"""SELECT COUNT(*) FROM images WHERE 1=1\n""" - images_query = f"""SELECT * FROM images WHERE 1=1\n""" - - query_conditions = "" - query_params = [] - - query_pagination = f"""ORDER BY created_at DESC LIMIT ? OFFSET ?\n""" - - # Final images query with pagination - images_query += query_conditions + query_pagination + ";" - # Add all the parameters - images_params = query_params.copy() - images_params.append(limit) - images_params.append(offset) - # Build the list of images, deserializing each row - self._cursor.execute(images_query, images_params) - result = cast(list[sqlite3.Row], self._cursor.fetchall()) - boards = [BoardRecord(**dict(row)) for row in result] - - # Set up and execute the count query, without pagination - count_query += query_conditions + ";" - count_params = query_params.copy() - self._cursor.execute(count_query, count_params) - count = self._cursor.fetchone()[0] - - except sqlite3.Error as e: - self._conn.rollback() - raise BoardRecordSaveException from e - finally: - self._lock.release() - - return OffsetPaginatedResults( - items=boards, offset=offset, limit=limit, total=count - ) \ No newline at end of file + return OffsetPaginatedResults[BoardDTO]( + items=board_dtos, offset=offset, limit=limit, total=len(board_dtos) + ) diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 96c6beea12..2ca9ad66ca 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -82,7 +82,6 @@ class ImageRecordStorageBase(ABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, - board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageRecord]: """Gets a page of image records.""" pass @@ -94,11 +93,6 @@ class ImageRecordStorageBase(ABC): """Deletes an image record.""" pass - @abstractmethod - def get_board_cover_photo(self, board_id: str) -> Optional[ImageRecord]: - """Gets the cover photo for a board.""" - pass - @abstractmethod def save( self, @@ -197,7 +191,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): AFTER UPDATE ON images FOR EACH ROW BEGIN - UPDATE images SET updated_at = current_timestamp + UPDATE images SET updated_at = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE image_name = old.image_name; END; """ @@ -268,14 +262,14 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): ) # Change the image's `is_intermediate`` flag - if changes.board_id is not None: + if changes.is_intermediate is not None: self._cursor.execute( f"""--sql UPDATE images SET board_id = ? WHERE image_name = ?; """, - (changes.board_id, image_name), + (changes.is_intermediate, image_name), ) self._conn.commit() @@ -284,32 +278,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): raise ImageRecordSaveException from e finally: self._lock.release() - - def get_board_cover_photo(self, board_id: str) -> ImageRecord | None: - try: - self._lock.acquire() - self._cursor.execute( - """ - SELECT * - FROM images - WHERE board_id = ? - ORDER BY created_at DESC - LIMIT 1 - """, - (board_id), - ) - self._conn.commit() - result = cast(Union[sqlite3.Row, None], self._cursor.fetchone()) - except sqlite3.Error as e: - self._conn.rollback() - raise ImageRecordNotFoundException from e - finally: - self._lock.release() - - if not result: - raise ImageRecordNotFoundException - - return deserialize_image_record(dict(result)) def get_many( self, @@ -318,7 +286,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, - board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageRecord]: try: self._lock.acquire() @@ -350,10 +317,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): query_conditions += f"""AND is_intermediate = ?\n""" query_params.append(is_intermediate) - if board_id is not None: - query_conditions += f"""AND board_id = ?\n""" - query_params.append(board_id) - query_pagination = f"""ORDER BY created_at DESC LIMIT ? OFFSET ?\n""" # Final images query with pagination @@ -371,7 +334,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): count_query += query_conditions + ";" count_params = query_params.copy() self._cursor.execute(count_query, count_params) - count = self._cursor.fetchone()[0] + count = cast(int, self._cursor.fetchone()[0]) except sqlite3.Error as e: self._conn.rollback() raise e diff --git a/invokeai/app/services/images.py b/invokeai/app/services/images.py index 173268563a..aa27e38d17 100644 --- a/invokeai/app/services/images.py +++ b/invokeai/app/services/images.py @@ -49,7 +49,7 @@ class ImageServiceABC(ABC): image_category: ImageCategory, node_id: Optional[str] = None, session_id: Optional[str] = None, - intermediate: bool = False, + is_intermediate: bool = False, ) -> ImageDTO: """Creates an image, storing the file and its metadata.""" pass @@ -79,7 +79,7 @@ class ImageServiceABC(ABC): pass @abstractmethod - def get_path(self, image_name: str) -> str: + def get_path(self, image_name: str, thumbnail: bool = False) -> str: """Gets an image's path.""" pass @@ -322,7 +322,6 @@ class ImageService(ImageServiceABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, - board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageDTO]: try: results = self._services.records.get_many( @@ -331,7 +330,6 @@ class ImageService(ImageServiceABC): image_origin, categories, is_intermediate, - board_id ) image_dtos = list( diff --git a/invokeai/app/services/invocation_services.py b/invokeai/app/services/invocation_services.py index d69e0b294f..10d1d91920 100644 --- a/invokeai/app/services/invocation_services.py +++ b/invokeai/app/services/invocation_services.py @@ -4,7 +4,9 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from logging import Logger - from invokeai.app.services.images import ImageService + from invokeai.app.services.board_images import BoardImagesServiceABC + from invokeai.app.services.boards import BoardServiceABC + from invokeai.app.services.images import ImageServiceABC from invokeai.backend import ModelManager from invokeai.app.services.events import EventServiceBase from invokeai.app.services.latent_storage import LatentsStorageBase @@ -14,7 +16,6 @@ if TYPE_CHECKING: from invokeai.app.services.config import InvokeAISettings from invokeai.app.services.graph import GraphExecutionState, LibraryGraph from invokeai.app.services.invoker import InvocationProcessorABC - from invokeai.app.services.boards import BoardStorageBase class InvocationServices: @@ -27,10 +28,9 @@ class InvocationServices: model_manager: "ModelManager" restoration: "RestorationServices" configuration: "InvokeAISettings" - images: "ImageService" - boards: "BoardStorageBase" - - # NOTE: we must forward-declare any types that include invocations, since invocations can use services + images: "ImageServiceABC" + boards: "BoardServiceABC" + board_images: "BoardImagesServiceABC" graph_library: "ItemStorageABC"["LibraryGraph"] graph_execution_manager: "ItemStorageABC"["GraphExecutionState"] processor: "InvocationProcessorABC" @@ -41,20 +41,23 @@ class InvocationServices: events: "EventServiceBase", logger: "Logger", latents: "LatentsStorageBase", - images: "ImageService", + images: "ImageServiceABC", + boards: "BoardServiceABC", + board_images: "BoardImagesServiceABC", queue: "InvocationQueueABC", graph_library: "ItemStorageABC"["LibraryGraph"], graph_execution_manager: "ItemStorageABC"["GraphExecutionState"], processor: "InvocationProcessorABC", restoration: "RestorationServices", configuration: "InvokeAISettings", - boards: "BoardStorageBase", ): self.model_manager = model_manager self.events = events self.logger = logger self.latents = latents self.images = images + self.boards = boards + self.board_images = board_images self.queue = queue self.graph_library = graph_library self.graph_execution_manager = graph_execution_manager diff --git a/invokeai/app/services/models/image_record.py b/invokeai/app/services/models/image_record.py index 98f370f337..d971d65916 100644 --- a/invokeai/app/services/models/image_record.py +++ b/invokeai/app/services/models/image_record.py @@ -48,11 +48,6 @@ class ImageRecord(BaseModel): description="A limited subset of the image's generation metadata. Retrieve the image's session for full metadata.", ) """A limited subset of the image's generation metadata. Retrieve the image's session for full metadata.""" - board_id: Optional[str] = Field( - default=None, - description="The board ID that this image belongs to.", - ) - """The board ID that this image belongs to.""" class ImageRecordChanges(BaseModel, extra=Extra.forbid): @@ -77,10 +72,6 @@ class ImageRecordChanges(BaseModel, extra=Extra.forbid): default=None, description="The image's new `is_intermediate` flag." ) """The image's new `is_intermediate` flag.""" - board_id: Optional[StrictStr] = Field( - default=None, description="The image's new board ID." - ) - """The image's new board ID.""" class ImageUrlsDTO(BaseModel): @@ -131,7 +122,6 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord: updated_at = image_dict.get("updated_at", get_iso_timestamp()) deleted_at = image_dict.get("deleted_at", get_iso_timestamp()) is_intermediate = image_dict.get("is_intermediate", False) - board_id = image_dict.get("board_id", None) raw_metadata = image_dict.get("metadata") @@ -153,5 +143,4 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord: updated_at=updated_at, deleted_at=deleted_at, is_intermediate=is_intermediate, - board_id=board_id, ) From 748016bdab0a3c5e12ced2b92ebc63d53b4e6246 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Wed, 14 Jun 2023 11:20:23 -0700 Subject: [PATCH 021/110] routes working --- invokeai/app/api/routers/board_images.py | 69 +++++++++ invokeai/app/api/routers/boards.py | 143 +++++++++--------- invokeai/app/api_app.py | 6 +- invokeai/app/services/board_images.py | 2 +- invokeai/app/services/board_record_storage.py | 35 +---- invokeai/app/services/boards.py | 4 +- invokeai/app/services/image_record_storage.py | 11 -- invokeai/app/services/models/board_record.py | 57 +++++++ 8 files changed, 203 insertions(+), 124 deletions(-) create mode 100644 invokeai/app/api/routers/board_images.py create mode 100644 invokeai/app/services/models/board_record.py diff --git a/invokeai/app/api/routers/board_images.py b/invokeai/app/api/routers/board_images.py new file mode 100644 index 0000000000..b206ab500d --- /dev/null +++ b/invokeai/app/api/routers/board_images.py @@ -0,0 +1,69 @@ +from fastapi import Body, HTTPException, Path, Query +from fastapi.routing import APIRouter +from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges +from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.board_record import BoardDTO +from invokeai.app.services.models.image_record import ImageDTO + +from ..dependencies import ApiDependencies + +board_images_router = APIRouter(prefix="/v1/board_images", tags=["boards"]) + + +@board_images_router.post( + "/", + operation_id="create_board_image", + responses={ + 201: {"description": "The image was added to a board successfully"}, + }, + status_code=201, +) +async def create_board_image( + board_id: str = Body(description="The id of the board to add to"), + image_name: str = Body(description="The name of the image to add"), +): + """Creates a board_image""" + try: + result = ApiDependencies.invoker.services.board_images.add_image_to_board(board_id=board_id, image_name=image_name) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to add to board") + +@board_images_router.delete( + "/", + operation_id="remove_board_image", + responses={ + 201: {"description": "The image was removed from the board successfully"}, + }, + status_code=201, +) +async def remove_board_image( + board_id: str = Body(description="The id of the board"), + image_name: str = Body(description="The name of the image to remove"), +): + """Deletes a board_image""" + try: + result = ApiDependencies.invoker.services.board_images.remove_image_from_board(board_id=board_id, image_name=image_name) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to update board") + + + +@board_images_router.get( + "/{board_id}", + operation_id="list_board_images", + response_model=OffsetPaginatedResults[ImageDTO], +) +async def list_board_images( + board_id: str = Path(description="The id of the board"), + offset: int = Query(default=0, description="The page offset"), + limit: int = Query(default=10, description="The number of boards per page"), +) -> OffsetPaginatedResults[ImageDTO]: + """Gets a list of images for a board""" + + results = ApiDependencies.invoker.services.board_images.get_images_for_board( + board_id, + ) + return results + diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index f3a76e08d3..618e8f990b 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -1,86 +1,79 @@ -# from fastapi import Body, HTTPException, Path, Query -# from fastapi.routing import APIRouter -# from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges -# from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from fastapi import Body, HTTPException, Path, Query +from fastapi.routing import APIRouter +from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges +from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.board_record import BoardDTO -# from ..dependencies import ApiDependencies +from ..dependencies import ApiDependencies -# boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) +boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) -# @boards_router.post( -# "/", -# operation_id="create_board", -# responses={ -# 201: {"description": "The board was created successfully"}, -# }, -# status_code=201, -# ) -# async def create_board( -# board_name: str = Body(description="The name of the board to create"), -# ): -# """Creates a board""" -# try: -# result = ApiDependencies.invoker.services.boards.save(board_name=board_name) -# return result -# except Exception as e: -# raise HTTPException(status_code=500, detail="Failed to create board") +@boards_router.post( + "/", + operation_id="create_board", + responses={ + 201: {"description": "The board was created successfully"}, + }, + status_code=201, +) +async def create_board( + board_name: str = Body(description="The name of the board to create"), +): + """Creates a board""" + try: + result = ApiDependencies.invoker.services.boards.create(board_name=board_name) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to create board") + +@boards_router.patch( + "/{board_id}", + operation_id="update_board", + responses={ + 201: {"description": "The board was updated successfully"}, + }, + status_code=201, +) +async def update_board( + board_id: str = Path(description="The id of board to update"), + changes: BoardChanges = Body(description="The changes to apply to the board"), +): + """Creates a board""" + try: + result = ApiDependencies.invoker.services.boards.update(board_id=board_id, changes=changes) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to update board") -# @boards_router.delete("/{board_id}", operation_id="delete_board") -# async def delete_board( -# board_id: str = Path(description="The id of board to delete"), -# ) -> None: -# """Deletes a board""" +@boards_router.delete("/{board_id}", operation_id="delete_board") +async def delete_board( + board_id: str = Path(description="The id of board to delete"), +) -> None: + """Deletes a board""" -# try: -# ApiDependencies.invoker.services.boards.delete(board_id=board_id) -# except Exception as e: -# # TODO: Does this need any exception handling at all? -# pass + try: + ApiDependencies.invoker.services.boards.delete(board_id=board_id) + except Exception as e: + # TODO: Does this need any exception handling at all? + pass -# @boards_router.get( -# "/", -# operation_id="list_boards", -# response_model=OffsetPaginatedResults[BoardRecord], -# ) -# async def list_boards( -# offset: int = Query(default=0, description="The page offset"), -# limit: int = Query(default=10, description="The number of boards per page"), -# ) -> OffsetPaginatedResults[BoardRecord]: -# """Gets a list of boards""" +@boards_router.get( + "/", + operation_id="list_boards", + response_model=OffsetPaginatedResults[BoardRecord], +) +async def list_boards( + offset: int = Query(default=0, description="The page offset"), + limit: int = Query(default=10, description="The number of boards per page"), +) -> OffsetPaginatedResults[BoardDTO]: + """Gets a list of boards""" -# results = ApiDependencies.invoker.services.boards.get_many( -# offset, -# limit, -# ) + results = ApiDependencies.invoker.services.boards.get_many( + offset, + limit, + ) + return results -# boards = list( -# map( -# lambda r: board_record_to_dto( -# r, -# generate_cover_photo_url(r.id) -# ), -# results.boards, -# ) -# ) - -# return boards - - - -# def board_record_to_dto( -# board_record: BoardRecord, cover_image_url: str -# ) -> BoardDTO: -# """Converts an image record to an image DTO.""" -# return BoardDTO( -# **board_record.dict(), -# cover_image_url=cover_image_url, -# ) - -# def generate_cover_photo_url(board_id: str) -> str | None: -# cover_photo = ApiDependencies.invoker.services.images._services.records.get_board_cover_photo(board_id) -# if cover_photo is not None: -# url = ApiDependencies.invoker.services.images._services.urls.get_image_url(cover_photo.image_origin, cover_photo.image_name) -# return url diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index 50228edf7e..22b4efec74 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -24,7 +24,7 @@ logger = InvokeAILogger.getLogger(config=app_config) import invokeai.frontend.web as web_dir from .api.dependencies import ApiDependencies -from .api.routers import sessions, models, images, boards +from .api.routers import sessions, models, images, boards, board_images from .api.sockets import SocketIO from .invocations.baseinvocation import BaseInvocation @@ -78,7 +78,9 @@ app.include_router(models.models_router, prefix="/api") app.include_router(images.images_router, prefix="/api") -# app.include_router(boards.boards_router, prefix="/api") +app.include_router(boards.boards_router, prefix="/api") + +app.include_router(board_images.board_images_router, prefix="/api") # Build a custom OpenAPI to include all outputs # TODO: can outputs be included on metadata of invocation schemas somehow? diff --git a/invokeai/app/services/board_images.py b/invokeai/app/services/board_images.py index dd2e104180..df2af4bbcf 100644 --- a/invokeai/app/services/board_images.py +++ b/invokeai/app/services/board_images.py @@ -2,7 +2,6 @@ from abc import ABC, abstractmethod from logging import Logger from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase from invokeai.app.services.board_record_storage import ( - BoardDTO, BoardRecord, BoardRecordStorageBase, ) @@ -11,6 +10,7 @@ from invokeai.app.services.image_record_storage import ( ImageRecordStorageBase, OffsetPaginatedResults, ) +from invokeai.app.services.models.board_record import BoardDTO from invokeai.app.services.models.image_record import ImageDTO, image_record_to_dto from invokeai.app.services.urls import UrlServiceBase diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index a954fe7ac4..8207470e6d 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -6,41 +6,11 @@ import threading from typing import Optional, Union import uuid from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.board_record import BoardRecord, deserialize_board_record from pydantic import BaseModel, Field, Extra -class BoardRecord(BaseModel): - """Deserialized board record.""" - - board_id: str = Field(description="The unique ID of the board.") - """The unique ID of the board.""" - board_name: str = Field(description="The name of the board.") - """The name of the board.""" - created_at: Union[datetime, str] = Field( - description="The created timestamp of the board." - ) - """The created timestamp of the image.""" - updated_at: Union[datetime, str] = Field( - description="The updated timestamp of the board." - ) - """The updated timestamp of the image.""" - cover_image_name: Optional[str] = Field( - description="The name of the cover image of the board." - ) - """The name of the cover image of the board.""" - - -class BoardDTO(BoardRecord): - """Deserialized board record with cover image URL and image count.""" - - cover_image_url: Optional[str] = Field( - description="The URL of the thumbnail of the board's cover image." - ) - """The URL of the thumbnail of the most recent image in the board.""" - image_count: int = Field(description="The number of images in the board.") - """The number of images in the board.""" - class BoardChanges(BaseModel, extra=Extra.forbid): board_name: Optional[str] = Field(description="The board's new name.") @@ -221,6 +191,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): return BoardRecord(**result) except sqlite3.Error as e: self._conn.rollback() + print(e) raise BoardRecordSaveException from e finally: self._lock.release() @@ -307,7 +278,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): ) result = cast(list[sqlite3.Row], self._cursor.fetchall()) - boards = [BoardRecord(**dict(row)) for row in result] + boards = list(map(lambda r: deserialize_board_record(dict(r)), result)) # Get the total number of boards self._cursor.execute( diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index 07d64e655a..ddfaacf79c 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -5,8 +5,6 @@ from invokeai.app.services.board_image_record_storage import BoardImageRecordSto from invokeai.app.services.board_images import board_record_to_dto from invokeai.app.services.board_record_storage import ( - BoardDTO, - BoardRecord, BoardChanges, BoardRecordStorageBase, ) @@ -14,7 +12,7 @@ from invokeai.app.services.image_record_storage import ( ImageRecordStorageBase, OffsetPaginatedResults, ) -from invokeai.app.services.models.image_record import ImageDTO +from invokeai.app.services.models.board_record import BoardDTO from invokeai.app.services.urls import UrlServiceBase diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 2ca9ad66ca..677e1d3445 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -261,17 +261,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): (changes.is_intermediate, image_name), ) - # Change the image's `is_intermediate`` flag - if changes.is_intermediate is not None: - self._cursor.execute( - f"""--sql - UPDATE images - SET board_id = ? - WHERE image_name = ?; - """, - (changes.is_intermediate, image_name), - ) - self._conn.commit() except sqlite3.Error as e: self._conn.rollback() diff --git a/invokeai/app/services/models/board_record.py b/invokeai/app/services/models/board_record.py new file mode 100644 index 0000000000..35c4cea9b0 --- /dev/null +++ b/invokeai/app/services/models/board_record.py @@ -0,0 +1,57 @@ +from typing import Optional, Union +from datetime import datetime +from pydantic import BaseModel, Extra, Field, StrictBool, StrictStr +from invokeai.app.util.misc import get_iso_timestamp + +class BoardRecord(BaseModel): + """Deserialized board record.""" + + board_id: str = Field(description="The unique ID of the board.") + """The unique ID of the board.""" + board_name: str = Field(description="The name of the board.") + """The name of the board.""" + created_at: Union[datetime, str] = Field( + description="The created timestamp of the board." + ) + """The created timestamp of the image.""" + updated_at: Union[datetime, str] = Field( + description="The updated timestamp of the board." + ) + """The updated timestamp of the image.""" + cover_image_name: Optional[str] = Field( + description="The name of the cover image of the board." + ) + """The name of the cover image of the board.""" + + +class BoardDTO(BoardRecord): + """Deserialized board record with cover image URL and image count.""" + + cover_image_url: Optional[str] = Field( + description="The URL of the thumbnail of the board's cover image." + ) + """The URL of the thumbnail of the most recent image in the board.""" + image_count: int = Field(description="The number of images in the board.") + """The number of images in the board.""" + + + + +def deserialize_board_record(board_dict: dict) -> BoardRecord: + """Deserializes a board record.""" + + # Retrieve all the values, setting "reasonable" defaults if they are not present. + + board_id = board_dict.get("board_id", "unknown") + board_name = board_dict.get("board_name", "unknown") + created_at = board_dict.get("created_at", get_iso_timestamp()) + updated_at = board_dict.get("updated_at", get_iso_timestamp()) + deleted_at = board_dict.get("deleted_at", get_iso_timestamp()) + + return BoardRecord( + board_id=board_id, + board_name=board_name, + created_at=created_at, + updated_at=updated_at, + deleted_at=deleted_at, + ) From c009f46b00037218344d8b791d13390eac41cde2 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Wed, 14 Jun 2023 14:24:58 -0400 Subject: [PATCH 022/110] regenerate api schema --- .../frontend/web/src/services/api/index.ts | 2 + .../src/services/api/models/BoardChanges.ts | 15 ++ .../src/services/api/models/BoardRecord.ts | 30 +++ .../api/models/Body_create_board_image.ts | 15 ++ .../api/models/Body_remove_board_image.ts | 15 ++ .../OffsetPaginatedResults_BoardRecord_.ts | 28 +++ .../services/api/services/BoardsService.ts | 210 ++++++++++++++++++ 7 files changed, 315 insertions(+) create mode 100644 invokeai/frontend/web/src/services/api/models/BoardChanges.ts create mode 100644 invokeai/frontend/web/src/services/api/models/BoardRecord.ts create mode 100644 invokeai/frontend/web/src/services/api/models/Body_create_board_image.ts create mode 100644 invokeai/frontend/web/src/services/api/models/Body_remove_board_image.ts create mode 100644 invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts create mode 100644 invokeai/frontend/web/src/services/api/services/BoardsService.ts diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index 7481a5daad..60db5eda3a 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -98,6 +98,7 @@ export type { MultiplyInvocation } from './models/MultiplyInvocation'; export type { NoiseInvocation } from './models/NoiseInvocation'; export type { NoiseOutput } from './models/NoiseOutput'; export type { NormalbaeImageProcessorInvocation } from './models/NormalbaeImageProcessorInvocation'; +export type { OffsetPaginatedResults_BoardRecord_ } from './models/OffsetPaginatedResults_BoardRecord_'; export type { OffsetPaginatedResults_ImageDTO_ } from './models/OffsetPaginatedResults_ImageDTO_'; export type { OpenposeImageProcessorInvocation } from './models/OpenposeImageProcessorInvocation'; export type { PaginatedResults_GraphExecutionState_ } from './models/PaginatedResults_GraphExecutionState_'; @@ -129,6 +130,7 @@ export type { VaeRepo } from './models/VaeRepo'; export type { ValidationError } from './models/ValidationError'; export type { ZoeDepthImageProcessorInvocation } from './models/ZoeDepthImageProcessorInvocation'; +export { BoardsService } from './services/BoardsService'; export { ImagesService } from './services/ImagesService'; export { ModelsService } from './services/ModelsService'; export { SessionsService } from './services/SessionsService'; diff --git a/invokeai/frontend/web/src/services/api/models/BoardChanges.ts b/invokeai/frontend/web/src/services/api/models/BoardChanges.ts new file mode 100644 index 0000000000..fb2bfa0cd9 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/BoardChanges.ts @@ -0,0 +1,15 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type BoardChanges = { + /** + * The board's new name. + */ + board_name?: string; + /** + * The name of the board's new cover image. + */ + cover_image_name?: string; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/BoardRecord.ts b/invokeai/frontend/web/src/services/api/models/BoardRecord.ts new file mode 100644 index 0000000000..50db9b22eb --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/BoardRecord.ts @@ -0,0 +1,30 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Deserialized board record. + */ +export type BoardRecord = { + /** + * The unique ID of the board. + */ + board_id: string; + /** + * The name of the board. + */ + board_name: string; + /** + * The created timestamp of the board. + */ + created_at: string; + /** + * The updated timestamp of the board. + */ + updated_at: string; + /** + * The name of the cover image of the board. + */ + cover_image_name?: string; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/Body_create_board_image.ts b/invokeai/frontend/web/src/services/api/models/Body_create_board_image.ts new file mode 100644 index 0000000000..47f8537eaa --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/Body_create_board_image.ts @@ -0,0 +1,15 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Body_create_board_image = { + /** + * The id of the board to add to + */ + board_id: string; + /** + * The name of the image to add + */ + image_name: string; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/Body_remove_board_image.ts b/invokeai/frontend/web/src/services/api/models/Body_remove_board_image.ts new file mode 100644 index 0000000000..6f5a3652d0 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/Body_remove_board_image.ts @@ -0,0 +1,15 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Body_remove_board_image = { + /** + * The id of the board + */ + board_id: string; + /** + * The name of the image to remove + */ + image_name: string; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts new file mode 100644 index 0000000000..7a6736dc54 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts @@ -0,0 +1,28 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { BoardRecord } from './BoardRecord'; + +/** + * Offset-paginated results + */ +export type OffsetPaginatedResults_BoardRecord_ = { + /** + * Items + */ + items: Array; + /** + * Offset from which to retrieve items + */ + offset: number; + /** + * Limit of items to get + */ + limit: number; + /** + * Total number of items in result + */ + total: number; +}; + diff --git a/invokeai/frontend/web/src/services/api/services/BoardsService.ts b/invokeai/frontend/web/src/services/api/services/BoardsService.ts new file mode 100644 index 0000000000..6533c09f03 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/services/BoardsService.ts @@ -0,0 +1,210 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { BoardChanges } from '../models/BoardChanges'; +import type { Body_create_board_image } from '../models/Body_create_board_image'; +import type { Body_remove_board_image } from '../models/Body_remove_board_image'; +import type { OffsetPaginatedResults_BoardRecord_ } from '../models/OffsetPaginatedResults_BoardRecord_'; +import type { OffsetPaginatedResults_ImageDTO_ } from '../models/OffsetPaginatedResults_ImageDTO_'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class BoardsService { + + /** + * List Boards + * Gets a list of boards + * @returns OffsetPaginatedResults_BoardRecord_ Successful Response + * @throws ApiError + */ + public static listBoards({ + offset, + limit = 10, + }: { + /** + * The page offset + */ + offset?: number, + /** + * The number of boards per page + */ + limit?: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/boards/', + query: { + 'offset': offset, + 'limit': limit, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Create Board + * Creates a board + * @returns any The board was created successfully + * @throws ApiError + */ + public static createBoard({ + requestBody, + }: { + requestBody: string, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/boards/', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Delete Board + * Deletes a board + * @returns any Successful Response + * @throws ApiError + */ + public static deleteBoard({ + boardId, + }: { + /** + * The id of board to delete + */ + boardId: string, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/v1/boards/{board_id}', + path: { + 'board_id': boardId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Update Board + * Creates a board + * @returns any The board was updated successfully + * @throws ApiError + */ + public static updateBoard({ + boardId, + requestBody, + }: { + /** + * The id of board to update + */ + boardId: string, + requestBody: BoardChanges, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'PATCH', + url: '/api/v1/boards/{board_id}', + path: { + 'board_id': boardId, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Create Board Image + * Creates a board_image + * @returns any The image was added to a board successfully + * @throws ApiError + */ + public static createBoardImage({ + requestBody, + }: { + requestBody: Body_create_board_image, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/board_images/', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Remove Board Image + * Deletes a board_image + * @returns any The image was removed from the board successfully + * @throws ApiError + */ + public static removeBoardImage({ + requestBody, + }: { + requestBody: Body_remove_board_image, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/v1/board_images/', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * List Board Images + * Gets a list of images for a board + * @returns OffsetPaginatedResults_ImageDTO_ Successful Response + * @throws ApiError + */ + public static listBoardImages({ + boardId, + offset, + limit = 10, + }: { + /** + * The id of the board + */ + boardId: string, + /** + * The page offset + */ + offset?: number, + /** + * The number of boards per page + */ + limit?: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/board_images/{board_id}', + path: { + 'board_id': boardId, + }, + query: { + 'offset': offset, + 'limit': limit, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + +} From e06c43adc82139e588bf6a82f9cbabf2554b5c7f Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Wed, 14 Jun 2023 16:53:01 -0400 Subject: [PATCH 023/110] lint fix --- .../listeners/socketio/socketConnected.ts | 3 + invokeai/frontend/web/src/app/store/store.ts | 3 + .../gallery/components/HoverableBoard.tsx | 101 ++++++++++ .../gallery/components/HoverableImage.tsx | 16 +- .../components/ImageGalleryContent.tsx | 178 +++++++++++++----- .../features/gallery/store/boardSelectors.ts | 3 + .../src/features/gallery/store/boardSlice.ts | 77 ++++++++ .../features/gallery/store/gallerySlice.ts | 9 + .../src/features/gallery/store/imagesSlice.ts | 13 ++ .../frontend/web/src/services/thunks/board.ts | 23 +++ .../frontend/web/src/services/types/guards.ts | 11 ++ 11 files changed, 392 insertions(+), 45 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts create mode 100644 invokeai/frontend/web/src/features/gallery/store/boardSlice.ts create mode 100644 invokeai/frontend/web/src/services/thunks/board.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index 3049d2c933..5bb02f60fa 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -4,6 +4,7 @@ import { appSocketConnected, socketConnected } from 'services/events/actions'; import { receivedPageOfImages } from 'services/thunks/image'; import { receivedModels } from 'services/thunks/model'; import { receivedOpenAPISchema } from 'services/thunks/schema'; +import { receivedBoards } from '../../../../../../services/thunks/board'; const moduleLog = log.child({ namespace: 'socketio' }); @@ -19,6 +20,8 @@ export const addSocketConnectedEventListener = () => { const { disabledTabs } = config; + dispatch(receivedBoards()); + if (!images.ids.length) { dispatch(receivedPageOfImages()); } diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index f577b73895..6198a414bf 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -22,6 +22,7 @@ import uiReducer from 'features/ui/store/uiSlice'; import hotkeysReducer from 'features/ui/store/hotkeysSlice'; import modelsReducer from 'features/system/store/modelSlice'; import nodesReducer from 'features/nodes/store/nodesSlice'; +import boardsReducer from 'features/gallery/store/boardSlice'; import { listenerMiddleware } from './middleware/listenerMiddleware'; @@ -47,6 +48,7 @@ const allReducers = { hotkeys: hotkeysReducer, images: imagesReducer, controlNet: controlNetReducer, + boards: boardsReducer, // session: sessionReducer, }; @@ -65,6 +67,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [ 'system', 'ui', 'controlNet', + 'boards', // 'hotkeys', // 'config', ]; diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx new file mode 100644 index 0000000000..c6762dbe54 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx @@ -0,0 +1,101 @@ +import { Box, Image, MenuItem, MenuList, Text } from '@chakra-ui/react'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { memo, useCallback, useState } from 'react'; +import { FaImage } from 'react-icons/fa'; +import { ContextMenu } from 'chakra-ui-contextmenu'; +import { useTranslation } from 'react-i18next'; +import { ExternalLinkIcon } from '@chakra-ui/icons'; +import { useAppToaster } from 'app/components/Toaster'; +import { BoardRecord } from 'services/api'; +import { EntityId, createSelector } from '@reduxjs/toolkit'; +import { + selectFilteredImagesIds, + selectImagesById, +} from '../store/imagesSlice'; +import { RootState } from '../../../app/store/store'; +import { defaultSelectorOptions } from '../../../app/store/util/defaultMemoizeOptions'; +import { useSelector } from 'react-redux'; + +interface HoverableBoardProps { + board: BoardRecord; +} + +/** + * Gallery image component with delete/use all/use seed buttons on hover. + */ +const HoverableBoard = memo(({ board }: HoverableBoardProps) => { + const dispatch = useAppDispatch(); + + const { board_name, board_id, cover_image_name } = board; + + const coverImage = useAppSelector((state) => + selectImagesById(state, cover_image_name as EntityId) + ); + + const { t } = useTranslation(); + + const handleSelectBoard = useCallback(() => { + // dispatch(imageSelected(board_id)); + }, []); + + return ( + + + menuProps={{ size: 'sm', isLazy: true }} + renderMenu={() => ( + + } + // onClickCapture={handleOpenInNewTab} + > + Sample Menu Item + + + )} + > + {(ref) => ( + + } + sx={{ + width: '100%', + height: '100%', + maxWidth: '100%', + maxHeight: '100%', + }} + /> + {board_name} + + )} + + + ); +}); + +HoverableBoard.displayName = 'HoverableBoard'; + +export default HoverableBoard; diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index b13d58e322..d52ba89d8f 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -2,7 +2,14 @@ import { Box, Flex, Icon, Image, MenuItem, MenuList } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { imageSelected } from 'features/gallery/store/gallerySlice'; import { memo, useCallback, useContext, useState } from 'react'; -import { FaCheck, FaExpand, FaImage, FaShare, FaTrash } from 'react-icons/fa'; +import { + FaCheck, + FaExpand, + FaFolder, + FaImage, + FaShare, + FaTrash, +} from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; import { resizeAndScaleCanvas, @@ -168,6 +175,10 @@ const HoverableImage = memo((props: HoverableImageProps) => { // dispatch(setIsLightboxOpen(true)); }; + const handleAddToFolder = useCallback(() => { + // dispatch(addImageToFolder(image)); + }, []); + const handleOpenInNewTab = () => { window.open(image.image_url, '_blank'); }; @@ -244,6 +255,9 @@ const HoverableImage = memo((props: HoverableImageProps) => { {t('parameters.sendToUnifiedCanvas')} )} + } onClickCapture={handleAddToFolder}> + Add to Folder + } diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index fe8690e379..a9752d4447 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -20,6 +20,7 @@ import { setGalleryImageObjectFit, setShouldAutoSwitchToNewImages, setShouldUseSingleGalleryColumn, + setGalleryView, } from 'features/gallery/store/gallerySlice'; import { togglePinGalleryPanel } from 'features/ui/store/uiSlice'; import { useOverlayScrollbars } from 'overlayscrollbars-react'; @@ -36,7 +37,7 @@ import { } from 'react'; import { useTranslation } from 'react-i18next'; import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs'; -import { FaImage, FaServer, FaWrench } from 'react-icons/fa'; +import { FaFolder, FaImage, FaPlus, FaServer, FaWrench } from 'react-icons/fa'; import { MdPhotoLibrary } from 'react-icons/md'; import HoverableImage from './HoverableImage'; @@ -53,22 +54,39 @@ import { selectImagesAll, } from '../store/imagesSlice'; import { receivedPageOfImages } from 'services/thunks/image'; +import { boardSelector } from '../store/boardSelectors'; +import { BoardRecord, ImageDTO } from '../../../services/api'; +import { isBoardRecord, isImageDTO } from '../../../services/types/guards'; +import HoverableBoard from './HoverableBoard'; +import IAIInput from '../../../common/components/IAIInput'; +import { boardCreated } from '../../../services/thunks/board'; -const categorySelector = createSelector( +const itemSelector = createSelector( [(state: RootState) => state], (state) => { - const { images } = state; - const { categories } = images; + const { images, boards, gallery } = state; - const allImages = selectImagesAll(state); - const filteredImages = allImages.filter((i) => - categories.includes(i.image_category) - ); + let items: Array = []; + let areMoreAvailable = false; + let isLoading = true; + + if (gallery.galleryView === 'images' || gallery.galleryView === 'assets') { + const { categories } = images; + + const allImages = selectImagesAll(state); + items = allImages.filter((i) => categories.includes(i.image_category)); + areMoreAvailable = items.length < images.total; + isLoading = images.isLoading; + } else if (gallery.galleryView === 'boards') { + items = Object.values(boards.entities) as BoardRecord[]; + areMoreAvailable = items.length < boards.total; + isLoading = boards.isLoading; + } return { - images: filteredImages, - isLoading: images.isLoading, - areMoreImagesAvailable: filteredImages.length < images.total, + items, + isLoading, + areMoreAvailable, categories: images.categories, }; }, @@ -76,18 +94,21 @@ const categorySelector = createSelector( ); const mainSelector = createSelector( - [gallerySelector, uiSelector], - (gallery, ui) => { + [gallerySelector, uiSelector, boardSelector], + (gallery, ui, boardState) => { const { galleryImageMinimumWidth, galleryImageObjectFit, shouldAutoSwitchToNewImages, shouldUseSingleGalleryColumn, selectedImage, + galleryView, } = gallery; const { shouldPinGallery } = ui; + const { entities: boards } = boardState; + return { shouldPinGallery, galleryImageMinimumWidth, @@ -95,6 +116,8 @@ const mainSelector = createSelector( shouldAutoSwitchToNewImages, shouldUseSingleGalleryColumn, selectedImage, + galleryView, + boards, }; }, defaultSelectorOptions @@ -126,21 +149,23 @@ const ImageGalleryContent = () => { shouldAutoSwitchToNewImages, shouldUseSingleGalleryColumn, selectedImage, + galleryView, + boards, } = useAppSelector(mainSelector); - const { images, areMoreImagesAvailable, isLoading, categories } = - useAppSelector(categorySelector); + const { items, areMoreAvailable, isLoading, categories } = + useAppSelector(itemSelector); const handleLoadMoreImages = useCallback(() => { dispatch(receivedPageOfImages()); }, [dispatch]); const handleEndReached = useMemo(() => { - if (areMoreImagesAvailable && !isLoading) { + if (areMoreAvailable && !isLoading) { return handleLoadMoreImages; } return undefined; - }, [areMoreImagesAvailable, handleLoadMoreImages, isLoading]); + }, [areMoreAvailable, handleLoadMoreImages, isLoading]); const handleChangeGalleryImageMinimumWidth = (v: number) => { dispatch(setGalleryImageMinimumWidth(v)); @@ -172,12 +197,24 @@ const ImageGalleryContent = () => { const handleClickImagesCategory = useCallback(() => { dispatch(imageCategoriesChanged(IMAGE_CATEGORIES)); + dispatch(setGalleryView('images')); }, [dispatch]); const handleClickAssetsCategory = useCallback(() => { dispatch(imageCategoriesChanged(ASSETS_CATEGORIES)); + dispatch(setGalleryView('assets')); }, [dispatch]); + const handleClickBoardsView = useCallback(() => { + dispatch(setGalleryView('boards')); + }, [dispatch]); + + const [newBoardName, setNewBoardName] = useState(''); + + const handleCreateNewBoard = () => { + dispatch(boardCreated({ requestBody: newBoardName })); + }; + return ( { tooltip={t('gallery.images')} aria-label={t('gallery.images')} onClick={handleClickImagesCategory} - isChecked={categories === IMAGE_CATEGORIES} + isChecked={galleryView === 'images'} size="sm" icon={} /> @@ -206,12 +243,47 @@ const ImageGalleryContent = () => { tooltip={t('gallery.assets')} aria-label={t('gallery.assets')} onClick={handleClickAssetsCategory} - isChecked={categories === ASSETS_CATEGORIES} + isChecked={galleryView === 'assets'} size="sm" icon={} /> + } + /> + } + /> + } + > + + setNewBoardName(e.target.value)} + /> + + Create + + + { - {images.length || areMoreImagesAvailable ? ( + {items.length || areMoreAvailable ? ( <> {shouldUseSingleGalleryColumn ? ( setScrollerRef(ref)} - itemContent={(index, image) => ( - - - - )} + itemContent={(index, item) => { + if (isImageDTO(item)) { + return ( + + + + ); + } else if (isBoardRecord(item)) { + return ( + + + + ); + } + }} /> ) : ( ( - - )} + itemContent={(index, item) => { + if (isImageDTO(item)) { + return ( + + ); + } else if (isBoardRecord(item)) { + return ( + + ); + } + }} /> )} - {areMoreImagesAvailable + {areMoreAvailable ? t('gallery.loadMore') : t('gallery.allImagesLoaded')} diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts b/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts new file mode 100644 index 0000000000..4bc98553af --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts @@ -0,0 +1,3 @@ +import { RootState } from 'app/store/store'; + +export const boardSelector = (state: RootState) => state.boards; diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts new file mode 100644 index 0000000000..6b4f1e9431 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -0,0 +1,77 @@ +import { + PayloadAction, + Update, + createEntityAdapter, + createSlice, +} from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { BoardRecord } from 'services/api'; +import { dateComparator } from 'common/util/dateComparator'; +import { receivedBoards } from '../../../services/thunks/board'; + +export const boardsAdapter = createEntityAdapter({ + selectId: (board) => board.board_id, + sortComparer: (a, b) => dateComparator(b.updated_at, a.updated_at), +}); + +type AdditionalBoardsState = { + offset: number; + limit: number; + total: number; + isLoading: boolean; +}; + +export const initialBoardsState = + boardsAdapter.getInitialState({ + offset: 0, + limit: 0, + total: 0, + isLoading: false, + }); + +export type BoardsState = typeof initialBoardsState; + +const boardsSlice = createSlice({ + name: 'boards', + initialState: initialBoardsState, + reducers: { + boardUpserted: (state, action: PayloadAction) => { + boardsAdapter.upsertOne(state, action.payload); + }, + boardUpdatedOne: (state, action: PayloadAction>) => { + boardsAdapter.updateOne(state, action.payload); + }, + boardRemoved: (state, action: PayloadAction) => { + boardsAdapter.removeOne(state, action.payload); + }, + }, + extraReducers: (builder) => { + builder.addCase(receivedBoards.pending, (state) => { + state.isLoading = true; + }); + builder.addCase(receivedBoards.rejected, (state) => { + state.isLoading = false; + }); + builder.addCase(receivedBoards.fulfilled, (state, action) => { + state.isLoading = false; + const { items, offset, limit, total } = action.payload; + state.offset = offset; + state.limit = limit; + state.total = total; + boardsAdapter.upsertMany(state, items); + }); + }, +}); + +export const { + selectAll: selectBoardsAll, + selectById: selectBoardsById, + selectEntities: selectBoardsEntities, + selectIds: selectBoardsIds, + selectTotal: selectBoardsTotal, +} = boardsAdapter.getSelectors((state) => state.boards); + +export const { boardUpserted, boardUpdatedOne, boardRemoved } = + boardsSlice.actions; + +export default boardsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index 4f250a7c3a..a8237a711d 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -12,6 +12,7 @@ export interface GalleryState { galleryImageObjectFit: GalleryImageObjectFitType; shouldAutoSwitchToNewImages: boolean; shouldUseSingleGalleryColumn: boolean; + galleryView: 'images' | 'assets' | 'boards'; } export const initialGalleryState: GalleryState = { @@ -19,6 +20,7 @@ export const initialGalleryState: GalleryState = { galleryImageObjectFit: 'cover', shouldAutoSwitchToNewImages: true, shouldUseSingleGalleryColumn: false, + galleryView: 'images', }; export const gallerySlice = createSlice({ @@ -48,6 +50,12 @@ export const gallerySlice = createSlice({ ) => { state.shouldUseSingleGalleryColumn = action.payload; }, + setGalleryView: ( + state, + action: PayloadAction<'images' | 'assets' | 'boards'> + ) => { + state.galleryView = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(imageUpserted, (state, action) => { @@ -75,6 +83,7 @@ export const { setGalleryImageObjectFit, setShouldAutoSwitchToNewImages, setShouldUseSingleGalleryColumn, + setGalleryView, } = gallerySlice.actions; export default gallerySlice.reducer; diff --git a/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts b/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts index 9c18380c54..0b2b9f0f58 100644 --- a/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts @@ -154,3 +154,16 @@ export const selectFilteredImagesIds = createSelector( .map((i) => i.image_name); } ); + +// export const selectImageById = createSelector( +// (state: RootState, imageId) => state, +// (state) => { +// const { +// images: { categories }, +// } = state; + +// return selectImagesAll(state) +// .filter((i) => categories.includes(i.image_category)) +// .map((i) => i.image_name); +// } +// ); diff --git a/invokeai/frontend/web/src/services/thunks/board.ts b/invokeai/frontend/web/src/services/thunks/board.ts new file mode 100644 index 0000000000..4ead87c4d4 --- /dev/null +++ b/invokeai/frontend/web/src/services/thunks/board.ts @@ -0,0 +1,23 @@ +import { createAppAsyncThunk } from '../../app/store/storeUtils'; +import { BoardsService } from '../api'; + +/** + * `BoardsService.listBoards()` thunk + */ +export const receivedBoards = createAppAsyncThunk( + 'api/receivedBoards', + async (_, { getState }) => { + const response = await BoardsService.listBoards({}); + return response; + } +); + +type BoardCreatedArg = Parameters<(typeof BoardsService)['createBoard']>[0]; + +export const boardCreated = createAppAsyncThunk( + 'api/boardCreated', + async (arg: BoardCreatedArg) => { + const response = await BoardsService.createBoard(arg); + return response; + } +); diff --git a/invokeai/frontend/web/src/services/types/guards.ts b/invokeai/frontend/web/src/services/types/guards.ts index 334c04e6ed..469b485cf3 100644 --- a/invokeai/frontend/web/src/services/types/guards.ts +++ b/invokeai/frontend/web/src/services/types/guards.ts @@ -11,6 +11,7 @@ import { LatentsOutput, ResourceOrigin, ImageDTO, + BoardRecord, } from 'services/api'; export const isImageDTO = (obj: unknown): obj is ImageDTO => { @@ -29,6 +30,16 @@ export const isImageDTO = (obj: unknown): obj is ImageDTO => { ); }; +export const isBoardRecord = (obj: unknown): obj is BoardRecord => { + return ( + isObject(obj) && + 'board_id' in obj && + isString(obj?.board_id) && + 'board_name' in obj && + isString(obj?.board_name) + ); +}; + export const isImageOutput = ( output: GraphExecutionState['results'][string] ): output is ImageOutput => output.type === 'image_output'; From 4b32322a58f8927eef3dfa38d555effd846ac207 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:41:36 +1000 Subject: [PATCH 024/110] feat(nodes): make board <> images a one-to-many relationship we can extend this to many-to-many in the future if desired. --- .../app/services/board_image_record_storage.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index b805087da8..abebe8c0a0 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -12,7 +12,7 @@ from invokeai.app.services.models.image_record import ( class BoardImageRecordStorageBase(ABC): - """Abstract base class for board-image relationship record storage.""" + """Abstract base class for the one-to-many board-image relationship record storage.""" @abstractmethod def add_image_to_board( @@ -45,7 +45,7 @@ class BoardImageRecordStorageBase(ABC): self, board_id: str, ) -> OffsetPaginatedResults[BoardRecord]: - """Gets images for a board.""" + """Gets boards for an image.""" pass @abstractmethod @@ -93,7 +93,9 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- updated via trigger updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), - PRIMARY KEY (board_id, image_name), + -- enforce one-to-many relationship between boards and images using PK + -- (we can extend this to many-to-many later) + PRIMARY KEY (image_name), FOREIGN KEY (board_id) REFERENCES boards (board_id) ON DELETE CASCADE, FOREIGN KEY (image_name) REFERENCES images (image_name) ON DELETE CASCADE ); @@ -184,7 +186,7 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): SELECT COUNT(*) FROM images WHERE 1=1; """ ) - count = self._cursor.fetchone()[0] + count = cast(int, self._cursor.fetchone()[0]) except sqlite3.Error as e: self._conn.rollback() @@ -222,7 +224,7 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): SELECT COUNT(*) FROM boards WHERE 1=1; """ ) - count = self._cursor.fetchone()[0] + count = cast(int, self._cursor.fetchone()[0]) except sqlite3.Error as e: self._conn.rollback() @@ -243,11 +245,10 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): """, (board_id,), ) - count = self._cursor.fetchone()[0] - + count = cast(int, self._cursor.fetchone()[0]) + return count except sqlite3.Error as e: self._conn.rollback() raise e finally: self._lock.release() - return count From dd1b3c9f35db137e3ed02a56d6b1e68f68645a29 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:42:18 +1000 Subject: [PATCH 025/110] fix(api): update API models to use BoardDTOs --- invokeai/app/api/routers/boards.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index 618e8f990b..7935db58f2 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -1,6 +1,6 @@ from fastapi import Body, HTTPException, Path, Query from fastapi.routing import APIRouter -from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges +from invokeai.app.services.board_record_storage import BoardChanges from invokeai.app.services.image_record_storage import OffsetPaginatedResults from invokeai.app.services.models.board_record import BoardDTO @@ -16,32 +16,39 @@ boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) 201: {"description": "The board was created successfully"}, }, status_code=201, + response_model=BoardDTO, ) async def create_board( board_name: str = Body(description="The name of the board to create"), -): +) -> BoardDTO: """Creates a board""" try: result = ApiDependencies.invoker.services.boards.create(board_name=board_name) return result except Exception as e: raise HTTPException(status_code=500, detail="Failed to create board") - + + @boards_router.patch( "/{board_id}", operation_id="update_board", responses={ - 201: {"description": "The board was updated successfully"}, + 201: { + "description": "The board was updated successfully", + }, }, status_code=201, + response_model=BoardDTO, ) async def update_board( board_id: str = Path(description="The id of board to update"), changes: BoardChanges = Body(description="The changes to apply to the board"), -): - """Creates a board""" +) -> BoardDTO: + """Updates a board""" try: - result = ApiDependencies.invoker.services.boards.update(board_id=board_id, changes=changes) + result = ApiDependencies.invoker.services.boards.update( + board_id=board_id, changes=changes + ) return result except Exception as e: raise HTTPException(status_code=500, detail="Failed to update board") @@ -63,7 +70,7 @@ async def delete_board( @boards_router.get( "/", operation_id="list_boards", - response_model=OffsetPaginatedResults[BoardRecord], + response_model=OffsetPaginatedResults[BoardDTO], ) async def list_boards( offset: int = Query(default=0, description="The page offset"), @@ -76,4 +83,3 @@ async def list_boards( limit, ) return results - From 48193b7fa7471e98ae1a4228960bd277b4d482c1 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:43:34 +1000 Subject: [PATCH 026/110] chore(ui): regen api client --- invokeai/frontend/web/src/services/api/index.ts | 7 +++++-- .../api/models/{BoardRecord.ts => BoardDTO.ts} | 12 ++++++++++-- .../web/src/services/api/models/Graph.ts | 2 +- .../services/api/models/GraphExecutionState.ts | 2 +- ..._.ts => OffsetPaginatedResults_BoardDTO_.ts} | 6 +++--- .../src/services/api/services/BoardsService.ts | 17 +++++++++-------- .../services/api/services/SessionsService.ts | 4 ++-- .../frontend/web/src/services/types/guards.ts | 4 ++-- 8 files changed, 33 insertions(+), 21 deletions(-) rename invokeai/frontend/web/src/services/api/models/{BoardRecord.ts => BoardDTO.ts} (62%) rename invokeai/frontend/web/src/services/api/models/{OffsetPaginatedResults_BoardRecord_.ts => OffsetPaginatedResults_BoardDTO_.ts} (71%) diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index 60db5eda3a..ef62245553 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -7,7 +7,10 @@ export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; export type { AddInvocation } from './models/AddInvocation'; -export type { BaseModelType } from './models/BaseModelType'; +export type { BoardChanges } from './models/BoardChanges'; +export type { BoardDTO } from './models/BoardDTO'; +export type { Body_create_board_image } from './models/Body_create_board_image'; +export type { Body_remove_board_image } from './models/Body_remove_board_image'; export type { Body_upload_image } from './models/Body_upload_image'; export type { CannyImageProcessorInvocation } from './models/CannyImageProcessorInvocation'; export type { CkptModelInfo } from './models/CkptModelInfo'; @@ -98,7 +101,7 @@ export type { MultiplyInvocation } from './models/MultiplyInvocation'; export type { NoiseInvocation } from './models/NoiseInvocation'; export type { NoiseOutput } from './models/NoiseOutput'; export type { NormalbaeImageProcessorInvocation } from './models/NormalbaeImageProcessorInvocation'; -export type { OffsetPaginatedResults_BoardRecord_ } from './models/OffsetPaginatedResults_BoardRecord_'; +export type { OffsetPaginatedResults_BoardDTO_ } from './models/OffsetPaginatedResults_BoardDTO_'; export type { OffsetPaginatedResults_ImageDTO_ } from './models/OffsetPaginatedResults_ImageDTO_'; export type { OpenposeImageProcessorInvocation } from './models/OpenposeImageProcessorInvocation'; export type { PaginatedResults_GraphExecutionState_ } from './models/PaginatedResults_GraphExecutionState_'; diff --git a/invokeai/frontend/web/src/services/api/models/BoardRecord.ts b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts similarity index 62% rename from invokeai/frontend/web/src/services/api/models/BoardRecord.ts rename to invokeai/frontend/web/src/services/api/models/BoardDTO.ts index 50db9b22eb..1b72f452ac 100644 --- a/invokeai/frontend/web/src/services/api/models/BoardRecord.ts +++ b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts @@ -3,9 +3,9 @@ /* eslint-disable */ /** - * Deserialized board record. + * Deserialized board record with cover image URL and image count. */ -export type BoardRecord = { +export type BoardDTO = { /** * The unique ID of the board. */ @@ -26,5 +26,13 @@ export type BoardRecord = { * The name of the cover image of the board. */ cover_image_name?: string; + /** + * The URL of the thumbnail of the board's cover image. + */ + cover_image_url?: string; + /** + * The number of images in the board. + */ + image_count: number; }; diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index e148954f16..0a724e2724 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -73,7 +73,7 @@ export type Graph = { /** * The nodes in this graph */ - nodes?: Record; + nodes?: Record; /** * The connections between nodes and their fields in this graph */ diff --git a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts index 602e7a2ebc..156cdc6092 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts @@ -48,7 +48,7 @@ export type GraphExecutionState = { /** * The results of node executions */ - results: Record; + results: Record; /** * Errors raised when executing nodes */ diff --git a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardDTO_.ts similarity index 71% rename from invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts rename to invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardDTO_.ts index 7a6736dc54..2e4734f469 100644 --- a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardRecord_.ts +++ b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_BoardDTO_.ts @@ -2,16 +2,16 @@ /* tslint:disable */ /* eslint-disable */ -import type { BoardRecord } from './BoardRecord'; +import type { BoardDTO } from './BoardDTO'; /** * Offset-paginated results */ -export type OffsetPaginatedResults_BoardRecord_ = { +export type OffsetPaginatedResults_BoardDTO_ = { /** * Items */ - items: Array; + items: Array; /** * Offset from which to retrieve items */ diff --git a/invokeai/frontend/web/src/services/api/services/BoardsService.ts b/invokeai/frontend/web/src/services/api/services/BoardsService.ts index 6533c09f03..9108e3fd51 100644 --- a/invokeai/frontend/web/src/services/api/services/BoardsService.ts +++ b/invokeai/frontend/web/src/services/api/services/BoardsService.ts @@ -2,9 +2,10 @@ /* tslint:disable */ /* eslint-disable */ import type { BoardChanges } from '../models/BoardChanges'; +import type { BoardDTO } from '../models/BoardDTO'; import type { Body_create_board_image } from '../models/Body_create_board_image'; import type { Body_remove_board_image } from '../models/Body_remove_board_image'; -import type { OffsetPaginatedResults_BoardRecord_ } from '../models/OffsetPaginatedResults_BoardRecord_'; +import type { OffsetPaginatedResults_BoardDTO_ } from '../models/OffsetPaginatedResults_BoardDTO_'; import type { OffsetPaginatedResults_ImageDTO_ } from '../models/OffsetPaginatedResults_ImageDTO_'; import type { CancelablePromise } from '../core/CancelablePromise'; @@ -16,7 +17,7 @@ export class BoardsService { /** * List Boards * Gets a list of boards - * @returns OffsetPaginatedResults_BoardRecord_ Successful Response + * @returns OffsetPaginatedResults_BoardDTO_ Successful Response * @throws ApiError */ public static listBoards({ @@ -31,7 +32,7 @@ export class BoardsService { * The number of boards per page */ limit?: number, - }): CancelablePromise { + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/boards/', @@ -48,14 +49,14 @@ export class BoardsService { /** * Create Board * Creates a board - * @returns any The board was created successfully + * @returns BoardDTO The board was created successfully * @throws ApiError */ public static createBoard({ requestBody, }: { requestBody: string, - }): CancelablePromise { + }): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/boards/', @@ -95,8 +96,8 @@ export class BoardsService { /** * Update Board - * Creates a board - * @returns any The board was updated successfully + * Updates a board + * @returns BoardDTO The board was updated successfully * @throws ApiError */ public static updateBoard({ @@ -108,7 +109,7 @@ export class BoardsService { */ boardId: string, requestBody: BoardChanges, - }): CancelablePromise { + }): CancelablePromise { return __request(OpenAPI, { method: 'PATCH', url: '/api/v1/boards/{board_id}', diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index 2e4a83b25f..6e9ce83aaf 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -175,7 +175,7 @@ export class SessionsService { * The id of the session */ sessionId: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -212,7 +212,7 @@ export class SessionsService { * The path to the node in the graph */ nodePath: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'PUT', diff --git a/invokeai/frontend/web/src/services/types/guards.ts b/invokeai/frontend/web/src/services/types/guards.ts index 469b485cf3..7ac0d95e6a 100644 --- a/invokeai/frontend/web/src/services/types/guards.ts +++ b/invokeai/frontend/web/src/services/types/guards.ts @@ -11,7 +11,7 @@ import { LatentsOutput, ResourceOrigin, ImageDTO, - BoardRecord, + BoardDTO, } from 'services/api'; export const isImageDTO = (obj: unknown): obj is ImageDTO => { @@ -30,7 +30,7 @@ export const isImageDTO = (obj: unknown): obj is ImageDTO => { ); }; -export const isBoardRecord = (obj: unknown): obj is BoardRecord => { +export const isBoardDTO = (obj: unknown): obj is BoardDTO => { return ( isObject(obj) && 'board_id' in obj && From 163ef2c941f36778ebcf518c38bda2882cef3f7b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:44:06 +1000 Subject: [PATCH 027/110] feat(ui): remove refs to BoardRecord in UI UI should only work w/ BoardDTO --- .../features/gallery/components/HoverableBoard.tsx | 4 ++-- .../gallery/components/ImageGalleryContent.tsx | 12 ++++++------ .../web/src/features/gallery/store/boardSlice.ts | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx index c6762dbe54..dc06fb389b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx @@ -6,7 +6,7 @@ import { ContextMenu } from 'chakra-ui-contextmenu'; import { useTranslation } from 'react-i18next'; import { ExternalLinkIcon } from '@chakra-ui/icons'; import { useAppToaster } from 'app/components/Toaster'; -import { BoardRecord } from 'services/api'; +import { BoardDTO } from 'services/api'; import { EntityId, createSelector } from '@reduxjs/toolkit'; import { selectFilteredImagesIds, @@ -17,7 +17,7 @@ import { defaultSelectorOptions } from '../../../app/store/util/defaultMemoizeOp import { useSelector } from 'react-redux'; interface HoverableBoardProps { - board: BoardRecord; + board: BoardDTO; } /** diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index a9752d4447..12194095df 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -55,8 +55,8 @@ import { } from '../store/imagesSlice'; import { receivedPageOfImages } from 'services/thunks/image'; import { boardSelector } from '../store/boardSelectors'; -import { BoardRecord, ImageDTO } from '../../../services/api'; -import { isBoardRecord, isImageDTO } from '../../../services/types/guards'; +import { BoardDTO, ImageDTO } from '../../../services/api'; +import { isBoardDTO, isImageDTO } from '../../../services/types/guards'; import HoverableBoard from './HoverableBoard'; import IAIInput from '../../../common/components/IAIInput'; import { boardCreated } from '../../../services/thunks/board'; @@ -66,7 +66,7 @@ const itemSelector = createSelector( (state) => { const { images, boards, gallery } = state; - let items: Array = []; + let items: Array = []; let areMoreAvailable = false; let isLoading = true; @@ -78,7 +78,7 @@ const itemSelector = createSelector( areMoreAvailable = items.length < images.total; isLoading = images.isLoading; } else if (gallery.galleryView === 'boards') { - items = Object.values(boards.entities) as BoardRecord[]; + items = Object.values(boards.entities) as BoardDTO[]; areMoreAvailable = items.length < boards.total; isLoading = boards.isLoading; } @@ -365,7 +365,7 @@ const ImageGalleryContent = () => { /> ); - } else if (isBoardRecord(item)) { + } else if (isBoardDTO(item)) { return ( @@ -395,7 +395,7 @@ const ImageGalleryContent = () => { } /> ); - } else if (isBoardRecord(item)) { + } else if (isBoardDTO(item)) { return ( ); diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index 6b4f1e9431..58457de527 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -5,11 +5,11 @@ import { createSlice, } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; -import { BoardRecord } from 'services/api'; +import { BoardDTO } from 'services/api'; import { dateComparator } from 'common/util/dateComparator'; import { receivedBoards } from '../../../services/thunks/board'; -export const boardsAdapter = createEntityAdapter({ +export const boardsAdapter = createEntityAdapter({ selectId: (board) => board.board_id, sortComparer: (a, b) => dateComparator(b.updated_at, a.updated_at), }); @@ -35,10 +35,10 @@ const boardsSlice = createSlice({ name: 'boards', initialState: initialBoardsState, reducers: { - boardUpserted: (state, action: PayloadAction) => { + boardUpserted: (state, action: PayloadAction) => { boardsAdapter.upsertOne(state, action.payload); }, - boardUpdatedOne: (state, action: PayloadAction>) => { + boardUpdatedOne: (state, action: PayloadAction>) => { boardsAdapter.updateOne(state, action.payload); }, boardRemoved: (state, action: PayloadAction) => { From 498bf0d0ba95b8dd598214a67759b1dab7cf8678 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:26:34 +1000 Subject: [PATCH 028/110] feat(db): add indices for `board_images` --- .../app/services/board_image_record_storage.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index abebe8c0a0..851e8502e1 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -102,6 +102,20 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): """ ) + # Add index for board id + self._cursor.execute( + """--sql + CREATE INDEX IF NOT EXISTS idx_board_images_board_id ON board_images (board_id); + """ + ) + + # Add index for board id, sorted by created_at + self._cursor.execute( + """--sql + CREATE INDEX IF NOT EXISTS idx_board_images_board_id_created_at ON board_images (board_id, created_at); + """ + ) + # Add trigger for `updated_at`. self._cursor.execute( """--sql From e1f9685b02fbbe30a4a8f9bd8ee44d5e2fb37749 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:26:57 +1000 Subject: [PATCH 029/110] feat(db): add index for `boards` --- invokeai/app/services/board_record_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index 8207470e6d..e7ba477a89 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -128,7 +128,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): self._cursor.execute( """--sql - CREATE INDEX IF NOT EXISTS idx_boards_created_at ON boards(created_at); + CREATE INDEX IF NOT EXISTS idx_boards_created_at ON boards (created_at); """ ) From 5865ecd530b0d33a9a5e5f48b12c98ce78c3f720 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:27:25 +1000 Subject: [PATCH 030/110] feat(db): add FK for `boards.cover_image_name` --- invokeai/app/services/board_record_storage.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index e7ba477a89..8b849a0501 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -121,7 +121,8 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): -- Updated via trigger updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- Soft delete, currently unused - deleted_at DATETIME + deleted_at DATETIME, + FOREIGN KEY (cover_image_name) REFERENCES images (image_name) ON DELETE SET NULL ); """ ) From d306a844478049ba398d4301c90ed5bc68263f42 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 15 Jun 2023 16:43:50 +1000 Subject: [PATCH 031/110] feat(ui): rough out boards UI --- .../components/Boards/AddBoardButton.tsx | 36 ++++++++ .../gallery/components/Boards/BoardsList.tsx | 53 ++++++++++++ .../{ => Boards}/HoverableBoard.tsx | 86 +++++++++++-------- .../components/ImageGalleryContent.tsx | 47 +++++----- .../src/features/gallery/store/boardSlice.ts | 10 ++- 5 files changed, 172 insertions(+), 60 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx rename invokeai/frontend/web/src/features/gallery/components/{ => Boards}/HoverableBoard.tsx (51%) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx new file mode 100644 index 0000000000..2f60ea2dac --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx @@ -0,0 +1,36 @@ +import { Flex, Icon, Text } from '@chakra-ui/react'; +import { FaPlus } from 'react-icons/fa'; + +const AddBoardButton = () => { + return ( + + + + + New Board + + ); +}; + +export default AddBoardButton; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx new file mode 100644 index 0000000000..064d40b2d3 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -0,0 +1,53 @@ +import { Grid } from '@chakra-ui/react'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { selectBoardsAll } from 'features/gallery/store/boardSlice'; +import { memo } from 'react'; +import HoverableBoard from './HoverableBoard'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import AddBoardButton from './AddBoardButton'; + +const selector = createSelector( + selectBoardsAll, + (boards) => { + return { boards }; + }, + defaultSelectorOptions +); + +const BoardsList = () => { + const { boards } = useAppSelector(selector); + + return ( + + + + {boards.map((board) => ( + + ))} + + + ); +}; + +export default memo(BoardsList); diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx similarity index 51% rename from invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx rename to invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index dc06fb389b..fa1caeac7a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -1,7 +1,15 @@ -import { Box, Image, MenuItem, MenuList, Text } from '@chakra-ui/react'; +import { + Box, + Flex, + Icon, + Image, + MenuItem, + MenuList, + Text, +} from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { memo, useCallback, useState } from 'react'; -import { FaImage } from 'react-icons/fa'; +import { PropsWithChildren, memo, useCallback, useState } from 'react'; +import { FaFolder, FaImage } from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; import { useTranslation } from 'react-i18next'; import { ExternalLinkIcon } from '@chakra-ui/icons'; @@ -11,10 +19,12 @@ import { EntityId, createSelector } from '@reduxjs/toolkit'; import { selectFilteredImagesIds, selectImagesById, -} from '../store/imagesSlice'; -import { RootState } from '../../../app/store/store'; -import { defaultSelectorOptions } from '../../../app/store/util/defaultMemoizeOptions'; +} from '../../store/imagesSlice'; +import { RootState } from '../../../../app/store/store'; +import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions'; import { useSelector } from 'react-redux'; +import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { boardIdSelected } from 'features/gallery/store/boardSlice'; interface HoverableBoardProps { board: BoardDTO; @@ -26,20 +36,16 @@ interface HoverableBoardProps { const HoverableBoard = memo(({ board }: HoverableBoardProps) => { const dispatch = useAppDispatch(); - const { board_name, board_id, cover_image_name } = board; - - const coverImage = useAppSelector((state) => - selectImagesById(state, cover_image_name as EntityId) - ); + const { board_name, board_id, cover_image_url } = board; const { t } = useTranslation(); const handleSelectBoard = useCallback(() => { - // dispatch(imageSelected(board_id)); - }, []); + dispatch(boardIdSelected(board_id)); + }, [board_id, dispatch]); return ( - + menuProps={{ size: 'sm', isLazy: true }} renderMenu={() => ( @@ -54,42 +60,50 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => { )} > {(ref) => ( - - } + - {board_name} - + > + {cover_image_url ? ( + } + sx={{}} + /> + ) : ( + + )} + + {board_name} + )} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 12194095df..d97d814adf 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -57,9 +57,11 @@ import { receivedPageOfImages } from 'services/thunks/image'; import { boardSelector } from '../store/boardSelectors'; import { BoardDTO, ImageDTO } from '../../../services/api'; import { isBoardDTO, isImageDTO } from '../../../services/types/guards'; -import HoverableBoard from './HoverableBoard'; +import HoverableBoard from './Boards/HoverableBoard'; import IAIInput from '../../../common/components/IAIInput'; import { boardCreated } from '../../../services/thunks/board'; +import BoardsList from './Boards/BoardsList'; +import { selectBoardsById } from '../store/boardSlice'; const itemSelector = createSelector( [(state: RootState) => state], @@ -70,24 +72,23 @@ const itemSelector = createSelector( let areMoreAvailable = false; let isLoading = true; - if (gallery.galleryView === 'images' || gallery.galleryView === 'assets') { - const { categories } = images; + const { categories } = images; - const allImages = selectImagesAll(state); - items = allImages.filter((i) => categories.includes(i.image_category)); - areMoreAvailable = items.length < images.total; - isLoading = images.isLoading; - } else if (gallery.galleryView === 'boards') { - items = Object.values(boards.entities) as BoardDTO[]; - areMoreAvailable = items.length < boards.total; - isLoading = boards.isLoading; - } + const allImages = selectImagesAll(state); + items = allImages.filter((i) => categories.includes(i.image_category)); + areMoreAvailable = items.length < images.total; + isLoading = images.isLoading; + + const selectedBoard = boards.selectedBoardId + ? selectBoardsById(state, boards.selectedBoardId) + : undefined; return { items, isLoading, areMoreAvailable, categories: images.categories, + selectedBoard, }; }, defaultSelectorOptions @@ -153,7 +154,7 @@ const ImageGalleryContent = () => { boards, } = useAppSelector(mainSelector); - const { items, areMoreAvailable, isLoading, categories } = + const { items, areMoreAvailable, isLoading, categories, selectedBoard } = useAppSelector(itemSelector); const handleLoadMoreImages = useCallback(() => { @@ -247,17 +248,14 @@ const ImageGalleryContent = () => { size="sm" icon={} /> - } - /> + {selectedBoard && ( + + {selectedBoard.board_name} + + )} - { Create - + */} { /> + + + {items.length || areMoreAvailable ? ( <> diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index 58457de527..de7ea1e828 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -1,4 +1,5 @@ import { + EntityId, PayloadAction, Update, createEntityAdapter, @@ -19,6 +20,7 @@ type AdditionalBoardsState = { limit: number; total: number; isLoading: boolean; + selectedBoardId: EntityId | null; }; export const initialBoardsState = @@ -27,6 +29,7 @@ export const initialBoardsState = limit: 0, total: 0, isLoading: false, + selectedBoardId: null, }); export type BoardsState = typeof initialBoardsState; @@ -44,6 +47,9 @@ const boardsSlice = createSlice({ boardRemoved: (state, action: PayloadAction) => { boardsAdapter.removeOne(state, action.payload); }, + boardIdSelected: (state, action: PayloadAction) => { + state.selectedBoardId = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(receivedBoards.pending, (state) => { @@ -71,7 +77,9 @@ export const { selectTotal: selectBoardsTotal, } = boardsAdapter.getSelectors((state) => state.boards); -export const { boardUpserted, boardUpdatedOne, boardRemoved } = +export const { boardUpserted, boardUpdatedOne, boardRemoved, boardIdSelected } = boardsSlice.actions; +export const boardsSelector = (state: RootState) => state.boards; + export default boardsSlice.reducer; From 8aac683319b617742d432978addb87958dd4d8f8 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 15 Jun 2023 13:31:24 -0400 Subject: [PATCH 032/110] can delete and rename boards --- .../components/Boards/AddBoardButton.tsx | 10 +++ .../components/Boards/AllImagesBoard.tsx | 45 +++++++++++ .../gallery/components/Boards/BoardsList.tsx | 23 ++++-- .../components/Boards/HoverableBoard.tsx | 74 +++++++++++------- .../components/ImageGalleryContent.tsx | 78 +++++++------------ .../src/features/gallery/store/boardSlice.ts | 24 +++++- .../frontend/web/src/services/thunks/board.ts | 18 +++++ 7 files changed, 185 insertions(+), 87 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx index 2f60ea2dac..d8828fe736 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx @@ -1,9 +1,19 @@ import { Flex, Icon, Text } from '@chakra-ui/react'; +import { useCallback } from 'react'; import { FaPlus } from 'react-icons/fa'; +import { useAppDispatch } from '../../../../app/store/storeHooks'; +import { boardCreated } from '../../../../services/thunks/board'; const AddBoardButton = () => { + const dispatch = useAppDispatch(); + + const handleCreateBoard = useCallback(() => { + dispatch(boardCreated({ requestBody: 'My Board' })); + }, [dispatch]); + return ( { + const dispatch = useDispatch(); + + const handleAllImagesBoardClick = () => { + dispatch(boardIdSelected(null)); + }; + + return ( + + + + + All Images + + ); +}; + +export default AllImagesBoard; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index 064d40b2d3..8603f28c9c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -2,22 +2,26 @@ import { Grid } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import { selectBoardsAll } from 'features/gallery/store/boardSlice'; -import { memo } from 'react'; +import { + boardsSelector, + selectBoardsAll, +} from 'features/gallery/store/boardSlice'; +import { memo, useState } from 'react'; import HoverableBoard from './HoverableBoard'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import AddBoardButton from './AddBoardButton'; +import AllImagesBoard from './AllImagesBoard'; const selector = createSelector( - selectBoardsAll, - (boards) => { - return { boards }; + [selectBoardsAll, boardsSelector], + (boards, boardsState) => { + return { boards, selectedBoardId: boardsState.selectedBoardId }; }, defaultSelectorOptions ); const BoardsList = () => { - const { boards } = useAppSelector(selector); + const { boards, selectedBoardId } = useAppSelector(selector); return ( { }} > + {boards.map((board) => ( - + ))} diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index fa1caeac7a..4bb63dbb5e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -1,49 +1,51 @@ import { Box, + Editable, + EditableInput, + EditablePreview, Flex, Icon, Image, MenuItem, MenuList, - Text, } from '@chakra-ui/react'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { PropsWithChildren, memo, useCallback, useState } from 'react'; -import { FaFolder, FaImage } from 'react-icons/fa'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { memo, useCallback } from 'react'; +import { FaFolder, FaTrash } from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; import { useTranslation } from 'react-i18next'; -import { ExternalLinkIcon } from '@chakra-ui/icons'; -import { useAppToaster } from 'app/components/Toaster'; import { BoardDTO } from 'services/api'; -import { EntityId, createSelector } from '@reduxjs/toolkit'; -import { - selectFilteredImagesIds, - selectImagesById, -} from '../../store/imagesSlice'; -import { RootState } from '../../../../app/store/store'; -import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions'; -import { useSelector } from 'react-redux'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; import { boardIdSelected } from 'features/gallery/store/boardSlice'; +import { boardDeleted, boardUpdated } from '../../../../services/thunks/board'; interface HoverableBoardProps { board: BoardDTO; + isSelected: boolean; } -/** - * Gallery image component with delete/use all/use seed buttons on hover. - */ -const HoverableBoard = memo(({ board }: HoverableBoardProps) => { +const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { const dispatch = useAppDispatch(); const { board_name, board_id, cover_image_url } = board; - const { t } = useTranslation(); - const handleSelectBoard = useCallback(() => { dispatch(boardIdSelected(board_id)); }, [board_id, dispatch]); + const handleDeleteBoard = useCallback(() => { + dispatch(boardDeleted(board_id)); + }, [board_id, dispatch]); + + const handleUpdateBoardName = (newBoardName: string) => { + dispatch( + boardUpdated({ + boardId: board_id, + requestBody: { board_name: newBoardName }, + }) + ); + }; + return ( @@ -51,10 +53,11 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => { renderMenu={() => ( } - // onClickCapture={handleOpenInNewTab} + sx={{ color: 'error.300' }} + icon={} + onClickCapture={handleDeleteBoard} > - Sample Menu Item + Delete Board )} @@ -64,7 +67,6 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => { position="relative" key={board_id} userSelect="none" - onClick={handleSelectBoard} ref={ref} sx={{ flexDir: 'column', @@ -77,12 +79,13 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => { }} > { )} - {board_name} + + { + handleUpdateBoardName(nextValue); + }} + > + + + )} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index d97d814adf..95c37cba61 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -37,7 +37,7 @@ import { } from 'react'; import { useTranslation } from 'react-i18next'; import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs'; -import { FaFolder, FaImage, FaPlus, FaServer, FaWrench } from 'react-icons/fa'; +import { FaImage, FaServer, FaWrench } from 'react-icons/fa'; import { MdPhotoLibrary } from 'react-icons/md'; import HoverableImage from './HoverableImage'; @@ -55,10 +55,6 @@ import { } from '../store/imagesSlice'; import { receivedPageOfImages } from 'services/thunks/image'; import { boardSelector } from '../store/boardSelectors'; -import { BoardDTO, ImageDTO } from '../../../services/api'; -import { isBoardDTO, isImageDTO } from '../../../services/types/guards'; -import HoverableBoard from './Boards/HoverableBoard'; -import IAIInput from '../../../common/components/IAIInput'; import { boardCreated } from '../../../services/thunks/board'; import BoardsList from './Boards/BoardsList'; import { selectBoardsById } from '../store/boardSlice'; @@ -66,18 +62,16 @@ import { selectBoardsById } from '../store/boardSlice'; const itemSelector = createSelector( [(state: RootState) => state], (state) => { - const { images, boards, gallery } = state; - - let items: Array = []; - let areMoreAvailable = false; - let isLoading = true; + const { images, boards } = state; const { categories } = images; const allImages = selectImagesAll(state); - items = allImages.filter((i) => categories.includes(i.image_category)); - areMoreAvailable = items.length < images.total; - isLoading = images.isLoading; + const items = allImages.filter((i) => + categories.includes(i.image_category) + ); + const areMoreAvailable = items.length < images.total; + const isLoading = images.isLoading; const selectedBoard = boards.selectedBoardId ? selectBoardsById(state, boards.selectedBoardId) @@ -353,27 +347,17 @@ const ImageGalleryContent = () => { data={items} endReached={handleEndReached} scrollerRef={(ref) => setScrollerRef(ref)} - itemContent={(index, item) => { - if (isImageDTO(item)) { - return ( - - - - ); - } else if (isBoardDTO(item)) { - return ( - - - - ); - } - }} + itemContent={(index, item) => ( + + + + )} /> ) : ( { List: ListContainer, }} scrollerRef={setScroller} - itemContent={(index, item) => { - if (isImageDTO(item)) { - return ( - - ); - } else if (isBoardDTO(item)) { - return ( - - ); - } - }} + itemContent={(index, item) => ( + + )} /> )} diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index de7ea1e828..d2e9a451d3 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -8,7 +8,12 @@ import { import { RootState } from 'app/store/store'; import { BoardDTO } from 'services/api'; import { dateComparator } from 'common/util/dateComparator'; -import { receivedBoards } from '../../../services/thunks/board'; +import { + boardCreated, + boardDeleted, + boardUpdated, + receivedBoards, +} from '../../../services/thunks/board'; export const boardsAdapter = createEntityAdapter({ selectId: (board) => board.board_id, @@ -26,7 +31,7 @@ type AdditionalBoardsState = { export const initialBoardsState = boardsAdapter.getInitialState({ offset: 0, - limit: 0, + limit: 50, total: 0, isLoading: false, selectedBoardId: null, @@ -47,7 +52,7 @@ const boardsSlice = createSlice({ boardRemoved: (state, action: PayloadAction) => { boardsAdapter.removeOne(state, action.payload); }, - boardIdSelected: (state, action: PayloadAction) => { + boardIdSelected: (state, action: PayloadAction) => { state.selectedBoardId = action.payload; }, }, @@ -66,6 +71,19 @@ const boardsSlice = createSlice({ state.total = total; boardsAdapter.upsertMany(state, items); }); + builder.addCase(boardCreated.fulfilled, (state, action) => { + const board = action.payload; + boardsAdapter.upsertOne(state, board); + }); + builder.addCase(boardUpdated.fulfilled, (state, action) => { + const board = action.payload; + boardsAdapter.upsertOne(state, board); + }); + builder.addCase(boardDeleted.pending, (state, action) => { + const boardId = action.meta.arg; + console.log({ boardId }); + boardsAdapter.removeOne(state, boardId); + }); }, }); diff --git a/invokeai/frontend/web/src/services/thunks/board.ts b/invokeai/frontend/web/src/services/thunks/board.ts index 4ead87c4d4..a536a3fdb0 100644 --- a/invokeai/frontend/web/src/services/thunks/board.ts +++ b/invokeai/frontend/web/src/services/thunks/board.ts @@ -21,3 +21,21 @@ export const boardCreated = createAppAsyncThunk( return response; } ); + +export const boardDeleted = createAppAsyncThunk( + 'api/boardDeleted', + async (boardId: string) => { + await BoardsService.deleteBoard({ boardId }); + return boardId; + } +); + +type BoardUpdatedArg = Parameters<(typeof BoardsService)['updateBoard']>[0]; + +export const boardUpdated = createAppAsyncThunk( + 'api/boardUpdated', + async (arg: BoardUpdatedArg) => { + const response = await BoardsService.updateBoard(arg); + return response; + } +); From dcfee2e1e4eeb4d52bb69c6c0a042606d148748e Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 15 Jun 2023 14:26:40 -0400 Subject: [PATCH 033/110] add searching to boards list --- .../gallery/components/Boards/BoardsList.tsx | 38 +++++++++++++++---- .../components/ImageGalleryContent.tsx | 5 +-- .../features/gallery/store/boardSelectors.ts | 22 ++++++++++- .../src/features/gallery/store/boardSlice.ts | 13 ++++++- 4 files changed, 64 insertions(+), 14 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index 8603f28c9c..9e7d1ab960 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -1,16 +1,19 @@ -import { Grid } from '@chakra-ui/react'; +import { Box, Grid, Input, Spacer } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; -import { useAppSelector } from 'app/store/storeHooks'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { boardsSelector, selectBoardsAll, + setBoardSearchText, } from 'features/gallery/store/boardSlice'; -import { memo, useState } from 'react'; +import { memo, useEffect, useState } from 'react'; import HoverableBoard from './HoverableBoard'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import AddBoardButton from './AddBoardButton'; import AllImagesBoard from './AllImagesBoard'; +import { searchBoardsSelector } from '../../store/boardSelectors'; +import { useSelector } from 'react-redux'; const selector = createSelector( [selectBoardsAll, boardsSelector], @@ -21,7 +24,16 @@ const selector = createSelector( ); const BoardsList = () => { - const { boards, selectedBoardId } = useAppSelector(selector); + const dispatch = useAppDispatch(); + const { selectedBoardId } = useAppSelector(selector); + const filteredBoards = useSelector(searchBoardsSelector); + + const [searchMode, setSearchMode] = useState(false); + + const handleBoardSearch = (searchTerm: string) => { + setSearchMode(searchTerm.length > 0); + dispatch(setBoardSearchText(searchTerm)); + }; return ( { }, }} > + + { + handleBoardSearch(e.target.value); + }} + /> + { gridAutoColumns: '4rem', }} > - - - {boards.map((board) => ( + {!searchMode && ( + <> + + + + )} + {filteredBoards.map((board) => ( { + (gallery, ui, boards) => { const { galleryImageMinimumWidth, galleryImageObjectFit, @@ -101,9 +101,6 @@ const mainSelector = createSelector( } = gallery; const { shouldPinGallery } = ui; - - const { entities: boards } = boardState; - return { shouldPinGallery, galleryImageMinimumWidth, diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts b/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts index 4bc98553af..3dac2b6e50 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts @@ -1,3 +1,23 @@ +import { createSelector } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; +import { selectBoardsAll } from './boardSlice'; -export const boardSelector = (state: RootState) => state.boards; +export const boardSelector = (state: RootState) => state.boards.entities; + +export const searchBoardsSelector = createSelector( + (state: RootState) => state, + (state) => { + const { + boards: { searchText }, + } = state; + + if (!searchText) { + // If no search text provided, return all entities + return selectBoardsAll(state); + } + + return selectBoardsAll(state).filter((i) => + i.board_name.toLowerCase().includes(searchText.toLowerCase()) + ); + } +); diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index d2e9a451d3..fc67b4f26a 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -26,6 +26,7 @@ type AdditionalBoardsState = { total: number; isLoading: boolean; selectedBoardId: EntityId | null; + searchText?: string; }; export const initialBoardsState = @@ -55,6 +56,9 @@ const boardsSlice = createSlice({ boardIdSelected: (state, action: PayloadAction) => { state.selectedBoardId = action.payload; }, + setBoardSearchText: (state, action: PayloadAction) => { + state.searchText = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(receivedBoards.pending, (state) => { @@ -95,8 +99,13 @@ export const { selectTotal: selectBoardsTotal, } = boardsAdapter.getSelectors((state) => state.boards); -export const { boardUpserted, boardUpdatedOne, boardRemoved, boardIdSelected } = - boardsSlice.actions; +export const { + boardUpserted, + boardUpdatedOne, + boardRemoved, + boardIdSelected, + setBoardSearchText, +} = boardsSlice.actions; export const boardsSelector = (state: RootState) => state.boards; From bd29e5e6552353495626c9e7862dd70de79fe67f Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 15 Jun 2023 14:57:14 -0400 Subject: [PATCH 034/110] UI tweaks --- .../src/features/gallery/components/Boards/HoverableBoard.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index 4bb63dbb5e..d368d4ab0b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -13,7 +13,6 @@ import { useAppDispatch } from 'app/store/storeHooks'; import { memo, useCallback } from 'react'; import { FaFolder, FaTrash } from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; -import { useTranslation } from 'react-i18next'; import { BoardDTO } from 'services/api'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; import { boardIdSelected } from 'features/gallery/store/boardSlice'; @@ -89,6 +88,7 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { w: 'full', h: 'full', aspectRatio: '1/1', + overflow: 'hidden', }} > {cover_image_url ? ( @@ -115,6 +115,7 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { > Date: Thu, 15 Jun 2023 18:40:29 -0400 Subject: [PATCH 035/110] [half-baked] adding image to board modal --- .../frontend/web/src/app/components/App.tsx | 2 + .../web/src/app/components/InvokeAIUI.tsx | 14 +- .../app/contexts/AddImageToBoardContext.tsx | 151 ++++++++++++++++++ .../Boards/UpdateImageBoardModal.tsx | 85 ++++++++++ .../gallery/components/HoverableImage.tsx | 14 +- .../src/features/gallery/store/boardSlice.ts | 6 + .../frontend/web/src/services/thunks/board.ts | 12 ++ 7 files changed, 274 insertions(+), 10 deletions(-) create mode 100644 invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index ddc6dace27..a11d8d048c 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -23,6 +23,7 @@ import GlobalHotkeys from './GlobalHotkeys'; import Toaster from './Toaster'; import DeleteImageModal from 'features/gallery/components/DeleteImageModal'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; +import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal'; const DEFAULT_CONFIG = {}; @@ -143,6 +144,7 @@ const App = ({ + diff --git a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx index 0537d1de2a..141e62652d 100644 --- a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx +++ b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx @@ -21,6 +21,8 @@ import { DeleteImageContext, DeleteImageContextProvider, } from 'app/contexts/DeleteImageContext'; +import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal'; +import { AddImageToBoardContextProvider } from '../contexts/AddImageToBoardContext'; const App = lazy(() => import('./App')); const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider')); @@ -76,11 +78,13 @@ const InvokeAIUI = ({ - + + + diff --git a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx new file mode 100644 index 0000000000..cf541dca01 --- /dev/null +++ b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx @@ -0,0 +1,151 @@ +import { useDisclosure } from '@chakra-ui/react'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { requestedImageDeletion } from 'features/gallery/store/actions'; +import { systemSelector } from 'features/system/store/systemSelectors'; +import { + PropsWithChildren, + createContext, + useCallback, + useEffect, + useState, +} from 'react'; +import { ImageDTO } from 'services/api'; +import { RootState } from 'app/store/store'; +import { canvasSelector } from 'features/canvas/store/canvasSelectors'; +import { controlNetSelector } from 'features/controlNet/store/controlNetSlice'; +import { nodesSelecter } from 'features/nodes/store/nodesSlice'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { some } from 'lodash-es'; +import { imageAddedToBoard } from '../../services/thunks/board'; + +export type ImageUsage = { + isInitialImage: boolean; + isCanvasImage: boolean; + isNodesImage: boolean; + isControlNetImage: boolean; +}; + +export const selectImageUsage = createSelector( + [ + generationSelector, + canvasSelector, + nodesSelecter, + controlNetSelector, + (state: RootState, image_name?: string) => image_name, + ], + (generation, canvas, nodes, controlNet, image_name) => { + const isInitialImage = generation.initialImage?.image_name === image_name; + + const isCanvasImage = canvas.layerState.objects.some( + (obj) => obj.kind === 'image' && obj.image.image_name === image_name + ); + + const isNodesImage = nodes.nodes.some((node) => { + return some( + node.data.inputs, + (input) => + input.type === 'image' && input.value?.image_name === image_name + ); + }); + + const isControlNetImage = some( + controlNet.controlNets, + (c) => + c.controlImage?.image_name === image_name || + c.processedControlImage?.image_name === image_name + ); + + const imageUsage: ImageUsage = { + isInitialImage, + isCanvasImage, + isNodesImage, + isControlNetImage, + }; + + return imageUsage; + }, + defaultSelectorOptions +); + +type AddImageToBoardContextValue = { + /** + * Whether the move image dialog is open. + */ + isOpen: boolean; + /** + * Closes the move image dialog. + */ + onClose: () => void; + /** + * The image pending movement + */ + image?: ImageDTO; + onClickAddToBoard: (image: ImageDTO) => void; + handleAddToBoard: (boardId: string) => void; +}; + +export const AddImageToBoardContext = + createContext({ + isOpen: false, + onClose: () => undefined, + onClickAddToBoard: () => undefined, + handleAddToBoard: () => undefined, + }); + +type Props = PropsWithChildren; + +export const AddImageToBoardContextProvider = (props: Props) => { + const [imageToMove, setImageToMove] = useState(); + const dispatch = useAppDispatch(); + const { isOpen, onOpen, onClose } = useDisclosure(); + + // Clean up after deleting or dismissing the modal + const closeAndClearImageToDelete = useCallback(() => { + setImageToMove(undefined); + onClose(); + }, [onClose]); + + const onClickAddToBoard = useCallback( + (image?: ImageDTO) => { + if (!image) { + return; + } + setImageToMove(image); + onOpen(); + }, + [setImageToMove, onOpen] + ); + + const handleAddToBoard = useCallback( + (boardId: string) => { + if (imageToMove) { + dispatch( + imageAddedToBoard({ + requestBody: { + board_id: boardId, + image_name: imageToMove.image_name, + }, + }) + ); + closeAndClearImageToDelete(); + } + }, + [closeAndClearImageToDelete, dispatch, imageToMove] + ); + + return ( + + {props.children} + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx new file mode 100644 index 0000000000..8a94764ab1 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx @@ -0,0 +1,85 @@ +import { + AlertDialog, + AlertDialogBody, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogOverlay, + Box, + Divider, + Flex, + Select, + Text, +} from '@chakra-ui/react'; +import IAIButton from 'common/components/IAIButton'; + +import { memo, useContext, useRef, useState } from 'react'; +import { AddImageToBoardContext } from '../../../../app/contexts/AddImageToBoardContext'; +import { useSelector } from 'react-redux'; +import { selectBoardsAll } from '../../store/boardSlice'; +import IAISelect from '../../../../common/components/IAISelect'; + +const UpdateImageBoardModal = () => { + const boards = useSelector(selectBoardsAll); + const [selectedBoard, setSelectedBoard] = useState( + undefined + ); + + const { isOpen, onClose, handleAddToBoard, image } = useContext( + AddImageToBoardContext + ); + + const cancelRef = useRef(null); + + const currentBoard = boards.filter( + (board) => board.board_id === image?.board_id + )[0]; + + return ( + + + + + Move Image to Board + + + + + + + Moving this image to a board will remove it from its existing + board. + + setSelectedBoard(e.target.value)} + validValues={boards.map((board) => board.board_name)} + /> + + + + + Cancel + { + if (selectedBoard) handleAddToBoard(selectedBoard); + }} + ml={3} + > + Add to Board + + + + + + ); +}; + +export default memo(UpdateImageBoardModal); diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index d52ba89d8f..b21c62785b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -34,6 +34,9 @@ import { useAppToaster } from 'app/components/Toaster'; import { ImageDTO } from 'services/api'; import { useDraggable } from '@dnd-kit/core'; import { DeleteImageContext } from 'app/contexts/DeleteImageContext'; +import { imageAddedToBoard } from '../../../services/thunks/board'; +import { setUpdateBoardModalOpen } from '../store/boardSlice'; +import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext'; export const selector = createSelector( [gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector], @@ -100,6 +103,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled; const { onDelete } = useContext(DeleteImageContext); + const { onClickAddToBoard } = useContext(AddImageToBoardContext); const handleDelete = useCallback(() => { onDelete(image); }, [image, onDelete]); @@ -175,9 +179,9 @@ const HoverableImage = memo((props: HoverableImageProps) => { // dispatch(setIsLightboxOpen(true)); }; - const handleAddToFolder = useCallback(() => { - // dispatch(addImageToFolder(image)); - }, []); + const handleAddToBoard = useCallback(() => { + onClickAddToBoard(image); + }, [image, onClickAddToBoard]); const handleOpenInNewTab = () => { window.open(image.image_url, '_blank'); @@ -255,8 +259,8 @@ const HoverableImage = memo((props: HoverableImageProps) => { {t('parameters.sendToUnifiedCanvas')} )} - } onClickCapture={handleAddToFolder}> - Add to Folder + } onClickCapture={handleAddToBoard}> + Add to Board ) => { state.searchText = action.payload; }, + setUpdateBoardModalOpen: (state, action: PayloadAction) => { + state.updateBoardModalOpen = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(receivedBoards.pending, (state) => { @@ -105,6 +110,7 @@ export const { boardRemoved, boardIdSelected, setBoardSearchText, + setUpdateBoardModalOpen, } = boardsSlice.actions; export const boardsSelector = (state: RootState) => state.boards; diff --git a/invokeai/frontend/web/src/services/thunks/board.ts b/invokeai/frontend/web/src/services/thunks/board.ts index a536a3fdb0..4535081e47 100644 --- a/invokeai/frontend/web/src/services/thunks/board.ts +++ b/invokeai/frontend/web/src/services/thunks/board.ts @@ -39,3 +39,15 @@ export const boardUpdated = createAppAsyncThunk( return response; } ); + +type ImageAddedToBoardArg = Parameters< + (typeof BoardsService)['createBoardImage'] +>[0]; + +export const imageAddedToBoard = createAppAsyncThunk( + 'api/imageAddedToBoard', + async (arg: ImageAddedToBoardArg) => { + const response = await BoardsService.createBoardImage(arg); + return response; + } +); From ca8f1a78289408f39df502461913a13092ee1b5e Mon Sep 17 00:00:00 2001 From: maryhipp Date: Thu, 15 Jun 2023 08:31:14 -0700 Subject: [PATCH 036/110] (api) use most recently generated image for cover photo --- invokeai/app/services/boards.py | 11 ++++---- invokeai/app/services/image_record_storage.py | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index ddfaacf79c..148af50103 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -136,11 +136,12 @@ class BoardService(BoardServiceABC): board_records = self._services.board_records.get_many(offset, limit) board_dtos = [] for r in board_records.items: - cover_image_url = ( - self._services.urls.get_image_url(r.cover_image_name, True) - if r.cover_image_name - else None - ) + cover_image = self._services.image_records.get_most_recent_image_for_board(r.board_id) + if (cover_image): + cover_image_url = self._services.urls.get_image_url(cover_image.image_name, True) + else: + cover_image_url = None + image_count = self._services.board_image_records.get_image_count_for_board( r.board_id ) diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 677e1d3445..bc59c4a27c 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -109,6 +109,11 @@ class ImageRecordStorageBase(ABC): """Saves an image record.""" pass + @abstractmethod + def get_most_recent_image_for_board(self, board_id: str) -> ImageRecord | None: + """Gets the most recent image for a board.""" + pass + class SqliteImageRecordStorage(ImageRecordStorageBase): _filename: str @@ -414,3 +419,26 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): raise ImageRecordSaveException from e finally: self._lock.release() + + def get_most_recent_image_for_board(self, board_id: str) -> Union[ImageRecord, None]: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT images.* + FROM images + JOIN board_images ON images.image_name = board_images.image_name + WHERE board_images.board_id = ? + ORDER BY images.created_at DESC + LIMIT 1; + """, + (board_id,), + ) + + result = cast(Union[sqlite3.Row, None], self._cursor.fetchone()) + finally: + self._lock.release() + if result is None: + return None + + return deserialize_image_record(dict(result)) From 4a0a718b96990fe471558a24f92b914fb4cf2514 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Thu, 15 Jun 2023 09:25:56 -0700 Subject: [PATCH 037/110] foiled by a comma --- invokeai/app/services/board_record_storage.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index 8b849a0501..65e256d040 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -154,12 +154,15 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): DELETE FROM boards WHERE board_id = ?; """, - (board_id), + (board_id,), ) self._conn.commit() except sqlite3.Error as e: self._conn.rollback() raise BoardRecordDeleteException from e + except Exception as e: + self._conn.rollback() + raise BoardRecordDeleteException from e finally: self._lock.release() @@ -192,7 +195,6 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): return BoardRecord(**result) except sqlite3.Error as e: self._conn.rollback() - print(e) raise BoardRecordSaveException from e finally: self._lock.release() From e4893e4031ff3d57171fe554e1f6931e6599ca04 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 16 Jun 2023 11:00:40 +1000 Subject: [PATCH 038/110] fix(db): return board records from CRUD methods --- invokeai/app/services/board_record_storage.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index 65e256d040..20a9683b02 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from datetime import datetime from typing import Optional, cast import sqlite3 import threading @@ -181,23 +180,12 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): (board_id, board_name), ) self._conn.commit() - - self._cursor.execute( - """--sql - SELECT * - FROM boards - WHERE board_id = ?; - """, - (board_id,), - ) - - result = self._cursor.fetchone() - return BoardRecord(**result) except sqlite3.Error as e: self._conn.rollback() raise BoardRecordSaveException from e finally: self._lock.release() + return self.get(board_id) def get( self, @@ -228,7 +216,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): self, board_id: str, changes: BoardChanges, - ) -> None: + ) -> BoardRecord: try: self._lock.acquire() @@ -260,6 +248,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): raise BoardRecordSaveException from e finally: self._lock.release() + return self.get(board_id) def get_many( self, From 70cc037a9cfd278e765744616cc9797441055906 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 16 Jun 2023 11:03:13 +1000 Subject: [PATCH 039/110] fix(ui): do not persist boards --- invokeai/frontend/web/src/app/store/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 6198a414bf..4032db3159 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -67,7 +67,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [ 'system', 'ui', 'controlNet', - 'boards', + // 'boards', // 'hotkeys', // 'config', ]; From d604d986f9bdbad1e9120b8163f019b10e324697 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 16 Jun 2023 15:52:32 +1000 Subject: [PATCH 040/110] feat(db, api): update get_board_for_image & service dependencies - previously was `get_boards_for_image`, returning a list of `BoardDTO`, now returns a single `board_id` --- invokeai/app/api/dependencies.py | 19 +++-- .../services/board_image_record_storage.py | 48 ++++------- invokeai/app/services/board_images.py | 42 ++-------- invokeai/app/services/images.py | 83 ++++++------------- invokeai/app/services/models/image_record.py | 9 +- 5 files changed, 69 insertions(+), 132 deletions(-) diff --git a/invokeai/app/api/dependencies.py b/invokeai/app/api/dependencies.py index 8889c70674..60f8c1b09d 100644 --- a/invokeai/app/api/dependencies.py +++ b/invokeai/app/api/dependencies.py @@ -12,7 +12,7 @@ from invokeai.app.services.board_images import ( from invokeai.app.services.board_record_storage import SqliteBoardRecordStorage from invokeai.app.services.boards import BoardService, BoardServiceDependencies from invokeai.app.services.image_record_storage import SqliteImageRecordStorage -from invokeai.app.services.images import ImageService +from invokeai.app.services.images import ImageService, ImageServiceDependencies from invokeai.app.services.metadata import CoreMetadataService from invokeai.app.services.resource_name import SimpleNameService from invokeai.app.services.urls import LocalUrlService @@ -106,13 +106,16 @@ class ApiDependencies: ) images = ImageService( - image_record_storage=image_record_storage, - image_file_storage=image_file_storage, - metadata=metadata, - url=urls, - logger=logger, - names=names, - graph_execution_manager=graph_execution_manager, + services=ImageServiceDependencies( + board_image_record_storage=board_image_record_storage, + image_record_storage=image_record_storage, + image_file_storage=image_file_storage, + metadata=metadata, + url=urls, + logger=logger, + names=names, + graph_execution_manager=graph_execution_manager, + ) ) services = InvocationServices( diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index 851e8502e1..2f1603be82 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod import sqlite3 import threading -from typing import cast +from typing import Union, cast from invokeai.app.services.board_record_storage import BoardRecord from invokeai.app.services.image_record_storage import OffsetPaginatedResults @@ -41,11 +41,11 @@ class BoardImageRecordStorageBase(ABC): pass @abstractmethod - def get_boards_for_image( + def get_board_for_image( self, - board_id: str, - ) -> OffsetPaginatedResults[BoardRecord]: - """Gets boards for an image.""" + image_name: str, + ) -> Union[str, None]: + """Gets an image's board id, if it has one.""" pass @abstractmethod @@ -134,7 +134,6 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): board_id: str, image_name: str, ) -> None: - """Adds an image to a board.""" try: self._lock.acquire() self._cursor.execute( @@ -156,7 +155,6 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): board_id: str, image_name: str, ) -> None: - """Removes an image from a board.""" try: self._lock.acquire() self._cursor.execute( @@ -179,7 +177,6 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): offset: int = 0, limit: int = 10, ) -> OffsetPaginatedResults[ImageRecord]: - """Gets images for a board.""" try: self._lock.acquire() self._cursor.execute( @@ -211,46 +208,31 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): items=images, offset=offset, limit=limit, total=count ) - def get_boards_for_image( + def get_board_for_image( self, - board_id: str, - offset: int = 0, - limit: int = 10, - ) -> OffsetPaginatedResults[BoardRecord]: - """Gets boards for an image.""" + image_name: str, + ) -> Union[str, None]: try: self._lock.acquire() self._cursor.execute( """--sql - SELECT boards.* + SELECT board_id FROM board_images - INNER JOIN boards ON board_images.board_id = boards.board_id - WHERE board_images.image_name = ? - ORDER BY board_images.updated_at DESC; + WHERE image_name = ?; """, - (board_id,), + (image_name,), ) - result = cast(list[sqlite3.Row], self._cursor.fetchall()) - boards = list(map(lambda r: BoardRecord(**r), result)) - - self._cursor.execute( - """--sql - SELECT COUNT(*) FROM boards WHERE 1=1; - """ - ) - count = cast(int, self._cursor.fetchone()[0]) - + result = self._cursor.fetchone() + if result is None: + return None + return cast(str, result[0]) except sqlite3.Error as e: self._conn.rollback() raise e finally: self._lock.release() - return OffsetPaginatedResults( - items=boards, offset=offset, limit=limit, total=count - ) def get_image_count_for_board(self, board_id: str) -> int: - """Gets the number of images for a board.""" try: self._lock.acquire() self._cursor.execute( diff --git a/invokeai/app/services/board_images.py b/invokeai/app/services/board_images.py index df2af4bbcf..cf16993a7a 100644 --- a/invokeai/app/services/board_images.py +++ b/invokeai/app/services/board_images.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod from logging import Logger +from typing import Union from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase from invokeai.app.services.board_record_storage import ( BoardRecord, @@ -45,11 +46,11 @@ class BoardImagesServiceABC(ABC): pass @abstractmethod - def get_boards_for_image( + def get_board_for_image( self, image_name: str, - ) -> OffsetPaginatedResults[BoardDTO]: - """Gets boards for an image.""" + ) -> Union[str, None]: + """Gets an image's board id, if it has one.""" pass @@ -110,6 +111,7 @@ class BoardImagesService(BoardImagesServiceABC): r, self._services.urls.get_image_url(r.image_name), self._services.urls.get_image_url(r.image_name, True), + board_id, ), image_records.items, ) @@ -121,38 +123,12 @@ class BoardImagesService(BoardImagesServiceABC): total=image_records.total, ) - def get_boards_for_image( + def get_board_for_image( self, image_name: str, - ) -> OffsetPaginatedResults[BoardDTO]: - board_records = self._services.board_image_records.get_boards_for_image( - image_name - ) - board_dtos = [] - - for r in board_records.items: - cover_image_url = ( - self._services.urls.get_image_url(r.cover_image_name, True) - if r.cover_image_name - else None - ) - image_count = self._services.board_image_records.get_image_count_for_board( - r.board_id - ) - board_dtos.append( - board_record_to_dto( - r, - cover_image_url, - image_count, - ) - ) - - return OffsetPaginatedResults[BoardDTO]( - items=board_dtos, - offset=board_records.offset, - limit=board_records.limit, - total=board_records.total, - ) + ) -> Union[str, None]: + board_id = self._services.board_image_records.get_board_for_image(image_name) + return board_id def board_record_to_dto( diff --git a/invokeai/app/services/images.py b/invokeai/app/services/images.py index aa27e38d17..5959116161 100644 --- a/invokeai/app/services/images.py +++ b/invokeai/app/services/images.py @@ -10,6 +10,7 @@ from invokeai.app.models.image import ( InvalidOriginException, ) from invokeai.app.models.metadata import ImageMetadata +from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase from invokeai.app.services.image_record_storage import ( ImageRecordDeleteException, ImageRecordNotFoundException, @@ -114,8 +115,9 @@ class ImageServiceABC(ABC): class ImageServiceDependencies: """Service dependencies for the ImageService.""" - records: ImageRecordStorageBase - files: ImageFileStorageBase + image_records: ImageRecordStorageBase + image_files: ImageFileStorageBase + board_image_records: BoardImageRecordStorageBase metadata: MetadataServiceBase urls: UrlServiceBase logger: Logger @@ -126,14 +128,16 @@ class ImageServiceDependencies: self, image_record_storage: ImageRecordStorageBase, image_file_storage: ImageFileStorageBase, + board_image_record_storage: BoardImageRecordStorageBase, metadata: MetadataServiceBase, url: UrlServiceBase, logger: Logger, names: NameServiceBase, graph_execution_manager: ItemStorageABC["GraphExecutionState"], ): - self.records = image_record_storage - self.files = image_file_storage + self.image_records = image_record_storage + self.image_files = image_file_storage + self.board_image_records = board_image_record_storage self.metadata = metadata self.urls = url self.logger = logger @@ -144,25 +148,8 @@ class ImageServiceDependencies: class ImageService(ImageServiceABC): _services: ImageServiceDependencies - def __init__( - self, - image_record_storage: ImageRecordStorageBase, - image_file_storage: ImageFileStorageBase, - metadata: MetadataServiceBase, - url: UrlServiceBase, - logger: Logger, - names: NameServiceBase, - graph_execution_manager: ItemStorageABC["GraphExecutionState"], - ): - self._services = ImageServiceDependencies( - image_record_storage=image_record_storage, - image_file_storage=image_file_storage, - metadata=metadata, - url=url, - logger=logger, - names=names, - graph_execution_manager=graph_execution_manager, - ) + def __init__(self, services: ImageServiceDependencies): + self._services = services def create( self, @@ -187,7 +174,7 @@ class ImageService(ImageServiceABC): try: # TODO: Consider using a transaction here to ensure consistency between storage and database - created_at = self._services.records.save( + self._services.image_records.save( # Non-nullable fields image_name=image_name, image_origin=image_origin, @@ -202,35 +189,15 @@ class ImageService(ImageServiceABC): metadata=metadata, ) - self._services.files.save( + self._services.image_files.save( image_name=image_name, image=image, metadata=metadata, ) - image_url = self._services.urls.get_image_url(image_name) - thumbnail_url = self._services.urls.get_image_url(image_name, True) + image_dto = self.get_dto(image_name) - return ImageDTO( - # Non-nullable fields - image_name=image_name, - image_origin=image_origin, - image_category=image_category, - width=width, - height=height, - # Nullable fields - node_id=node_id, - session_id=session_id, - metadata=metadata, - # Meta fields - created_at=created_at, - updated_at=created_at, # this is always the same as the created_at at this time - deleted_at=None, - is_intermediate=is_intermediate, - # Extra non-nullable fields for DTO - image_url=image_url, - thumbnail_url=thumbnail_url, - ) + return image_dto except ImageRecordSaveException: self._services.logger.error("Failed to save image record") raise @@ -247,7 +214,7 @@ class ImageService(ImageServiceABC): changes: ImageRecordChanges, ) -> ImageDTO: try: - self._services.records.update(image_name, changes) + self._services.image_records.update(image_name, changes) return self.get_dto(image_name) except ImageRecordSaveException: self._services.logger.error("Failed to update image record") @@ -258,7 +225,7 @@ class ImageService(ImageServiceABC): def get_pil_image(self, image_name: str) -> PILImageType: try: - return self._services.files.get(image_name) + return self._services.image_files.get(image_name) except ImageFileNotFoundException: self._services.logger.error("Failed to get image file") raise @@ -268,7 +235,7 @@ class ImageService(ImageServiceABC): def get_record(self, image_name: str) -> ImageRecord: try: - return self._services.records.get(image_name) + return self._services.image_records.get(image_name) except ImageRecordNotFoundException: self._services.logger.error("Image record not found") raise @@ -278,12 +245,13 @@ class ImageService(ImageServiceABC): def get_dto(self, image_name: str) -> ImageDTO: try: - image_record = self._services.records.get(image_name) + image_record = self._services.image_records.get(image_name) image_dto = image_record_to_dto( image_record, self._services.urls.get_image_url(image_name), self._services.urls.get_image_url(image_name, True), + self._services.board_image_records.get_board_for_image(image_name), ) return image_dto @@ -296,14 +264,14 @@ class ImageService(ImageServiceABC): def get_path(self, image_name: str, thumbnail: bool = False) -> str: try: - return self._services.files.get_path(image_name, thumbnail) + return self._services.image_files.get_path(image_name, thumbnail) except Exception as e: self._services.logger.error("Problem getting image path") raise e def validate_path(self, path: str) -> bool: try: - return self._services.files.validate_path(path) + return self._services.image_files.validate_path(path) except Exception as e: self._services.logger.error("Problem validating image path") raise e @@ -324,7 +292,7 @@ class ImageService(ImageServiceABC): is_intermediate: Optional[bool] = None, ) -> OffsetPaginatedResults[ImageDTO]: try: - results = self._services.records.get_many( + results = self._services.image_records.get_many( offset, limit, image_origin, @@ -338,6 +306,9 @@ class ImageService(ImageServiceABC): r, self._services.urls.get_image_url(r.image_name), self._services.urls.get_image_url(r.image_name, True), + self._services.board_image_records.get_board_for_image( + r.image_name + ), ), results.items, ) @@ -355,8 +326,8 @@ class ImageService(ImageServiceABC): def delete(self, image_name: str): try: - self._services.files.delete(image_name) - self._services.records.delete(image_name) + self._services.image_files.delete(image_name) + self._services.image_records.delete(image_name) except ImageRecordDeleteException: self._services.logger.error(f"Failed to delete image record") raise diff --git a/invokeai/app/services/models/image_record.py b/invokeai/app/services/models/image_record.py index d971d65916..cc02016cf9 100644 --- a/invokeai/app/services/models/image_record.py +++ b/invokeai/app/services/models/image_record.py @@ -86,19 +86,24 @@ class ImageUrlsDTO(BaseModel): class ImageDTO(ImageRecord, ImageUrlsDTO): - """Deserialized image record, enriched for the frontend with URLs.""" + """Deserialized image record, enriched for the frontend.""" + board_id: Union[str, None] = Field( + description="The id of the board the image belongs to, if one exists." + ) + """The id of the board the image belongs to, if one exists.""" pass def image_record_to_dto( - image_record: ImageRecord, image_url: str, thumbnail_url: str + image_record: ImageRecord, image_url: str, thumbnail_url: str, board_id: Union[str, None] ) -> ImageDTO: """Converts an image record to an image DTO.""" return ImageDTO( **image_record.dict(), image_url=image_url, thumbnail_url=thumbnail_url, + board_id=board_id, ) From 49a02c157bf29eefecca75f40e04ae9df040931b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 16 Jun 2023 15:53:17 +1000 Subject: [PATCH 041/110] feat(ui): fix UpdateImageBoardModal select --- .../components/Boards/UpdateImageBoardModal.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx index 8a94764ab1..b5e00d7801 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx @@ -18,12 +18,11 @@ import { AddImageToBoardContext } from '../../../../app/contexts/AddImageToBoard import { useSelector } from 'react-redux'; import { selectBoardsAll } from '../../store/boardSlice'; import IAISelect from '../../../../common/components/IAISelect'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; const UpdateImageBoardModal = () => { const boards = useSelector(selectBoardsAll); - const [selectedBoard, setSelectedBoard] = useState( - undefined - ); + const [selectedBoard, setSelectedBoard] = useState(null); const { isOpen, onClose, handleAddToBoard, image } = useContext( AddImageToBoardContext @@ -55,10 +54,14 @@ const UpdateImageBoardModal = () => { Moving this image to a board will remove it from its existing board. - setSelectedBoard(e.target.value)} - validValues={boards.map((board) => board.board_name)} + onChange={(v) => setSelectedBoard(v)} + value={selectedBoard} + data={boards.map((board) => ({ + label: board.board_name, + value: board.board_id, + }))} /> From 95b9c8e505534ff065ed9660c0459b5cba724bd1 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Fri, 16 Jun 2023 10:21:55 -0700 Subject: [PATCH 042/110] return cover_image_name since urls change, override one from db for now --- invokeai/app/services/board_images.py | 8 ++--- invokeai/app/services/boards.py | 31 ++++++++++---------- invokeai/app/services/models/board_record.py | 4 +-- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/invokeai/app/services/board_images.py b/invokeai/app/services/board_images.py index cf16993a7a..072effbfae 100644 --- a/invokeai/app/services/board_images.py +++ b/invokeai/app/services/board_images.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from logging import Logger -from typing import Union +from typing import List, Union from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase from invokeai.app.services.board_record_storage import ( BoardRecord, @@ -132,11 +132,11 @@ class BoardImagesService(BoardImagesServiceABC): def board_record_to_dto( - board_record: BoardRecord, cover_image_url: str | None, image_count: int + board_record: BoardRecord, cover_image_name: str | None, image_count: int ) -> BoardDTO: """Converts a board record to a board DTO.""" return BoardDTO( - **board_record.dict(), - cover_image_url=cover_image_url, + **board_record.dict(exclude={'cover_image_name'}), + cover_image_name=cover_image_name, image_count=image_count, ) diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index 148af50103..93def0cafd 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -101,15 +101,15 @@ class BoardService(BoardServiceABC): def get_dto(self, board_id: str) -> BoardDTO: board_record = self._services.board_records.get(board_id) - cover_image_url = ( - self._services.urls.get_image_url(board_record.cover_image_name, True) - if board_record.cover_image_name - else None - ) + cover_image = self._services.image_records.get_most_recent_image_for_board(board_recordboard_id) + if (cover_image): + cover_image_name = cover_image.image_name + else: + cover_image_name = None image_count = self._services.board_image_records.get_image_count_for_board( board_id ) - return board_record_to_dto(board_record, cover_image_url, image_count) + return board_record_to_dto(board_record, cover_image_name, image_count) def update( self, @@ -117,15 +117,16 @@ class BoardService(BoardServiceABC): changes: BoardChanges, ) -> BoardDTO: board_record = self._services.board_records.update(board_id, changes) - cover_image_url = ( - self._services.urls.get_image_url(board_record.cover_image_name, True) - if board_record.cover_image_name - else None - ) + cover_image = self._services.image_records.get_most_recent_image_for_board(board_record.board_id) + if (cover_image): + cover_image_name = cover_image.image_name + else: + cover_image_name = None + image_count = self._services.board_image_records.get_image_count_for_board( board_id ) - return board_record_to_dto(board_record, cover_image_url, image_count) + return board_record_to_dto(board_record, cover_image_name, image_count) def delete(self, board_id: str) -> None: self._services.board_records.delete(board_id) @@ -138,14 +139,14 @@ class BoardService(BoardServiceABC): for r in board_records.items: cover_image = self._services.image_records.get_most_recent_image_for_board(r.board_id) if (cover_image): - cover_image_url = self._services.urls.get_image_url(cover_image.image_name, True) + cover_image_name = cover_image.image_name else: - cover_image_url = None + cover_image_name = None image_count = self._services.board_image_records.get_image_count_for_board( r.board_id ) - board_dtos.append(board_record_to_dto(r, cover_image_url, image_count)) + board_dtos.append(board_record_to_dto(r, cover_image_name, image_count)) return OffsetPaginatedResults[BoardDTO]( items=board_dtos, offset=offset, limit=limit, total=len(board_dtos) diff --git a/invokeai/app/services/models/board_record.py b/invokeai/app/services/models/board_record.py index 35c4cea9b0..16748570c4 100644 --- a/invokeai/app/services/models/board_record.py +++ b/invokeai/app/services/models/board_record.py @@ -27,8 +27,8 @@ class BoardRecord(BaseModel): class BoardDTO(BoardRecord): """Deserialized board record with cover image URL and image count.""" - cover_image_url: Optional[str] = Field( - description="The URL of the thumbnail of the board's cover image." + cover_image_name: Optional[str] = Field( + description="The name of the board's cover image." ) """The URL of the thumbnail of the most recent image in the board.""" image_count: int = Field(description="The number of images in the board.") From f9f3c91a83a37ae2d280f7912d8c18466c99eb06 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Fri, 16 Jun 2023 13:06:59 -0400 Subject: [PATCH 043/110] drag and drop to move image to board, a bit of board list UI --- .../app/contexts/AddImageToBoardContext.tsx | 62 +-------- .../gallery/components/Boards/BoardsList.tsx | 123 +++++++++++------- .../components/Boards/HoverableBoard.tsx | 64 ++++++--- .../Boards/UpdateImageBoardModal.tsx | 13 +- .../components/ImageGalleryContent.tsx | 35 +---- .../web/src/services/api/models/ImageDTO.ts | 6 +- 6 files changed, 139 insertions(+), 164 deletions(-) diff --git a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx index cf541dca01..da3dcb2239 100644 --- a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx +++ b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx @@ -1,23 +1,7 @@ import { useDisclosure } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import { requestedImageDeletion } from 'features/gallery/store/actions'; -import { systemSelector } from 'features/system/store/systemSelectors'; -import { - PropsWithChildren, - createContext, - useCallback, - useEffect, - useState, -} from 'react'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { PropsWithChildren, createContext, useCallback, useState } from 'react'; import { ImageDTO } from 'services/api'; -import { RootState } from 'app/store/store'; -import { canvasSelector } from 'features/canvas/store/canvasSelectors'; -import { controlNetSelector } from 'features/controlNet/store/controlNetSlice'; -import { nodesSelecter } from 'features/nodes/store/nodesSlice'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { some } from 'lodash-es'; import { imageAddedToBoard } from '../../services/thunks/board'; export type ImageUsage = { @@ -27,48 +11,6 @@ export type ImageUsage = { isControlNetImage: boolean; }; -export const selectImageUsage = createSelector( - [ - generationSelector, - canvasSelector, - nodesSelecter, - controlNetSelector, - (state: RootState, image_name?: string) => image_name, - ], - (generation, canvas, nodes, controlNet, image_name) => { - const isInitialImage = generation.initialImage?.image_name === image_name; - - const isCanvasImage = canvas.layerState.objects.some( - (obj) => obj.kind === 'image' && obj.image.image_name === image_name - ); - - const isNodesImage = nodes.nodes.some((node) => { - return some( - node.data.inputs, - (input) => - input.type === 'image' && input.value?.image_name === image_name - ); - }); - - const isControlNetImage = some( - controlNet.controlNets, - (c) => - c.controlImage?.image_name === image_name || - c.processedControlImage?.image_name === image_name - ); - - const imageUsage: ImageUsage = { - isInitialImage, - isCanvasImage, - isNodesImage, - isControlNetImage, - }; - - return imageUsage; - }, - defaultSelectorOptions -); - type AddImageToBoardContextValue = { /** * Whether the move image dialog is open. diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index 9e7d1ab960..1f84d3be0e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -1,4 +1,13 @@ -import { Box, Grid, Input, Spacer } from '@chakra-ui/react'; +import { + Box, + Divider, + Grid, + Input, + InputGroup, + InputRightElement, + Spacer, + useDisclosure, +} from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; @@ -14,19 +23,25 @@ import AddBoardButton from './AddBoardButton'; import AllImagesBoard from './AllImagesBoard'; import { searchBoardsSelector } from '../../store/boardSelectors'; import { useSelector } from 'react-redux'; +import IAICollapse from '../../../../common/components/IAICollapse'; +import { CloseIcon } from '@chakra-ui/icons'; const selector = createSelector( [selectBoardsAll, boardsSelector], (boards, boardsState) => { - return { boards, selectedBoardId: boardsState.selectedBoardId }; + const selectedBoard = boards.find( + (board) => board.board_id === boardsState.selectedBoardId + ); + return { selectedBoard, searchText: boardsState.searchText }; }, defaultSelectorOptions ); const BoardsList = () => { const dispatch = useAppDispatch(); - const { selectedBoardId } = useAppSelector(selector); + const { selectedBoard, searchText } = useAppSelector(selector); const filteredBoards = useSelector(searchBoardsSelector); + const { isOpen, onToggle } = useDisclosure(); const [searchMode, setSearchMode] = useState(false); @@ -34,52 +49,68 @@ const BoardsList = () => { setSearchMode(searchTerm.length > 0); dispatch(setBoardSearchText(searchTerm)); }; + const clearBoardSearch = () => { + setSearchMode(false); + dispatch(setBoardSearchText('')); + }; return ( - - - { - handleBoardSearch(e.target.value); + + <> + + + { + handleBoardSearch(e.target.value); + }} + /> + {searchText && searchText.length && ( + + + + )} + + + - - - {!searchMode && ( - <> - - - - )} - {filteredBoards.map((board) => ( - - ))} - - + > + + {!searchMode && ( + <> + + + + )} + {filteredBoards.map((board) => ( + + ))} + + + + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index d368d4ab0b..6fd6ac41be 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -4,19 +4,34 @@ import { EditableInput, EditablePreview, Flex, - Icon, - Image, MenuItem, MenuList, } from '@chakra-ui/react'; -import { useAppDispatch } from 'app/store/storeHooks'; + +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { memo, useCallback } from 'react'; -import { FaFolder, FaTrash } from 'react-icons/fa'; +import { FaTrash } from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; -import { BoardDTO } from 'services/api'; +import { BoardDTO, ImageDTO } from 'services/api'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; import { boardIdSelected } from 'features/gallery/store/boardSlice'; -import { boardDeleted, boardUpdated } from '../../../../services/thunks/board'; +import { + boardDeleted, + boardUpdated, + imageAddedToBoard, +} from '../../../../services/thunks/board'; +import { selectImagesAll } from '../../store/imagesSlice'; +import IAIDndImage from '../../../../common/components/IAIDndImage'; +import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions'; +import { createSelector } from '@reduxjs/toolkit'; + +const selector = createSelector( + [selectImagesAll], + (images) => { + return { images }; + }, + defaultSelectorOptions +); interface HoverableBoardProps { board: BoardDTO; @@ -25,6 +40,7 @@ interface HoverableBoardProps { const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { const dispatch = useAppDispatch(); + const { images } = useAppSelector(selector); const { board_name, board_id, cover_image_url } = board; @@ -45,6 +61,23 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { ); }; + const handleDrop = useCallback( + (droppedImage: ImageDTO) => { + if (droppedImage.board_id === board_id) { + return; + } + dispatch( + imageAddedToBoard({ + requestBody: { + board_id, + image_name: droppedImage.image_name, + }, + }) + ); + }, + [board_id, dispatch] + ); + return ( @@ -91,19 +124,12 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { overflow: 'hidden', }} > - {cover_image_url ? ( - } - sx={{}} - /> - ) : ( - - )} + } + isUploadDisabled={true} + /> { const boards = useSelector(selectBoardsAll); - const [selectedBoard, setSelectedBoard] = useState(null); - const { isOpen, onClose, handleAddToBoard, image } = useContext( AddImageToBoardContext ); + const [selectedBoard, setSelectedBoard] = useState(); const cancelRef = useRef(null); @@ -50,10 +49,12 @@ const UpdateImageBoardModal = () => { - - Moving this image to a board will remove it from its existing - board. - + {currentBoard && ( + + Moving this image from{' '} + {currentBoard.board_name} to + + )} setSelectedBoard(v)} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 647477ce39..c1c414ad8b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -240,39 +240,10 @@ const ImageGalleryContent = () => { icon={} /> - {selectedBoard && ( - - {selectedBoard.board_name} - - )} + + {selectedBoard ? selectedBoard.board_name : 'All Images'} + - {/* } - /> - } - > - - setNewBoardName(e.target.value)} - /> - - Create - - - */} Date: Fri, 16 Jun 2023 13:38:37 -0400 Subject: [PATCH 044/110] handle long board names --- .../src/features/gallery/components/ImageGalleryContent.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index c1c414ad8b..adb7791afb 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -221,6 +221,7 @@ const ImageGalleryContent = () => { ref={resizeObserverRef} alignItems="center" justifyContent="space-between" + gap={1} > { /> - {selectedBoard ? selectedBoard.board_name : 'All Images'} + + {selectedBoard ? selectedBoard.board_name : 'All Images'} + Date: Fri, 16 Jun 2023 14:40:12 -0400 Subject: [PATCH 045/110] add boardToAddTo state so that result can be added to board when generation is complete --- .../socketio/socketInvocationComplete.ts | 15 ++++++++++++++- .../web/src/features/gallery/store/boardSlice.ts | 5 ++--- .../web/src/features/system/store/systemSlice.ts | 3 +++ .../frontend/web/src/services/events/actions.ts | 4 ++-- .../web/src/services/events/middleware.ts | 1 + .../src/services/events/util/setEventListeners.ts | 1 + 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts index c9ab894ddb..24e8eb312f 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts @@ -9,6 +9,7 @@ import { imageMetadataReceived } from 'services/thunks/image'; import { sessionCanceled } from 'services/thunks/session'; import { isImageOutput } from 'services/types/guards'; import { progressImageSet } from 'features/system/store/systemSlice'; +import { imageAddedToBoard } from '../../../../../../services/thunks/board'; const moduleLog = log.child({ namespace: 'socketio' }); const nodeDenylist = ['dataURL_image']; @@ -24,7 +25,8 @@ export const addInvocationCompleteEventListener = () => { const sessionId = action.payload.data.graph_execution_state_id; - const { cancelType, isCancelScheduled } = getState().system; + const { cancelType, isCancelScheduled, boardIdToAddTo } = + getState().system; // Handle scheduled cancelation if (cancelType === 'scheduled' && isCancelScheduled) { @@ -38,6 +40,17 @@ export const addInvocationCompleteEventListener = () => { if (isImageOutput(result) && !nodeDenylist.includes(node.type)) { const { image_name } = result.image; + if (boardIdToAddTo) { + dispatch( + imageAddedToBoard({ + requestBody: { + board_id: boardIdToAddTo, + image_name, + }, + }) + ); + } + // Get its metadata dispatch( imageMetadataReceived({ diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index 390d43d033..76ab757e5e 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -25,7 +25,7 @@ type AdditionalBoardsState = { limit: number; total: number; isLoading: boolean; - selectedBoardId: EntityId | null; + selectedBoardId?: string; searchText?: string; updateBoardModalOpen: boolean; }; @@ -36,7 +36,6 @@ export const initialBoardsState = limit: 50, total: 0, isLoading: false, - selectedBoardId: null, updateBoardModalOpen: false, }); @@ -55,7 +54,7 @@ const boardsSlice = createSlice({ boardRemoved: (state, action: PayloadAction) => { boardsAdapter.removeOne(state, action.payload); }, - boardIdSelected: (state, action: PayloadAction) => { + boardIdSelected: (state, action: PayloadAction) => { state.selectedBoardId = action.payload; }, setBoardSearchText: (state, action: PayloadAction) => { diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index b17f497f6c..f86415cf37 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -95,6 +95,7 @@ export interface SystemState { shouldAntialiasProgressImage: boolean; language: keyof typeof LANGUAGES; isUploading: boolean; + boardIdToAddTo?: string; } export const initialSystemState: SystemState = { @@ -225,6 +226,7 @@ export const systemSlice = createSlice({ */ builder.addCase(appSocketSubscribed, (state, action) => { state.sessionId = action.payload.sessionId; + state.boardIdToAddTo = action.payload.boardId; state.canceledSession = ''; }); @@ -233,6 +235,7 @@ export const systemSlice = createSlice({ */ builder.addCase(appSocketUnsubscribed, (state) => { state.sessionId = null; + state.boardIdToAddTo = undefined; }); /** diff --git a/invokeai/frontend/web/src/services/events/actions.ts b/invokeai/frontend/web/src/services/events/actions.ts index 5832cb24b1..ed154b9cd8 100644 --- a/invokeai/frontend/web/src/services/events/actions.ts +++ b/invokeai/frontend/web/src/services/events/actions.ts @@ -53,14 +53,14 @@ export const appSocketDisconnected = createAction( * Do not use. Only for use in middleware. */ export const socketSubscribed = createAction< - BaseSocketPayload & { sessionId: string } + BaseSocketPayload & { sessionId: string; boardId: string | undefined } >('socket/socketSubscribed'); /** * App-level Socket.IO Subscribed */ export const appSocketSubscribed = createAction< - BaseSocketPayload & { sessionId: string } + BaseSocketPayload & { sessionId: string; boardId: string | undefined } >('socket/appSocketSubscribed'); /** diff --git a/invokeai/frontend/web/src/services/events/middleware.ts b/invokeai/frontend/web/src/services/events/middleware.ts index f1eb844f2c..5b427b1690 100644 --- a/invokeai/frontend/web/src/services/events/middleware.ts +++ b/invokeai/frontend/web/src/services/events/middleware.ts @@ -85,6 +85,7 @@ export const socketMiddleware = () => { socketSubscribed({ sessionId: sessionId, timestamp: getTimestamp(), + boardId: getState().boards.selectedBoardId, }) ); } diff --git a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts index 2c4cba510a..62b5864185 100644 --- a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts +++ b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts @@ -44,6 +44,7 @@ export const setEventListeners = (arg: SetEventListenersArg) => { socketSubscribed({ sessionId, timestamp: getTimestamp(), + boardId: getState().boards.selectedBoardId, }) ); } From fe10a9f74786ca3fc706085da8279df0c3f2fa09 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Fri, 16 Jun 2023 15:01:22 -0400 Subject: [PATCH 046/110] render cover image based on URL in image entities --- .../components/Boards/HoverableBoard.tsx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index 6fd6ac41be..055fa8f68b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -20,18 +20,26 @@ import { boardUpdated, imageAddedToBoard, } from '../../../../services/thunks/board'; -import { selectImagesAll } from '../../store/imagesSlice'; +import { selectImagesAll, selectImagesById } from '../../store/imagesSlice'; import IAIDndImage from '../../../../common/components/IAIDndImage'; import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions'; import { createSelector } from '@reduxjs/toolkit'; +import { RootState } from '../../../../app/store/store'; -const selector = createSelector( - [selectImagesAll], - (images) => { - return { images }; - }, - defaultSelectorOptions -); +const coverImageSelector = (imageName: string | undefined) => + createSelector( + [(state: RootState) => state], + (state) => { + const coverImage = imageName + ? selectImagesById(state, imageName) + : undefined; + + return { + coverImage, + }; + }, + defaultSelectorOptions + ); interface HoverableBoardProps { board: BoardDTO; @@ -40,9 +48,11 @@ interface HoverableBoardProps { const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { const dispatch = useAppDispatch(); - const { images } = useAppSelector(selector); + const { coverImage } = useAppSelector( + coverImageSelector(board?.cover_image_name) + ); - const { board_name, board_id, cover_image_url } = board; + const { board_name, board_id } = board; const handleSelectBoard = useCallback(() => { dispatch(boardIdSelected(board_id)); @@ -125,7 +135,7 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { }} > } isUploadDisabled={true} From daadf6ebfdf0586fe8266f056dee77e92007ee40 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 16 Jun 2023 17:50:54 +1000 Subject: [PATCH 047/110] feat(ui): add board image count badge --- .../components/Boards/HoverableBoard.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index 055fa8f68b..fdde7528cb 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -6,6 +6,7 @@ import { Flex, MenuItem, MenuList, + Text, } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; @@ -162,6 +163,22 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { }} /> + + {board.image_count} + )} From 8bce234542f135d913f6a5b0142e55b35663f480 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:49:14 +1000 Subject: [PATCH 048/110] feat(db): update image-board relationships on add Functionally, `add_image_to_board()` now moves images between boards. --- invokeai/app/services/board_image_record_storage.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index 2f1603be82..73fad13406 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -139,9 +139,10 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): self._cursor.execute( """--sql INSERT INTO board_images (board_id, image_name) - VALUES (?, ?); + VALUES (?, ?) + ON CONFLICT (image_name) DO UPDATE SET board_id = ?; """, - (board_id, image_name), + (board_id, image_name, board_id), ) self._conn.commit() except sqlite3.Error as e: From 21f0d0b0c1ce170b09628b366f449f15b40b68eb Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:50:02 +1000 Subject: [PATCH 049/110] fix(db): fix `deserialize_board_record()` It was not adding `cover_image_name` --- invokeai/app/services/models/board_record.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/app/services/models/board_record.py b/invokeai/app/services/models/board_record.py index 16748570c4..bf7a33e8b9 100644 --- a/invokeai/app/services/models/board_record.py +++ b/invokeai/app/services/models/board_record.py @@ -35,8 +35,6 @@ class BoardDTO(BoardRecord): """The number of images in the board.""" - - def deserialize_board_record(board_dict: dict) -> BoardRecord: """Deserializes a board record.""" @@ -44,14 +42,16 @@ def deserialize_board_record(board_dict: dict) -> BoardRecord: board_id = board_dict.get("board_id", "unknown") board_name = board_dict.get("board_name", "unknown") + cover_image_name = board_dict.get("cover_image_name", "unknown") created_at = board_dict.get("created_at", get_iso_timestamp()) updated_at = board_dict.get("updated_at", get_iso_timestamp()) - deleted_at = board_dict.get("deleted_at", get_iso_timestamp()) + # deleted_at = board_dict.get("deleted_at", get_iso_timestamp()) return BoardRecord( board_id=board_id, board_name=board_name, + cover_image_name=cover_image_name, created_at=created_at, updated_at=updated_at, - deleted_at=deleted_at, + # deleted_at=deleted_at, ) From 9ef64016c7381043dee3cb3794a560ba993fccc9 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:50:33 +1000 Subject: [PATCH 050/110] feat(db): sort board by `created_at` --- invokeai/app/services/board_record_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index 20a9683b02..ec56122c9f 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -263,7 +263,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): """--sql SELECT * FROM boards - ORDER BY updated_at DESC + ORDER BY created_at DESC LIMIT ? OFFSET ?; """, (limit, offset), From 661a94b3de30caaebd27c07f072f77704852efa2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:51:05 +1000 Subject: [PATCH 051/110] feat(db): add `get_all()` method for boards This is needed to show the full list of boards in the update boards modal. --- invokeai/app/api/routers/boards.py | 32 ++++++++----- invokeai/app/services/board_record_storage.py | 39 +++++++++++++++- invokeai/app/services/boards.py | 46 ++++++++++++++++--- invokeai/app/services/models/board_record.py | 1 + 4 files changed, 98 insertions(+), 20 deletions(-) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index 7935db58f2..a6f226fef8 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -1,3 +1,4 @@ +from typing import Optional, Union from fastapi import Body, HTTPException, Path, Query from fastapi.routing import APIRouter from invokeai.app.services.board_record_storage import BoardChanges @@ -19,7 +20,7 @@ boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) response_model=BoardDTO, ) async def create_board( - board_name: str = Body(description="The name of the board to create"), + board_name: str = Query(description="The name of the board to create"), ) -> BoardDTO: """Creates a board""" try: @@ -70,16 +71,25 @@ async def delete_board( @boards_router.get( "/", operation_id="list_boards", - response_model=OffsetPaginatedResults[BoardDTO], + response_model=Union[OffsetPaginatedResults[BoardDTO], list[BoardDTO]], ) async def list_boards( - offset: int = Query(default=0, description="The page offset"), - limit: int = Query(default=10, description="The number of boards per page"), -) -> OffsetPaginatedResults[BoardDTO]: + all: Optional[bool] = Query(default=None, description="Whether to list all boards"), + offset: Optional[int] = Query(default=None, description="The page offset"), + limit: Optional[int] = Query( + default=None, description="The number of boards per page" + ), +) -> Union[OffsetPaginatedResults[BoardDTO], list[BoardDTO]]: """Gets a list of boards""" - - results = ApiDependencies.invoker.services.boards.get_many( - offset, - limit, - ) - return results + if all: + return ApiDependencies.invoker.services.boards.get_all() + elif offset is not None and limit is not None: + return ApiDependencies.invoker.services.boards.get_many( + offset, + limit, + ) + else: + raise HTTPException( + status_code=400, + detail="Invalid request: Must provide either 'all' or both 'offset' and 'limit'", + ) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index ec56122c9f..15ea9cc5a7 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -5,12 +5,14 @@ import threading from typing import Optional, Union import uuid from invokeai.app.services.image_record_storage import OffsetPaginatedResults -from invokeai.app.services.models.board_record import BoardRecord, deserialize_board_record +from invokeai.app.services.models.board_record import ( + BoardRecord, + deserialize_board_record, +) from pydantic import BaseModel, Field, Extra - class BoardChanges(BaseModel, extra=Extra.forbid): board_name: Optional[str] = Field(description="The board's new name.") cover_image_name: Optional[str] = Field( @@ -81,6 +83,13 @@ class BoardRecordStorageBase(ABC): """Gets many board records.""" pass + @abstractmethod + def get_all( + self, + ) -> list[BoardRecord]: + """Gets all board records.""" + pass + class SqliteBoardRecordStorage(BoardRecordStorageBase): _filename: str @@ -292,3 +301,29 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): raise e finally: self._lock.release() + + def get_all( + self, + ) -> list[BoardRecord]: + try: + self._lock.acquire() + + # Get all the boards + self._cursor.execute( + """--sql + SELECT * + FROM boards + ORDER BY created_at DESC + """ + ) + + result = cast(list[sqlite3.Row], self._cursor.fetchall()) + boards = list(map(lambda r: deserialize_board_record(dict(r)), result)) + + return boards + + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index 93def0cafd..9361322e6c 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -61,6 +61,13 @@ class BoardServiceABC(ABC): """Gets many boards.""" pass + @abstractmethod + def get_all( + self, + ) -> list[BoardDTO]: + """Gets all boards.""" + pass + class BoardServiceDependencies: """Service dependencies for the BoardService.""" @@ -101,8 +108,10 @@ class BoardService(BoardServiceABC): def get_dto(self, board_id: str) -> BoardDTO: board_record = self._services.board_records.get(board_id) - cover_image = self._services.image_records.get_most_recent_image_for_board(board_recordboard_id) - if (cover_image): + cover_image = self._services.image_records.get_most_recent_image_for_board( + board_record.board_id + ) + if cover_image: cover_image_name = cover_image.image_name else: cover_image_name = None @@ -117,12 +126,14 @@ class BoardService(BoardServiceABC): changes: BoardChanges, ) -> BoardDTO: board_record = self._services.board_records.update(board_id, changes) - cover_image = self._services.image_records.get_most_recent_image_for_board(board_record.board_id) - if (cover_image): + cover_image = self._services.image_records.get_most_recent_image_for_board( + board_record.board_id + ) + if cover_image: cover_image_name = cover_image.image_name else: cover_image_name = None - + image_count = self._services.board_image_records.get_image_count_for_board( board_id ) @@ -137,8 +148,10 @@ class BoardService(BoardServiceABC): board_records = self._services.board_records.get_many(offset, limit) board_dtos = [] for r in board_records.items: - cover_image = self._services.image_records.get_most_recent_image_for_board(r.board_id) - if (cover_image): + cover_image = self._services.image_records.get_most_recent_image_for_board( + r.board_id + ) + if cover_image: cover_image_name = cover_image.image_name else: cover_image_name = None @@ -151,3 +164,22 @@ class BoardService(BoardServiceABC): return OffsetPaginatedResults[BoardDTO]( items=board_dtos, offset=offset, limit=limit, total=len(board_dtos) ) + + def get_all(self) -> list[BoardDTO]: + board_records = self._services.board_records.get_all() + board_dtos = [] + for r in board_records: + cover_image = self._services.image_records.get_most_recent_image_for_board( + r.board_id + ) + if cover_image: + cover_image_name = cover_image.image_name + else: + cover_image_name = None + + image_count = self._services.board_image_records.get_image_count_for_board( + r.board_id + ) + board_dtos.append(board_record_to_dto(r, cover_image_name, image_count)) + + return board_dtos \ No newline at end of file diff --git a/invokeai/app/services/models/board_record.py b/invokeai/app/services/models/board_record.py index bf7a33e8b9..325ddd094e 100644 --- a/invokeai/app/services/models/board_record.py +++ b/invokeai/app/services/models/board_record.py @@ -3,6 +3,7 @@ from datetime import datetime from pydantic import BaseModel, Extra, Field, StrictBool, StrictStr from invokeai.app.util.misc import get_iso_timestamp + class BoardRecord(BaseModel): """Deserialized board record.""" From cfda128e06d87c67140c4be008eadd4c585031fe Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 00:07:24 +1000 Subject: [PATCH 052/110] feat(ui): wip boards via `rtk-query` --- .../app/contexts/AddImageToBoardContext.tsx | 17 +-- .../middleware/listenerMiddleware/index.ts | 8 + .../listeners/imageAddedToBoard.ts | 40 +++++ .../listeners/imageDeleted.ts | 21 ++- invokeai/frontend/web/src/app/store/store.ts | 3 + .../components/Boards/AddBoardButton.tsx | 21 ++- .../gallery/components/Boards/BoardsList.tsx | 26 +++- .../components/Boards/HoverableBoard.tsx | 23 +-- .../Boards/UpdateImageBoardModal.tsx | 35 +++-- .../components/CurrentImageButtons.tsx | 15 +- .../components/CurrentImagePreview.tsx | 13 +- .../gallery/components/HoverableImage.tsx | 19 +-- .../components/ImageGalleryContent.tsx | 14 +- .../ImageMetadataViewer.tsx | 16 +- .../components/NextPrevImageButtons.tsx | 18 ++- .../features/gallery/store/gallerySlice.ts | 20 +-- .../web/src/services/api/models/BoardDTO.ts | 6 +- .../services/api/services/BoardsService.ts | 24 ++- .../frontend/web/src/services/apiSlice.ts | 144 ++++++++++++++++++ .../frontend/web/src/services/thunks/board.ts | 2 +- 20 files changed, 356 insertions(+), 129 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts create mode 100644 invokeai/frontend/web/src/services/apiSlice.ts diff --git a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx index da3dcb2239..d29c1c8a48 100644 --- a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx +++ b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx @@ -3,6 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks'; import { PropsWithChildren, createContext, useCallback, useState } from 'react'; import { ImageDTO } from 'services/api'; import { imageAddedToBoard } from '../../services/thunks/board'; +import { useAddImageToBoardMutation } from 'services/apiSlice'; export type ImageUsage = { isInitialImage: boolean; @@ -43,6 +44,8 @@ export const AddImageToBoardContextProvider = (props: Props) => { const dispatch = useAppDispatch(); const { isOpen, onOpen, onClose } = useDisclosure(); + const [addImageToBoard, result] = useAddImageToBoardMutation(); + // Clean up after deleting or dismissing the modal const closeAndClearImageToDelete = useCallback(() => { setImageToMove(undefined); @@ -63,18 +66,14 @@ export const AddImageToBoardContextProvider = (props: Props) => { const handleAddToBoard = useCallback( (boardId: string) => { if (imageToMove) { - dispatch( - imageAddedToBoard({ - requestBody: { - board_id: boardId, - image_name: imageToMove.image_name, - }, - }) - ); + addImageToBoard({ + board_id: boardId, + image_name: imageToMove.image_name, + }); closeAndClearImageToDelete(); } }, - [closeAndClearImageToDelete, dispatch, imageToMove] + [addImageToBoard, closeAndClearImageToDelete, imageToMove] ); return ( diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 8c073e81d6..15fd48fbb2 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -73,6 +73,10 @@ import { addImageCategoriesChangedListener } from './listeners/imageCategoriesCh import { addControlNetImageProcessedListener } from './listeners/controlNetImageProcessed'; import { addControlNetAutoProcessListener } from './listeners/controlNetAutoProcess'; import { addUpdateImageUrlsOnConnectListener } from './listeners/updateImageUrlsOnConnect'; +import { + addImageAddedToBoardFulfilledListener, + addImageAddedToBoardRejectedListener, +} from './listeners/imageAddedToBoard'; export const listenerMiddleware = createListenerMiddleware(); @@ -183,3 +187,7 @@ addControlNetAutoProcessListener(); // Update image URLs on connect addUpdateImageUrlsOnConnectListener(); + +// Boards +addImageAddedToBoardFulfilledListener(); +addImageAddedToBoardRejectedListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts new file mode 100644 index 0000000000..0f404cab68 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts @@ -0,0 +1,40 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { imageMetadataReceived } from 'services/thunks/image'; +import { api } from 'services/apiSlice'; + +const moduleLog = log.child({ namespace: 'boards' }); + +export const addImageAddedToBoardFulfilledListener = () => { + startAppListening({ + matcher: api.endpoints.addImageToBoard.matchFulfilled, + effect: (action, { getState, dispatch }) => { + const { board_id, image_name } = action.meta.arg.originalArgs; + + moduleLog.debug( + { data: { board_id, image_name } }, + 'Image added to board' + ); + + dispatch( + imageMetadataReceived({ + imageName: image_name, + }) + ); + }, + }); +}; + +export const addImageAddedToBoardRejectedListener = () => { + startAppListening({ + matcher: api.endpoints.addImageToBoard.matchRejected, + effect: (action, { getState, dispatch }) => { + const { board_id, image_name } = action.meta.arg.originalArgs; + + moduleLog.debug( + { data: { board_id, image_name } }, + 'Problem adding image to board' + ); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts index 4c0c057242..9792137bbe 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts @@ -13,6 +13,7 @@ import { resetCanvas } from 'features/canvas/store/canvasSlice'; import { controlNetReset } from 'features/controlNet/store/controlNetSlice'; import { clearInitialImage } from 'features/parameters/store/generationSlice'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; +import { api } from 'services/apiSlice'; const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' }); @@ -22,7 +23,7 @@ const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' }); export const addRequestedImageDeletionListener = () => { startAppListening({ actionCreator: requestedImageDeletion, - effect: (action, { dispatch, getState }) => { + effect: async (action, { dispatch, getState, condition }) => { const { image, imageUsage } = action.payload; const { image_name } = image; @@ -30,7 +31,7 @@ export const addRequestedImageDeletionListener = () => { const state = getState(); const selectedImage = state.gallery.selectedImage; - if (selectedImage && selectedImage.image_name === image_name) { + if (selectedImage && selectedImage === image_name) { const ids = selectImagesIds(state); const entities = selectImagesEntities(state); @@ -51,7 +52,7 @@ export const addRequestedImageDeletionListener = () => { const newSelectedImage = entities[newSelectedImageId]; if (newSelectedImageId) { - dispatch(imageSelected(newSelectedImage)); + dispatch(imageSelected(newSelectedImageId)); } else { dispatch(imageSelected()); } @@ -79,7 +80,19 @@ export const addRequestedImageDeletionListener = () => { dispatch(imageRemoved(image_name)); // Delete from server - dispatch(imageDeleted({ imageName: image_name })); + const { requestId } = dispatch(imageDeleted({ imageName: image_name })); + + // Wait for successful deletion, then trigger boards to re-fetch + const wasImageDeleted = await condition( + (action) => action.meta.requestId === requestId, + 30000 + ); + + if (wasImageDeleted) { + dispatch( + api.util.invalidateTags([{ type: 'Board', id: image.board_id }]) + ); + } }, }); }; diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 4032db3159..a9011f9356 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -33,6 +33,7 @@ import { actionsDenylist } from './middleware/devtools/actionsDenylist'; import { serialize } from './enhancers/reduxRemember/serialize'; import { unserialize } from './enhancers/reduxRemember/unserialize'; import { LOCALSTORAGE_PREFIX } from './constants'; +import { api } from 'services/apiSlice'; const allReducers = { canvas: canvasReducer, @@ -49,6 +50,7 @@ const allReducers = { images: imagesReducer, controlNet: controlNetReducer, boards: boardsReducer, + [api.reducerPath]: api.reducer, // session: sessionReducer, }; @@ -87,6 +89,7 @@ export const store = configureStore({ immutableCheck: false, serializableCheck: false, }) + .concat(api.middleware) .concat(dynamicMiddlewares) .prepend(listenerMiddleware.middleware), devTools: { diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx index d8828fe736..284e6558ac 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx @@ -1,19 +1,20 @@ -import { Flex, Icon, Text } from '@chakra-ui/react'; +import { Flex, Icon, Spinner, Text } from '@chakra-ui/react'; import { useCallback } from 'react'; import { FaPlus } from 'react-icons/fa'; -import { useAppDispatch } from '../../../../app/store/storeHooks'; -import { boardCreated } from '../../../../services/thunks/board'; +import { useCreateBoardMutation } from 'services/apiSlice'; + +const DEFAULT_BOARD_NAME = 'My Board'; const AddBoardButton = () => { - const dispatch = useAppDispatch(); + const [createBoard, { isLoading }] = useCreateBoardMutation(); const handleCreateBoard = useCallback(() => { - dispatch(boardCreated({ requestBody: 'My Board' })); - }, [dispatch]); + createBoard(DEFAULT_BOARD_NAME); + }, [createBoard]); return ( { aspectRatio: '1/1', }} > - + {isLoading ? ( + + ) : ( + + )} New Board diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index 1f84d3be0e..be849e625e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -25,6 +25,7 @@ import { searchBoardsSelector } from '../../store/boardSelectors'; import { useSelector } from 'react-redux'; import IAICollapse from '../../../../common/components/IAICollapse'; import { CloseIcon } from '@chakra-ui/icons'; +import { useListBoardsQuery } from 'services/apiSlice'; const selector = createSelector( [selectBoardsAll, boardsSelector], @@ -40,9 +41,17 @@ const selector = createSelector( const BoardsList = () => { const dispatch = useAppDispatch(); const { selectedBoard, searchText } = useAppSelector(selector); - const filteredBoards = useSelector(searchBoardsSelector); + // const filteredBoards = useSelector(searchBoardsSelector); const { isOpen, onToggle } = useDisclosure(); + const { data } = useListBoardsQuery({ offset: 0, limit: 8 }); + + const filteredBoards = searchText + ? data?.items.filter((board) => + board.board_name.toLowerCase().includes(searchText.toLowerCase()) + ) + : data.items; + const [searchMode, setSearchMode] = useState(false); const handleBoardSearch = (searchTerm: string) => { @@ -100,13 +109,14 @@ const BoardsList = () => { )} - {filteredBoards.map((board) => ( - - ))} + {filteredBoards && + filteredBoards.map((board) => ( + + ))} diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index fdde7528cb..7ae864f55b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -26,6 +26,10 @@ import IAIDndImage from '../../../../common/components/IAIDndImage'; import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions'; import { createSelector } from '@reduxjs/toolkit'; import { RootState } from '../../../../app/store/store'; +import { + useDeleteBoardMutation, + useUpdateBoardMutation, +} from 'services/apiSlice'; const coverImageSelector = (imageName: string | undefined) => createSelector( @@ -59,19 +63,20 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { dispatch(boardIdSelected(board_id)); }, [board_id, dispatch]); - const handleDeleteBoard = useCallback(() => { - dispatch(boardDeleted(board_id)); - }, [board_id, dispatch]); + const [updateBoard, { isLoading: isUpdateBoardLoading }] = + useUpdateBoardMutation(); + + const [deleteBoard, { isLoading: isDeleteBoardLoading }] = + useDeleteBoardMutation(); const handleUpdateBoardName = (newBoardName: string) => { - dispatch( - boardUpdated({ - boardId: board_id, - requestBody: { board_name: newBoardName }, - }) - ); + updateBoard({ board_id, changes: { board_name: newBoardName } }); }; + const handleDeleteBoard = useCallback(() => { + deleteBoard(board_id); + }, [board_id, deleteBoard]); + const handleDrop = useCallback( (droppedImage: ImageDTO) => { if (droppedImage.board_id === board_id) { diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx index 9136e23e03..edd4d215af 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx @@ -9,6 +9,7 @@ import { Divider, Flex, Select, + Spinner, Text, } from '@chakra-ui/react'; import IAIButton from 'common/components/IAIButton'; @@ -19,9 +20,11 @@ import { useSelector } from 'react-redux'; import { selectBoardsAll } from '../../store/boardSlice'; import IAISelect from '../../../../common/components/IAISelect'; import IAIMantineSelect from 'common/components/IAIMantineSelect'; +import { useListAllBoardsQuery } from 'services/apiSlice'; const UpdateImageBoardModal = () => { - const boards = useSelector(selectBoardsAll); + // const boards = useSelector(selectBoardsAll); + const { data: boards, isFetching } = useListAllBoardsQuery(); const { isOpen, onClose, handleAddToBoard, image } = useContext( AddImageToBoardContext ); @@ -29,9 +32,9 @@ const UpdateImageBoardModal = () => { const cancelRef = useRef(null); - const currentBoard = boards.filter( + const currentBoard = boards?.find( (board) => board.board_id === image?.board_id - )[0]; + ); return ( { {currentBoard.board_name} to )} - setSelectedBoard(v)} - value={selectedBoard} - data={boards.map((board) => ({ - label: board.board_name, - value: board.board_id, - }))} - /> + {isFetching ? ( + + ) : ( + setSelectedBoard(v)} + value={selectedBoard} + data={(boards ?? []).map((board) => ({ + label: board.board_name, + value: board.board_id, + }))} + /> + )} @@ -73,7 +80,9 @@ const UpdateImageBoardModal = () => { isDisabled={!selectedBoard} colorScheme="accent" onClick={() => { - if (selectedBoard) handleAddToBoard(selectedBoard); + if (selectedBoard) { + handleAddToBoard(selectedBoard); + } }} ml={3} > diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index a5eaeb4c71..169a965be0 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -51,9 +51,12 @@ import { useAppToaster } from 'app/components/Toaster'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { DeleteImageContext } from 'app/contexts/DeleteImageContext'; import { DeleteImageButton } from './DeleteImageModal'; +import { selectImagesById } from '../store/imagesSlice'; +import { RootState } from 'app/store/store'; const currentImageButtonsSelector = createSelector( [ + (state: RootState) => state, systemSelector, gallerySelector, postprocessingSelector, @@ -61,7 +64,7 @@ const currentImageButtonsSelector = createSelector( lightboxSelector, activeTabNameSelector, ], - (system, gallery, postprocessing, ui, lightbox, activeTabName) => { + (state, system, gallery, postprocessing, ui, lightbox, activeTabName) => { const { isProcessing, isConnected, @@ -81,6 +84,8 @@ const currentImageButtonsSelector = createSelector( shouldShowProgressInViewer, } = ui; + const imageDTO = selectImagesById(state, gallery.selectedImage ?? ''); + const { selectedImage } = gallery; return { @@ -97,10 +102,10 @@ const currentImageButtonsSelector = createSelector( activeTabName, isLightboxOpen, shouldHidePreview, - image: selectedImage, - seed: selectedImage?.metadata?.seed, - prompt: selectedImage?.metadata?.positive_conditioning, - negativePrompt: selectedImage?.metadata?.negative_conditioning, + image: imageDTO, + seed: imageDTO?.metadata?.seed, + prompt: imageDTO?.metadata?.positive_conditioning, + negativePrompt: imageDTO?.metadata?.negative_conditioning, shouldShowProgressInViewer, }; }, diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index c591206a27..649cae7682 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -15,6 +15,8 @@ import { imageSelected } from '../store/gallerySlice'; import IAIDndImage from 'common/components/IAIDndImage'; import { ImageDTO } from 'services/api'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { RootState } from 'app/store/store'; +import { selectImagesById } from '../store/imagesSlice'; export const imagesSelector = createSelector( [uiSelector, gallerySelector, systemSelector], @@ -29,7 +31,7 @@ export const imagesSelector = createSelector( return { shouldShowImageDetails, shouldHidePreview, - image: selectedImage, + selectedImage, progressImage, shouldShowProgressInViewer, shouldAntialiasProgressImage, @@ -45,11 +47,16 @@ export const imagesSelector = createSelector( const CurrentImagePreview = () => { const { shouldShowImageDetails, - image, + selectedImage, progressImage, shouldShowProgressInViewer, shouldAntialiasProgressImage, } = useAppSelector(imagesSelector); + + const image = useAppSelector((state: RootState) => + selectImagesById(state, selectedImage ?? '') + ); + const dispatch = useAppDispatch(); const handleDrop = useCallback( @@ -57,7 +64,7 @@ const CurrentImagePreview = () => { if (droppedImage.image_name === image?.image_name) { return; } - dispatch(imageSelected(droppedImage)); + dispatch(imageSelected(droppedImage.image_name)); }, [dispatch, image?.image_name] ); diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index b21c62785b..86ec3436f0 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -72,17 +72,10 @@ interface HoverableImageProps { isSelected: boolean; } -const memoEqualityCheck = ( - prev: HoverableImageProps, - next: HoverableImageProps -) => - prev.image.image_name === next.image.image_name && - prev.isSelected === next.isSelected; - /** * Gallery image component with delete/use all/use seed buttons on hover. */ -const HoverableImage = memo((props: HoverableImageProps) => { +const HoverableImage = (props: HoverableImageProps) => { const dispatch = useAppDispatch(); const { activeTabName, @@ -121,7 +114,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { const handleMouseOut = () => setIsHovered(false); const handleSelectImage = useCallback(() => { - dispatch(imageSelected(image)); + dispatch(imageSelected(image.image_name)); }, [image, dispatch]); // Recall parameters handlers @@ -260,7 +253,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { )} } onClickCapture={handleAddToBoard}> - Add to Board + {image.board_id ? 'Change Board' : 'Add to Board'} { ); -}, memoEqualityCheck); +}; -HoverableImage.displayName = 'HoverableImage'; - -export default HoverableImage; +export default memo(HoverableImage); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index adb7791afb..48bd2bde74 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -201,12 +201,6 @@ const ImageGalleryContent = () => { dispatch(setGalleryView('boards')); }, [dispatch]); - const [newBoardName, setNewBoardName] = useState(''); - - const handleCreateNewBoard = () => { - dispatch(boardCreated({ requestBody: newBoardName })); - }; - return ( { )} @@ -344,9 +336,7 @@ const ImageGalleryContent = () => { )} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx index 892516a3cc..e5cb4cf4a8 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx @@ -93,19 +93,11 @@ type ImageMetadataViewerProps = { image: ImageDTO; }; -// TODO: I don't know if this is needed. -const memoEqualityCheck = ( - prev: ImageMetadataViewerProps, - next: ImageMetadataViewerProps -) => prev.image.image_name === next.image.image_name; - -// TODO: Show more interesting information in this component. - /** * Image metadata viewer overlays currently selected image and provides * access to any of its metadata for use in processing. */ -const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => { +const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => { const dispatch = useAppDispatch(); const { recallBothPrompts, @@ -333,8 +325,6 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => { ); -}, memoEqualityCheck); +}; -ImageMetadataViewer.displayName = 'ImageMetadataViewer'; - -export default ImageMetadataViewer; +export default memo(ImageMetadataViewer); diff --git a/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx index 82e7a0d623..b1f06ad433 100644 --- a/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx @@ -42,7 +42,7 @@ export const nextPrevImageButtonsSelector = createSelector( } const currentImageIndex = filteredImageIds.findIndex( - (i) => i === selectedImage.image_name + (i) => i === selectedImage ); const nextImageIndex = clamp( @@ -71,6 +71,8 @@ export const nextPrevImageButtonsSelector = createSelector( !isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1, nextImage, prevImage, + nextImageId, + prevImageId, }; }, { @@ -84,7 +86,7 @@ const NextPrevImageButtons = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const { isOnFirstImage, isOnLastImage, nextImage, prevImage } = + const { isOnFirstImage, isOnLastImage, nextImageId, prevImageId } = useAppSelector(nextPrevImageButtonsSelector); const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] = @@ -99,19 +101,19 @@ const NextPrevImageButtons = () => { }, []); const handlePrevImage = useCallback(() => { - dispatch(imageSelected(prevImage)); - }, [dispatch, prevImage]); + dispatch(imageSelected(prevImageId)); + }, [dispatch, prevImageId]); const handleNextImage = useCallback(() => { - dispatch(imageSelected(nextImage)); - }, [dispatch, nextImage]); + dispatch(imageSelected(nextImageId)); + }, [dispatch, nextImageId]); useHotkeys( 'left', () => { handlePrevImage(); }, - [prevImage] + [prevImageId] ); useHotkeys( @@ -119,7 +121,7 @@ const NextPrevImageButtons = () => { () => { handleNextImage(); }, - [nextImage] + [nextImageId] ); return ( diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index a8237a711d..b07ab487ae 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -7,7 +7,7 @@ import { imageUrlsReceived } from 'services/thunks/image'; type GalleryImageObjectFitType = 'contain' | 'cover'; export interface GalleryState { - selectedImage?: ImageDTO; + selectedImage?: string; galleryImageMinimumWidth: number; galleryImageObjectFit: GalleryImageObjectFitType; shouldAutoSwitchToNewImages: boolean; @@ -27,7 +27,7 @@ export const gallerySlice = createSlice({ name: 'gallery', initialState: initialGalleryState, reducers: { - imageSelected: (state, action: PayloadAction) => { + imageSelected: (state, action: PayloadAction) => { state.selectedImage = action.payload; // TODO: if the user selects an image, disable the auto switch? // state.shouldAutoSwitchToNewImages = false; @@ -63,17 +63,17 @@ export const gallerySlice = createSlice({ state.shouldAutoSwitchToNewImages && action.payload.image_category === 'general' ) { - state.selectedImage = action.payload; + state.selectedImage = action.payload.image_name; } }); - builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - const { image_name, image_url, thumbnail_url } = action.payload; + // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { + // const { image_name, image_url, thumbnail_url } = action.payload; - if (state.selectedImage?.image_name === image_name) { - state.selectedImage.image_url = image_url; - state.selectedImage.thumbnail_url = thumbnail_url; - } - }); + // if (state.selectedImage?.image_name === image_name) { + // state.selectedImage.image_url = image_url; + // state.selectedImage.thumbnail_url = thumbnail_url; + // } + // }); }, }); diff --git a/invokeai/frontend/web/src/services/api/models/BoardDTO.ts b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts index 1b72f452ac..ee3c29a797 100644 --- a/invokeai/frontend/web/src/services/api/models/BoardDTO.ts +++ b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts @@ -23,13 +23,9 @@ export type BoardDTO = { */ updated_at: string; /** - * The name of the cover image of the board. + * The name of the board's cover image. */ cover_image_name?: string; - /** - * The URL of the thumbnail of the board's cover image. - */ - cover_image_url?: string; /** * The number of images in the board. */ diff --git a/invokeai/frontend/web/src/services/api/services/BoardsService.ts b/invokeai/frontend/web/src/services/api/services/BoardsService.ts index 9108e3fd51..bda2b70e75 100644 --- a/invokeai/frontend/web/src/services/api/services/BoardsService.ts +++ b/invokeai/frontend/web/src/services/api/services/BoardsService.ts @@ -17,13 +17,18 @@ export class BoardsService { /** * List Boards * Gets a list of boards - * @returns OffsetPaginatedResults_BoardDTO_ Successful Response + * @returns any Successful Response * @throws ApiError */ public static listBoards({ + all, offset, - limit = 10, + limit, }: { + /** + * Whether to list all boards + */ + all?: boolean, /** * The page offset */ @@ -32,11 +37,12 @@ export class BoardsService { * The number of boards per page */ limit?: number, - }): CancelablePromise { + }): CancelablePromise<(OffsetPaginatedResults_BoardDTO_ | Array)> { return __request(OpenAPI, { method: 'GET', url: '/api/v1/boards/', query: { + 'all': all, 'offset': offset, 'limit': limit, }, @@ -53,15 +59,19 @@ export class BoardsService { * @throws ApiError */ public static createBoard({ - requestBody, + boardName, }: { - requestBody: string, + /** + * The name of the board to create + */ + boardName: string, }): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/boards/', - body: requestBody, - mediaType: 'application/json', + query: { + 'board_name': boardName, + }, errors: { 422: `Validation Error`, }, diff --git a/invokeai/frontend/web/src/services/apiSlice.ts b/invokeai/frontend/web/src/services/apiSlice.ts new file mode 100644 index 0000000000..09eb061e29 --- /dev/null +++ b/invokeai/frontend/web/src/services/apiSlice.ts @@ -0,0 +1,144 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { BoardDTO } from './api/models/BoardDTO'; +import { OffsetPaginatedResults_BoardDTO_ } from './api/models/OffsetPaginatedResults_BoardDTO_'; +import { BoardChanges } from './api/models/BoardChanges'; +import { OffsetPaginatedResults_ImageDTO_ } from './api/models/OffsetPaginatedResults_ImageDTO_'; + +type ListBoardsArg = { offset: number; limit: number }; +type UpdateBoardArg = { board_id: string; changes: BoardChanges }; +type AddImageToBoardArg = { board_id: string; image_name: string }; +type RemoveImageFromBoardArg = { board_id: string; image_name: string }; +type ListBoardImagesArg = { board_id: string; offset: number; limit: number }; + +export const api = createApi({ + baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5173/api/v1/' }), + reducerPath: 'api', + tagTypes: ['Board'], + endpoints: (build) => ({ + /** + * Boards Queries + */ + listBoards: build.query({ + query: (arg) => ({ url: 'boards/', params: arg }), + providesTags: (result, error, arg) => { + if (!result) { + // Provide the broad 'Board' tag until there is a response + return ['Board']; + } + + // Provide the broad 'Board' tab, and individual tags for each board + return [ + ...result.items.map(({ board_id }) => ({ + type: 'Board' as const, + id: board_id, + })), + 'Board', + ]; + }, + }), + + listAllBoards: build.query, void>({ + query: () => ({ + url: 'boards/', + params: { all: true }, + }), + providesTags: (result, error, arg) => { + if (!result) { + // Provide the broad 'Board' tag until there is a response + return ['Board']; + } + + // Provide the broad 'Board' tab, and individual tags for each board + return [ + ...result.map(({ board_id }) => ({ + type: 'Board' as const, + id: board_id, + })), + 'Board', + ]; + }, + }), + + /** + * Boards Mutations + */ + + createBoard: build.mutation({ + query: (board_name) => ({ + url: `boards/`, + method: 'POST', + params: { board_name }, + }), + invalidatesTags: ['Board'], + }), + + updateBoard: build.mutation({ + query: ({ board_id, changes }) => ({ + url: `boards/${board_id}`, + method: 'PATCH', + body: changes, + }), + invalidatesTags: (result, error, arg) => [ + { type: 'Board', id: arg.board_id }, + ], + }), + + deleteBoard: build.mutation({ + query: (board_id) => ({ url: `boards/${board_id}`, method: 'DELETE' }), + invalidatesTags: (result, error, arg) => [{ type: 'Board', id: arg }], + }), + + /** + * Board Images Queries + */ + + listBoardImages: build.query< + OffsetPaginatedResults_ImageDTO_, + ListBoardImagesArg + >({ + query: ({ board_id, offset, limit }) => ({ + url: `board_images/${board_id}`, + method: 'DELETE', + body: { offset, limit }, + }), + }), + + /** + * Board Images Mutations + */ + + addImageToBoard: build.mutation({ + query: ({ board_id, image_name }) => ({ + url: `board_images/`, + method: 'POST', + body: { board_id, image_name }, + }), + invalidatesTags: ['Board'], + // invalidatesTags: (result, error, arg) => [ + // { type: 'Board', id: arg.board_id }, + // ], + }), + + removeImageFromBoard: build.mutation({ + query: ({ board_id, image_name }) => ({ + url: `board_images/`, + method: 'DELETE', + body: { board_id, image_name }, + }), + invalidatesTags: (result, error, arg) => [ + { type: 'Board', id: arg.board_id }, + ], + }), + }), +}); + +export const { + useListBoardsQuery, + useListAllBoardsQuery, + useCreateBoardMutation, + useUpdateBoardMutation, + useDeleteBoardMutation, + useAddImageToBoardMutation, + useRemoveImageFromBoardMutation, + useListBoardImagesQuery, +} = api; diff --git a/invokeai/frontend/web/src/services/thunks/board.ts b/invokeai/frontend/web/src/services/thunks/board.ts index 4535081e47..03c59dba10 100644 --- a/invokeai/frontend/web/src/services/thunks/board.ts +++ b/invokeai/frontend/web/src/services/thunks/board.ts @@ -42,7 +42,7 @@ export const boardUpdated = createAppAsyncThunk( type ImageAddedToBoardArg = Parameters< (typeof BoardsService)['createBoardImage'] ->[0]; +>[0]['requestBody']; export const imageAddedToBoard = createAppAsyncThunk( 'api/imageAddedToBoard', From 8d3bec57d52cadf1c4037d788d2a1bdb2e13d982 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:48:59 +1000 Subject: [PATCH 053/110] feat(ui): store only image name in parameters Images that are used as parameters (e.g. init image, canvas images) are stored as full `ImageDTO` objects in state, separate from and duplicating any object representing those same objects in the `imagesSlice`. We cannot store only image names as parameters, then pull the full `ImageDTO` from `imagesSlice`, because if an image is not on a loaded page, it doesn't exist in `imagesSlice`. For example, if you scroll down a few pages in the gallery and send that image to canvas, on reloading the app, the canvas will be unable to load that image. We solved this temporarily by storing the full `ImageDTO` object wherever it was needed, but this is both inefficient and allows for stale `ImageDTO`s across the app. One other possible solution was to just fetch the `ImageDTO` for all images at startup, and insert them into the `imagesSlice`, but then we run into an issue where we are displaying images in the gallery totally out of context. For example, if an image from several pages into the gallery was sent to canvas, and the user refreshes, we'd display the first 20 images in gallery. Then to populate the canvas, we'd fetch that image we sent to canvas and add it to `imagesSlice`. Now we'd have 21 images in the gallery: 1 to 20 and whichever image we sent to canvas. Weird. Using `rtk-query` solves this by allowing us to very easily fetch individual images in the components that need them, and not directly interact with `imagesSlice`. This commit changes all references to images-as-parameters to store only the name of the image, and not the full `ImageDTO` object. Then, we use an `rtk-query` generated `useGetImageDTOQuery()` hook in each of those components to fetch the image. We can use cache invalidation when we mutate any image to trigger automated re-running of the query and all the images are automatically kept up to date. This also obviates the need for the convoluted URL fetching scheme for images that are used as parameters. The `imagesSlice` still need this handling unfortunately. --- .../src/app/contexts/DeleteImageContext.tsx | 10 +- .../listeners/controlNetImageProcessed.ts | 4 +- .../socketio/socketInvocationComplete.ts | 9 +- .../listeners/updateImageUrlsOnConnect.ts | 14 +- .../canvas/components/IAICanvasImage.tsx | 19 +- .../components/IAICanvasObjectRenderer.tsx | 9 +- .../components/IAICanvasStagingArea.tsx | 6 +- .../src/features/canvas/store/canvasSlice.ts | 38 +- .../src/features/canvas/store/canvasTypes.ts | 2 +- .../components/ControlNetImagePreview.tsx | 33 +- .../controlNet/store/controlNetSlice.ts | 40 +- .../gallery/components/Boards/BoardsList.tsx | 2 +- .../components/CurrentImagePreview.tsx | 15 +- .../fields/ImageInputFieldComponent.tsx | 17 +- .../web/src/features/nodes/types/types.ts | 2 +- .../nodes/util/addControlNetToLinearGraph.ts | 6 +- .../graphBuilders/buildImageToImageGraph.ts | 416 ++++++++++++++++++ .../nodeBuilders/buildImageToImageNode.ts | 2 +- .../ImageToImage/InitialImagePreview.tsx | 19 +- .../parameters/store/generationSlice.ts | 18 +- .../frontend/web/src/services/apiSlice.ts | 89 ++-- 21 files changed, 630 insertions(+), 140 deletions(-) create mode 100644 invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts diff --git a/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx index 8263b48114..50d80dcf28 100644 --- a/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx +++ b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx @@ -35,25 +35,23 @@ export const selectImageUsage = createSelector( (state: RootState, image_name?: string) => image_name, ], (generation, canvas, nodes, controlNet, image_name) => { - const isInitialImage = generation.initialImage?.image_name === image_name; + const isInitialImage = generation.initialImage === image_name; const isCanvasImage = canvas.layerState.objects.some( - (obj) => obj.kind === 'image' && obj.image.image_name === image_name + (obj) => obj.kind === 'image' && obj.imageName === image_name ); const isNodesImage = nodes.nodes.some((node) => { return some( node.data.inputs, - (input) => - input.type === 'image' && input.value?.image_name === image_name + (input) => input.type === 'image' && input.value === image_name ); }); const isControlNetImage = some( controlNet.controlNets, (c) => - c.controlImage?.image_name === image_name || - c.processedControlImage?.image_name === image_name + c.controlImage === image_name || c.processedControlImage === image_name ); const imageUsage: ImageUsage = { diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts index ce1b515b84..7ff9a5118c 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts @@ -34,7 +34,7 @@ export const addControlNetImageProcessedListener = () => { [controlNet.processorNode.id]: { ...controlNet.processorNode, is_intermediate: true, - image: pick(controlNet.controlImage, ['image_name']), + image: { image_name: controlNet.controlImage }, }, }, }; @@ -81,7 +81,7 @@ export const addControlNetImageProcessedListener = () => { dispatch( controlNetProcessedImageChanged({ controlNetId, - processedControlImage, + processedControlImage: processedControlImage.image_name, }) ); } diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts index 24e8eb312f..680f9c7041 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts @@ -10,6 +10,7 @@ import { sessionCanceled } from 'services/thunks/session'; import { isImageOutput } from 'services/types/guards'; import { progressImageSet } from 'features/system/store/systemSlice'; import { imageAddedToBoard } from '../../../../../../services/thunks/board'; +import { api } from 'services/apiSlice'; const moduleLog = log.child({ namespace: 'socketio' }); const nodeDenylist = ['dataURL_image']; @@ -42,11 +43,9 @@ export const addInvocationCompleteEventListener = () => { if (boardIdToAddTo) { dispatch( - imageAddedToBoard({ - requestBody: { - board_id: boardIdToAddTo, - image_name, - }, + api.endpoints.addImageToBoard.initiate({ + board_id: boardIdToAddTo, + image_name, }) ); } diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts index 7cb8012848..22182833b0 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts @@ -22,7 +22,7 @@ const selectAllUsedImages = createSelector( selectImagesEntities, ], (generation, canvas, nodes, controlNet, imageEntities) => { - const allUsedImages: ImageDTO[] = []; + const allUsedImages: string[] = []; if (generation.initialImage) { allUsedImages.push(generation.initialImage); @@ -30,30 +30,30 @@ const selectAllUsedImages = createSelector( canvas.layerState.objects.forEach((obj) => { if (obj.kind === 'image') { - allUsedImages.push(obj.image); + allUsedImages.push(obj.image.image_name); } }); nodes.nodes.forEach((node) => { forEach(node.data.inputs, (input) => { if (input.type === 'image' && input.value) { - allUsedImages.push(input.value); + allUsedImages.push(input.value.image_name); } }); }); forEach(controlNet.controlNets, (c) => { if (c.controlImage) { - allUsedImages.push(c.controlImage); + allUsedImages.push(c.controlImage.image_name); } if (c.processedControlImage) { - allUsedImages.push(c.processedControlImage); + allUsedImages.push(c.processedControlImage.image_name); } }); forEach(imageEntities, (image) => { if (image) { - allUsedImages.push(image); + allUsedImages.push(image.image_name); } }); @@ -80,7 +80,7 @@ export const addUpdateImageUrlsOnConnectListener = () => { `Fetching new image URLs for ${allUsedImages.length} images` ); - allUsedImages.forEach(({ image_name }) => { + allUsedImages.forEach((image_name) => { dispatch( imageUrlsReceived({ imageName: image_name, diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasImage.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasImage.tsx index b8757eff0c..c3132f0285 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasImage.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasImage.tsx @@ -1,14 +1,21 @@ -import { Image } from 'react-konva'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; +import { Image, Rect } from 'react-konva'; +import { useGetImageDTOQuery } from 'services/apiSlice'; import useImage from 'use-image'; +import { CanvasImage } from '../store/canvasTypes'; type IAICanvasImageProps = { - url: string; - x: number; - y: number; + canvasImage: CanvasImage; }; const IAICanvasImage = (props: IAICanvasImageProps) => { - const { url, x, y } = props; - const [image] = useImage(url, 'anonymous'); + const { width, height, x, y, imageName } = props.canvasImage; + const { data: imageDTO } = useGetImageDTOQuery(imageName ?? skipToken); + const [image] = useImage(imageDTO?.image_url ?? '', 'anonymous'); + + if (!imageDTO) { + return ; + } + return ; }; diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasObjectRenderer.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasObjectRenderer.tsx index ea04aa95c8..ec1e87cca7 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasObjectRenderer.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasObjectRenderer.tsx @@ -39,14 +39,7 @@ const IAICanvasObjectRenderer = () => { {objects.map((obj, i) => { if (isCanvasBaseImage(obj)) { - return ( - - ); + return ; } else if (isCanvasBaseLine(obj)) { const line = ( { return ( {shouldShowStagingImage && currentStagingAreaImage && ( - + )} {shouldShowStagingOutline && ( diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index b7092bf7e0..3e40c1211d 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -203,7 +203,7 @@ export const canvasSlice = createSlice({ y: 0, width: width, height: height, - image: image, + imageName: image.image_name, }, ], }; @@ -325,7 +325,7 @@ export const canvasSlice = createSlice({ kind: 'image', layer: 'base', ...state.layerState.stagingArea.boundingBox, - image, + imageName: image.image_name, }); state.layerState.stagingArea.selectedImageIndex = @@ -865,25 +865,25 @@ export const canvasSlice = createSlice({ state.doesCanvasNeedScaling = true; }); - builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - const { image_name, image_url, thumbnail_url } = action.payload; + // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { + // const { image_name, image_url, thumbnail_url } = action.payload; - state.layerState.objects.forEach((object) => { - if (object.kind === 'image') { - if (object.image.image_name === image_name) { - object.image.image_url = image_url; - object.image.thumbnail_url = thumbnail_url; - } - } - }); + // state.layerState.objects.forEach((object) => { + // if (object.kind === 'image') { + // if (object.image.image_name === image_name) { + // object.image.image_url = image_url; + // object.image.thumbnail_url = thumbnail_url; + // } + // } + // }); - state.layerState.stagingArea.images.forEach((stagedImage) => { - if (stagedImage.image.image_name === image_name) { - stagedImage.image.image_url = image_url; - stagedImage.image.thumbnail_url = thumbnail_url; - } - }); - }); + // state.layerState.stagingArea.images.forEach((stagedImage) => { + // if (stagedImage.image.image_name === image_name) { + // stagedImage.image.image_url = image_url; + // stagedImage.image.thumbnail_url = thumbnail_url; + // } + // }); + // }); }, }); diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts index ae78287a7b..9294e10d32 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts @@ -38,7 +38,7 @@ export type CanvasImage = { y: number; width: number; height: number; - image: ImageDTO; + imageName: string; }; export type CanvasMaskLine = { diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx index b8d8896dad..a121875f59 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx @@ -14,6 +14,8 @@ import { AnimatePresence, motion } from 'framer-motion'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; import IAIIconButton from 'common/components/IAIIconButton'; import { FaUndo } from 'react-icons/fa'; +import { useGetImageDTOQuery } from 'services/apiSlice'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; const selector = createSelector( controlNetSelector, @@ -31,24 +33,45 @@ type Props = { const ControlNetImagePreview = (props: Props) => { const { imageSx } = props; - const { controlNetId, controlImage, processedControlImage, processorType } = - props.controlNet; + const { + controlNetId, + controlImage: controlImageName, + processedControlImage: processedControlImageName, + processorType, + } = props.controlNet; const dispatch = useAppDispatch(); const { pendingControlImages } = useAppSelector(selector); const [isMouseOverImage, setIsMouseOverImage] = useState(false); + const { + data: controlImage, + isLoading: isLoadingControlImage, + isError: isErrorControlImage, + isSuccess: isSuccessControlImage, + } = useGetImageDTOQuery(controlImageName ?? skipToken); + + const { + data: processedControlImage, + isLoading: isLoadingProcessedControlImage, + isError: isErrorProcessedControlImage, + isSuccess: isSuccessProcessedControlImage, + } = useGetImageDTOQuery(processedControlImageName ?? skipToken); + const handleDrop = useCallback( (droppedImage: ImageDTO) => { - if (controlImage?.image_name === droppedImage.image_name) { + if (controlImageName === droppedImage.image_name) { return; } setIsMouseOverImage(false); dispatch( - controlNetImageChanged({ controlNetId, controlImage: droppedImage }) + controlNetImageChanged({ + controlNetId, + controlImage: droppedImage.image_name, + }) ); }, - [controlImage, controlNetId, dispatch] + [controlImageName, controlNetId, dispatch] ); const handleResetControlImage = useCallback(() => { diff --git a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts index f1b62cd997..5a54bdcd74 100644 --- a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts +++ b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts @@ -39,8 +39,8 @@ export type ControlNetConfig = { weight: number; beginStepPct: number; endStepPct: number; - controlImage: ImageDTO | null; - processedControlImage: ImageDTO | null; + controlImage: string | null; + processedControlImage: string | null; processorType: ControlNetProcessorType; processorNode: RequiredControlNetProcessorNode; shouldAutoConfig: boolean; @@ -80,7 +80,7 @@ export const controlNetSlice = createSlice({ }, controlNetAddedFromImage: ( state, - action: PayloadAction<{ controlNetId: string; controlImage: ImageDTO }> + action: PayloadAction<{ controlNetId: string; controlImage: string }> ) => { const { controlNetId, controlImage } = action.payload; state.controlNets[controlNetId] = { @@ -108,7 +108,7 @@ export const controlNetSlice = createSlice({ state, action: PayloadAction<{ controlNetId: string; - controlImage: ImageDTO | null; + controlImage: string | null; }> ) => { const { controlNetId, controlImage } = action.payload; @@ -125,7 +125,7 @@ export const controlNetSlice = createSlice({ state, action: PayloadAction<{ controlNetId: string; - processedControlImage: ImageDTO | null; + processedControlImage: string | null; }> ) => { const { controlNetId, processedControlImage } = action.payload; @@ -260,30 +260,30 @@ export const controlNetSlice = createSlice({ // Preemptively remove the image from the gallery const { imageName } = action.meta.arg; forEach(state.controlNets, (c) => { - if (c.controlImage?.image_name === imageName) { + if (c.controlImage === imageName) { c.controlImage = null; c.processedControlImage = null; } - if (c.processedControlImage?.image_name === imageName) { + if (c.processedControlImage === imageName) { c.processedControlImage = null; } }); }); - builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - const { image_name, image_url, thumbnail_url } = action.payload; + // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { + // const { image_name, image_url, thumbnail_url } = action.payload; - forEach(state.controlNets, (c) => { - if (c.controlImage?.image_name === image_name) { - c.controlImage.image_url = image_url; - c.controlImage.thumbnail_url = thumbnail_url; - } - if (c.processedControlImage?.image_name === image_name) { - c.processedControlImage.image_url = image_url; - c.processedControlImage.thumbnail_url = thumbnail_url; - } - }); - }); + // forEach(state.controlNets, (c) => { + // if (c.controlImage?.image_name === image_name) { + // c.controlImage.image_url = image_url; + // c.controlImage.thumbnail_url = thumbnail_url; + // } + // if (c.processedControlImage?.image_name === image_name) { + // c.processedControlImage.image_url = image_url; + // c.processedControlImage.thumbnail_url = thumbnail_url; + // } + // }); + // }); builder.addCase(appSocketInvocationError, (state, action) => { state.pendingControlImages = []; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index be849e625e..5854c3fe7c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -50,7 +50,7 @@ const BoardsList = () => { ? data?.items.filter((board) => board.board_name.toLowerCase().includes(searchText.toLowerCase()) ) - : data.items; + : data?.items; const [searchMode, setSearchMode] = useState(false); diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index 649cae7682..bff32f1d78 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -17,6 +17,8 @@ import { ImageDTO } from 'services/api'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; import { RootState } from 'app/store/store'; import { selectImagesById } from '../store/imagesSlice'; +import { useGetImageDTOQuery } from 'services/apiSlice'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; export const imagesSelector = createSelector( [uiSelector, gallerySelector, systemSelector], @@ -53,9 +55,16 @@ const CurrentImagePreview = () => { shouldAntialiasProgressImage, } = useAppSelector(imagesSelector); - const image = useAppSelector((state: RootState) => - selectImagesById(state, selectedImage ?? '') - ); + // const image = useAppSelector((state: RootState) => + // selectImagesById(state, selectedImage ?? '') + // ); + + const { + data: image, + isLoading, + isError, + isSuccess, + } = useGetImageDTOQuery(selectedImage ?? skipToken); const dispatch = useAppDispatch(); diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx index dc4590e6ca..c5a3a1970b 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx @@ -11,6 +11,8 @@ import { FieldComponentProps } from './types'; import IAIDndImage from 'common/components/IAIDndImage'; import { ImageDTO } from 'services/api'; import { Flex } from '@chakra-ui/react'; +import { useGetImageDTOQuery } from 'services/apiSlice'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; const ImageInputFieldComponent = ( props: FieldComponentProps @@ -19,9 +21,16 @@ const ImageInputFieldComponent = ( const dispatch = useAppDispatch(); + const { + data: image, + isLoading, + isError, + isSuccess, + } = useGetImageDTOQuery(field.value ?? skipToken); + const handleDrop = useCallback( (droppedImage: ImageDTO) => { - if (field.value?.image_name === droppedImage.image_name) { + if (field.value === droppedImage.image_name) { return; } @@ -29,11 +38,11 @@ const ImageInputFieldComponent = ( fieldValueChanged({ nodeId, fieldName: field.name, - value: droppedImage, + value: droppedImage.image_name, }) ); }, - [dispatch, field.name, field.value?.image_name, nodeId] + [dispatch, field.name, field.value, nodeId] ); const handleReset = useCallback(() => { @@ -56,7 +65,7 @@ const ImageInputFieldComponent = ( }} > { + const { + positivePrompt, + negativePrompt, + model, + cfgScale: cfg_scale, + scheduler, + steps, + initialImage, + img2imgStrength: strength, + shouldFitToWidthHeight, + width, + height, + iterations, + seed, + shouldRandomizeSeed, + } = state.generation; + + if (!initialImage) { + moduleLog.error('No initial image found in state'); + throw new Error('No initial image found in state'); + } + + const graph: NonNullableGraph = { + nodes: {}, + edges: [], + }; + + // Create the positive conditioning (prompt) node + const positiveConditioningNode: CompelInvocation = { + id: POSITIVE_CONDITIONING, + type: 'compel', + prompt: positivePrompt, + model, + }; + + // Negative conditioning + const negativeConditioningNode: CompelInvocation = { + id: NEGATIVE_CONDITIONING, + type: 'compel', + prompt: negativePrompt, + model, + }; + + // This will encode the raster image to latents - but it may get its `image` from a resize node, + // so we do not set its `image` property yet + const imageToLatentsNode: ImageToLatentsInvocation = { + id: IMAGE_TO_LATENTS, + type: 'i2l', + model, + }; + + // This does the actual img2img inference + const latentsToLatentsNode: LatentsToLatentsInvocation = { + id: LATENTS_TO_LATENTS, + type: 'l2l', + cfg_scale, + model, + scheduler, + steps, + strength, + }; + + // Finally we decode the latents back to an image + const latentsToImageNode: LatentsToImageInvocation = { + id: LATENTS_TO_IMAGE, + type: 'l2i', + model, + }; + + // Add all those nodes to the graph + graph.nodes[POSITIVE_CONDITIONING] = positiveConditioningNode; + graph.nodes[NEGATIVE_CONDITIONING] = negativeConditioningNode; + graph.nodes[IMAGE_TO_LATENTS] = imageToLatentsNode; + graph.nodes[LATENTS_TO_LATENTS] = latentsToLatentsNode; + graph.nodes[LATENTS_TO_IMAGE] = latentsToImageNode; + + // Connect the prompt nodes to the imageToLatents node + graph.edges.push({ + source: { node_id: POSITIVE_CONDITIONING, field: 'conditioning' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'positive_conditioning', + }, + }); + graph.edges.push({ + source: { node_id: NEGATIVE_CONDITIONING, field: 'conditioning' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'negative_conditioning', + }, + }); + + // Connect the image-encoding node + graph.edges.push({ + source: { node_id: IMAGE_TO_LATENTS, field: 'latents' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'latents', + }, + }); + + // Connect the image-decoding node + graph.edges.push({ + source: { node_id: LATENTS_TO_LATENTS, field: 'latents' }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'latents', + }, + }); + + /** + * Now we need to handle iterations and random seeds. There are four possible scenarios: + * - Single iteration, explicit seed + * - Single iteration, random seed + * - Multiple iterations, explicit seed + * - Multiple iterations, random seed + * + * They all have different graphs and connections. + */ + + // Single iteration, explicit seed + if (!shouldRandomizeSeed && iterations === 1) { + // Noise node using the explicit seed + const noiseNode: NoiseInvocation = { + id: NOISE, + type: 'noise', + seed: seed, + }; + + graph.nodes[NOISE] = noiseNode; + + // Connect noise to l2l + graph.edges.push({ + source: { node_id: NOISE, field: 'noise' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'noise', + }, + }); + } + + // Single iteration, random seed + if (shouldRandomizeSeed && iterations === 1) { + // Random int node to generate the seed + const randomIntNode: RandomIntInvocation = { + id: RANDOM_INT, + type: 'rand_int', + }; + + // Noise node without any seed + const noiseNode: NoiseInvocation = { + id: NOISE, + type: 'noise', + }; + + graph.nodes[RANDOM_INT] = randomIntNode; + graph.nodes[NOISE] = noiseNode; + + // Connect random int to the seed of the noise node + graph.edges.push({ + source: { node_id: RANDOM_INT, field: 'a' }, + destination: { + node_id: NOISE, + field: 'seed', + }, + }); + + // Connect noise to l2l + graph.edges.push({ + source: { node_id: NOISE, field: 'noise' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'noise', + }, + }); + } + + // Multiple iterations, explicit seed + if (!shouldRandomizeSeed && iterations > 1) { + // Range of size node to generate `iterations` count of seeds - range of size generates a collection + // of ints from `start` to `start + size`. The `start` is the seed, and the `size` is the number of + // iterations. + const rangeOfSizeNode: RangeOfSizeInvocation = { + id: RANGE_OF_SIZE, + type: 'range_of_size', + start: seed, + size: iterations, + }; + + // Iterate node to iterate over the seeds generated by the range of size node + const iterateNode: IterateInvocation = { + id: ITERATE, + type: 'iterate', + }; + + // Noise node without any seed + const noiseNode: NoiseInvocation = { + id: NOISE, + type: 'noise', + }; + + // Adding to the graph + graph.nodes[RANGE_OF_SIZE] = rangeOfSizeNode; + graph.nodes[ITERATE] = iterateNode; + graph.nodes[NOISE] = noiseNode; + + // Connect range of size to iterate + graph.edges.push({ + source: { node_id: RANGE_OF_SIZE, field: 'collection' }, + destination: { + node_id: ITERATE, + field: 'collection', + }, + }); + + // Connect iterate to noise + graph.edges.push({ + source: { + node_id: ITERATE, + field: 'item', + }, + destination: { + node_id: NOISE, + field: 'seed', + }, + }); + + // Connect noise to l2l + graph.edges.push({ + source: { node_id: NOISE, field: 'noise' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'noise', + }, + }); + } + + // Multiple iterations, random seed + if (shouldRandomizeSeed && iterations > 1) { + // Random int node to generate the seed + const randomIntNode: RandomIntInvocation = { + id: RANDOM_INT, + type: 'rand_int', + }; + + // Range of size node to generate `iterations` count of seeds - range of size generates a collection + const rangeOfSizeNode: RangeOfSizeInvocation = { + id: RANGE_OF_SIZE, + type: 'range_of_size', + size: iterations, + }; + + // Iterate node to iterate over the seeds generated by the range of size node + const iterateNode: IterateInvocation = { + id: ITERATE, + type: 'iterate', + }; + + // Noise node without any seed + const noiseNode: NoiseInvocation = { + id: NOISE, + type: 'noise', + width, + height, + }; + + // Adding to the graph + graph.nodes[RANDOM_INT] = randomIntNode; + graph.nodes[RANGE_OF_SIZE] = rangeOfSizeNode; + graph.nodes[ITERATE] = iterateNode; + graph.nodes[NOISE] = noiseNode; + + // Connect random int to the start of the range of size so the range starts on the random first seed + graph.edges.push({ + source: { node_id: RANDOM_INT, field: 'a' }, + destination: { node_id: RANGE_OF_SIZE, field: 'start' }, + }); + + // Connect range of size to iterate + graph.edges.push({ + source: { node_id: RANGE_OF_SIZE, field: 'collection' }, + destination: { + node_id: ITERATE, + field: 'collection', + }, + }); + + // Connect iterate to noise + graph.edges.push({ + source: { + node_id: ITERATE, + field: 'item', + }, + destination: { + node_id: NOISE, + field: 'seed', + }, + }); + + // Connect noise to l2l + graph.edges.push({ + source: { node_id: NOISE, field: 'noise' }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'noise', + }, + }); + } + + if ( + shouldFitToWidthHeight && + (initialImage.width !== width || initialImage.height !== height) + ) { + // The init image needs to be resized to the specified width and height before being passed to `IMAGE_TO_LATENTS` + + // Create a resize node, explicitly setting its image + const resizeNode: ImageResizeInvocation = { + id: RESIZE, + type: 'img_resize', + image: { + image_name: initialImage, + }, + is_intermediate: true, + height, + width, + }; + + graph.nodes[RESIZE] = resizeNode; + + // The `RESIZE` node then passes its image to `IMAGE_TO_LATENTS` + graph.edges.push({ + source: { node_id: RESIZE, field: 'image' }, + destination: { + node_id: IMAGE_TO_LATENTS, + field: 'image', + }, + }); + + // The `RESIZE` node also passes its width and height to `NOISE` + graph.edges.push({ + source: { node_id: RESIZE, field: 'width' }, + destination: { + node_id: NOISE, + field: 'width', + }, + }); + + graph.edges.push({ + source: { node_id: RESIZE, field: 'height' }, + destination: { + node_id: NOISE, + field: 'height', + }, + }); + } else { + // We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly + set(graph.nodes[IMAGE_TO_LATENTS], 'image', { + image_name: initialImage, + }); + + // Pass the image's dimensions to the `NOISE` node + graph.edges.push({ + source: { node_id: IMAGE_TO_LATENTS, field: 'width' }, + destination: { + node_id: NOISE, + field: 'width', + }, + }); + graph.edges.push({ + source: { node_id: IMAGE_TO_LATENTS, field: 'height' }, + destination: { + node_id: NOISE, + field: 'height', + }, + }); + } + + addControlNetToLinearGraph(graph, LATENTS_TO_LATENTS, state); + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts index e29b46af70..cc88328729 100644 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts @@ -57,7 +57,7 @@ export const buildImg2ImgNode = ( } imageToImageNode.image = { - image_name: initialImage.image_name, + image_name: initialImage, }; } diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx index fa415074e6..d1f473b833 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx @@ -11,6 +11,8 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAIDndImage from 'common/components/IAIDndImage'; import { ImageDTO } from 'services/api'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { useGetImageDTOQuery } from 'services/apiSlice'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; const selector = createSelector( [generationSelector], @@ -27,14 +29,21 @@ const InitialImagePreview = () => { const { initialImage } = useAppSelector(selector); const dispatch = useAppDispatch(); + const { + data: image, + isLoading, + isError, + isSuccess, + } = useGetImageDTOQuery(initialImage ?? skipToken); + const handleDrop = useCallback( - (droppedImage: ImageDTO) => { - if (droppedImage.image_name === initialImage?.image_name) { + ({ image_name }: ImageDTO) => { + if (image_name === initialImage) { return; } - dispatch(initialImageChanged(droppedImage)); + dispatch(initialImageChanged(image_name)); }, - [dispatch, initialImage?.image_name] + [dispatch, initialImage] ); const handleReset = useCallback(() => { @@ -53,7 +62,7 @@ const InitialImagePreview = () => { }} > } diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 961ea1b8af..001fc35138 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -24,7 +24,7 @@ export interface GenerationState { height: HeightParam; img2imgStrength: StrengthParam; infillMethod: string; - initialImage?: ImageDTO; + initialImage?: string; iterations: number; perlin: number; positivePrompt: PositivePromptParam; @@ -211,7 +211,7 @@ export const generationSlice = createSlice({ setShouldUseNoiseSettings: (state, action: PayloadAction) => { state.shouldUseNoiseSettings = action.payload; }, - initialImageChanged: (state, action: PayloadAction) => { + initialImageChanged: (state, action: PayloadAction) => { state.initialImage = action.payload; }, modelSelected: (state, action: PayloadAction) => { @@ -233,14 +233,14 @@ export const generationSlice = createSlice({ } }); - builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - const { image_name, image_url, thumbnail_url } = action.payload; + // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { + // const { image_name, image_url, thumbnail_url } = action.payload; - if (state.initialImage?.image_name === image_name) { - state.initialImage.image_url = image_url; - state.initialImage.thumbnail_url = thumbnail_url; - } - }); + // if (state.initialImage?.image_name === image_name) { + // state.initialImage.image_url = image_url; + // state.initialImage.thumbnail_url = thumbnail_url; + // } + // }); }, }); diff --git a/invokeai/frontend/web/src/services/apiSlice.ts b/invokeai/frontend/web/src/services/apiSlice.ts index 09eb061e29..9a1521ce5a 100644 --- a/invokeai/frontend/web/src/services/apiSlice.ts +++ b/invokeai/frontend/web/src/services/apiSlice.ts @@ -1,8 +1,18 @@ -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { + TagDescription, + createApi, + fetchBaseQuery, +} from '@reduxjs/toolkit/query/react'; import { BoardDTO } from './api/models/BoardDTO'; import { OffsetPaginatedResults_BoardDTO_ } from './api/models/OffsetPaginatedResults_BoardDTO_'; import { BoardChanges } from './api/models/BoardChanges'; import { OffsetPaginatedResults_ImageDTO_ } from './api/models/OffsetPaginatedResults_ImageDTO_'; +import { ImageDTO } from './api/models/ImageDTO'; +import { + FullTagDescription, + TagTypesFrom, + TagTypesFromApi, +} from '@reduxjs/toolkit/dist/query/endpointDefinitions'; type ListBoardsArg = { offset: number; limit: number }; type UpdateBoardArg = { board_id: string; changes: BoardChanges }; @@ -10,10 +20,15 @@ type AddImageToBoardArg = { board_id: string; image_name: string }; type RemoveImageFromBoardArg = { board_id: string; image_name: string }; type ListBoardImagesArg = { board_id: string; offset: number; limit: number }; +const tagTypes = ['Board', 'Image']; +type ApiFullTagDescription = FullTagDescription<(typeof tagTypes)[number]>; + +const LIST = 'LIST'; + export const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5173/api/v1/' }), reducerPath: 'api', - tagTypes: ['Board'], + tagTypes, endpoints: (build) => ({ /** * Boards Queries @@ -21,19 +36,20 @@ export const api = createApi({ listBoards: build.query({ query: (arg) => ({ url: 'boards/', params: arg }), providesTags: (result, error, arg) => { - if (!result) { - // Provide the broad 'Board' tag until there is a response - return ['Board']; + // any list of boards + const tags: ApiFullTagDescription[] = [{ id: 'Board', type: LIST }]; + + if (result) { + // and individual tags for each board + tags.push( + ...result.items.map(({ board_id }) => ({ + type: 'Board' as const, + id: board_id, + })) + ); } - // Provide the broad 'Board' tab, and individual tags for each board - return [ - ...result.items.map(({ board_id }) => ({ - type: 'Board' as const, - id: board_id, - })), - 'Board', - ]; + return tags; }, }), @@ -43,19 +59,20 @@ export const api = createApi({ params: { all: true }, }), providesTags: (result, error, arg) => { - if (!result) { - // Provide the broad 'Board' tag until there is a response - return ['Board']; + // any list of boards + const tags: ApiFullTagDescription[] = [{ id: 'Board', type: LIST }]; + + if (result) { + // and individual tags for each board + tags.push( + ...result.map(({ board_id }) => ({ + type: 'Board' as const, + id: board_id, + })) + ); } - // Provide the broad 'Board' tab, and individual tags for each board - return [ - ...result.map(({ board_id }) => ({ - type: 'Board' as const, - id: board_id, - })), - 'Board', - ]; + return tags; }, }), @@ -113,10 +130,10 @@ export const api = createApi({ method: 'POST', body: { board_id, image_name }, }), - invalidatesTags: ['Board'], - // invalidatesTags: (result, error, arg) => [ - // { type: 'Board', id: arg.board_id }, - // ], + invalidatesTags: (result, error, arg) => [ + { type: 'Board', id: arg.board_id }, + { type: 'Image', id: arg.image_name }, + ], }), removeImageFromBoard: build.mutation({ @@ -127,8 +144,23 @@ export const api = createApi({ }), invalidatesTags: (result, error, arg) => [ { type: 'Board', id: arg.board_id }, + { type: 'Image', id: arg.image_name }, ], }), + + /** + * Image Queries + */ + getImageDTO: build.query({ + query: (image_name) => ({ url: `images/${image_name}/metadata` }), + providesTags: (result, error, arg) => { + const tags: ApiFullTagDescription[] = [{ type: 'Image', id: arg }]; + if (result?.board_id) { + tags.push({ type: 'Board', id: result.board_id }); + } + return tags; + }, + }), }), }); @@ -141,4 +173,5 @@ export const { useAddImageToBoardMutation, useRemoveImageFromBoardMutation, useListBoardImagesQuery, + useGetImageDTOQuery, } = api; From 3e0ee838cff792def0134c879222e40b1ec3a917 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:24:30 +1000 Subject: [PATCH 054/110] fix(ui): add initial image dimensions to state We need to access the initial image dimensions during the creation of the `ImageToImage` graph to determine if we need to resize the image. Because the `initialImage` is now just an image name, we need to either store (easy) or dynamically retrieve its dimensions during graph creation (a bit less easy). Took the easiest path. May need to revise this in the future. --- .../web/src/app/contexts/DeleteImageContext.tsx | 2 +- .../listenerMiddleware/listeners/imageUploaded.ts | 7 ++++++- .../listeners/updateImageUrlsOnConnect.ts | 10 +++++----- .../nodes/util/graphBuilders/buildImageToImageGraph.ts | 4 ++-- .../nodes/util/nodeBuilders/buildImageToImageNode.ts | 2 +- .../Parameters/ImageToImage/InitialImagePreview.tsx | 8 ++++---- .../src/features/parameters/store/generationSlice.ts | 7 ++++--- 7 files changed, 23 insertions(+), 17 deletions(-) diff --git a/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx index 50d80dcf28..d01298944b 100644 --- a/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx +++ b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx @@ -35,7 +35,7 @@ export const selectImageUsage = createSelector( (state: RootState, image_name?: string) => image_name, ], (generation, canvas, nodes, controlNet, image_name) => { - const isInitialImage = generation.initialImage === image_name; + const isInitialImage = generation.initialImage?.imageName === image_name; const isCanvasImage = canvas.layerState.objects.some( (obj) => obj.kind === 'image' && obj.imageName === image_name diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts index 40ed062353..fc44d206c8 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -46,7 +46,12 @@ export const addImageUploadedFulfilledListener = () => { if (postUploadAction?.type === 'SET_CONTROLNET_IMAGE') { const { controlNetId } = postUploadAction; - dispatch(controlNetImageChanged({ controlNetId, controlImage: image })); + dispatch( + controlNetImageChanged({ + controlNetId, + controlImage: image.image_name, + }) + ); return; } diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts index 22182833b0..b9ddcea4c3 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts @@ -25,29 +25,29 @@ const selectAllUsedImages = createSelector( const allUsedImages: string[] = []; if (generation.initialImage) { - allUsedImages.push(generation.initialImage); + allUsedImages.push(generation.initialImage.imageName); } canvas.layerState.objects.forEach((obj) => { if (obj.kind === 'image') { - allUsedImages.push(obj.image.image_name); + allUsedImages.push(obj.imageName); } }); nodes.nodes.forEach((node) => { forEach(node.data.inputs, (input) => { if (input.type === 'image' && input.value) { - allUsedImages.push(input.value.image_name); + allUsedImages.push(input.value); } }); }); forEach(controlNet.controlNets, (c) => { if (c.controlImage) { - allUsedImages.push(c.controlImage.image_name); + allUsedImages.push(c.controlImage); } if (c.processedControlImage) { - allUsedImages.push(c.processedControlImage.image_name); + allUsedImages.push(c.processedControlImage); } }); diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts index efd490c292..a9c5f85462 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts @@ -353,7 +353,7 @@ export const buildImageToImageGraph = (state: RootState): Graph => { id: RESIZE, type: 'img_resize', image: { - image_name: initialImage, + image_name: initialImage.imageName, }, is_intermediate: true, height, @@ -390,7 +390,7 @@ export const buildImageToImageGraph = (state: RootState): Graph => { } else { // We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly set(graph.nodes[IMAGE_TO_LATENTS], 'image', { - image_name: initialImage, + image_name: initialImage.imageName, }); // Pass the image's dimensions to the `NOISE` node diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts index cc88328729..6ebd014876 100644 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts @@ -57,7 +57,7 @@ export const buildImg2ImgNode = ( } imageToImageNode.image = { - image_name: initialImage, + image_name: initialImage.imageName, }; } diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx index d1f473b833..e4b3a9191e 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx @@ -34,14 +34,14 @@ const InitialImagePreview = () => { isLoading, isError, isSuccess, - } = useGetImageDTOQuery(initialImage ?? skipToken); + } = useGetImageDTOQuery(initialImage?.imageName ?? skipToken); const handleDrop = useCallback( - ({ image_name }: ImageDTO) => { - if (image_name === initialImage) { + (droppedImage: ImageDTO) => { + if (droppedImage.image_name === initialImage?.imageName) { return; } - dispatch(initialImageChanged(image_name)); + dispatch(initialImageChanged(droppedImage)); }, [dispatch, initialImage] ); diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 001fc35138..2facb65f04 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -24,7 +24,7 @@ export interface GenerationState { height: HeightParam; img2imgStrength: StrengthParam; infillMethod: string; - initialImage?: string; + initialImage?: { imageName: string; width: number; height: number }; iterations: number; perlin: number; positivePrompt: PositivePromptParam; @@ -211,8 +211,9 @@ export const generationSlice = createSlice({ setShouldUseNoiseSettings: (state, action: PayloadAction) => { state.shouldUseNoiseSettings = action.payload; }, - initialImageChanged: (state, action: PayloadAction) => { - state.initialImage = action.payload; + initialImageChanged: (state, action: PayloadAction) => { + const { image_name, width, height } = action.payload; + state.initialImage = { imageName: image_name, width, height }; }, modelSelected: (state, action: PayloadAction) => { state.model = action.payload; From be3bdae8477623f3af612fbd6569931659418411 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:54:57 +1000 Subject: [PATCH 055/110] fix: resolve rebase conflicts --- invokeai/app/api/dependencies.py | 1 - .../graphBuilders/buildImageToImageGraph.ts | 416 ------------------ .../buildLinearImageToImageGraph.ts | 4 +- 3 files changed, 2 insertions(+), 419 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts diff --git a/invokeai/app/api/dependencies.py b/invokeai/app/api/dependencies.py index 60f8c1b09d..efeb778922 100644 --- a/invokeai/app/api/dependencies.py +++ b/invokeai/app/api/dependencies.py @@ -29,7 +29,6 @@ from ..services.invoker import Invoker from ..services.processor import DefaultInvocationProcessor from ..services.sqlite import SqliteItemStorage from ..services.model_manager_service import ModelManagerService -from ..services.boards import SqliteBoardStorage from .events import FastAPIEventService diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts deleted file mode 100644 index a9c5f85462..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts +++ /dev/null @@ -1,416 +0,0 @@ -import { RootState } from 'app/store/store'; -import { - CompelInvocation, - Graph, - ImageResizeInvocation, - ImageToLatentsInvocation, - IterateInvocation, - LatentsToImageInvocation, - LatentsToLatentsInvocation, - NoiseInvocation, - RandomIntInvocation, - RangeOfSizeInvocation, -} from 'services/api'; -import { NonNullableGraph } from 'features/nodes/types/types'; -import { log } from 'app/logging/useLogger'; -import { set } from 'lodash-es'; -import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; - -const moduleLog = log.child({ namespace: 'nodes' }); - -const POSITIVE_CONDITIONING = 'positive_conditioning'; -const NEGATIVE_CONDITIONING = 'negative_conditioning'; -const IMAGE_TO_LATENTS = 'image_to_latents'; -const LATENTS_TO_LATENTS = 'latents_to_latents'; -const LATENTS_TO_IMAGE = 'latents_to_image'; -const RESIZE = 'resize_image'; -const NOISE = 'noise'; -const RANDOM_INT = 'rand_int'; -const RANGE_OF_SIZE = 'range_of_size'; -const ITERATE = 'iterate'; - -/** - * Builds the Image to Image tab graph. - */ -export const buildImageToImageGraph = (state: RootState): Graph => { - const { - positivePrompt, - negativePrompt, - model, - cfgScale: cfg_scale, - scheduler, - steps, - initialImage, - img2imgStrength: strength, - shouldFitToWidthHeight, - width, - height, - iterations, - seed, - shouldRandomizeSeed, - } = state.generation; - - if (!initialImage) { - moduleLog.error('No initial image found in state'); - throw new Error('No initial image found in state'); - } - - const graph: NonNullableGraph = { - nodes: {}, - edges: [], - }; - - // Create the positive conditioning (prompt) node - const positiveConditioningNode: CompelInvocation = { - id: POSITIVE_CONDITIONING, - type: 'compel', - prompt: positivePrompt, - model, - }; - - // Negative conditioning - const negativeConditioningNode: CompelInvocation = { - id: NEGATIVE_CONDITIONING, - type: 'compel', - prompt: negativePrompt, - model, - }; - - // This will encode the raster image to latents - but it may get its `image` from a resize node, - // so we do not set its `image` property yet - const imageToLatentsNode: ImageToLatentsInvocation = { - id: IMAGE_TO_LATENTS, - type: 'i2l', - model, - }; - - // This does the actual img2img inference - const latentsToLatentsNode: LatentsToLatentsInvocation = { - id: LATENTS_TO_LATENTS, - type: 'l2l', - cfg_scale, - model, - scheduler, - steps, - strength, - }; - - // Finally we decode the latents back to an image - const latentsToImageNode: LatentsToImageInvocation = { - id: LATENTS_TO_IMAGE, - type: 'l2i', - model, - }; - - // Add all those nodes to the graph - graph.nodes[POSITIVE_CONDITIONING] = positiveConditioningNode; - graph.nodes[NEGATIVE_CONDITIONING] = negativeConditioningNode; - graph.nodes[IMAGE_TO_LATENTS] = imageToLatentsNode; - graph.nodes[LATENTS_TO_LATENTS] = latentsToLatentsNode; - graph.nodes[LATENTS_TO_IMAGE] = latentsToImageNode; - - // Connect the prompt nodes to the imageToLatents node - graph.edges.push({ - source: { node_id: POSITIVE_CONDITIONING, field: 'conditioning' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'positive_conditioning', - }, - }); - graph.edges.push({ - source: { node_id: NEGATIVE_CONDITIONING, field: 'conditioning' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'negative_conditioning', - }, - }); - - // Connect the image-encoding node - graph.edges.push({ - source: { node_id: IMAGE_TO_LATENTS, field: 'latents' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'latents', - }, - }); - - // Connect the image-decoding node - graph.edges.push({ - source: { node_id: LATENTS_TO_LATENTS, field: 'latents' }, - destination: { - node_id: LATENTS_TO_IMAGE, - field: 'latents', - }, - }); - - /** - * Now we need to handle iterations and random seeds. There are four possible scenarios: - * - Single iteration, explicit seed - * - Single iteration, random seed - * - Multiple iterations, explicit seed - * - Multiple iterations, random seed - * - * They all have different graphs and connections. - */ - - // Single iteration, explicit seed - if (!shouldRandomizeSeed && iterations === 1) { - // Noise node using the explicit seed - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - seed: seed, - }; - - graph.nodes[NOISE] = noiseNode; - - // Connect noise to l2l - graph.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'noise', - }, - }); - } - - // Single iteration, random seed - if (shouldRandomizeSeed && iterations === 1) { - // Random int node to generate the seed - const randomIntNode: RandomIntInvocation = { - id: RANDOM_INT, - type: 'rand_int', - }; - - // Noise node without any seed - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - }; - - graph.nodes[RANDOM_INT] = randomIntNode; - graph.nodes[NOISE] = noiseNode; - - // Connect random int to the seed of the noise node - graph.edges.push({ - source: { node_id: RANDOM_INT, field: 'a' }, - destination: { - node_id: NOISE, - field: 'seed', - }, - }); - - // Connect noise to l2l - graph.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'noise', - }, - }); - } - - // Multiple iterations, explicit seed - if (!shouldRandomizeSeed && iterations > 1) { - // Range of size node to generate `iterations` count of seeds - range of size generates a collection - // of ints from `start` to `start + size`. The `start` is the seed, and the `size` is the number of - // iterations. - const rangeOfSizeNode: RangeOfSizeInvocation = { - id: RANGE_OF_SIZE, - type: 'range_of_size', - start: seed, - size: iterations, - }; - - // Iterate node to iterate over the seeds generated by the range of size node - const iterateNode: IterateInvocation = { - id: ITERATE, - type: 'iterate', - }; - - // Noise node without any seed - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - }; - - // Adding to the graph - graph.nodes[RANGE_OF_SIZE] = rangeOfSizeNode; - graph.nodes[ITERATE] = iterateNode; - graph.nodes[NOISE] = noiseNode; - - // Connect range of size to iterate - graph.edges.push({ - source: { node_id: RANGE_OF_SIZE, field: 'collection' }, - destination: { - node_id: ITERATE, - field: 'collection', - }, - }); - - // Connect iterate to noise - graph.edges.push({ - source: { - node_id: ITERATE, - field: 'item', - }, - destination: { - node_id: NOISE, - field: 'seed', - }, - }); - - // Connect noise to l2l - graph.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'noise', - }, - }); - } - - // Multiple iterations, random seed - if (shouldRandomizeSeed && iterations > 1) { - // Random int node to generate the seed - const randomIntNode: RandomIntInvocation = { - id: RANDOM_INT, - type: 'rand_int', - }; - - // Range of size node to generate `iterations` count of seeds - range of size generates a collection - const rangeOfSizeNode: RangeOfSizeInvocation = { - id: RANGE_OF_SIZE, - type: 'range_of_size', - size: iterations, - }; - - // Iterate node to iterate over the seeds generated by the range of size node - const iterateNode: IterateInvocation = { - id: ITERATE, - type: 'iterate', - }; - - // Noise node without any seed - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - width, - height, - }; - - // Adding to the graph - graph.nodes[RANDOM_INT] = randomIntNode; - graph.nodes[RANGE_OF_SIZE] = rangeOfSizeNode; - graph.nodes[ITERATE] = iterateNode; - graph.nodes[NOISE] = noiseNode; - - // Connect random int to the start of the range of size so the range starts on the random first seed - graph.edges.push({ - source: { node_id: RANDOM_INT, field: 'a' }, - destination: { node_id: RANGE_OF_SIZE, field: 'start' }, - }); - - // Connect range of size to iterate - graph.edges.push({ - source: { node_id: RANGE_OF_SIZE, field: 'collection' }, - destination: { - node_id: ITERATE, - field: 'collection', - }, - }); - - // Connect iterate to noise - graph.edges.push({ - source: { - node_id: ITERATE, - field: 'item', - }, - destination: { - node_id: NOISE, - field: 'seed', - }, - }); - - // Connect noise to l2l - graph.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'noise', - }, - }); - } - - if ( - shouldFitToWidthHeight && - (initialImage.width !== width || initialImage.height !== height) - ) { - // The init image needs to be resized to the specified width and height before being passed to `IMAGE_TO_LATENTS` - - // Create a resize node, explicitly setting its image - const resizeNode: ImageResizeInvocation = { - id: RESIZE, - type: 'img_resize', - image: { - image_name: initialImage.imageName, - }, - is_intermediate: true, - height, - width, - }; - - graph.nodes[RESIZE] = resizeNode; - - // The `RESIZE` node then passes its image to `IMAGE_TO_LATENTS` - graph.edges.push({ - source: { node_id: RESIZE, field: 'image' }, - destination: { - node_id: IMAGE_TO_LATENTS, - field: 'image', - }, - }); - - // The `RESIZE` node also passes its width and height to `NOISE` - graph.edges.push({ - source: { node_id: RESIZE, field: 'width' }, - destination: { - node_id: NOISE, - field: 'width', - }, - }); - - graph.edges.push({ - source: { node_id: RESIZE, field: 'height' }, - destination: { - node_id: NOISE, - field: 'height', - }, - }); - } else { - // We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly - set(graph.nodes[IMAGE_TO_LATENTS], 'image', { - image_name: initialImage.imageName, - }); - - // Pass the image's dimensions to the `NOISE` node - graph.edges.push({ - source: { node_id: IMAGE_TO_LATENTS, field: 'width' }, - destination: { - node_id: NOISE, - field: 'width', - }, - }); - graph.edges.push({ - source: { node_id: IMAGE_TO_LATENTS, field: 'height' }, - destination: { - node_id: NOISE, - field: 'height', - }, - }); - } - - addControlNetToLinearGraph(graph, LATENTS_TO_LATENTS, state); - - return graph; -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts index 1f2c8327e0..78a6623a16 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts @@ -274,7 +274,7 @@ export const buildLinearImageToImageGraph = ( id: RESIZE, type: 'img_resize', image: { - image_name: initialImage.image_name, + image_name: initialImage.imageName, }, is_intermediate: true, width, @@ -311,7 +311,7 @@ export const buildLinearImageToImageGraph = ( } else { // We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly set(graph.nodes[IMAGE_TO_LATENTS], 'image', { - image_name: initialImage.image_name, + image_name: initialImage.imageName, }); // Pass the image's dimensions to the `NOISE` node From ac477cf5d6e44e610f9c5e5a0952fe5cb8cd17b0 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 15:10:51 +1000 Subject: [PATCH 056/110] fix(ui): improve image deletion handling --- .../listeners/imageDeleted.ts | 10 +++---- .../components/Boards/HoverableBoard.tsx | 26 ++++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts index 9792137bbe..32bfcbba07 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts @@ -6,7 +6,6 @@ import { clamp } from 'lodash-es'; import { imageSelected } from 'features/gallery/store/gallerySlice'; import { imageRemoved, - selectImagesEntities, selectImagesIds, } from 'features/gallery/store/imagesSlice'; import { resetCanvas } from 'features/canvas/store/canvasSlice'; @@ -33,7 +32,6 @@ export const addRequestedImageDeletionListener = () => { if (selectedImage && selectedImage === image_name) { const ids = selectImagesIds(state); - const entities = selectImagesEntities(state); const deletedImageIndex = ids.findIndex( (result) => result.toString() === image_name @@ -49,10 +47,8 @@ export const addRequestedImageDeletionListener = () => { const newSelectedImageId = filteredIds[newSelectedImageIndex]; - const newSelectedImage = entities[newSelectedImageId]; - if (newSelectedImageId) { - dispatch(imageSelected(newSelectedImageId)); + dispatch(imageSelected(newSelectedImageId as string)); } else { dispatch(imageSelected()); } @@ -84,7 +80,9 @@ export const addRequestedImageDeletionListener = () => { // Wait for successful deletion, then trigger boards to re-fetch const wasImageDeleted = await condition( - (action) => action.meta.requestId === requestId, + (action): action is ReturnType => + imageDeleted.fulfilled.match(action) && + action.meta.requestId === requestId, 30000 ); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index 7ae864f55b..71e080ff17 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -27,9 +27,12 @@ import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoiz import { createSelector } from '@reduxjs/toolkit'; import { RootState } from '../../../../app/store/store'; import { + useAddImageToBoardMutation, useDeleteBoardMutation, + useGetImageDTOQuery, useUpdateBoardMutation, } from 'services/apiSlice'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; const coverImageSelector = (imageName: string | undefined) => createSelector( @@ -53,8 +56,9 @@ interface HoverableBoardProps { const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { const dispatch = useAppDispatch(); - const { coverImage } = useAppSelector( - coverImageSelector(board?.cover_image_name) + + const { data: coverImage } = useGetImageDTOQuery( + board.cover_image_name ?? skipToken ); const { board_name, board_id } = board; @@ -69,6 +73,9 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { const [deleteBoard, { isLoading: isDeleteBoardLoading }] = useDeleteBoardMutation(); + const [addImageToBoard, { isLoading: isAddImageToBoardLoading }] = + useAddImageToBoardMutation(); + const handleUpdateBoardName = (newBoardName: string) => { updateBoard({ board_id, changes: { board_name: newBoardName } }); }; @@ -82,16 +89,9 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { if (droppedImage.board_id === board_id) { return; } - dispatch( - imageAddedToBoard({ - requestBody: { - board_id, - image_name: droppedImage.image_name, - }, - }) - ); + addImageToBoard({ board_id, image_name: droppedImage.image_name }); }, - [board_id, dispatch] + [addImageToBoard, board_id] ); return ( @@ -141,7 +141,9 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { }} > } isUploadDisabled={true} From 2489d5459fbf8b8400f445b6315322ae08df9fee Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 15:51:14 +1000 Subject: [PATCH 057/110] chore(ui): regen api client --- .../listenerMiddleware/listeners/socketio/socketConnected.ts | 3 --- invokeai/frontend/web/src/services/api/index.ts | 1 + invokeai/frontend/web/src/services/api/models/Graph.ts | 2 +- .../web/src/services/api/models/GraphExecutionState.ts | 2 +- invokeai/frontend/web/src/services/api/models/ModelsList.ts | 2 +- .../frontend/web/src/services/api/services/SessionsService.ts | 4 ++-- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index 5bb02f60fa..3049d2c933 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -4,7 +4,6 @@ import { appSocketConnected, socketConnected } from 'services/events/actions'; import { receivedPageOfImages } from 'services/thunks/image'; import { receivedModels } from 'services/thunks/model'; import { receivedOpenAPISchema } from 'services/thunks/schema'; -import { receivedBoards } from '../../../../../../services/thunks/board'; const moduleLog = log.child({ namespace: 'socketio' }); @@ -20,8 +19,6 @@ export const addSocketConnectedEventListener = () => { const { disabledTabs } = config; - dispatch(receivedBoards()); - if (!images.ids.length) { dispatch(receivedPageOfImages()); } diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index ef62245553..acb7a411f7 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -7,6 +7,7 @@ export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; export type { AddInvocation } from './models/AddInvocation'; +export type { BaseModelType } from './models/BaseModelType'; export type { BoardChanges } from './models/BoardChanges'; export type { BoardDTO } from './models/BoardDTO'; export type { Body_create_board_image } from './models/Body_create_board_image'; diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index 0a724e2724..e148954f16 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -73,7 +73,7 @@ export type Graph = { /** * The nodes in this graph */ - nodes?: Record; + nodes?: Record; /** * The connections between nodes and their fields in this graph */ diff --git a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts index 156cdc6092..602e7a2ebc 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts @@ -48,7 +48,7 @@ export type GraphExecutionState = { /** * The results of node executions */ - results: Record; + results: Record; /** * Errors raised when executing nodes */ diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index a2d88d1967..40c2ebb1b9 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -12,6 +12,6 @@ import type { invokeai__backend__model_management__models__textual_inversion__Te import type { invokeai__backend__model_management__models__vae__VaeModel__Config } from './invokeai__backend__model_management__models__vae__VaeModel__Config'; export type ModelsList = { - models: Record>>; + models: Record>>; }; diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index 6e9ce83aaf..2e4a83b25f 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -175,7 +175,7 @@ export class SessionsService { * The id of the session */ sessionId: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -212,7 +212,7 @@ export class SessionsService { * The path to the node in the graph */ nodePath: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'PUT', From bd533426fc0d238a5afdec9a2e6aeb308f5b5d51 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:58:22 +1000 Subject: [PATCH 058/110] feat(ui): first pass at boards styling --- .../web/src/common/components/IAIDndImage.tsx | 4 +- .../common/components/IAIImageFallback.tsx | 46 +++++- .../components/ControlNetImagePreview.tsx | 4 +- .../components/Boards/AddBoardButton.tsx | 42 ++--- .../components/Boards/AllImagesBoard.tsx | 26 ++- .../gallery/components/Boards/BoardsList.tsx | 73 +++++---- .../components/Boards/HoverableBoard.tsx | 149 +++++++++--------- .../components/CurrentImagePreview.tsx | 4 +- .../components/ImageGalleryContent.tsx | 126 +++++++++------ .../components/SelectedItemOverlay.tsx | 26 +++ .../ImageToImage/InitialImagePreview.tsx | 4 +- 11 files changed, 300 insertions(+), 204 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/SelectedItemOverlay.tsx diff --git a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx index 669a68c88a..e54b4a8872 100644 --- a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx +++ b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx @@ -9,7 +9,7 @@ import { import { useDraggable, useDroppable } from '@dnd-kit/core'; import { useCombinedRefs } from '@dnd-kit/utilities'; import IAIIconButton from 'common/components/IAIIconButton'; -import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; import { AnimatePresence } from 'framer-motion'; import { ReactElement, SyntheticEvent, useCallback } from 'react'; @@ -53,7 +53,7 @@ const IAIDndImage = (props: IAIDndImageProps) => { isDropDisabled = false, isDragDisabled = false, isUploadDisabled = false, - fallback = , + fallback = , payloadImage, minSize = 24, postUploadAction, diff --git a/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx b/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx index 3d34fbca9e..03a00d5b1c 100644 --- a/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx +++ b/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx @@ -1,10 +1,20 @@ -import { Flex, FlexProps, Spinner, SpinnerProps } from '@chakra-ui/react'; +import { + As, + Flex, + FlexProps, + Icon, + IconProps, + Spinner, + SpinnerProps, +} from '@chakra-ui/react'; +import { ReactElement } from 'react'; +import { FaImage } from 'react-icons/fa'; type Props = FlexProps & { spinnerProps?: SpinnerProps; }; -export const IAIImageFallback = (props: Props) => { +export const IAIImageLoadingFallback = (props: Props) => { const { spinnerProps, ...rest } = props; const { sx, ...restFlexProps } = rest; return ( @@ -25,3 +35,35 @@ export const IAIImageFallback = (props: Props) => { ); }; + +type IAINoImageFallbackProps = { + flexProps?: FlexProps; + iconProps?: IconProps; + as?: As; +}; + +export const IAINoImageFallback = (props: IAINoImageFallbackProps) => { + const { sx: flexSx, ...restFlexProps } = props.flexProps ?? { sx: {} }; + const { sx: iconSx, ...restIconProps } = props.iconProps ?? { sx: {} }; + return ( + + + + ); +}; diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx index a121875f59..217caf9461 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx @@ -11,7 +11,7 @@ import IAIDndImage from 'common/components/IAIDndImage'; import { createSelector } from '@reduxjs/toolkit'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { AnimatePresence, motion } from 'framer-motion'; -import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; import IAIIconButton from 'common/components/IAIIconButton'; import { FaUndo } from 'react-icons/fa'; import { useGetImageDTOQuery } from 'services/apiSlice'; @@ -173,7 +173,7 @@ const ControlNetImagePreview = (props: Props) => { h: 'full', }} > - + )} {controlImage && ( diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx index 284e6558ac..632cebcb33 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx @@ -1,6 +1,5 @@ -import { Flex, Icon, Spinner, Text } from '@chakra-ui/react'; +import IAIButton from 'common/components/IAIButton'; import { useCallback } from 'react'; -import { FaPlus } from 'react-icons/fa'; import { useCreateBoardMutation } from 'services/apiSlice'; const DEFAULT_BOARD_NAME = 'My Board'; @@ -13,38 +12,15 @@ const AddBoardButton = () => { }, [createBoard]); return ( - - - {isLoading ? ( - - ) : ( - - )} - - New Board - + Add Board + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx index 51a7609678..51e95b64c4 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx @@ -2,12 +2,15 @@ import { Flex, Icon, Text } from '@chakra-ui/react'; import { FaImages } from 'react-icons/fa'; import { boardIdSelected } from '../../store/boardSlice'; import { useDispatch } from 'react-redux'; +import { IAINoImageFallback } from 'common/components/IAIImageFallback'; +import { AnimatePresence } from 'framer-motion'; +import { SelectedItemOverlay } from '../SelectedItemOverlay'; const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => { const dispatch = useDispatch(); const handleAllImagesBoardClick = () => { - dispatch(boardIdSelected(null)); + dispatch(boardIdSelected()); }; return ( @@ -19,25 +22,34 @@ const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => { cursor: 'pointer', w: 'full', h: 'full', - gap: 1, + borderRadius: 'base', }} onClick={handleAllImagesBoardClick} > - + + + {isSelected && } + - All Images + + All Images + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index 5854c3fe7c..fb68021dee 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -1,12 +1,11 @@ import { - Box, - Divider, + Collapse, + Flex, Grid, + IconButton, Input, InputGroup, InputRightElement, - Spacer, - useDisclosure, } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; @@ -16,33 +15,36 @@ import { selectBoardsAll, setBoardSearchText, } from 'features/gallery/store/boardSlice'; -import { memo, useEffect, useState } from 'react'; +import { memo, useState } from 'react'; import HoverableBoard from './HoverableBoard'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import AddBoardButton from './AddBoardButton'; import AllImagesBoard from './AllImagesBoard'; -import { searchBoardsSelector } from '../../store/boardSelectors'; -import { useSelector } from 'react-redux'; -import IAICollapse from '../../../../common/components/IAICollapse'; import { CloseIcon } from '@chakra-ui/icons'; import { useListBoardsQuery } from 'services/apiSlice'; const selector = createSelector( [selectBoardsAll, boardsSelector], (boards, boardsState) => { - const selectedBoard = boards.find( - (board) => board.board_id === boardsState.selectedBoardId - ); - return { selectedBoard, searchText: boardsState.searchText }; + // const selectedBoard = boards.find( + // (board) => board.board_id === boardsState.selectedBoardId + // ); + // return { selectedBoard, searchText: boardsState.searchText }; + const { selectedBoardId, searchText } = boardsState; + return { selectedBoardId, searchText }; }, defaultSelectorOptions ); -const BoardsList = () => { +type Props = { + isOpen: boolean; +}; + +const BoardsList = (props: Props) => { + const { isOpen } = props; const dispatch = useAppDispatch(); - const { selectedBoard, searchText } = useAppSelector(selector); + const { selectedBoardId, searchText } = useAppSelector(selector); // const filteredBoards = useSelector(searchBoardsSelector); - const { isOpen, onToggle } = useDisclosure(); const { data } = useListBoardsQuery({ offset: 0, limit: 8 }); @@ -64,9 +66,18 @@ const BoardsList = () => { }; return ( - - <> - + + + { /> {searchText && searchText.length && ( - + } + /> )} - + + { className="list-container" sx={{ gap: 2, - gridTemplateRows: '5rem 5rem', + gridTemplateRows: '5.5rem 5.5rem', gridAutoFlow: 'column dense', gridAutoColumns: '4rem', }} > - {!searchMode && ( - <> - - - - )} + {!searchMode && } {filteredBoards && filteredBoards.map((board) => ( ))} - - + + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index 71e080ff17..a2c07e4870 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -1,31 +1,22 @@ import { + Badge, Box, Editable, EditableInput, EditablePreview, Flex, + Image, MenuItem, MenuList, - Text, } from '@chakra-ui/react'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useAppDispatch } from 'app/store/storeHooks'; import { memo, useCallback } from 'react'; -import { FaTrash } from 'react-icons/fa'; +import { FaFolder, FaTrash } from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; import { BoardDTO, ImageDTO } from 'services/api'; -import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { IAINoImageFallback } from 'common/components/IAIImageFallback'; import { boardIdSelected } from 'features/gallery/store/boardSlice'; -import { - boardDeleted, - boardUpdated, - imageAddedToBoard, -} from '../../../../services/thunks/board'; -import { selectImagesAll, selectImagesById } from '../../store/imagesSlice'; -import IAIDndImage from '../../../../common/components/IAIDndImage'; -import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions'; -import { createSelector } from '@reduxjs/toolkit'; -import { RootState } from '../../../../app/store/store'; import { useAddImageToBoardMutation, useDeleteBoardMutation, @@ -33,21 +24,10 @@ import { useUpdateBoardMutation, } from 'services/apiSlice'; import { skipToken } from '@reduxjs/toolkit/dist/query'; - -const coverImageSelector = (imageName: string | undefined) => - createSelector( - [(state: RootState) => state], - (state) => { - const coverImage = imageName - ? selectImagesById(state, imageName) - : undefined; - - return { - coverImage, - }; - }, - defaultSelectorOptions - ); +import { useDroppable } from '@dnd-kit/core'; +import { AnimatePresence } from 'framer-motion'; +import IAIDropOverlay from 'common/components/IAIDropOverlay'; +import { SelectedItemOverlay } from '../SelectedItemOverlay'; interface HoverableBoardProps { board: BoardDTO; @@ -94,6 +74,17 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { [addImageToBoard, board_id] ); + const { + isOver, + setNodeRef, + active: isDropActive, + } = useDroppable({ + id: `board_droppable_${board_id}`, + data: { + handleDrop, + }, + }); + return ( @@ -112,7 +103,6 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { > {(ref) => ( { cursor: 'pointer', w: 'full', h: 'full', - gap: 1, }} > - } - isUploadDisabled={true} - /> + {board.cover_image_name && coverImage?.image_url && ( + + )} + {!(board.cover_image_name && coverImage?.image_url) && ( + + )} + + {board.image_count} + + + {isSelected && } + + + {isDropActive && } + - { - handleUpdateBoardName(nextValue); - }} - > - - + { + handleUpdateBoardName(nextValue); }} - /> - - - {board.image_count} - + > + + + + )} diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index bff32f1d78..49376b4807 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -14,7 +14,7 @@ import { useAppToaster } from 'app/components/Toaster'; import { imageSelected } from '../store/gallerySlice'; import IAIDndImage from 'common/components/IAIDndImage'; import { ImageDTO } from 'services/api'; -import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; import { RootState } from 'app/store/store'; import { selectImagesById } from '../store/imagesSlice'; import { useGetImageDTOQuery } from 'services/apiSlice'; @@ -116,7 +116,7 @@ const CurrentImagePreview = () => { } + fallback={} isUploadDisabled={true} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 48bd2bde74..8648962c8c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -1,12 +1,15 @@ import { Box, + Button, ButtonGroup, Flex, FlexProps, Grid, Icon, Text, + VStack, forwardRef, + useDisclosure, } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; @@ -54,10 +57,10 @@ import { selectImagesAll, } from '../store/imagesSlice'; import { receivedPageOfImages } from 'services/thunks/image'; -import { boardSelector } from '../store/boardSelectors'; -import { boardCreated } from '../../../services/thunks/board'; import BoardsList from './Boards/BoardsList'; -import { selectBoardsById } from '../store/boardSlice'; +import { boardsSelector, selectBoardsById } from '../store/boardSlice'; +import { ChevronUpIcon } from '@chakra-ui/icons'; +import { useListAllBoardsQuery } from 'services/apiSlice'; const itemSelector = createSelector( [(state: RootState) => state], @@ -89,7 +92,7 @@ const itemSelector = createSelector( ); const mainSelector = createSelector( - [gallerySelector, uiSelector, boardSelector], + [gallerySelector, uiSelector, boardsSelector], (gallery, ui, boards) => { const { galleryImageMinimumWidth, @@ -109,7 +112,7 @@ const mainSelector = createSelector( shouldUseSingleGalleryColumn, selectedImage, galleryView, - boards, + selectedBoardId: boards.selectedBoardId, }; }, defaultSelectorOptions @@ -142,12 +145,18 @@ const ImageGalleryContent = () => { shouldUseSingleGalleryColumn, selectedImage, galleryView, - boards, + selectedBoardId, } = useAppSelector(mainSelector); - const { items, areMoreAvailable, isLoading, categories, selectedBoard } = + const { items, areMoreAvailable, isLoading, categories } = useAppSelector(itemSelector); + const { selectedBoard } = useListAllBoardsQuery(undefined, { + selectFromResult: ({ data }) => ({ + selectedBoard: data?.find((b) => b.board_id === selectedBoardId), + }), + }); + const handleLoadMoreImages = useCallback(() => { dispatch(receivedPageOfImages()); }, [dispatch]); @@ -159,6 +168,8 @@ const ImageGalleryContent = () => { return undefined; }, [areMoreAvailable, handleLoadMoreImages, isLoading]); + const { isOpen: isBoardListOpen, onToggle } = useDisclosure(); + const handleChangeGalleryImageMinimumWidth = (v: number) => { dispatch(setGalleryImageMinimumWidth(v)); }; @@ -197,50 +208,71 @@ const ImageGalleryContent = () => { dispatch(setGalleryView('assets')); }, [dispatch]); - const handleClickBoardsView = useCallback(() => { - dispatch(setGalleryView('boards')); - }, [dispatch]); - return ( - - - - + + + } + /> + } + /> + + } - /> - } - /> - - - - {selectedBoard ? selectedBoard.board_name : 'All Images'} - - - + variant="ghost" + sx={{ + w: 'full', + justifyContent: 'center', + alignItems: 'center', + px: 2, + _hover: { + bg: 'base.800', + }, + }} + > + + {selectedBoard ? selectedBoard.board_name : 'All Images'} + + + { icon={shouldPinGallery ? : } /> - - - + + + - + {items.length || areMoreAvailable ? ( <> @@ -378,7 +410,7 @@ const ImageGalleryContent = () => { )} - + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/SelectedItemOverlay.tsx b/invokeai/frontend/web/src/features/gallery/components/SelectedItemOverlay.tsx new file mode 100644 index 0000000000..7038b4b64f --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/SelectedItemOverlay.tsx @@ -0,0 +1,26 @@ +import { motion } from 'framer-motion'; + +export const SelectedItemOverlay = () => ( + +); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx index e4b3a9191e..fbfa00e2a1 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx @@ -10,7 +10,7 @@ import { generationSelector } from 'features/parameters/store/generationSelector import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAIDndImage from 'common/components/IAIDndImage'; import { ImageDTO } from 'services/api'; -import { IAIImageFallback } from 'common/components/IAIImageFallback'; +import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; import { useGetImageDTOQuery } from 'services/apiSlice'; import { skipToken } from '@reduxjs/toolkit/dist/query'; @@ -65,7 +65,7 @@ const InitialImagePreview = () => { image={image} onDrop={handleDrop} onReset={handleReset} - fallback={} + fallback={} postUploadAction={{ type: 'SET_INITIAL_IMAGE' }} withResetIcon /> From abd6561140bc1b6280506828bbd17a860d4c9ede Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 18:10:26 +1000 Subject: [PATCH 059/110] feat(ui): just fetch all boards instead of paginating them --- .../features/gallery/components/Boards/BoardsList.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index fb68021dee..81ee4be725 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -21,7 +21,7 @@ import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import AddBoardButton from './AddBoardButton'; import AllImagesBoard from './AllImagesBoard'; import { CloseIcon } from '@chakra-ui/icons'; -import { useListBoardsQuery } from 'services/apiSlice'; +import { useListAllBoardsQuery } from 'services/apiSlice'; const selector = createSelector( [selectBoardsAll, boardsSelector], @@ -44,15 +44,14 @@ const BoardsList = (props: Props) => { const { isOpen } = props; const dispatch = useAppDispatch(); const { selectedBoardId, searchText } = useAppSelector(selector); - // const filteredBoards = useSelector(searchBoardsSelector); - const { data } = useListBoardsQuery({ offset: 0, limit: 8 }); + const { data: boards } = useListAllBoardsQuery(); const filteredBoards = searchText - ? data?.items.filter((board) => + ? boards?.filter((board) => board.board_name.toLowerCase().includes(searchText.toLowerCase()) ) - : data?.items; + : boards; const [searchMode, setSearchMode] = useState(false); From 3c032c0767154eb7caa0e964a9b1accac98982c2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 19:55:00 +1000 Subject: [PATCH 060/110] feat(ui): only auto-add image to board if is not intermediate --- .../socketio/socketInvocationComplete.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts index 680f9c7041..bef0a2ccdb 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts @@ -41,15 +41,6 @@ export const addInvocationCompleteEventListener = () => { if (isImageOutput(result) && !nodeDenylist.includes(node.type)) { const { image_name } = result.image; - if (boardIdToAddTo) { - dispatch( - api.endpoints.addImageToBoard.initiate({ - board_id: boardIdToAddTo, - image_name, - }) - ); - } - // Get its metadata dispatch( imageMetadataReceived({ @@ -69,6 +60,15 @@ export const addInvocationCompleteEventListener = () => { dispatch(addImageToStagingArea(imageDTO)); } + if (boardIdToAddTo && !imageDTO.is_intermediate) { + dispatch( + api.endpoints.addImageToBoard.initiate({ + board_id: boardIdToAddTo, + image_name, + }) + ); + } + dispatch(progressImageSet(null)); } // pass along the socket event as an application action From 67a75f68952cdac27a88b17c61f86d9fadf8b8e8 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 19:56:19 +1000 Subject: [PATCH 061/110] feat(api, db): support `board_id` filter on images service `get_many()` --- invokeai/app/api/routers/images.py | 4 ++ .../services/board_image_record_storage.py | 1 + invokeai/app/services/image_record_storage.py | 49 ++++++++++++++++--- invokeai/app/services/images.py | 3 ++ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/invokeai/app/api/routers/images.py b/invokeai/app/api/routers/images.py index 11453d97f1..a8c84b81b9 100644 --- a/invokeai/app/api/routers/images.py +++ b/invokeai/app/api/routers/images.py @@ -221,6 +221,9 @@ async def list_images_with_metadata( is_intermediate: Optional[bool] = Query( default=None, description="Whether to list intermediate images" ), + board_id: Optional[str] = Query( + default=None, description="The board id to filter by" + ), offset: int = Query(default=0, description="The page offset"), limit: int = Query(default=10, description="The number of images per page"), ) -> OffsetPaginatedResults[ImageDTO]: @@ -232,6 +235,7 @@ async def list_images_with_metadata( image_origin, categories, is_intermediate, + board_id, ) return image_dtos diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index 73fad13406..45d21520f8 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -178,6 +178,7 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): offset: int = 0, limit: int = 10, ) -> OffsetPaginatedResults[ImageRecord]: + # TODO: this isn't paginated yet? try: self._lock.acquire() self._cursor.execute( diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index bc59c4a27c..8b5a77da2c 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -82,6 +82,7 @@ class ImageRecordStorageBase(ABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageRecord]: """Gets a page of image records.""" pass @@ -280,20 +281,42 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageRecord]: try: self._lock.acquire() # Manually build two queries - one for the count, one for the records - count_query = f"""SELECT COUNT(*) FROM images WHERE 1=1\n""" - images_query = f"""SELECT * FROM images WHERE 1=1\n""" + # count_query = """--sql + # SELECT COUNT(*) FROM images WHERE 1=1 + # """ + + count_query = """--sql + SELECT COUNT(*) + FROM images + LEFT JOIN board_images ON board_images.image_name = images.image_name + WHERE 1=1 + """ + + images_query = """--sql + SELECT images.* + FROM images + LEFT JOIN board_images ON board_images.image_name = images.image_name + WHERE 1=1 + """ + + # images_query = """--sql + # SELECT * FROM images WHERE 1=1 + # """ query_conditions = "" query_params = [] if image_origin is not None: - query_conditions += f"""AND image_origin = ?\n""" + query_conditions += """--sql + AND images.image_origin = ? + """ query_params.append(image_origin.value) if categories is not None: @@ -301,17 +324,31 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): category_strings = list(map(lambda c: c.value, set(categories))) # Create the correct length of placeholders placeholders = ",".join("?" * len(category_strings)) - query_conditions += f"AND image_category IN ( {placeholders} )\n" + + query_conditions += f"""--sql + AND images.image_category IN ( {placeholders} ) + """ # Unpack the included categories into the query params for c in category_strings: query_params.append(c) if is_intermediate is not None: - query_conditions += f"""AND is_intermediate = ?\n""" + query_conditions += """--sql + AND images.is_intermediate = ? + """ + query_params.append(is_intermediate) - query_pagination = f"""ORDER BY created_at DESC LIMIT ? OFFSET ?\n""" + if board_id is not None: + query_conditions += """--sql + AND board_images.board_id = ? + """ + query_params.append(board_id) + + query_pagination = """--sql + ORDER BY images.created_at DESC LIMIT ? OFFSET ? + """ # Final images query with pagination images_query += query_conditions + query_pagination + ";" diff --git a/invokeai/app/services/images.py b/invokeai/app/services/images.py index 5959116161..542f874f1d 100644 --- a/invokeai/app/services/images.py +++ b/invokeai/app/services/images.py @@ -102,6 +102,7 @@ class ImageServiceABC(ABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageDTO]: """Gets a paginated list of image DTOs.""" pass @@ -290,6 +291,7 @@ class ImageService(ImageServiceABC): image_origin: Optional[ResourceOrigin] = None, categories: Optional[list[ImageCategory]] = None, is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, ) -> OffsetPaginatedResults[ImageDTO]: try: results = self._services.image_records.get_many( @@ -298,6 +300,7 @@ class ImageService(ImageServiceABC): image_origin, categories, is_intermediate, + board_id, ) image_dtos = list( From d501986610c9bfbb8f70aa9f7f8cf6bc05c754a8 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 19:56:55 +1000 Subject: [PATCH 062/110] chore(ui): regen api client --- invokeai/frontend/web/src/services/api/models/ModelsList.ts | 2 +- .../frontend/web/src/services/api/services/ImagesService.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index 40c2ebb1b9..a2d88d1967 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -12,6 +12,6 @@ import type { invokeai__backend__model_management__models__textual_inversion__Te import type { invokeai__backend__model_management__models__vae__VaeModel__Config } from './invokeai__backend__model_management__models__vae__VaeModel__Config'; export type ModelsList = { - models: Record>>; + models: Record>>; }; diff --git a/invokeai/frontend/web/src/services/api/services/ImagesService.ts b/invokeai/frontend/web/src/services/api/services/ImagesService.ts index d0eae92d4b..bfdef887a0 100644 --- a/invokeai/frontend/web/src/services/api/services/ImagesService.ts +++ b/invokeai/frontend/web/src/services/api/services/ImagesService.ts @@ -25,6 +25,7 @@ export class ImagesService { imageOrigin, categories, isIntermediate, + boardId, offset, limit = 10, }: { @@ -40,6 +41,10 @@ export class ImagesService { * Whether to list intermediate images */ isIntermediate?: boolean, + /** + * The board id to filter by + */ + boardId?: string, /** * The page offset */ @@ -56,6 +61,7 @@ export class ImagesService { 'image_origin': imageOrigin, 'categories': categories, 'is_intermediate': isIntermediate, + 'board_id': boardId, 'offset': offset, 'limit': limit, }, From f560a462a02412627649284c4325641f12216f0f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 19:57:22 +1000 Subject: [PATCH 063/110] feat(ui): rudimentary categorized gallery image fetching --- .../components/ImageGalleryContent.tsx | 56 ++++++++++++------- .../frontend/web/src/services/thunks/image.ts | 53 ++++++++++++++---- 2 files changed, 78 insertions(+), 31 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 8648962c8c..bd69425525 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -56,36 +56,39 @@ import { imageCategoriesChanged, selectImagesAll, } from '../store/imagesSlice'; -import { receivedPageOfImages } from 'services/thunks/image'; +import { + IMAGES_PER_PAGE, + receivedImages, + receivedPageOfImages, +} from 'services/thunks/image'; import BoardsList from './Boards/BoardsList'; -import { boardsSelector, selectBoardsById } from '../store/boardSlice'; +import { boardsSelector } from '../store/boardSlice'; import { ChevronUpIcon } from '@chakra-ui/icons'; import { useListAllBoardsQuery } from 'services/apiSlice'; const itemSelector = createSelector( [(state: RootState) => state], (state) => { - const { images, boards } = state; - - const { categories } = images; + const { categories, total, isLoading } = state.images; + const { selectedBoardId } = state.boards; const allImages = selectImagesAll(state); - const items = allImages.filter((i) => - categories.includes(i.image_category) - ); - const areMoreAvailable = items.length < images.total; - const isLoading = images.isLoading; - const selectedBoard = boards.selectedBoardId - ? selectBoardsById(state, boards.selectedBoardId) - : undefined; + const images = allImages.filter((i) => { + const isInCategory = categories.includes(i.image_category); + const isInSelectedBoard = selectedBoardId + ? i.board_id === selectedBoardId + : true; + return isInCategory && isInSelectedBoard; + }); + + const areMoreAvailable = images.length < total; return { - items, + images, isLoading, areMoreAvailable, - categories: images.categories, - selectedBoard, + categories, }; }, defaultSelectorOptions @@ -148,7 +151,7 @@ const ImageGalleryContent = () => { selectedBoardId, } = useAppSelector(mainSelector); - const { items, areMoreAvailable, isLoading, categories } = + const { images, areMoreAvailable, isLoading, categories } = useAppSelector(itemSelector); const { selectedBoard } = useListAllBoardsQuery(undefined, { @@ -158,7 +161,7 @@ const ImageGalleryContent = () => { }); const handleLoadMoreImages = useCallback(() => { - dispatch(receivedPageOfImages()); + dispatch(receivedPageOfImages({})); }, [dispatch]); const handleEndReached = useMemo(() => { @@ -208,6 +211,17 @@ const ImageGalleryContent = () => { dispatch(setGalleryView('assets')); }, [dispatch]); + useEffect(() => { + if (images.length < 20) { + dispatch( + receivedPageOfImages({ + categories, + boardId: selectedBoardId, + }) + ); + } + }, [categories, dispatch, images.length, selectedBoardId]); + return ( { - {items.length || areMoreAvailable ? ( + {images.length || areMoreAvailable ? ( <> {shouldUseSingleGalleryColumn ? ( setScrollerRef(ref)} itemContent={(index, item) => ( @@ -357,7 +371,7 @@ const ImageGalleryContent = () => { ) : ( { + async (arg: ImagesListedArg, { getState }) => { const state = getState(); const { categories } = state.images; + const { selectedBoardId } = state.boards; - const totalImagesInFilter = selectImagesAll(state).filter((i) => - categories.includes(i.image_category) - ).length; - - const response = await ImagesService.listImagesWithMetadata({ - categories, - isIntermediate: false, - offset: totalImagesInFilter, - limit: IMAGES_PER_PAGE, + const images = selectImagesAll(state).filter((i) => { + const isInCategory = categories.includes(i.image_category); + const isInSelectedBoard = selectedBoardId + ? i.board_id === selectedBoardId + : true; + return isInCategory && isInSelectedBoard; }); + + let queryArg: ReceivedImagesArg = {}; + + if (size(arg)) { + queryArg = { ...DEFAULT_IMAGES_LISTED_ARG, ...arg }; + } else { + queryArg = { + ...DEFAULT_IMAGES_LISTED_ARG, + categories, + offset: images.length, + }; + } + + const response = await ImagesService.listImagesWithMetadata(queryArg); + return response; + } +); + +type ReceivedImagesArg = Parameters< + (typeof ImagesService)['listImagesWithMetadata'] +>[0]; + +/** + * `ImagesService.listImagesWithMetadata()` thunk + */ +export const receivedImages = createAppAsyncThunk( + 'api/receivedImages', + async (arg: ReceivedImagesArg, { getState }) => { + const response = await ImagesService.listImagesWithMetadata(arg); return response; } ); From 26b75b85f7e69e9e61e2cdc8a6686ffc15165598 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 20:00:29 +1000 Subject: [PATCH 064/110] fix(ui): if deleting selected board, deselect it --- .../web/src/features/gallery/store/boardSlice.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index 76ab757e5e..96c1c055e4 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -14,6 +14,7 @@ import { boardUpdated, receivedBoards, } from '../../../services/thunks/board'; +import { api } from 'services/apiSlice'; export const boardsAdapter = createEntityAdapter({ selectId: (board) => board.board_id, @@ -92,6 +93,14 @@ const boardsSlice = createSlice({ console.log({ boardId }); boardsAdapter.removeOne(state, boardId); }); + builder.addMatcher( + api.endpoints.deleteBoard.matchFulfilled, + (state, action) => { + if (action.meta.arg.originalArgs === state.selectedBoardId) { + state.selectedBoardId = undefined; + } + } + ); }, }); From 083a0fc4cfd6bebfd44e40712cedcd46c928c12f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 20:10:28 +1000 Subject: [PATCH 065/110] tidy(ui): remove references to boardsAdapter --- .../gallery/components/Boards/BoardsList.tsx | 9 +- .../src/features/gallery/store/boardSlice.ts | 97 ++----------------- 2 files changed, 11 insertions(+), 95 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index 81ee4be725..738693a278 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -12,7 +12,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { boardsSelector, - selectBoardsAll, setBoardSearchText, } from 'features/gallery/store/boardSlice'; import { memo, useState } from 'react'; @@ -24,12 +23,8 @@ import { CloseIcon } from '@chakra-ui/icons'; import { useListAllBoardsQuery } from 'services/apiSlice'; const selector = createSelector( - [selectBoardsAll, boardsSelector], - (boards, boardsState) => { - // const selectedBoard = boards.find( - // (board) => board.board_id === boardsState.selectedBoardId - // ); - // return { selectedBoard, searchText: boardsState.searchText }; + [boardsSelector], + (boardsState) => { const { selectedBoardId, searchText } = boardsState; return { selectedBoardId, searchText }; }, diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index 96c1c055e4..8fc9bfa486 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -1,60 +1,22 @@ -import { - EntityId, - PayloadAction, - Update, - createEntityAdapter, - createSlice, -} from '@reduxjs/toolkit'; +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; -import { BoardDTO } from 'services/api'; -import { dateComparator } from 'common/util/dateComparator'; -import { - boardCreated, - boardDeleted, - boardUpdated, - receivedBoards, -} from '../../../services/thunks/board'; import { api } from 'services/apiSlice'; -export const boardsAdapter = createEntityAdapter({ - selectId: (board) => board.board_id, - sortComparer: (a, b) => dateComparator(b.updated_at, a.updated_at), -}); - -type AdditionalBoardsState = { - offset: number; - limit: number; - total: number; - isLoading: boolean; +type BoardsState = { + searchText: string; selectedBoardId?: string; - searchText?: string; updateBoardModalOpen: boolean; }; -export const initialBoardsState = - boardsAdapter.getInitialState({ - offset: 0, - limit: 50, - total: 0, - isLoading: false, - updateBoardModalOpen: false, - }); - -export type BoardsState = typeof initialBoardsState; +export const initialBoardsState: BoardsState = { + updateBoardModalOpen: false, + searchText: '', +}; const boardsSlice = createSlice({ name: 'boards', initialState: initialBoardsState, reducers: { - boardUpserted: (state, action: PayloadAction) => { - boardsAdapter.upsertOne(state, action.payload); - }, - boardUpdatedOne: (state, action: PayloadAction>) => { - boardsAdapter.updateOne(state, action.payload); - }, - boardRemoved: (state, action: PayloadAction) => { - boardsAdapter.removeOne(state, action.payload); - }, boardIdSelected: (state, action: PayloadAction) => { state.selectedBoardId = action.payload; }, @@ -66,33 +28,6 @@ const boardsSlice = createSlice({ }, }, extraReducers: (builder) => { - builder.addCase(receivedBoards.pending, (state) => { - state.isLoading = true; - }); - builder.addCase(receivedBoards.rejected, (state) => { - state.isLoading = false; - }); - builder.addCase(receivedBoards.fulfilled, (state, action) => { - state.isLoading = false; - const { items, offset, limit, total } = action.payload; - state.offset = offset; - state.limit = limit; - state.total = total; - boardsAdapter.upsertMany(state, items); - }); - builder.addCase(boardCreated.fulfilled, (state, action) => { - const board = action.payload; - boardsAdapter.upsertOne(state, board); - }); - builder.addCase(boardUpdated.fulfilled, (state, action) => { - const board = action.payload; - boardsAdapter.upsertOne(state, board); - }); - builder.addCase(boardDeleted.pending, (state, action) => { - const boardId = action.meta.arg; - console.log({ boardId }); - boardsAdapter.removeOne(state, boardId); - }); builder.addMatcher( api.endpoints.deleteBoard.matchFulfilled, (state, action) => { @@ -104,22 +39,8 @@ const boardsSlice = createSlice({ }, }); -export const { - selectAll: selectBoardsAll, - selectById: selectBoardsById, - selectEntities: selectBoardsEntities, - selectIds: selectBoardsIds, - selectTotal: selectBoardsTotal, -} = boardsAdapter.getSelectors((state) => state.boards); - -export const { - boardUpserted, - boardUpdatedOne, - boardRemoved, - boardIdSelected, - setBoardSearchText, - setUpdateBoardModalOpen, -} = boardsSlice.actions; +export const { boardIdSelected, setBoardSearchText, setUpdateBoardModalOpen } = + boardsSlice.actions; export const boardsSelector = (state: RootState) => state.boards; From e2ee8102c2411e28514e77aa8372151ec390ff0a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 20:11:09 +1000 Subject: [PATCH 066/110] tidy(db): tidy `image_record_storage.py` --- invokeai/app/services/image_record_storage.py | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 8b5a77da2c..c34d2ca5c8 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -273,7 +273,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): raise ImageRecordSaveException from e finally: self._lock.release() - + def get_many( self, offset: int = 0, @@ -287,11 +287,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): self._lock.acquire() # Manually build two queries - one for the count, one for the records - - # count_query = """--sql - # SELECT COUNT(*) FROM images WHERE 1=1 - # """ - count_query = """--sql SELECT COUNT(*) FROM images @@ -306,10 +301,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): WHERE 1=1 """ - # images_query = """--sql - # SELECT * FROM images WHERE 1=1 - # """ - query_conditions = "" query_params = [] @@ -320,7 +311,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): query_params.append(image_origin.value) if categories is not None: - ## Convert the enum values to unique list of strings + # Convert the enum values to unique list of strings category_strings = list(map(lambda c: c.value, set(categories))) # Create the correct length of placeholders placeholders = ",".join("?" * len(category_strings)) @@ -337,13 +328,14 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): query_conditions += """--sql AND images.is_intermediate = ? """ - + query_params.append(is_intermediate) if board_id is not None: query_conditions += """--sql AND board_images.board_id = ? """ + query_params.append(board_id) query_pagination = """--sql @@ -457,7 +449,9 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): finally: self._lock.release() - def get_most_recent_image_for_board(self, board_id: str) -> Union[ImageRecord, None]: + def get_most_recent_image_for_board( + self, board_id: str + ) -> Union[ImageRecord, None]: try: self._lock.acquire() self._cursor.execute( @@ -477,5 +471,5 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): self._lock.release() if result is None: return None - + return deserialize_image_record(dict(result)) From 4545f3209fe0328a90cc165ed0f56ed356e512da Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 20:40:12 +1000 Subject: [PATCH 067/110] fix(ui): fix bug with image deletion not removing image from gallery --- .../middleware/listenerMiddleware/index.ts | 2 ++ .../listeners/boardIdSelected.ts | 28 +++++++++++++++++++ .../listeners/imageDeleted.ts | 4 +-- .../components/ImageGalleryContent.tsx | 11 -------- 4 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 15fd48fbb2..4ec185bd83 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -77,6 +77,7 @@ import { addImageAddedToBoardFulfilledListener, addImageAddedToBoardRejectedListener, } from './listeners/imageAddedToBoard'; +import { addBoardIdSelectedListener } from './listeners/boardIdSelected'; export const listenerMiddleware = createListenerMiddleware(); @@ -191,3 +192,4 @@ addUpdateImageUrlsOnConnectListener(); // Boards addImageAddedToBoardFulfilledListener(); addImageAddedToBoardRejectedListener(); +addBoardIdSelectedListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts new file mode 100644 index 0000000000..3889130a9c --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts @@ -0,0 +1,28 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { boardIdSelected } from 'features/gallery/store/boardSlice'; +import { selectImagesAll } from 'features/gallery/store/imagesSlice'; +import { receivedPageOfImages } from 'services/thunks/image'; + +const moduleLog = log.child({ namespace: 'boards' }); + +export const addBoardIdSelectedListener = () => { + startAppListening({ + actionCreator: boardIdSelected, + effect: (action, { getState, dispatch }) => { + const boardId = action.payload; + const state = getState(); + const { categories } = state.images; + + const images = selectImagesAll(state).filter((i) => { + const isInCategory = categories.includes(i.image_category); + const isInSelectedBoard = boardId ? i.board_id === boardId : true; + return isInCategory && isInSelectedBoard; + }); + + if (images.length === 0) { + dispatch(receivedPageOfImages({ categories, boardId })); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts index 32bfcbba07..224aa0d2aa 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts @@ -14,7 +14,7 @@ import { clearInitialImage } from 'features/parameters/store/generationSlice'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { api } from 'services/apiSlice'; -const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' }); +const moduleLog = log.child({ namespace: 'image' }); /** * Called when the user requests an image deletion @@ -30,7 +30,7 @@ export const addRequestedImageDeletionListener = () => { const state = getState(); const selectedImage = state.gallery.selectedImage; - if (selectedImage && selectedImage === image_name) { + if (selectedImage === image_name) { const ids = selectImagesIds(state); const deletedImageIndex = ids.findIndex( diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index bd69425525..866a68f0b6 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -211,17 +211,6 @@ const ImageGalleryContent = () => { dispatch(setGalleryView('assets')); }, [dispatch]); - useEffect(() => { - if (images.length < 20) { - dispatch( - receivedPageOfImages({ - categories, - boardId: selectedBoardId, - }) - ); - } - }, [categories, dispatch, images.length, selectedBoardId]); - return ( Date: Wed, 21 Jun 2023 21:27:15 +1000 Subject: [PATCH 068/110] fix(ui): fix gallery image fetching for board categories --- .../listeners/boardIdSelected.ts | 27 ++++++++++++++++--- .../components/ImageGalleryContent.tsx | 18 ++++++++----- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts index 3889130a9c..30fb696ac3 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts @@ -2,7 +2,8 @@ import { log } from 'app/logging/useLogger'; import { startAppListening } from '..'; import { boardIdSelected } from 'features/gallery/store/boardSlice'; import { selectImagesAll } from 'features/gallery/store/imagesSlice'; -import { receivedPageOfImages } from 'services/thunks/image'; +import { IMAGES_PER_PAGE, receivedPageOfImages } from 'services/thunks/image'; +import { api } from 'services/apiSlice'; const moduleLog = log.child({ namespace: 'boards' }); @@ -11,16 +12,36 @@ export const addBoardIdSelectedListener = () => { actionCreator: boardIdSelected, effect: (action, { getState, dispatch }) => { const boardId = action.payload; + + // we need to check if we need to fetch more images + + if (!boardId) { + // a board was unselected - we don't need to do anything + return; + } + const state = getState(); const { categories } = state.images; - const images = selectImagesAll(state).filter((i) => { + const filteredImages = selectImagesAll(state).filter((i) => { const isInCategory = categories.includes(i.image_category); const isInSelectedBoard = boardId ? i.board_id === boardId : true; return isInCategory && isInSelectedBoard; }); - if (images.length === 0) { + // get the board from the cache + const { data: boards } = api.endpoints.listAllBoards.select()(state); + const board = boards?.find((b) => b.board_id === boardId); + if (!board) { + // can't find the board in cache... + return; + } + + // if we haven't loaded one full page of images from this board, load more + if ( + filteredImages.length < board.image_count && + filteredImages.length < IMAGES_PER_PAGE + ) { dispatch(receivedPageOfImages({ categories, boardId })); } }, diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 866a68f0b6..c2f8a30409 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -69,7 +69,7 @@ import { useListAllBoardsQuery } from 'services/apiSlice'; const itemSelector = createSelector( [(state: RootState) => state], (state) => { - const { categories, total, isLoading } = state.images; + const { categories, total: allImagesTotal, isLoading } = state.images; const { selectedBoardId } = state.boards; const allImages = selectImagesAll(state); @@ -82,12 +82,10 @@ const itemSelector = createSelector( return isInCategory && isInSelectedBoard; }); - const areMoreAvailable = images.length < total; - return { images, + allImagesTotal, isLoading, - areMoreAvailable, categories, }; }, @@ -151,8 +149,7 @@ const ImageGalleryContent = () => { selectedBoardId, } = useAppSelector(mainSelector); - const { images, areMoreAvailable, isLoading, categories } = - useAppSelector(itemSelector); + const { images, isLoading, allImagesTotal } = useAppSelector(itemSelector); const { selectedBoard } = useListAllBoardsQuery(undefined, { selectFromResult: ({ data }) => ({ @@ -160,6 +157,15 @@ const ImageGalleryContent = () => { }), }); + const filteredImagesTotal = useMemo( + () => selectedBoard?.image_count ?? allImagesTotal, + [allImagesTotal, selectedBoard?.image_count] + ); + + const areMoreAvailable = useMemo(() => { + return images.length < filteredImagesTotal; + }, [images.length, filteredImagesTotal]); + const handleLoadMoreImages = useCallback(() => { dispatch(receivedPageOfImages({})); }, [dispatch]); From d3e6f0130c0ed71e711982c59034e99114685571 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 21 Jun 2023 22:00:14 +1000 Subject: [PATCH 069/110] fix(ui): fix issue with gallery not letting you load more images To determine whether the Load More button should work, we need to keep track of how many images are left to load for a given board or category. The Assets tab doesn't work, though. Need to figure out a better way to handle this. --- .../listeners/boardIdSelected.ts | 53 ++++++++++++++++++- .../listeners/imageCategoriesChanged.ts | 12 +++-- .../listeners/socketio/socketConnected.ts | 7 ++- .../components/CurrentImagePreview.tsx | 10 ++-- .../components/ImageGalleryContent.tsx | 20 +++---- .../features/gallery/store/gallerySlice.ts | 2 - .../src/features/gallery/store/imagesSlice.ts | 13 ++++- .../frontend/web/src/services/thunks/image.ts | 6 ++- 8 files changed, 96 insertions(+), 27 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts index 30fb696ac3..8e4667fd14 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts @@ -4,6 +4,7 @@ import { boardIdSelected } from 'features/gallery/store/boardSlice'; import { selectImagesAll } from 'features/gallery/store/imagesSlice'; import { IMAGES_PER_PAGE, receivedPageOfImages } from 'services/thunks/image'; import { api } from 'services/apiSlice'; +import { imageSelected } from 'features/gallery/store/gallerySlice'; const moduleLog = log.child({ namespace: 'boards' }); @@ -15,12 +16,62 @@ export const addBoardIdSelectedListener = () => { // we need to check if we need to fetch more images + const state = getState(); + const allImages = selectImagesAll(state); + + if (!boardId) { + // a board was unselected + dispatch(imageSelected(allImages[0]?.image_name)); + return; + } + + const { categories } = state.images; + + const filteredImages = allImages.filter((i) => { + const isInCategory = categories.includes(i.image_category); + const isInSelectedBoard = boardId ? i.board_id === boardId : true; + return isInCategory && isInSelectedBoard; + }); + + // get the board from the cache + const { data: boards } = api.endpoints.listAllBoards.select()(state); + const board = boards?.find((b) => b.board_id === boardId); + + if (!board) { + // can't find the board in cache... + dispatch(imageSelected(allImages[0]?.image_name)); + return; + } + + console.log('setting image'); + dispatch(imageSelected(board.cover_image_name)); + + // if we haven't loaded one full page of images from this board, load more + if ( + filteredImages.length < board.image_count && + filteredImages.length < IMAGES_PER_PAGE + ) { + dispatch(receivedPageOfImages({ categories, boardId })); + } + }, + }); +}; + +export const addBoardIdSelected_changeSelectedImage_listener = () => { + startAppListening({ + actionCreator: boardIdSelected, + effect: (action, { getState, dispatch }) => { + const boardId = action.payload; + + const state = getState(); + + // we need to check if we need to fetch more images + if (!boardId) { // a board was unselected - we don't need to do anything return; } - const state = getState(); const { categories } = state.images; const filteredImages = selectImagesAll(state).filter((i) => { diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageCategoriesChanged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageCategoriesChanged.ts index 85d56d3913..8f01b8d7b8 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageCategoriesChanged.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageCategoriesChanged.ts @@ -12,12 +12,16 @@ export const addImageCategoriesChangedListener = () => { startAppListening({ actionCreator: imageCategoriesChanged, effect: (action, { getState, dispatch }) => { - const filteredImagesCount = selectFilteredImagesAsArray( - getState() - ).length; + const state = getState(); + const filteredImagesCount = selectFilteredImagesAsArray(state).length; if (!filteredImagesCount) { - dispatch(receivedPageOfImages()); + dispatch( + receivedPageOfImages({ + categories: action.payload, + boardId: state.boards.selectedBoardId, + }) + ); } }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index 3049d2c933..9fe554fee1 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -20,7 +20,12 @@ export const addSocketConnectedEventListener = () => { const { disabledTabs } = config; if (!images.ids.length) { - dispatch(receivedPageOfImages()); + dispatch( + receivedPageOfImages({ + categories: ['general'], + isIntermediate: false, + }) + ); } if (!models.ids.length) { diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index 49376b4807..5426fee3b1 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -9,14 +9,10 @@ import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer'; import NextPrevImageButtons from './NextPrevImageButtons'; import { memo, useCallback } from 'react'; import { systemSelector } from 'features/system/store/systemSelectors'; -import { configSelector } from '../../system/store/configSelectors'; -import { useAppToaster } from 'app/components/Toaster'; import { imageSelected } from '../store/gallerySlice'; import IAIDndImage from 'common/components/IAIDndImage'; import { ImageDTO } from 'services/api'; import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; -import { RootState } from 'app/store/store'; -import { selectImagesById } from '../store/imagesSlice'; import { useGetImageDTOQuery } from 'services/apiSlice'; import { skipToken } from '@reduxjs/toolkit/dist/query'; @@ -114,14 +110,14 @@ const CurrentImagePreview = () => { }} > } isUploadDisabled={true} /> )} - {shouldShowImageDetails && image && ( + {shouldShowImageDetails && image && selectedImage && ( { )} - {!shouldShowImageDetails && image && ( + {!shouldShowImageDetails && image && selectedImage && ( { shouldUseSingleGalleryColumn, selectedImage, galleryView, - selectedBoardId, } = useAppSelector(mainSelector); - const { images, isLoading, allImagesTotal } = useAppSelector(itemSelector); + const { images, isLoading, allImagesTotal, categories, selectedBoardId } = + useAppSelector(itemSelector); const { selectedBoard } = useListAllBoardsQuery(undefined, { selectFromResult: ({ data }) => ({ @@ -167,8 +164,13 @@ const ImageGalleryContent = () => { }, [images.length, filteredImagesTotal]); const handleLoadMoreImages = useCallback(() => { - dispatch(receivedPageOfImages({})); - }, [dispatch]); + dispatch( + receivedPageOfImages({ + categories, + boardId: selectedBoardId, + }) + ); + }, [categories, dispatch, selectedBoardId]); const handleEndReached = useMemo(() => { if (areMoreAvailable && !isLoading) { diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index b07ab487ae..b7fc0809a6 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -1,8 +1,6 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import { ImageDTO } from 'services/api'; import { imageUpserted } from './imagesSlice'; -import { imageUrlsReceived } from 'services/thunks/image'; type GalleryImageObjectFitType = 'contain' | 'cover'; diff --git a/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts b/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts index 0b2b9f0f58..25a3341532 100644 --- a/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts @@ -11,7 +11,6 @@ import { dateComparator } from 'common/util/dateComparator'; import { keyBy } from 'lodash-es'; import { imageDeleted, - imageMetadataReceived, imageUrlsReceived, receivedPageOfImages, } from 'services/thunks/image'; @@ -74,11 +73,21 @@ const imagesSlice = createSlice({ }); builder.addCase(receivedPageOfImages.fulfilled, (state, action) => { state.isLoading = false; + const { boardId, categories, imageOrigin, isIntermediate } = + action.meta.arg; + const { items, offset, limit, total } = action.payload; + imagesAdapter.upsertMany(state, items); + + if (!categories?.includes('general') || boardId) { + // need to skip updating the total images count if the images recieved were for a specific board + // TODO: this doesn't work when on the Asset tab/category... + return; + } + state.offset = offset; state.limit = limit; state.total = total; - imagesAdapter.upsertMany(state, items); }); builder.addCase(imageDeleted.pending, (state, action) => { // Image deleted diff --git a/invokeai/frontend/web/src/services/thunks/image.ts b/invokeai/frontend/web/src/services/thunks/image.ts index 88da7fbcb4..fe198cf6f9 100644 --- a/invokeai/frontend/web/src/services/thunks/image.ts +++ b/invokeai/frontend/web/src/services/thunks/image.ts @@ -148,7 +148,11 @@ export const receivedPageOfImages = createAppAsyncThunk( let queryArg: ReceivedImagesArg = {}; if (size(arg)) { - queryArg = { ...DEFAULT_IMAGES_LISTED_ARG, ...arg }; + queryArg = { + ...DEFAULT_IMAGES_LISTED_ARG, + offset: images.length, + ...arg, + }; } else { queryArg = { ...DEFAULT_IMAGES_LISTED_ARG, From 6ee0e197bb21e95befb0bb39007fa7936ecf528f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:42:30 +1000 Subject: [PATCH 070/110] feat(db): add `deleted_at` to `board_images` --- invokeai/app/services/board_image_record_storage.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index 45d21520f8..ae2f1a9895 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -95,6 +95,8 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- enforce one-to-many relationship between boards and images using PK -- (we can extend this to many-to-many later) + -- Soft delete, currently unused + deleted_at DATETIME, PRIMARY KEY (image_name), FOREIGN KEY (board_id) REFERENCES boards (board_id) ON DELETE CASCADE, FOREIGN KEY (image_name) REFERENCES images (image_name) ON DELETE CASCADE From 922319cb84a9aff5ba4d83014b2c20d300bc7e52 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:48:23 +1000 Subject: [PATCH 071/110] fix(ui): fix first added board doesn't show until refresh Had incorrect `invalidatesTags` array for the mutation. --- invokeai/frontend/web/src/services/apiSlice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/services/apiSlice.ts b/invokeai/frontend/web/src/services/apiSlice.ts index 9a1521ce5a..2d42931b0b 100644 --- a/invokeai/frontend/web/src/services/apiSlice.ts +++ b/invokeai/frontend/web/src/services/apiSlice.ts @@ -86,7 +86,7 @@ export const api = createApi({ method: 'POST', params: { board_name }, }), - invalidatesTags: ['Board'], + invalidatesTags: [{ id: 'Board', type: LIST }], }), updateBoard: build.mutation({ From 2ffead000c12158145f553b211237d5f23bb5c77 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:48:51 +1000 Subject: [PATCH 072/110] tidy(ui): remove `console.log()` --- .../middleware/listenerMiddleware/listeners/boardIdSelected.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts index 8e4667fd14..eab4389ceb 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts @@ -43,7 +43,6 @@ export const addBoardIdSelectedListener = () => { return; } - console.log('setting image'); dispatch(imageSelected(board.cover_image_name)); // if we haven't loaded one full page of images from this board, load more From a00ad6ac03da84519873d24f74578b050028df32 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:58:15 +1000 Subject: [PATCH 073/110] feat(ui): dropping image on `All Images` board removes it from board --- .../components/Boards/AllImagesBoard.tsx | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx index 51e95b64c4..e506c88e2d 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx @@ -1,10 +1,15 @@ -import { Flex, Icon, Text } from '@chakra-ui/react'; +import { Flex, Text } from '@chakra-ui/react'; import { FaImages } from 'react-icons/fa'; import { boardIdSelected } from '../../store/boardSlice'; import { useDispatch } from 'react-redux'; import { IAINoImageFallback } from 'common/components/IAIImageFallback'; import { AnimatePresence } from 'framer-motion'; import { SelectedItemOverlay } from '../SelectedItemOverlay'; +import { useCallback } from 'react'; +import { ImageDTO } from 'services/api'; +import { useRemoveImageFromBoardMutation } from 'services/apiSlice'; +import { useDroppable } from '@dnd-kit/core'; +import IAIDropOverlay from 'common/components/IAIDropOverlay'; const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => { const dispatch = useDispatch(); @@ -13,6 +18,33 @@ const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => { dispatch(boardIdSelected()); }; + const [removeImageFromBoard, { isLoading }] = + useRemoveImageFromBoardMutation(); + + const handleDrop = useCallback( + (droppedImage: ImageDTO) => { + if (!droppedImage.board_id) { + return; + } + removeImageFromBoard({ + board_id: droppedImage.board_id, + image_name: droppedImage.image_name, + }); + }, + [removeImageFromBoard] + ); + + const { + isOver, + setNodeRef, + active: isDropActive, + } = useDroppable({ + id: `board_droppable_all_images`, + data: { + handleDrop, + }, + }); + return ( { onClick={handleAllImagesBoardClick} > { {isSelected && } + + {isDropActive && } + Date: Thu, 22 Jun 2023 11:20:11 +1000 Subject: [PATCH 074/110] fix(ui): fix board's image list not updating when image removed from board --- .../middleware/listenerMiddleware/index.ts | 12 ++++++ .../listeners/imageRemovedFromBoard.ts | 40 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageRemovedFromBoard.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 4ec185bd83..cb641d00db 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -78,6 +78,10 @@ import { addImageAddedToBoardRejectedListener, } from './listeners/imageAddedToBoard'; import { addBoardIdSelectedListener } from './listeners/boardIdSelected'; +import { + addImageRemovedFromBoardFulfilledListener, + addImageRemovedFromBoardRejectedListener, +} from './listeners/imageRemovedFromBoard'; export const listenerMiddleware = createListenerMiddleware(); @@ -97,6 +101,12 @@ export type AppListenerEffect = ListenerEffect< AppDispatch >; +/** + * The RTK listener middleware is a lightweight alternative sagas/observables. + * + * Most side effect logic should live in a listener. + */ + // Image uploaded addImageUploadedFulfilledListener(); addImageUploadedRejectedListener(); @@ -192,4 +202,6 @@ addUpdateImageUrlsOnConnectListener(); // Boards addImageAddedToBoardFulfilledListener(); addImageAddedToBoardRejectedListener(); +addImageRemovedFromBoardFulfilledListener(); +addImageRemovedFromBoardRejectedListener(); addBoardIdSelectedListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageRemovedFromBoard.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageRemovedFromBoard.ts new file mode 100644 index 0000000000..40847ade3a --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageRemovedFromBoard.ts @@ -0,0 +1,40 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { imageMetadataReceived } from 'services/thunks/image'; +import { api } from 'services/apiSlice'; + +const moduleLog = log.child({ namespace: 'boards' }); + +export const addImageRemovedFromBoardFulfilledListener = () => { + startAppListening({ + matcher: api.endpoints.removeImageFromBoard.matchFulfilled, + effect: (action, { getState, dispatch }) => { + const { board_id, image_name } = action.meta.arg.originalArgs; + + moduleLog.debug( + { data: { board_id, image_name } }, + 'Image added to board' + ); + + dispatch( + imageMetadataReceived({ + imageName: image_name, + }) + ); + }, + }); +}; + +export const addImageRemovedFromBoardRejectedListener = () => { + startAppListening({ + matcher: api.endpoints.removeImageFromBoard.matchRejected, + effect: (action, { getState, dispatch }) => { + const { board_id, image_name } = action.meta.arg.originalArgs; + + moduleLog.debug( + { data: { board_id, image_name } }, + 'Problem adding image to board' + ); + }, + }); +}; From 79f0c4d3c4b918e49fd2a84cf6778482e6634b24 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 11:21:12 +1000 Subject: [PATCH 075/110] feat(ui): add remove from board to image context menu --- .../gallery/components/HoverableImage.tsx | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index 86ec3436f0..2482e5a1d0 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -34,9 +34,8 @@ import { useAppToaster } from 'app/components/Toaster'; import { ImageDTO } from 'services/api'; import { useDraggable } from '@dnd-kit/core'; import { DeleteImageContext } from 'app/contexts/DeleteImageContext'; -import { imageAddedToBoard } from '../../../services/thunks/board'; -import { setUpdateBoardModalOpen } from '../store/boardSlice'; import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext'; +import { useRemoveImageFromBoardMutation } from 'services/apiSlice'; export const selector = createSelector( [gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector], @@ -110,6 +109,8 @@ const HoverableImage = (props: HoverableImageProps) => { }, }); + const [removeFromBoard] = useRemoveImageFromBoardMutation(); + const handleMouseOver = () => setIsHovered(true); const handleMouseOut = () => setIsHovered(false); @@ -176,6 +177,13 @@ const HoverableImage = (props: HoverableImageProps) => { onClickAddToBoard(image); }, [image, onClickAddToBoard]); + const handleRemoveFromBoard = useCallback(() => { + if (!image.board_id) { + return; + } + removeFromBoard({ board_id: image.board_id, image_name: image.image_name }); + }, [image.board_id, image.image_name, removeFromBoard]); + const handleOpenInNewTab = () => { window.open(image.image_url, '_blank'); }; @@ -255,6 +263,14 @@ const HoverableImage = (props: HoverableImageProps) => { } onClickCapture={handleAddToBoard}> {image.board_id ? 'Change Board' : 'Add to Board'} + {image.board_id && ( + } + onClickCapture={handleRemoveFromBoard} + > + Remove from Board + + )} } From 3c04340f3f649a3f1720380c29de43069531f7ac Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 11:31:51 +1000 Subject: [PATCH 076/110] tidy(ui): tidy up update image board modal --- .../web/src/app/contexts/AddImageToBoardContext.tsx | 3 --- .../gallery/components/Boards/UpdateImageBoardModal.tsx | 9 ++------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx index d29c1c8a48..f5a856d3d8 100644 --- a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx +++ b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx @@ -1,8 +1,6 @@ import { useDisclosure } from '@chakra-ui/react'; -import { useAppDispatch } from 'app/store/storeHooks'; import { PropsWithChildren, createContext, useCallback, useState } from 'react'; import { ImageDTO } from 'services/api'; -import { imageAddedToBoard } from '../../services/thunks/board'; import { useAddImageToBoardMutation } from 'services/apiSlice'; export type ImageUsage = { @@ -41,7 +39,6 @@ type Props = PropsWithChildren; export const AddImageToBoardContextProvider = (props: Props) => { const [imageToMove, setImageToMove] = useState(); - const dispatch = useAppDispatch(); const { isOpen, onOpen, onClose } = useDisclosure(); const [addImageToBoard, result] = useAddImageToBoardMutation(); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx index edd4d215af..b16bddd6b4 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx @@ -6,9 +6,7 @@ import { AlertDialogHeader, AlertDialogOverlay, Box, - Divider, Flex, - Select, Spinner, Text, } from '@chakra-ui/react'; @@ -16,9 +14,6 @@ import IAIButton from 'common/components/IAIButton'; import { memo, useContext, useRef, useState } from 'react'; import { AddImageToBoardContext } from '../../../../app/contexts/AddImageToBoardContext'; -import { useSelector } from 'react-redux'; -import { selectBoardsAll } from '../../store/boardSlice'; -import IAISelect from '../../../../common/components/IAISelect'; import IAIMantineSelect from 'common/components/IAIMantineSelect'; import { useListAllBoardsQuery } from 'services/apiSlice'; @@ -46,7 +41,7 @@ const UpdateImageBoardModal = () => { - Move Image to Board + {currentBoard ? 'Move Image to Board' : 'Add Image to Board'} @@ -86,7 +81,7 @@ const UpdateImageBoardModal = () => { }} ml={3} > - Add to Board + {currentBoard ? 'Move' : 'Add'} From 10008859a43c872fded94381ae1349b4458dfbcd Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 11:34:18 +1000 Subject: [PATCH 077/110] tidy(ui): remove all refs to boards thunks --- .../socketio/socketInvocationComplete.ts | 1 - .../frontend/web/src/services/thunks/board.ts | 53 ------------------- 2 files changed, 54 deletions(-) delete mode 100644 invokeai/frontend/web/src/services/thunks/board.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts index bef0a2ccdb..c204f0bdfb 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts @@ -9,7 +9,6 @@ import { imageMetadataReceived } from 'services/thunks/image'; import { sessionCanceled } from 'services/thunks/session'; import { isImageOutput } from 'services/types/guards'; import { progressImageSet } from 'features/system/store/systemSlice'; -import { imageAddedToBoard } from '../../../../../../services/thunks/board'; import { api } from 'services/apiSlice'; const moduleLog = log.child({ namespace: 'socketio' }); diff --git a/invokeai/frontend/web/src/services/thunks/board.ts b/invokeai/frontend/web/src/services/thunks/board.ts deleted file mode 100644 index 03c59dba10..0000000000 --- a/invokeai/frontend/web/src/services/thunks/board.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { createAppAsyncThunk } from '../../app/store/storeUtils'; -import { BoardsService } from '../api'; - -/** - * `BoardsService.listBoards()` thunk - */ -export const receivedBoards = createAppAsyncThunk( - 'api/receivedBoards', - async (_, { getState }) => { - const response = await BoardsService.listBoards({}); - return response; - } -); - -type BoardCreatedArg = Parameters<(typeof BoardsService)['createBoard']>[0]; - -export const boardCreated = createAppAsyncThunk( - 'api/boardCreated', - async (arg: BoardCreatedArg) => { - const response = await BoardsService.createBoard(arg); - return response; - } -); - -export const boardDeleted = createAppAsyncThunk( - 'api/boardDeleted', - async (boardId: string) => { - await BoardsService.deleteBoard({ boardId }); - return boardId; - } -); - -type BoardUpdatedArg = Parameters<(typeof BoardsService)['updateBoard']>[0]; - -export const boardUpdated = createAppAsyncThunk( - 'api/boardUpdated', - async (arg: BoardUpdatedArg) => { - const response = await BoardsService.updateBoard(arg); - return response; - } -); - -type ImageAddedToBoardArg = Parameters< - (typeof BoardsService)['createBoardImage'] ->[0]['requestBody']; - -export const imageAddedToBoard = createAppAsyncThunk( - 'api/imageAddedToBoard', - async (arg: ImageAddedToBoardArg) => { - const response = await BoardsService.createBoardImage(arg); - return response; - } -); From 285195bf72c788406829277c541d8ea2f535a74d Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 12:41:41 +1000 Subject: [PATCH 078/110] feat(api): add `get_board` route --- invokeai/app/api/routers/boards.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index a6f226fef8..55cd7c8ca2 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -30,6 +30,19 @@ async def create_board( raise HTTPException(status_code=500, detail="Failed to create board") +@boards_router.get("/{board_id}", operation_id="get_board", response_model=BoardDTO) +async def get_board( + board_id: str = Path(description="The id of board to get"), +) -> BoardDTO: + """Gets a board""" + + try: + result = ApiDependencies.invoker.services.boards.get_dto(board_id=board_id) + return result + except Exception as e: + raise HTTPException(status_code=404, detail="Board not found") + + @boards_router.patch( "/{board_id}", operation_id="update_board", From 19a6e5dad877e67ed938361c6de178a34778faf2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 12:42:02 +1000 Subject: [PATCH 079/110] chore(ui): regen api client --- .../services/api/services/BoardsService.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/invokeai/frontend/web/src/services/api/services/BoardsService.ts b/invokeai/frontend/web/src/services/api/services/BoardsService.ts index bda2b70e75..236c765cb9 100644 --- a/invokeai/frontend/web/src/services/api/services/BoardsService.ts +++ b/invokeai/frontend/web/src/services/api/services/BoardsService.ts @@ -78,6 +78,32 @@ export class BoardsService { }); } + /** + * Get Board + * Gets a board + * @returns BoardDTO Successful Response + * @throws ApiError + */ + public static getBoard({ + boardId, + }: { + /** + * The id of board to get + */ + boardId: string, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/boards/{board_id}', + path: { + 'board_id': boardId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** * Delete Board * Deletes a board From 6779f1a5ad2f669f3976249f32d8e8a739b1a23c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:11:25 +1000 Subject: [PATCH 080/110] fix(db): update models for boards w/ nullable `deleted_at` --- invokeai/app/services/board_image_record_storage.py | 4 ++-- invokeai/app/services/models/board_record.py | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index ae2f1a9895..7aff41860c 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -93,10 +93,10 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- updated via trigger updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), - -- enforce one-to-many relationship between boards and images using PK - -- (we can extend this to many-to-many later) -- Soft delete, currently unused deleted_at DATETIME, + -- enforce one-to-many relationship between boards and images using PK + -- (we can extend this to many-to-many later) PRIMARY KEY (image_name), FOREIGN KEY (board_id) REFERENCES boards (board_id) ON DELETE CASCADE, FOREIGN KEY (image_name) REFERENCES images (image_name) ON DELETE CASCADE diff --git a/invokeai/app/services/models/board_record.py b/invokeai/app/services/models/board_record.py index 325ddd094e..bf5401b209 100644 --- a/invokeai/app/services/models/board_record.py +++ b/invokeai/app/services/models/board_record.py @@ -19,6 +19,10 @@ class BoardRecord(BaseModel): description="The updated timestamp of the board." ) """The updated timestamp of the image.""" + deleted_at: Union[datetime, str, None] = Field( + description="The deleted timestamp of the board." + ) + """The updated timestamp of the image.""" cover_image_name: Optional[str] = Field( description="The name of the cover image of the board." ) @@ -46,7 +50,7 @@ def deserialize_board_record(board_dict: dict) -> BoardRecord: cover_image_name = board_dict.get("cover_image_name", "unknown") created_at = board_dict.get("created_at", get_iso_timestamp()) updated_at = board_dict.get("updated_at", get_iso_timestamp()) - # deleted_at = board_dict.get("deleted_at", get_iso_timestamp()) + deleted_at = board_dict.get("deleted_at", get_iso_timestamp()) return BoardRecord( board_id=board_id, @@ -54,5 +58,5 @@ def deserialize_board_record(board_dict: dict) -> BoardRecord: cover_image_name=cover_image_name, created_at=created_at, updated_at=updated_at, - # deleted_at=deleted_at, + deleted_at=deleted_at, ) From 2d889e133ddff03d4745df52a66fab69eca6c2d5 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:11:47 +1000 Subject: [PATCH 081/110] chore(ui): regen api client --- invokeai/frontend/web/src/services/api/models/BoardDTO.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/invokeai/frontend/web/src/services/api/models/BoardDTO.ts b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts index ee3c29a797..bbcc6f1dd6 100644 --- a/invokeai/frontend/web/src/services/api/models/BoardDTO.ts +++ b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts @@ -22,6 +22,10 @@ export type BoardDTO = { * The updated timestamp of the board. */ updated_at: string; + /** + * The deleted timestamp of the board. + */ + deleted_at?: string; /** * The name of the board's cover image. */ From 9838dda1b76f0bd6c347ceab663ad2385e528537 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sat, 17 Jun 2023 21:14:37 +1200 Subject: [PATCH 082/110] chore: Update model config type names --- .../model_management/models/controlnet.py | 4 ++-- .../backend/model_management/models/lora.py | 2 +- .../models/stable_diffusion.py | 18 +++++++++--------- .../models/textual_inversion.py | 2 +- .../backend/model_management/models/vae.py | 3 ++- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/invokeai/backend/model_management/models/controlnet.py b/invokeai/backend/model_management/models/controlnet.py index d75c55010a..687afbffbd 100644 --- a/invokeai/backend/model_management/models/controlnet.py +++ b/invokeai/backend/model_management/models/controlnet.py @@ -18,7 +18,7 @@ class ControlNetModel(ModelBase): #model_class: Type #model_size: int - class Config(ModelConfigBase): + class ControlNetModelConfig(ModelConfigBase): format: Union[Literal["checkpoint"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): @@ -82,6 +82,6 @@ class ControlNetModel(ModelBase): base_model: BaseModelType, ) -> str: if cls.detect_format(model_path) != "diffusers": - raise NotImlemetedError("Checkpoint controlnet models currently unsupported") + raise NotImplementedError("Checkpoint controlnet models currently unsupported") else: return model_path diff --git a/invokeai/backend/model_management/models/lora.py b/invokeai/backend/model_management/models/lora.py index bcf3224ece..60865817b9 100644 --- a/invokeai/backend/model_management/models/lora.py +++ b/invokeai/backend/model_management/models/lora.py @@ -15,7 +15,7 @@ from ..lora import LoRAModel as LoRAModelRaw class LoRAModel(ModelBase): #model_size: int - class Config(ModelConfigBase): + class LoraModelConfig(ModelConfigBase): format: Union[Literal["lycoris"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): diff --git a/invokeai/backend/model_management/models/stable_diffusion.py b/invokeai/backend/model_management/models/stable_diffusion.py index bd519c88c8..9856069ea5 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -22,12 +22,12 @@ from omegaconf import OmegaConf class StableDiffusion1Model(DiffusersModel): - class DiffusersConfig(ModelConfigBase): + class StableDiffusion1DiffusersModelConfig(ModelConfigBase): format: Literal["diffusers"] vae: Optional[str] = Field(None) variant: ModelVariantType - class CheckpointConfig(ModelConfigBase): + class StableDiffusion1CheckpointModelConfig(ModelConfigBase): format: Literal["checkpoint"] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) @@ -107,7 +107,7 @@ class StableDiffusion1Model(DiffusersModel): ) -> str: assert model_path == config.path - if isinstance(config, cls.CheckpointConfig): + if isinstance(config, cls.CheckpointModelConfig): return _convert_ckpt_and_cache( version=BaseModelType.StableDiffusion1, model_config=config, @@ -120,14 +120,14 @@ class StableDiffusion1Model(DiffusersModel): class StableDiffusion2Model(DiffusersModel): # TODO: check that configs overwriten properly - class DiffusersConfig(ModelConfigBase): + class StableDiffusion2DiffusersModelConfig(ModelConfigBase): format: Literal["diffusers"] vae: Optional[str] = Field(None) variant: ModelVariantType prediction_type: SchedulerPredictionType upcast_attention: bool - class CheckpointConfig(ModelConfigBase): + class StableDiffusion2CheckpointModelConfig(ModelConfigBase): format: Literal["checkpoint"] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) @@ -220,7 +220,7 @@ class StableDiffusion2Model(DiffusersModel): ) -> str: assert model_path == config.path - if isinstance(config, cls.CheckpointConfig): + if isinstance(config, cls.CheckpointModelConfig): return _convert_ckpt_and_cache( version=BaseModelType.StableDiffusion2, model_config=config, @@ -256,7 +256,7 @@ def _select_ckpt_config(version: BaseModelType, variant: ModelVariantType): # TODO: rework def _convert_ckpt_and_cache( version: BaseModelType, - model_config: Union[StableDiffusion1Model.CheckpointConfig, StableDiffusion2Model.CheckpointConfig], + model_config: Union[StableDiffusion1Model.StableDiffusion1CheckpointModelConfig, StableDiffusion2Model.StableDiffusion2CheckpointModelConfig], output_path: str, ) -> str: """ @@ -281,8 +281,8 @@ def _convert_ckpt_and_cache( prediction_type = SchedulerPredictionType.Epsilon elif version == BaseModelType.StableDiffusion2: - upcast_attention = config.upcast_attention - prediction_type = config.prediction_type + upcast_attention = model_config.upcast_attention + prediction_type = model_config.prediction_type else: raise Exception(f"Unknown model provided: {version}") diff --git a/invokeai/backend/model_management/models/textual_inversion.py b/invokeai/backend/model_management/models/textual_inversion.py index 66847f53eb..453a8ad671 100644 --- a/invokeai/backend/model_management/models/textual_inversion.py +++ b/invokeai/backend/model_management/models/textual_inversion.py @@ -15,7 +15,7 @@ from ..lora import TextualInversionModel as TextualInversionModelRaw class TextualInversionModel(ModelBase): #model_size: int - class Config(ModelConfigBase): + class TextualInversionModelConfig(ModelConfigBase): format: None def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): diff --git a/invokeai/backend/model_management/models/vae.py b/invokeai/backend/model_management/models/vae.py index 1edb57ccc4..f285648323 100644 --- a/invokeai/backend/model_management/models/vae.py +++ b/invokeai/backend/model_management/models/vae.py @@ -1,5 +1,6 @@ import os import torch +import safetensors from pathlib import Path from typing import Optional, Union, Literal from .base import ( @@ -22,7 +23,7 @@ class VaeModel(ModelBase): #vae_class: Type #model_size: int - class Config(ModelConfigBase): + class VAEModelConfig(ModelConfigBase): format: Union[Literal["checkpoint"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): From 663f4935f58c9097186288a82ae7ed76aefe9141 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sat, 17 Jun 2023 21:29:32 +1200 Subject: [PATCH 083/110] chore: Rebuild API --- .../frontend/web/src/services/api/index.ts | 20 ++ .../src/services/api/models/AddInvocation.ts | 1 - .../services/api/models/Body_upload_image.ts | 1 - .../models/CannyImageProcessorInvocation.ts | 1 - .../src/services/api/models/CkptModelInfo.ts | 1 - .../web/src/services/api/models/ClipField.ts | 3 + .../services/api/models/CollectInvocation.ts | 1 - .../api/models/CollectInvocationOutput.ts | 1 - .../web/src/services/api/models/ColorField.ts | 1 - .../services/api/models/CompelInvocation.ts | 1 - .../src/services/api/models/CompelOutput.ts | 1 - .../services/api/models/ConditioningField.ts | 1 - .../ContentShuffleImageProcessorInvocation.ts | 1 - .../src/services/api/models/ControlField.ts | 1 - .../api/models/ControlNetInvocation.ts | 1 - .../api/models/ControlNetModelConfig.ts | 13 ++ .../src/services/api/models/ControlOutput.ts | 1 - .../services/api/models/CreateModelRequest.ts | 1 - .../api/models/CvInpaintInvocation.ts | 1 - .../services/api/models/DiffusersModelInfo.ts | 1 - .../services/api/models/DivideInvocation.ts | 1 - .../api/models/DynamicPromptInvocation.ts | 1 - .../web/src/services/api/models/Edge.ts | 1 - .../src/services/api/models/EdgeConnection.ts | 1 - .../api/models/FloatCollectionOutput.ts | 1 - .../api/models/FloatLinearRangeInvocation.ts | 1 - .../src/services/api/models/FloatOutput.ts | 1 - .../web/src/services/api/models/Graph.ts | 5 +- .../api/models/GraphExecutionState.ts | 5 +- .../services/api/models/GraphInvocation.ts | 1 - .../api/models/GraphInvocationOutput.ts | 1 - .../api/models/HTTPValidationError.ts | 1 - .../api/models/HedImageProcessorInvocation.ts | 1 - .../api/models/ImageBlurInvocation.ts | 1 - .../api/models/ImageChannelInvocation.ts | 1 - .../api/models/ImageConvertInvocation.ts | 1 - .../api/models/ImageCropInvocation.ts | 1 - .../web/src/services/api/models/ImageDTO.ts | 1 - .../web/src/services/api/models/ImageField.ts | 1 - .../api/models/ImageInverseLerpInvocation.ts | 1 - .../api/models/ImageLerpInvocation.ts | 1 - .../src/services/api/models/ImageMetadata.ts | 1 - .../api/models/ImageMultiplyInvocation.ts | 1 - .../src/services/api/models/ImageOutput.ts | 1 - .../api/models/ImagePasteInvocation.ts | 1 - .../api/models/ImageProcessorInvocation.ts | 1 - .../services/api/models/ImageRecordChanges.ts | 1 - .../api/models/ImageResizeInvocation.ts | 1 - .../api/models/ImageScaleInvocation.ts | 1 - .../api/models/ImageToImageInvocation.ts | 76 ++++++ .../api/models/ImageToLatentsInvocation.ts | 1 - .../src/services/api/models/ImageUrlsDTO.ts | 1 - .../api/models/InfillColorInvocation.ts | 1 - .../api/models/InfillPatchMatchInvocation.ts | 1 - .../api/models/InfillTileInvocation.ts | 1 - .../services/api/models/InpaintInvocation.ts | 1 - .../api/models/IntCollectionOutput.ts | 1 - .../web/src/services/api/models/IntOutput.ts | 1 - .../services/api/models/IterateInvocation.ts | 1 - .../api/models/IterateInvocationOutput.ts | 1 - .../src/services/api/models/LatentsField.ts | 1 - .../src/services/api/models/LatentsOutput.ts | 1 - .../api/models/LatentsToImageInvocation.ts | 1 - .../api/models/LatentsToLatentsInvocation.ts | 1 - .../LineartAnimeImageProcessorInvocation.ts | 1 - .../models/LineartImageProcessorInvocation.ts | 1 - .../api/models/LoadImageInvocation.ts | 1 - .../web/src/services/api/models/LoraInfo.ts | 3 + .../api/models/LoraLoaderInvocation.ts | 3 + .../services/api/models/LoraLoaderOutput.ts | 3 + .../services/api/models/LoraModelConfig.ts | 13 ++ .../api/models/MaskFromAlphaInvocation.ts | 1 - .../web/src/services/api/models/MaskOutput.ts | 1 - .../MediapipeFaceProcessorInvocation.ts | 1 - .../MidasDepthImageProcessorInvocation.ts | 1 - .../models/MlsdImageProcessorInvocation.ts | 1 - .../web/src/services/api/models/ModelInfo.ts | 3 + .../services/api/models/ModelLoaderOutput.ts | 3 + .../web/src/services/api/models/ModelsList.ts | 15 +- .../services/api/models/MultiplyInvocation.ts | 1 - .../services/api/models/NoiseInvocation.ts | 1 - .../src/services/api/models/NoiseOutput.ts | 1 - .../NormalbaeImageProcessorInvocation.ts | 1 - .../OffsetPaginatedResults_ImageDTO_.ts | 1 - .../OpenposeImageProcessorInvocation.ts | 1 - .../PaginatedResults_GraphExecutionState_.ts | 1 - .../api/models/ParamFloatInvocation.ts | 1 - .../services/api/models/ParamIntInvocation.ts | 1 - .../models/PidiImageProcessorInvocation.ts | 1 - .../api/models/PromptCollectionOutput.ts | 1 - .../src/services/api/models/PromptOutput.ts | 1 - .../api/models/RandomIntInvocation.ts | 1 - .../api/models/RandomRangeInvocation.ts | 1 - .../services/api/models/RangeInvocation.ts | 1 - .../api/models/RangeOfSizeInvocation.ts | 1 - .../api/models/ResizeLatentsInvocation.ts | 1 - .../api/models/RestoreFaceInvocation.ts | 1 - .../api/models/SD1ModelLoaderInvocation.ts | 3 + .../api/models/SD2ModelLoaderInvocation.ts | 3 + .../api/models/ScaleLatentsInvocation.ts | 1 - .../api/models/ShowImageInvocation.ts | 1 - .../StableDiffusion1CheckpointModelConfig.ts | 17 ++ .../StableDiffusion1DiffusersModelConfig.ts | 16 ++ .../StableDiffusion2CheckpointModelConfig.ts | 20 ++ .../StableDiffusion2DiffusersModelConfig.ts | 19 ++ .../api/models/StepParamEasingInvocation.ts | 1 - .../services/api/models/SubtractInvocation.ts | 1 - .../api/models/TextToImageInvocation.ts | 64 +++++ .../api/models/TextToLatentsInvocation.ts | 1 - .../api/models/TextualInversionModelConfig.ts | 13 ++ .../web/src/services/api/models/UNetField.ts | 3 + .../services/api/models/UpscaleInvocation.ts | 1 - .../src/services/api/models/VAEModelConfig.ts | 13 ++ .../web/src/services/api/models/VaeField.ts | 3 + .../web/src/services/api/models/VaeRepo.ts | 1 - .../services/api/models/ValidationError.ts | 1 - .../ZoeDepthImageProcessorInvocation.ts | 1 - .../services/api/services/ImagesService.ts | 156 ++++++++----- .../services/api/services/ModelsService.ts | 31 ++- .../services/api/services/SessionsService.ts | 219 ++++++++++-------- 120 files changed, 576 insertions(+), 262 deletions(-) create mode 100644 invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts create mode 100644 invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index acb7a411f7..f3aec17eb6 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -8,10 +8,13 @@ export type { OpenAPIConfig } from './core/OpenAPI'; export type { AddInvocation } from './models/AddInvocation'; export type { BaseModelType } from './models/BaseModelType'; +<<<<<<< HEAD export type { BoardChanges } from './models/BoardChanges'; export type { BoardDTO } from './models/BoardDTO'; export type { Body_create_board_image } from './models/Body_create_board_image'; export type { Body_remove_board_image } from './models/Body_remove_board_image'; +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) export type { Body_upload_image } from './models/Body_upload_image'; export type { CannyImageProcessorInvocation } from './models/CannyImageProcessorInvocation'; export type { CkptModelInfo } from './models/CkptModelInfo'; @@ -25,6 +28,7 @@ export type { ConditioningField } from './models/ConditioningField'; export type { ContentShuffleImageProcessorInvocation } from './models/ContentShuffleImageProcessorInvocation'; export type { ControlField } from './models/ControlField'; export type { ControlNetInvocation } from './models/ControlNetInvocation'; +export type { ControlNetModelConfig } from './models/ControlNetModelConfig'; export type { ControlOutput } from './models/ControlOutput'; export type { CreateModelRequest } from './models/CreateModelRequest'; export type { CvInpaintInvocation } from './models/CvInpaintInvocation'; @@ -87,6 +91,10 @@ export type { LoadImageInvocation } from './models/LoadImageInvocation'; export type { LoraInfo } from './models/LoraInfo'; export type { LoraLoaderInvocation } from './models/LoraLoaderInvocation'; export type { LoraLoaderOutput } from './models/LoraLoaderOutput'; +<<<<<<< HEAD +======= +export type { LoraModelConfig } from './models/LoraModelConfig'; +>>>>>>> 76dd749b1 (chore: Rebuild API) export type { MaskFromAlphaInvocation } from './models/MaskFromAlphaInvocation'; export type { MaskOutput } from './models/MaskOutput'; export type { MediapipeFaceProcessorInvocation } from './models/MediapipeFaceProcessorInvocation'; @@ -123,13 +131,25 @@ export type { SchedulerPredictionType } from './models/SchedulerPredictionType'; export type { SD1ModelLoaderInvocation } from './models/SD1ModelLoaderInvocation'; export type { SD2ModelLoaderInvocation } from './models/SD2ModelLoaderInvocation'; export type { ShowImageInvocation } from './models/ShowImageInvocation'; +export type { StableDiffusion1CheckpointModelConfig } from './models/StableDiffusion1CheckpointModelConfig'; +export type { StableDiffusion1DiffusersModelConfig } from './models/StableDiffusion1DiffusersModelConfig'; +export type { StableDiffusion2CheckpointModelConfig } from './models/StableDiffusion2CheckpointModelConfig'; +export type { StableDiffusion2DiffusersModelConfig } from './models/StableDiffusion2DiffusersModelConfig'; export type { StepParamEasingInvocation } from './models/StepParamEasingInvocation'; export type { SubModelType } from './models/SubModelType'; export type { SubtractInvocation } from './models/SubtractInvocation'; export type { TextToLatentsInvocation } from './models/TextToLatentsInvocation'; +<<<<<<< HEAD export type { UNetField } from './models/UNetField'; export type { UpscaleInvocation } from './models/UpscaleInvocation'; export type { VaeField } from './models/VaeField'; +======= +export type { TextualInversionModelConfig } from './models/TextualInversionModelConfig'; +export type { UNetField } from './models/UNetField'; +export type { UpscaleInvocation } from './models/UpscaleInvocation'; +export type { VaeField } from './models/VaeField'; +export type { VAEModelConfig } from './models/VAEModelConfig'; +>>>>>>> 76dd749b1 (chore: Rebuild API) export type { VaeRepo } from './models/VaeRepo'; export type { ValidationError } from './models/ValidationError'; export type { ZoeDepthImageProcessorInvocation } from './models/ZoeDepthImageProcessorInvocation'; diff --git a/invokeai/frontend/web/src/services/api/models/AddInvocation.ts b/invokeai/frontend/web/src/services/api/models/AddInvocation.ts index e9671a918f..b7c1c88187 100644 --- a/invokeai/frontend/web/src/services/api/models/AddInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/AddInvocation.ts @@ -24,4 +24,3 @@ export type AddInvocation = { */ 'b'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts b/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts index b81146d3ab..fd26ed49e0 100644 --- a/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts +++ b/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts @@ -5,4 +5,3 @@ export type Body_upload_image = { file: Blob; }; - diff --git a/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts index d5203867ac..3f1a5b3d46 100644 --- a/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts @@ -30,4 +30,3 @@ export type CannyImageProcessorInvocation = { */ high_threshold?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts b/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts index cfa4357725..464474736f 100644 --- a/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts @@ -37,4 +37,3 @@ export type CkptModelInfo = { */ height?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ClipField.ts b/invokeai/frontend/web/src/services/api/models/ClipField.ts index f9ef2cc683..f267fe85d9 100644 --- a/invokeai/frontend/web/src/services/api/models/ClipField.ts +++ b/invokeai/frontend/web/src/services/api/models/ClipField.ts @@ -19,4 +19,7 @@ export type ClipField = { */ loras: Array; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts b/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts index f190ab7073..a0fe38613c 100644 --- a/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts @@ -24,4 +24,3 @@ export type CollectInvocation = { */ collection?: Array; }; - diff --git a/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts index a5976242ea..62c785f374 100644 --- a/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts @@ -12,4 +12,3 @@ export type CollectInvocationOutput = { */ collection: Array; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ColorField.ts b/invokeai/frontend/web/src/services/api/models/ColorField.ts index e0a609ec12..25167433d4 100644 --- a/invokeai/frontend/web/src/services/api/models/ColorField.ts +++ b/invokeai/frontend/web/src/services/api/models/ColorField.ts @@ -20,4 +20,3 @@ export type ColorField = { */ 'a': number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts b/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts index dd381ef22c..642e11023b 100644 --- a/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts @@ -26,4 +26,3 @@ export type CompelInvocation = { */ clip?: ClipField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/CompelOutput.ts b/invokeai/frontend/web/src/services/api/models/CompelOutput.ts index 94f1fcb282..b42ab73c74 100644 --- a/invokeai/frontend/web/src/services/api/models/CompelOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/CompelOutput.ts @@ -14,4 +14,3 @@ export type CompelOutput = { */ conditioning?: ConditioningField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ConditioningField.ts b/invokeai/frontend/web/src/services/api/models/ConditioningField.ts index 7e53a63b42..da227dd4f9 100644 --- a/invokeai/frontend/web/src/services/api/models/ConditioningField.ts +++ b/invokeai/frontend/web/src/services/api/models/ConditioningField.ts @@ -8,4 +8,3 @@ export type ConditioningField = { */ conditioning_name: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts index e3f67ec9be..43f6100db8 100644 --- a/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts @@ -42,4 +42,3 @@ export type ContentShuffleImageProcessorInvocation = { */ 'f'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ControlField.ts b/invokeai/frontend/web/src/services/api/models/ControlField.ts index 0479684d2c..8de332b82d 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlField.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlField.ts @@ -26,4 +26,3 @@ export type ControlField = { */ end_step_percent: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts b/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts index 42268b8295..31e22a1bba 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts @@ -38,4 +38,3 @@ export type ControlNetInvocation = { */ end_step_percent?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts b/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts new file mode 100644 index 0000000000..109fc39b7d --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; + +export type ControlNetModelConfig = { + path: string; + description?: string; + format: ('checkpoint' | 'diffusers'); + default?: boolean; + error?: ModelError; +}; diff --git a/invokeai/frontend/web/src/services/api/models/ControlOutput.ts b/invokeai/frontend/web/src/services/api/models/ControlOutput.ts index a3cc5530c1..625d8f670d 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlOutput.ts @@ -14,4 +14,3 @@ export type ControlOutput = { */ control?: ControlField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts b/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts index 0b0f52b8fe..80976f126f 100644 --- a/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts +++ b/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts @@ -15,4 +15,3 @@ export type CreateModelRequest = { */ info: (CkptModelInfo | DiffusersModelInfo); }; - diff --git a/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts index 874df93c30..4f1c33483e 100644 --- a/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts @@ -26,4 +26,3 @@ export type CvInpaintInvocation = { */ mask?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts b/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts index 4e722ddb80..ea175e351a 100644 --- a/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts @@ -31,4 +31,3 @@ export type DiffusersModelInfo = { */ path?: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts b/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts index fd5b3475ae..5b37dea710 100644 --- a/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts @@ -24,4 +24,3 @@ export type DivideInvocation = { */ 'b'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts b/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts index f7323a489b..79dc958d0f 100644 --- a/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts @@ -28,4 +28,3 @@ export type DynamicPromptInvocation = { */ combinatorial?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/Edge.ts b/invokeai/frontend/web/src/services/api/models/Edge.ts index bba275cb26..e72108f74a 100644 --- a/invokeai/frontend/web/src/services/api/models/Edge.ts +++ b/invokeai/frontend/web/src/services/api/models/Edge.ts @@ -14,4 +14,3 @@ export type Edge = { */ destination: EdgeConnection; }; - diff --git a/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts b/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts index ecbddccd76..ab4c6d354b 100644 --- a/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts +++ b/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts @@ -12,4 +12,3 @@ export type EdgeConnection = { */ field: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts b/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts index a3f08247a4..fb9e4164e6 100644 --- a/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts @@ -12,4 +12,3 @@ export type FloatCollectionOutput = { */ collection?: Array; }; - diff --git a/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts index e0fd4a1caa..a9e67d8135 100644 --- a/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts @@ -28,4 +28,3 @@ export type FloatLinearRangeInvocation = { */ steps?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/FloatOutput.ts b/invokeai/frontend/web/src/services/api/models/FloatOutput.ts index 2331936b30..db2784ed9f 100644 --- a/invokeai/frontend/web/src/services/api/models/FloatOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/FloatOutput.ts @@ -12,4 +12,3 @@ export type FloatOutput = { */ param?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index e148954f16..3f51247701 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -73,10 +73,13 @@ export type Graph = { /** * The nodes in this graph */ +<<<<<<< HEAD nodes?: Record; +======= + nodes?: Record; +>>>>>>> 76dd749b1 (chore: Rebuild API) /** * The connections between nodes and their fields in this graph */ edges?: Array; }; - diff --git a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts index 602e7a2ebc..24ec8c9d6d 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts @@ -48,7 +48,11 @@ export type GraphExecutionState = { /** * The results of node executions */ +<<<<<<< HEAD results: Record; +======= + results: Record; +>>>>>>> 76dd749b1 (chore: Rebuild API) /** * Errors raised when executing nodes */ @@ -62,4 +66,3 @@ export type GraphExecutionState = { */ source_prepared_mapping: Record>; }; - diff --git a/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts b/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts index 8512faae74..a7e3d6c948 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts @@ -22,4 +22,3 @@ export type GraphInvocation = { */ graph?: Graph; }; - diff --git a/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts index af0aae3edb..219a4a675d 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts @@ -8,4 +8,3 @@ export type GraphInvocationOutput = { type: 'graph_output'; }; - diff --git a/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts b/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts index 5e13adc4e5..69908c3bba 100644 --- a/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts +++ b/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts @@ -7,4 +7,3 @@ import type { ValidationError } from './ValidationError'; export type HTTPValidationError = { detail?: Array; }; - diff --git a/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts index 1132012c5a..387e8c8634 100644 --- a/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts @@ -34,4 +34,3 @@ export type HedImageProcessorInvocation = { */ scribble?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts index 3ba86d8fab..6466efcd82 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts @@ -30,4 +30,3 @@ export type ImageBlurInvocation = { */ blur_type?: 'gaussian' | 'box'; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts index 47bfd4110f..d6abae5eae 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts @@ -26,4 +26,3 @@ export type ImageChannelInvocation = { */ channel?: 'A' | 'R' | 'G' | 'B'; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts index 4bd59d03b0..d303c7ce79 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts @@ -26,4 +26,3 @@ export type ImageConvertInvocation = { */ mode?: 'L' | 'RGB' | 'RGBA' | 'CMYK' | 'YCbCr' | 'LAB' | 'HSV' | 'I' | 'F'; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts index 5207ebbf6d..e29e177aa7 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts @@ -38,4 +38,3 @@ export type ImageCropInvocation = { */ height?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageDTO.ts b/invokeai/frontend/web/src/services/api/models/ImageDTO.ts index 4e273e8854..1e0ea0648f 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageDTO.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageDTO.ts @@ -71,4 +71,3 @@ export type ImageDTO = { */ board_id?: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageField.ts b/invokeai/frontend/web/src/services/api/models/ImageField.ts index baf3bf2b54..c4c67a0674 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageField.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageField.ts @@ -11,4 +11,3 @@ export type ImageField = { */ image_name: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts index 0347d4dc38..63220c8047 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts @@ -30,4 +30,3 @@ export type ImageInverseLerpInvocation = { */ max?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts index 388c86061c..444c7e6467 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts @@ -30,4 +30,3 @@ export type ImageLerpInvocation = { */ max?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts b/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts index 0b2af78799..8aecdddc4f 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts @@ -78,4 +78,3 @@ export type ImageMetadata = { */ extra?: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts index 751ee49158..724061fce8 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts @@ -26,4 +26,3 @@ export type ImageMultiplyInvocation = { */ image2?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageOutput.ts b/invokeai/frontend/web/src/services/api/models/ImageOutput.ts index d7db0c11de..c030632926 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageOutput.ts @@ -22,4 +22,3 @@ export type ImageOutput = { */ height: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts index c883b9a5d8..5af28452b6 100644 --- a/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts @@ -38,4 +38,3 @@ export type ImagePasteInvocation = { */ 'y'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts index 0d995c4e68..e0058a78ca 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts @@ -22,4 +22,3 @@ export type ImageProcessorInvocation = { */ image?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts b/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts index e597cd907d..209b6d8e88 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts @@ -26,4 +26,3 @@ export type ImageRecordChanges = { */ is_intermediate?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts index 3b096c83b7..f277516ccd 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts @@ -34,4 +34,3 @@ export type ImageResizeInvocation = { */ resample_mode?: 'nearest' | 'box' | 'bilinear' | 'hamming' | 'bicubic' | 'lanczos'; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts index bf4da28a4a..709e1cc66a 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts @@ -30,4 +30,3 @@ export type ImageScaleInvocation = { */ resample_mode?: 'nearest' | 'box' | 'bilinear' | 'hamming' | 'bicubic' | 'lanczos'; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts new file mode 100644 index 0000000000..faa9d164a8 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts @@ -0,0 +1,76 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ImageField } from './ImageField'; + +/** + * Generates an image using img2img. + */ +export type ImageToImageInvocation = { + /** + * The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Whether or not this node is an intermediate node. + */ + is_intermediate?: boolean; + type?: 'img2img'; + /** + * The prompt to generate an image from + */ + prompt?: string; + /** + * The seed to use (omit for random) + */ + seed?: number; + /** + * The number of steps to use to generate the image + */ + steps?: number; + /** + * The width of the resulting image + */ + width?: number; + /** + * The height of the resulting image + */ + height?: number; + /** + * The Classifier-Free Guidance, higher values may result in a result closer to the prompt + */ + cfg_scale?: number; + /** + * The scheduler to use + */ + scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; + /** + * The model to use (currently ignored) + */ + model?: string; + /** + * Whether or not to produce progress images during generation + */ + progress_images?: boolean; + /** + * The control model to use + */ + control_model?: string; + /** + * The processed control image + */ + control_image?: ImageField; + /** + * The input image + */ + image?: ImageField; + /** + * The strength of the original image + */ + strength?: number; + /** + * Whether or not the result should be fit to the aspect ratio of the input image + */ + fit?: boolean; +}; diff --git a/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts index ace0ed8e3c..65df90351a 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts @@ -31,4 +31,3 @@ export type ImageToLatentsInvocation = { */ tiled?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts b/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts index 1e0ff322e8..ad45d2047e 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts @@ -19,4 +19,3 @@ export type ImageUrlsDTO = { */ thumbnail_url: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts index 3e637b299c..6d60bbe226 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts @@ -27,4 +27,3 @@ export type InfillColorInvocation = { */ color?: ColorField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts index 325bfe2080..bf6e8012b7 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts @@ -22,4 +22,3 @@ export type InfillPatchMatchInvocation = { */ image?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts index dfb1cbc61d..551e00da16 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts @@ -30,4 +30,3 @@ export type InfillTileInvocation = { */ seed?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts index 8fb9ad3d54..5e7d3f4b92 100644 --- a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts @@ -118,4 +118,3 @@ export type InpaintInvocation = { */ inpaint_replace?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts b/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts index 93a115f980..1e60ee8009 100644 --- a/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts @@ -12,4 +12,3 @@ export type IntCollectionOutput = { */ collection?: Array; }; - diff --git a/invokeai/frontend/web/src/services/api/models/IntOutput.ts b/invokeai/frontend/web/src/services/api/models/IntOutput.ts index eeea6c68b4..58655d0858 100644 --- a/invokeai/frontend/web/src/services/api/models/IntOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/IntOutput.ts @@ -12,4 +12,3 @@ export type IntOutput = { */ 'a'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts b/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts index 15bf92dfea..b6a70156c3 100644 --- a/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts @@ -24,4 +24,3 @@ export type IterateInvocation = { */ index?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts index ce8d9f8c4b..2eeffd05fd 100644 --- a/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts @@ -12,4 +12,3 @@ export type IterateInvocationOutput = { */ item: any; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LatentsField.ts b/invokeai/frontend/web/src/services/api/models/LatentsField.ts index bc6a525f7c..e7446e9cb3 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsField.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsField.ts @@ -11,4 +11,3 @@ export type LatentsField = { */ latents_name: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts b/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts index 3e9c2f60e4..edf388dbf6 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts @@ -22,4 +22,3 @@ export type LatentsOutput = { */ height: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts index 865eeff554..1235962d8f 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts @@ -31,4 +31,3 @@ export type LatentsToImageInvocation = { */ tiled?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts index 4273115963..4f0f49d961 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts @@ -61,4 +61,3 @@ export type LatentsToLatentsInvocation = { */ strength?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts index 5d239536d5..7c655480c7 100644 --- a/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts @@ -30,4 +30,3 @@ export type LineartAnimeImageProcessorInvocation = { */ image_resolution?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts index 17720e689b..af3a527f3a 100644 --- a/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts @@ -34,4 +34,3 @@ export type LineartImageProcessorInvocation = { */ coarse?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts index f20d983f9b..469e7200ad 100644 --- a/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts @@ -22,4 +22,3 @@ export type LoadImageInvocation = { */ image?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/LoraInfo.ts b/invokeai/frontend/web/src/services/api/models/LoraInfo.ts index 1a575d4147..d4499530ac 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraInfo.ts @@ -28,4 +28,7 @@ export type LoraInfo = { */ weight: number; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts index b93281c5a7..b448a7a8ad 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts @@ -35,4 +35,7 @@ export type LoraLoaderInvocation = { */ clip?: ClipField; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts b/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts index 1fed1ebc58..54e02d49e5 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts @@ -19,4 +19,7 @@ export type LoraLoaderOutput = { */ clip?: ClipField; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts b/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts new file mode 100644 index 0000000000..d5b3a02eff --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; + +export type LoraModelConfig = { + path: string; + description?: string; + format: ('lycoris' | 'diffusers'); + default?: boolean; + error?: ModelError; +}; diff --git a/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts b/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts index e3693f6d98..a031c0e05f 100644 --- a/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts @@ -26,4 +26,3 @@ export type MaskFromAlphaInvocation = { */ invert?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/MaskOutput.ts b/invokeai/frontend/web/src/services/api/models/MaskOutput.ts index d4594fe6e9..05d2b36d58 100644 --- a/invokeai/frontend/web/src/services/api/models/MaskOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/MaskOutput.ts @@ -22,4 +22,3 @@ export type MaskOutput = { */ height?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts index aa7b966b4b..76e89422e9 100644 --- a/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts @@ -30,4 +30,3 @@ export type MediapipeFaceProcessorInvocation = { */ min_confidence?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts index bd274228db..14cf26f075 100644 --- a/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts @@ -30,4 +30,3 @@ export type MidasDepthImageProcessorInvocation = { */ bg_th?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts index 0e81c9a4b8..b2a15b5861 100644 --- a/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts @@ -38,4 +38,3 @@ export type MlsdImageProcessorInvocation = { */ thr_d?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ModelInfo.ts b/invokeai/frontend/web/src/services/api/models/ModelInfo.ts index e87799d142..d11bb523c7 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelInfo.ts @@ -24,4 +24,7 @@ export type ModelInfo = { */ submodel?: SubModelType; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts b/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts index 5b5b51e71f..2599d650cb 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts @@ -24,4 +24,7 @@ export type ModelLoaderOutput = { */ vae?: VaeField; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index a2d88d1967..bd6e8bf4da 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ +<<<<<<< HEAD import type { invokeai__backend__model_management__models__controlnet__ControlNetModel__Config } from './invokeai__backend__model_management__models__controlnet__ControlNetModel__Config'; import type { invokeai__backend__model_management__models__lora__LoRAModel__Config } from './invokeai__backend__model_management__models__lora__LoRAModel__Config'; import type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig } from './invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig'; @@ -13,5 +14,17 @@ import type { invokeai__backend__model_management__models__vae__VaeModel__Config export type ModelsList = { models: Record>>; -}; +======= +import type { ControlNetModelConfig } from './ControlNetModelConfig'; +import type { LoraModelConfig } from './LoraModelConfig'; +import type { StableDiffusion1CheckpointModelConfig } from './StableDiffusion1CheckpointModelConfig'; +import type { StableDiffusion1DiffusersModelConfig } from './StableDiffusion1DiffusersModelConfig'; +import type { StableDiffusion2CheckpointModelConfig } from './StableDiffusion2CheckpointModelConfig'; +import type { StableDiffusion2DiffusersModelConfig } from './StableDiffusion2DiffusersModelConfig'; +import type { TextualInversionModelConfig } from './TextualInversionModelConfig'; +import type { VAEModelConfig } from './VAEModelConfig'; +export type ModelsList = { + models: Record>>; +>>>>>>> 76dd749b1 (chore: Rebuild API) +}; diff --git a/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts b/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts index 9fd716f33d..6a3b17feea 100644 --- a/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts @@ -24,4 +24,3 @@ export type MultiplyInvocation = { */ 'b'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts b/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts index 239a24bfe5..22846f5345 100644 --- a/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts @@ -28,4 +28,3 @@ export type NoiseInvocation = { */ height?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts b/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts index f1832d7aa2..cb1b13ef25 100644 --- a/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts @@ -22,4 +22,3 @@ export type NoiseOutput = { */ height: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts index 400068171e..29fcebf567 100644 --- a/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts @@ -30,4 +30,3 @@ export type NormalbaeImageProcessorInvocation = { */ image_resolution?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts index 3408bea6db..2b22b247b4 100644 --- a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts +++ b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts @@ -25,4 +25,3 @@ export type OffsetPaginatedResults_ImageDTO_ = { */ total: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts index 982ce8ade7..80c136546d 100644 --- a/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts @@ -34,4 +34,3 @@ export type OpenposeImageProcessorInvocation = { */ image_resolution?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts b/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts index dd9f50cd4a..cb5914f677 100644 --- a/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts +++ b/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts @@ -29,4 +29,3 @@ export type PaginatedResults_GraphExecutionState_ = { */ total: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts b/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts index 87c01f847f..4e9087237b 100644 --- a/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts @@ -20,4 +20,3 @@ export type ParamFloatInvocation = { */ param?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts b/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts index 7a45d0a0ac..f47c3b8f01 100644 --- a/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts @@ -20,4 +20,3 @@ export type ParamIntInvocation = { */ 'a'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts index 91c9dc0ce5..96433218f7 100644 --- a/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts @@ -38,4 +38,3 @@ export type PidiImageProcessorInvocation = { */ scribble?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts b/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts index 4444ab4d33..ffab4ca09a 100644 --- a/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts @@ -16,4 +16,3 @@ export type PromptCollectionOutput = { */ count: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/PromptOutput.ts b/invokeai/frontend/web/src/services/api/models/PromptOutput.ts index 5bca3f3037..f9dcfd1b08 100644 --- a/invokeai/frontend/web/src/services/api/models/PromptOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/PromptOutput.ts @@ -12,4 +12,3 @@ export type PromptOutput = { */ prompt: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts b/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts index a2f7c2f02a..c4fa84cc8e 100644 --- a/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts @@ -24,4 +24,3 @@ export type RandomIntInvocation = { */ high?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts index 925511578d..5625324a1e 100644 --- a/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts @@ -32,4 +32,3 @@ export type RandomRangeInvocation = { */ seed?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts index 3681602a95..5292d32156 100644 --- a/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts @@ -28,4 +28,3 @@ export type RangeInvocation = { */ step?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts index 7dfac68d39..d97826099a 100644 --- a/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts @@ -28,4 +28,3 @@ export type RangeOfSizeInvocation = { */ step?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts index 9a7b6c61e4..500514f3c9 100644 --- a/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts @@ -38,4 +38,3 @@ export type ResizeLatentsInvocation = { */ antialias?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts b/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts index 0bacb5d805..5cfc165e23 100644 --- a/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts @@ -26,4 +26,3 @@ export type RestoreFaceInvocation = { */ strength?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts index 9a8a23077a..0f287be7bb 100644 --- a/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts @@ -20,4 +20,7 @@ export type SD1ModelLoaderInvocation = { */ model_name?: string; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts index f477c11a8d..5afc63a387 100644 --- a/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts @@ -20,4 +20,7 @@ export type SD2ModelLoaderInvocation = { */ model_name?: string; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts index 506b21e540..a65308dcba 100644 --- a/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts @@ -34,4 +34,3 @@ export type ScaleLatentsInvocation = { */ antialias?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts index 1b73055584..c6bceda651 100644 --- a/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts @@ -22,4 +22,3 @@ export type ShowImageInvocation = { */ image?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts new file mode 100644 index 0000000000..e9ed1bbfc2 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts @@ -0,0 +1,17 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; +import type { ModelVariantType } from './ModelVariantType'; + +export type StableDiffusion1CheckpointModelConfig = { + path: string; + description?: string; + format: 'checkpoint'; + default?: boolean; + error?: ModelError; + vae?: string; + config?: string; + variant: ModelVariantType; +}; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts new file mode 100644 index 0000000000..db51f6c7b9 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts @@ -0,0 +1,16 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; +import type { ModelVariantType } from './ModelVariantType'; + +export type StableDiffusion1DiffusersModelConfig = { + path: string; + description?: string; + format: 'diffusers'; + default?: boolean; + error?: ModelError; + vae?: string; + variant: ModelVariantType; +}; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts new file mode 100644 index 0000000000..74a341d861 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts @@ -0,0 +1,20 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; +import type { ModelVariantType } from './ModelVariantType'; +import type { SchedulerPredictionType } from './SchedulerPredictionType'; + +export type StableDiffusion2CheckpointModelConfig = { + path: string; + description?: string; + format: 'checkpoint'; + default?: boolean; + error?: ModelError; + vae?: string; + config?: string; + variant: ModelVariantType; + prediction_type: SchedulerPredictionType; + upcast_attention: boolean; +}; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts new file mode 100644 index 0000000000..1ac441b2a6 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts @@ -0,0 +1,19 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; +import type { ModelVariantType } from './ModelVariantType'; +import type { SchedulerPredictionType } from './SchedulerPredictionType'; + +export type StableDiffusion2DiffusersModelConfig = { + path: string; + description?: string; + format: 'diffusers'; + default?: boolean; + error?: ModelError; + vae?: string; + variant: ModelVariantType; + prediction_type: SchedulerPredictionType; + upcast_attention: boolean; +}; diff --git a/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts b/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts index 2cff38b3e5..dca4fa8e82 100644 --- a/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts @@ -56,4 +56,3 @@ export type StepParamEasingInvocation = { */ show_easing_plot?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts b/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts index 23334bd891..a1b8ca5628 100644 --- a/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts @@ -24,4 +24,3 @@ export type SubtractInvocation = { */ 'b'?: number; }; - diff --git a/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts new file mode 100644 index 0000000000..26d6117573 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts @@ -0,0 +1,64 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ImageField } from './ImageField'; + +/** + * Generates an image using text2img. + */ +export type TextToImageInvocation = { + /** + * The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Whether or not this node is an intermediate node. + */ + is_intermediate?: boolean; + type?: 'txt2img'; + /** + * The prompt to generate an image from + */ + prompt?: string; + /** + * The seed to use (omit for random) + */ + seed?: number; + /** + * The number of steps to use to generate the image + */ + steps?: number; + /** + * The width of the resulting image + */ + width?: number; + /** + * The height of the resulting image + */ + height?: number; + /** + * The Classifier-Free Guidance, higher values may result in a result closer to the prompt + */ + cfg_scale?: number; + /** + * The scheduler to use + */ + scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; + /** + * The model to use (currently ignored) + */ + model?: string; + /** + * Whether or not to produce progress images during generation + */ + progress_images?: boolean; + /** + * The control model to use + */ + control_model?: string; + /** + * The processed control image + */ + control_image?: ImageField; +}; diff --git a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts index cf8229b1f7..b0b8ec5fc1 100644 --- a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts @@ -53,4 +53,3 @@ export type TextToLatentsInvocation = { */ control?: (ControlField | Array); }; - diff --git a/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts b/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts new file mode 100644 index 0000000000..34ef4791bc --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; + +export type TextualInversionModelConfig = { + path: string; + description?: string; + format: null; + default?: boolean; + error?: ModelError; +}; diff --git a/invokeai/frontend/web/src/services/api/models/UNetField.ts b/invokeai/frontend/web/src/services/api/models/UNetField.ts index ad3b1ddb5b..f0f247c860 100644 --- a/invokeai/frontend/web/src/services/api/models/UNetField.ts +++ b/invokeai/frontend/web/src/services/api/models/UNetField.ts @@ -19,4 +19,7 @@ export type UNetField = { */ loras: Array; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts b/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts index d0aca63964..3b42906e39 100644 --- a/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts @@ -30,4 +30,3 @@ export type UpscaleInvocation = { */ level?: 2 | 4; }; - diff --git a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts b/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts new file mode 100644 index 0000000000..ffaba2f808 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelError } from './ModelError'; + +export type VAEModelConfig = { + path: string; + description?: string; + format: ('checkpoint' | 'diffusers'); + default?: boolean; + error?: ModelError; +}; diff --git a/invokeai/frontend/web/src/services/api/models/VaeField.ts b/invokeai/frontend/web/src/services/api/models/VaeField.ts index bfe2793887..8d3b6f4e53 100644 --- a/invokeai/frontend/web/src/services/api/models/VaeField.ts +++ b/invokeai/frontend/web/src/services/api/models/VaeField.ts @@ -10,4 +10,7 @@ export type VaeField = { */ vae: ModelInfo; }; +<<<<<<< HEAD +======= +>>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/VaeRepo.ts b/invokeai/frontend/web/src/services/api/models/VaeRepo.ts index 0e233626c6..cb6e33c199 100644 --- a/invokeai/frontend/web/src/services/api/models/VaeRepo.ts +++ b/invokeai/frontend/web/src/services/api/models/VaeRepo.ts @@ -16,4 +16,3 @@ export type VaeRepo = { */ subfolder?: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ValidationError.ts b/invokeai/frontend/web/src/services/api/models/ValidationError.ts index 14e1fdecd0..92697e1d74 100644 --- a/invokeai/frontend/web/src/services/api/models/ValidationError.ts +++ b/invokeai/frontend/web/src/services/api/models/ValidationError.ts @@ -7,4 +7,3 @@ export type ValidationError = { msg: string; type: string; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts index 6caded8f04..0dbc99c9e3 100644 --- a/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts @@ -22,4 +22,3 @@ export type ZoeDepthImageProcessorInvocation = { */ image?: ImageField; }; - diff --git a/invokeai/frontend/web/src/services/api/services/ImagesService.ts b/invokeai/frontend/web/src/services/api/services/ImagesService.ts index bfdef887a0..f372d4fa87 100644 --- a/invokeai/frontend/web/src/services/api/services/ImagesService.ts +++ b/invokeai/frontend/web/src/services/api/services/ImagesService.ts @@ -22,6 +22,7 @@ export class ImagesService { * @throws ApiError */ public static listImagesWithMetadata({ +<<<<<<< HEAD imageOrigin, categories, isIntermediate, @@ -54,6 +55,35 @@ export class ImagesService { */ limit?: number, }): CancelablePromise { +======= +imageOrigin, +categories, +isIntermediate, +offset, +limit = 10, +}: { +/** + * The origin of images to list + */ +imageOrigin?: ResourceOrigin, +/** + * The categories of image to include + */ +categories?: Array, +/** + * Whether to list intermediate images + */ +isIntermediate?: boolean, +/** + * The page offset + */ +offset?: number, +/** + * The number of images per page + */ +limit?: number, +}): CancelablePromise { +>>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/', @@ -78,25 +108,25 @@ export class ImagesService { * @throws ApiError */ public static uploadImage({ - imageCategory, - isIntermediate, - formData, - sessionId, - }: { - /** - * The category of the image - */ - imageCategory: ImageCategory, - /** - * Whether this is an intermediate image - */ - isIntermediate: boolean, - formData: Body_upload_image, - /** - * The session ID associated with this upload, if any - */ - sessionId?: string, - }): CancelablePromise { +imageCategory, +isIntermediate, +formData, +sessionId, +}: { +/** + * The category of the image + */ +imageCategory: ImageCategory, +/** + * Whether this is an intermediate image + */ +isIntermediate: boolean, +formData: Body_upload_image, +/** + * The session ID associated with this upload, if any + */ +sessionId?: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/images/', @@ -121,13 +151,13 @@ export class ImagesService { * @throws ApiError */ public static getImageFull({ - imageName, - }: { - /** - * The name of full-resolution image file to get - */ - imageName: string, - }): CancelablePromise { +imageName, +}: { +/** + * The name of full-resolution image file to get + */ +imageName: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}', @@ -148,13 +178,13 @@ export class ImagesService { * @throws ApiError */ public static deleteImage({ - imageName, - }: { - /** - * The name of the image to delete - */ - imageName: string, - }): CancelablePromise { +imageName, +}: { +/** + * The name of the image to delete + */ +imageName: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/images/{image_name}', @@ -174,15 +204,15 @@ export class ImagesService { * @throws ApiError */ public static updateImage({ - imageName, - requestBody, - }: { - /** - * The name of the image to update - */ - imageName: string, - requestBody: ImageRecordChanges, - }): CancelablePromise { +imageName, +requestBody, +}: { +/** + * The name of the image to update + */ +imageName: string, +requestBody: ImageRecordChanges, +}): CancelablePromise { return __request(OpenAPI, { method: 'PATCH', url: '/api/v1/images/{image_name}', @@ -204,13 +234,13 @@ export class ImagesService { * @throws ApiError */ public static getImageMetadata({ - imageName, - }: { - /** - * The name of image to get - */ - imageName: string, - }): CancelablePromise { +imageName, +}: { +/** + * The name of image to get + */ +imageName: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}/metadata', @@ -230,13 +260,13 @@ export class ImagesService { * @throws ApiError */ public static getImageThumbnail({ - imageName, - }: { - /** - * The name of thumbnail image file to get - */ - imageName: string, - }): CancelablePromise { +imageName, +}: { +/** + * The name of thumbnail image file to get + */ +imageName: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}/thumbnail', @@ -257,13 +287,13 @@ export class ImagesService { * @throws ApiError */ public static getImageUrls({ - imageName, - }: { - /** - * The name of the image whose URL to get - */ - imageName: string, - }): CancelablePromise { +imageName, +}: { +/** + * The name of the image whose URL to get + */ +imageName: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}/urls', diff --git a/invokeai/frontend/web/src/services/api/services/ModelsService.ts b/invokeai/frontend/web/src/services/api/services/ModelsService.ts index 54580ce204..248f4a352e 100644 --- a/invokeai/frontend/web/src/services/api/services/ModelsService.ts +++ b/invokeai/frontend/web/src/services/api/services/ModelsService.ts @@ -19,6 +19,7 @@ export class ModelsService { * @throws ApiError */ public static listModels({ +<<<<<<< HEAD baseModel, modelType, }: { @@ -31,6 +32,20 @@ export class ModelsService { */ modelType?: ModelType, }): CancelablePromise { +======= +baseModel, +modelType, +}: { +/** + * Base model + */ +baseModel?: BaseModelType, +/** + * The type of model to get + */ +modelType?: ModelType, +}): CancelablePromise { +>>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'GET', url: '/api/v1/models/', @@ -51,10 +66,10 @@ export class ModelsService { * @throws ApiError */ public static updateModel({ - requestBody, - }: { - requestBody: CreateModelRequest, - }): CancelablePromise { +requestBody, +}: { +requestBody: CreateModelRequest, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/models/', @@ -73,10 +88,10 @@ export class ModelsService { * @throws ApiError */ public static delModel({ - modelName, - }: { - modelName: string, - }): CancelablePromise { +modelName, +}: { +modelName: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/models/{model_name}', diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index 2e4a83b25f..937cff9c05 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -80,23 +80,23 @@ export class SessionsService { * @throws ApiError */ public static listSessions({ - page, - perPage = 10, - query = '', - }: { - /** - * The page of results to get - */ - page?: number, - /** - * The number of results per page - */ - perPage?: number, - /** - * The query string to search for - */ - query?: string, - }): CancelablePromise { +page, +perPage = 10, +query = '', +}: { +/** + * The page of results to get + */ +page?: number, +/** + * The number of results per page + */ +perPage?: number, +/** + * The query string to search for + */ +query?: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/sessions/', @@ -118,10 +118,10 @@ export class SessionsService { * @throws ApiError */ public static createSession({ - requestBody, - }: { - requestBody?: Graph, - }): CancelablePromise { +requestBody, +}: { +requestBody?: Graph, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/sessions/', @@ -141,13 +141,13 @@ export class SessionsService { * @throws ApiError */ public static getSession({ - sessionId, - }: { - /** - * The id of the session to get - */ - sessionId: string, - }): CancelablePromise { +sessionId, +}: { +/** + * The id of the session to get + */ +sessionId: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/sessions/{session_id}', @@ -168,6 +168,7 @@ export class SessionsService { * @throws ApiError */ public static addNode({ +<<<<<<< HEAD sessionId, requestBody, }: { @@ -177,6 +178,17 @@ export class SessionsService { sessionId: string, requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { +======= +sessionId, +requestBody, +}: { +/** + * The id of the session + */ +sessionId: string, +requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | CompelInvocation | LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CvInpaintInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | DynamicPromptInvocation | RestoreFaceInvocation | UpscaleInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | ImageToImageInvocation | LatentsToLatentsInvocation | InpaintInvocation), +}): CancelablePromise { +>>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'POST', url: '/api/v1/sessions/{session_id}/nodes', @@ -200,6 +212,7 @@ export class SessionsService { * @throws ApiError */ public static updateNode({ +<<<<<<< HEAD sessionId, nodePath, requestBody, @@ -214,6 +227,22 @@ export class SessionsService { nodePath: string, requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { +======= +sessionId, +nodePath, +requestBody, +}: { +/** + * The id of the session + */ +sessionId: string, +/** + * The path to the node in the graph + */ +nodePath: string, +requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | CompelInvocation | LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CvInpaintInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | DynamicPromptInvocation | RestoreFaceInvocation | UpscaleInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | ImageToImageInvocation | LatentsToLatentsInvocation | InpaintInvocation), +}): CancelablePromise { +>>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'PUT', url: '/api/v1/sessions/{session_id}/nodes/{node_path}', @@ -238,18 +267,18 @@ export class SessionsService { * @throws ApiError */ public static deleteNode({ - sessionId, - nodePath, - }: { - /** - * The id of the session - */ - sessionId: string, - /** - * The path to the node to delete - */ - nodePath: string, - }): CancelablePromise { +sessionId, +nodePath, +}: { +/** + * The id of the session + */ +sessionId: string, +/** + * The path to the node to delete + */ +nodePath: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/sessions/{session_id}/nodes/{node_path}', @@ -272,15 +301,15 @@ export class SessionsService { * @throws ApiError */ public static addEdge({ - sessionId, - requestBody, - }: { - /** - * The id of the session - */ - sessionId: string, - requestBody: Edge, - }): CancelablePromise { +sessionId, +requestBody, +}: { +/** + * The id of the session + */ +sessionId: string, +requestBody: Edge, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/sessions/{session_id}/edges', @@ -304,33 +333,33 @@ export class SessionsService { * @throws ApiError */ public static deleteEdge({ - sessionId, - fromNodeId, - fromField, - toNodeId, - toField, - }: { - /** - * The id of the session - */ - sessionId: string, - /** - * The id of the node the edge is coming from - */ - fromNodeId: string, - /** - * The field of the node the edge is coming from - */ - fromField: string, - /** - * The id of the node the edge is going to - */ - toNodeId: string, - /** - * The field of the node the edge is going to - */ - toField: string, - }): CancelablePromise { +sessionId, +fromNodeId, +fromField, +toNodeId, +toField, +}: { +/** + * The id of the session + */ +sessionId: string, +/** + * The id of the node the edge is coming from + */ +fromNodeId: string, +/** + * The field of the node the edge is coming from + */ +fromField: string, +/** + * The id of the node the edge is going to + */ +toNodeId: string, +/** + * The field of the node the edge is going to + */ +toField: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/sessions/{session_id}/edges/{from_node_id}/{from_field}/{to_node_id}/{to_field}', @@ -356,18 +385,18 @@ export class SessionsService { * @throws ApiError */ public static invokeSession({ - sessionId, - all = false, - }: { - /** - * The id of the session to invoke - */ - sessionId: string, - /** - * Whether or not to invoke all remaining invocations - */ - all?: boolean, - }): CancelablePromise { +sessionId, +all = false, +}: { +/** + * The id of the session to invoke + */ +sessionId: string, +/** + * Whether or not to invoke all remaining invocations + */ +all?: boolean, +}): CancelablePromise { return __request(OpenAPI, { method: 'PUT', url: '/api/v1/sessions/{session_id}/invoke', @@ -392,13 +421,13 @@ export class SessionsService { * @throws ApiError */ public static cancelSessionInvoke({ - sessionId, - }: { - /** - * The id of the session to cancel - */ - sessionId: string, - }): CancelablePromise { +sessionId, +}: { +/** + * The id of the session to cancel + */ +sessionId: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/sessions/{session_id}/invoke', From bf0d5f4cfc0537a0ccb2834f4978b4dcb1cdbbe6 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sat, 17 Jun 2023 22:04:28 +1200 Subject: [PATCH 084/110] fix: Update missing name types to new names --- invokeai/backend/model_management/models/stable_diffusion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/backend/model_management/models/stable_diffusion.py b/invokeai/backend/model_management/models/stable_diffusion.py index 9856069ea5..0ac88c8a94 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -107,7 +107,7 @@ class StableDiffusion1Model(DiffusersModel): ) -> str: assert model_path == config.path - if isinstance(config, cls.CheckpointModelConfig): + if isinstance(config, cls.StableDiffusion1CheckpointModelConfig): return _convert_ckpt_and_cache( version=BaseModelType.StableDiffusion1, model_config=config, @@ -220,7 +220,7 @@ class StableDiffusion2Model(DiffusersModel): ) -> str: assert model_path == config.path - if isinstance(config, cls.CheckpointModelConfig): + if isinstance(config, cls.StableDiffusion2CheckpointModelConfig): return _convert_ckpt_and_cache( version=BaseModelType.StableDiffusion2, model_config=config, From 01d17601b84b974f6376b087617ed3b50a3344c2 Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Sat, 17 Jun 2023 17:15:36 +0300 Subject: [PATCH 085/110] Generate config names for openapi --- invokeai/app/api/routers/models.py | 4 ++-- .../backend/model_management/models/__init__.py | 16 ++++++++++++++-- .../model_management/models/controlnet.py | 2 +- invokeai/backend/model_management/models/lora.py | 2 +- .../model_management/models/stable_diffusion.py | 14 +++++++------- .../model_management/models/textual_inversion.py | 2 +- invokeai/backend/model_management/models/vae.py | 2 +- 7 files changed, 27 insertions(+), 15 deletions(-) diff --git a/invokeai/app/api/routers/models.py b/invokeai/app/api/routers/models.py index f510279f18..0abcc19dcf 100644 --- a/invokeai/app/api/routers/models.py +++ b/invokeai/app/api/routers/models.py @@ -7,8 +7,8 @@ from fastapi.routing import APIRouter, HTTPException from pydantic import BaseModel, Field, parse_obj_as from ..dependencies import ApiDependencies from invokeai.backend import BaseModelType, ModelType -from invokeai.backend.model_management.models import get_all_model_configs -MODEL_CONFIGS = Union[tuple(get_all_model_configs())] +from invokeai.backend.model_management.models import OPENAPI_MODEL_CONFIGS +MODEL_CONFIGS = Union[tuple(OPENAPI_MODEL_CONFIGS)] models_router = APIRouter(prefix="/v1/models", tags=["models"]) diff --git a/invokeai/backend/model_management/models/__init__.py b/invokeai/backend/model_management/models/__init__.py index 40995498bf..eff71798a5 100644 --- a/invokeai/backend/model_management/models/__init__.py +++ b/invokeai/backend/model_management/models/__init__.py @@ -29,10 +29,22 @@ MODEL_CLASSES = { #}, } -def get_all_model_configs(): +def _get_all_model_configs(): configs = set() for models in MODEL_CLASSES.values(): for _, model in models.items(): configs.update(model._get_configs().values()) configs.discard(None) - return list(configs) # TODO: set, list or tuple + return list(configs) + +MODEL_CONFIGS = _get_all_model_configs() +OPENAPI_MODEL_CONFIGS = list() + +for cfg in MODEL_CONFIGS: + model_name, cfg_name = cfg.__qualname__.split('.')[-2:] + openapi_cfg_name = model_name + cfg_name + name_wrapper = type(openapi_cfg_name, (cfg,), {}) + + #globals()[name] = value + vars()[openapi_cfg_name] = name_wrapper + OPENAPI_MODEL_CONFIGS.append(name_wrapper) diff --git a/invokeai/backend/model_management/models/controlnet.py b/invokeai/backend/model_management/models/controlnet.py index 687afbffbd..de9926c83e 100644 --- a/invokeai/backend/model_management/models/controlnet.py +++ b/invokeai/backend/model_management/models/controlnet.py @@ -18,7 +18,7 @@ class ControlNetModel(ModelBase): #model_class: Type #model_size: int - class ControlNetModelConfig(ModelConfigBase): + class Config(ModelConfigBase): format: Union[Literal["checkpoint"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): diff --git a/invokeai/backend/model_management/models/lora.py b/invokeai/backend/model_management/models/lora.py index 60865817b9..bcf3224ece 100644 --- a/invokeai/backend/model_management/models/lora.py +++ b/invokeai/backend/model_management/models/lora.py @@ -15,7 +15,7 @@ from ..lora import LoRAModel as LoRAModelRaw class LoRAModel(ModelBase): #model_size: int - class LoraModelConfig(ModelConfigBase): + class Config(ModelConfigBase): format: Union[Literal["lycoris"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): diff --git a/invokeai/backend/model_management/models/stable_diffusion.py b/invokeai/backend/model_management/models/stable_diffusion.py index 0ac88c8a94..20aaae23a6 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -22,12 +22,12 @@ from omegaconf import OmegaConf class StableDiffusion1Model(DiffusersModel): - class StableDiffusion1DiffusersModelConfig(ModelConfigBase): + class DiffusersConfig(ModelConfigBase): format: Literal["diffusers"] vae: Optional[str] = Field(None) variant: ModelVariantType - class StableDiffusion1CheckpointModelConfig(ModelConfigBase): + class CheckpointConfig(ModelConfigBase): format: Literal["checkpoint"] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) @@ -107,7 +107,7 @@ class StableDiffusion1Model(DiffusersModel): ) -> str: assert model_path == config.path - if isinstance(config, cls.StableDiffusion1CheckpointModelConfig): + if isinstance(config, cls.CheckpointConfig): return _convert_ckpt_and_cache( version=BaseModelType.StableDiffusion1, model_config=config, @@ -120,14 +120,14 @@ class StableDiffusion1Model(DiffusersModel): class StableDiffusion2Model(DiffusersModel): # TODO: check that configs overwriten properly - class StableDiffusion2DiffusersModelConfig(ModelConfigBase): + class DiffusersConfig(ModelConfigBase): format: Literal["diffusers"] vae: Optional[str] = Field(None) variant: ModelVariantType prediction_type: SchedulerPredictionType upcast_attention: bool - class StableDiffusion2CheckpointModelConfig(ModelConfigBase): + class CheckpointConfig(ModelConfigBase): format: Literal["checkpoint"] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) @@ -220,7 +220,7 @@ class StableDiffusion2Model(DiffusersModel): ) -> str: assert model_path == config.path - if isinstance(config, cls.StableDiffusion2CheckpointModelConfig): + if isinstance(config, cls.CheckpointConfig): return _convert_ckpt_and_cache( version=BaseModelType.StableDiffusion2, model_config=config, @@ -256,7 +256,7 @@ def _select_ckpt_config(version: BaseModelType, variant: ModelVariantType): # TODO: rework def _convert_ckpt_and_cache( version: BaseModelType, - model_config: Union[StableDiffusion1Model.StableDiffusion1CheckpointModelConfig, StableDiffusion2Model.StableDiffusion2CheckpointModelConfig], + model_config: Union[StableDiffusion1Model.CheckpointConfig, StableDiffusion2Model.CheckpointConfig], output_path: str, ) -> str: """ diff --git a/invokeai/backend/model_management/models/textual_inversion.py b/invokeai/backend/model_management/models/textual_inversion.py index 453a8ad671..66847f53eb 100644 --- a/invokeai/backend/model_management/models/textual_inversion.py +++ b/invokeai/backend/model_management/models/textual_inversion.py @@ -15,7 +15,7 @@ from ..lora import TextualInversionModel as TextualInversionModelRaw class TextualInversionModel(ModelBase): #model_size: int - class TextualInversionModelConfig(ModelConfigBase): + class Config(ModelConfigBase): format: None def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): diff --git a/invokeai/backend/model_management/models/vae.py b/invokeai/backend/model_management/models/vae.py index f285648323..b78617869a 100644 --- a/invokeai/backend/model_management/models/vae.py +++ b/invokeai/backend/model_management/models/vae.py @@ -23,7 +23,7 @@ class VaeModel(ModelBase): #vae_class: Type #model_size: int - class VAEModelConfig(ModelConfigBase): + class Config(ModelConfigBase): format: Union[Literal["checkpoint"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): From e37421131399e94c0b303478b9130df69e9731d5 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 03:00:16 +1200 Subject: [PATCH 086/110] chore: Rebuild API with new Model API names --- invokeai/frontend/web/src/services/api/index.ts | 16 ++++++++++++---- .../src/services/api/models/LoraModelConfig.ts | 2 +- .../web/src/services/api/models/ModelsList.ts | 16 ++++++++++------ ... => StableDiffusion1ModelCheckpointConfig.ts} | 2 +- ...s => StableDiffusion1ModelDiffusersConfig.ts} | 2 +- ... => StableDiffusion2ModelCheckpointConfig.ts} | 2 +- ...s => StableDiffusion2ModelDiffusersConfig.ts} | 2 +- .../src/services/api/models/VAEModelConfig.ts | 2 +- 8 files changed, 28 insertions(+), 16 deletions(-) rename invokeai/frontend/web/src/services/api/models/{StableDiffusion1CheckpointModelConfig.ts => StableDiffusion1ModelCheckpointConfig.ts} (86%) rename invokeai/frontend/web/src/services/api/models/{StableDiffusion1DiffusersModelConfig.ts => StableDiffusion1ModelDiffusersConfig.ts} (86%) rename invokeai/frontend/web/src/services/api/models/{StableDiffusion2CheckpointModelConfig.ts => StableDiffusion2ModelCheckpointConfig.ts} (90%) rename invokeai/frontend/web/src/services/api/models/{StableDiffusion2DiffusersModelConfig.ts => StableDiffusion2ModelDiffusersConfig.ts} (90%) diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index f3aec17eb6..a738a9aafd 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -92,9 +92,13 @@ export type { LoraInfo } from './models/LoraInfo'; export type { LoraLoaderInvocation } from './models/LoraLoaderInvocation'; export type { LoraLoaderOutput } from './models/LoraLoaderOutput'; <<<<<<< HEAD +<<<<<<< HEAD ======= export type { LoraModelConfig } from './models/LoraModelConfig'; >>>>>>> 76dd749b1 (chore: Rebuild API) +======= +export type { LoRAModelConfig } from './models/LoRAModelConfig'; +>>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) export type { MaskFromAlphaInvocation } from './models/MaskFromAlphaInvocation'; export type { MaskOutput } from './models/MaskOutput'; export type { MediapipeFaceProcessorInvocation } from './models/MediapipeFaceProcessorInvocation'; @@ -131,10 +135,10 @@ export type { SchedulerPredictionType } from './models/SchedulerPredictionType'; export type { SD1ModelLoaderInvocation } from './models/SD1ModelLoaderInvocation'; export type { SD2ModelLoaderInvocation } from './models/SD2ModelLoaderInvocation'; export type { ShowImageInvocation } from './models/ShowImageInvocation'; -export type { StableDiffusion1CheckpointModelConfig } from './models/StableDiffusion1CheckpointModelConfig'; -export type { StableDiffusion1DiffusersModelConfig } from './models/StableDiffusion1DiffusersModelConfig'; -export type { StableDiffusion2CheckpointModelConfig } from './models/StableDiffusion2CheckpointModelConfig'; -export type { StableDiffusion2DiffusersModelConfig } from './models/StableDiffusion2DiffusersModelConfig'; +export type { StableDiffusion1ModelCheckpointConfig } from './models/StableDiffusion1ModelCheckpointConfig'; +export type { StableDiffusion1ModelDiffusersConfig } from './models/StableDiffusion1ModelDiffusersConfig'; +export type { StableDiffusion2ModelCheckpointConfig } from './models/StableDiffusion2ModelCheckpointConfig'; +export type { StableDiffusion2ModelDiffusersConfig } from './models/StableDiffusion2ModelDiffusersConfig'; export type { StepParamEasingInvocation } from './models/StepParamEasingInvocation'; export type { SubModelType } from './models/SubModelType'; export type { SubtractInvocation } from './models/SubtractInvocation'; @@ -148,8 +152,12 @@ export type { TextualInversionModelConfig } from './models/TextualInversionModel export type { UNetField } from './models/UNetField'; export type { UpscaleInvocation } from './models/UpscaleInvocation'; export type { VaeField } from './models/VaeField'; +<<<<<<< HEAD export type { VAEModelConfig } from './models/VAEModelConfig'; >>>>>>> 76dd749b1 (chore: Rebuild API) +======= +export type { VaeModelConfig } from './models/VaeModelConfig'; +>>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) export type { VaeRepo } from './models/VaeRepo'; export type { ValidationError } from './models/ValidationError'; export type { ZoeDepthImageProcessorInvocation } from './models/ZoeDepthImageProcessorInvocation'; diff --git a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts b/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts index d5b3a02eff..a62d1a3f4b 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts @@ -4,7 +4,7 @@ import type { ModelError } from './ModelError'; -export type LoraModelConfig = { +export type LoRAModelConfig = { path: string; description?: string; format: ('lycoris' | 'diffusers'); diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index bd6e8bf4da..db41b33048 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -16,15 +16,19 @@ export type ModelsList = { models: Record>>; ======= import type { ControlNetModelConfig } from './ControlNetModelConfig'; -import type { LoraModelConfig } from './LoraModelConfig'; -import type { StableDiffusion1CheckpointModelConfig } from './StableDiffusion1CheckpointModelConfig'; -import type { StableDiffusion1DiffusersModelConfig } from './StableDiffusion1DiffusersModelConfig'; -import type { StableDiffusion2CheckpointModelConfig } from './StableDiffusion2CheckpointModelConfig'; -import type { StableDiffusion2DiffusersModelConfig } from './StableDiffusion2DiffusersModelConfig'; +import type { LoRAModelConfig } from './LoRAModelConfig'; +import type { StableDiffusion1ModelCheckpointConfig } from './StableDiffusion1ModelCheckpointConfig'; +import type { StableDiffusion1ModelDiffusersConfig } from './StableDiffusion1ModelDiffusersConfig'; +import type { StableDiffusion2ModelCheckpointConfig } from './StableDiffusion2ModelCheckpointConfig'; +import type { StableDiffusion2ModelDiffusersConfig } from './StableDiffusion2ModelDiffusersConfig'; import type { TextualInversionModelConfig } from './TextualInversionModelConfig'; -import type { VAEModelConfig } from './VAEModelConfig'; +import type { VaeModelConfig } from './VaeModelConfig'; export type ModelsList = { +<<<<<<< HEAD models: Record>>; >>>>>>> 76dd749b1 (chore: Rebuild API) +======= + models: Record>>; +>>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) }; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts similarity index 86% rename from invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts rename to invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts index e9ed1bbfc2..7b3f90cd0a 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion1CheckpointModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts @@ -5,7 +5,7 @@ import type { ModelError } from './ModelError'; import type { ModelVariantType } from './ModelVariantType'; -export type StableDiffusion1CheckpointModelConfig = { +export type StableDiffusion1ModelCheckpointConfig = { path: string; description?: string; format: 'checkpoint'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts similarity index 86% rename from invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts rename to invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts index db51f6c7b9..ec634b9692 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion1DiffusersModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts @@ -5,7 +5,7 @@ import type { ModelError } from './ModelError'; import type { ModelVariantType } from './ModelVariantType'; -export type StableDiffusion1DiffusersModelConfig = { +export type StableDiffusion1ModelDiffusersConfig = { path: string; description?: string; format: 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts similarity index 90% rename from invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts rename to invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts index 74a341d861..bcf9801314 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion2CheckpointModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts @@ -6,7 +6,7 @@ import type { ModelError } from './ModelError'; import type { ModelVariantType } from './ModelVariantType'; import type { SchedulerPredictionType } from './SchedulerPredictionType'; -export type StableDiffusion2CheckpointModelConfig = { +export type StableDiffusion2ModelCheckpointConfig = { path: string; description?: string; format: 'checkpoint'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts similarity index 90% rename from invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts rename to invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts index 1ac441b2a6..3f3142dae3 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion2DiffusersModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts @@ -6,7 +6,7 @@ import type { ModelError } from './ModelError'; import type { ModelVariantType } from './ModelVariantType'; import type { SchedulerPredictionType } from './SchedulerPredictionType'; -export type StableDiffusion2DiffusersModelConfig = { +export type StableDiffusion2ModelDiffusersConfig = { path: string; description?: string; format: 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts b/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts index ffaba2f808..8d0d2e8304 100644 --- a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts @@ -4,7 +4,7 @@ import type { ModelError } from './ModelError'; -export type VAEModelConfig = { +export type VaeModelConfig = { path: string; description?: string; format: ('checkpoint' | 'diffusers'); From f8d7477c7ae5dfa71cbf27fd4ad54874ed6c6819 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 07:01:44 +1200 Subject: [PATCH 087/110] wip: Add 2.x Models to the Model List --- .../enhancers/reduxRemember/serialize.ts | 3 +- .../enhancers/reduxRemember/unserialize.ts | 6 ++- .../listeners/socketio/socketConnected.ts | 14 +++-- invokeai/frontend/web/src/app/store/store.ts | 23 ++++---- .../fields/ModelInputFieldComponent.tsx | 32 +++-------- .../parameters/store/generationSlice.ts | 6 +-- .../system/components/ModelSelect.tsx | 36 ++++++++++--- .../src/features/system/store/modelSlice.ts | 47 ---------------- .../system/store/models/sd1ModelSlice.ts | 53 ++++++++++++++++++ .../system/store/models/sd2ModelSlice.ts | 53 ++++++++++++++++++ .../system/store/modelsPersistDenylist.ts | 7 ++- .../src/features/system/store/systemSlice.ts | 24 ++++----- .../frontend/web/src/services/thunks/model.ts | 54 +++++++++++++------ 13 files changed, 228 insertions(+), 130 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/system/store/modelSlice.ts create mode 100644 invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts create mode 100644 invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts index 5025ca081a..e498ecb749 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts @@ -18,7 +18,8 @@ const serializationDenylist: { gallery: galleryPersistDenylist, generation: generationPersistDenylist, lightbox: lightboxPersistDenylist, - models: modelsPersistDenylist, + sd1models: modelsPersistDenylist, + sd2models: modelsPersistDenylist, nodes: nodesPersistDenylist, postprocessing: postprocessingPersistDenylist, system: systemPersistDenylist, diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts index c6af5f3612..93cc19f832 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts @@ -7,7 +7,8 @@ import { initialNodesState } from 'features/nodes/store/nodesSlice'; import { initialGenerationState } from 'features/parameters/store/generationSlice'; import { initialPostprocessingState } from 'features/parameters/store/postprocessingSlice'; import { initialConfigState } from 'features/system/store/configSlice'; -import { initialModelsState } from 'features/system/store/modelSlice'; +import { sd1InitialModelsState } from 'features/system/store/models/sd1ModelSlice'; +import { sd2InitialModelsState } from 'features/system/store/models/sd2ModelSlice'; import { initialSystemState } from 'features/system/store/systemSlice'; import { initialHotkeysState } from 'features/ui/store/hotkeysSlice'; import { initialUIState } from 'features/ui/store/uiSlice'; @@ -21,7 +22,8 @@ const initialStates: { gallery: initialGalleryState, generation: initialGenerationState, lightbox: initialLightboxState, - models: initialModelsState, + sd1models: sd1InitialModelsState, + sd2models: sd2InitialModelsState, nodes: initialNodesState, postprocessing: initialPostprocessingState, system: initialSystemState, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index 9fe554fee1..b257b470bd 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -1,9 +1,9 @@ -import { startAppListening } from '../..'; import { log } from 'app/logging/useLogger'; import { appSocketConnected, socketConnected } from 'services/events/actions'; import { receivedPageOfImages } from 'services/thunks/image'; -import { receivedModels } from 'services/thunks/model'; +import { getModels } from 'services/thunks/model'; import { receivedOpenAPISchema } from 'services/thunks/schema'; +import { startAppListening } from '../..'; const moduleLog = log.child({ namespace: 'socketio' }); @@ -15,7 +15,7 @@ export const addSocketConnectedEventListener = () => { moduleLog.debug({ timestamp }, 'Connected'); - const { models, nodes, config, images } = getState(); + const { sd1models, sd2models, nodes, config, images } = getState(); const { disabledTabs } = config; @@ -28,8 +28,12 @@ export const addSocketConnectedEventListener = () => { ); } - if (!models.ids.length) { - dispatch(receivedModels()); + if (!sd1models.ids.length) { + dispatch(getModels({ baseModel: 'sd-1', modelType: 'pipeline' })); + } + + if (!sd2models.ids.length) { + dispatch(getModels({ baseModel: 'sd-2', modelType: 'pipeline' })); } if (!nodes.schema && !disabledTabs.includes('nodes')) { diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index a9011f9356..4ecc9eb9bf 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -5,34 +5,37 @@ import { configureStore, } from '@reduxjs/toolkit'; -import { rememberReducer, rememberEnhancer } from 'redux-remember'; import dynamicMiddlewares from 'redux-dynamic-middlewares'; +import { rememberEnhancer, rememberReducer } from 'redux-remember'; import canvasReducer from 'features/canvas/store/canvasSlice'; +import controlNetReducer from 'features/controlNet/store/controlNetSlice'; import galleryReducer from 'features/gallery/store/gallerySlice'; import imagesReducer from 'features/gallery/store/imagesSlice'; import lightboxReducer from 'features/lightbox/store/lightboxSlice'; import generationReducer from 'features/parameters/store/generationSlice'; -import controlNetReducer from 'features/controlNet/store/controlNetSlice'; import postprocessingReducer from 'features/parameters/store/postprocessingSlice'; import systemReducer from 'features/system/store/systemSlice'; // import sessionReducer from 'features/system/store/sessionSlice'; -import configReducer from 'features/system/store/configSlice'; -import uiReducer from 'features/ui/store/uiSlice'; -import hotkeysReducer from 'features/ui/store/hotkeysSlice'; -import modelsReducer from 'features/system/store/modelSlice'; import nodesReducer from 'features/nodes/store/nodesSlice'; import boardsReducer from 'features/gallery/store/boardSlice'; +import configReducer from 'features/system/store/configSlice'; +import hotkeysReducer from 'features/ui/store/hotkeysSlice'; +import uiReducer from 'features/ui/store/uiSlice'; import { listenerMiddleware } from './middleware/listenerMiddleware'; import { actionSanitizer } from './middleware/devtools/actionSanitizer'; -import { stateSanitizer } from './middleware/devtools/stateSanitizer'; import { actionsDenylist } from './middleware/devtools/actionsDenylist'; +import { stateSanitizer } from './middleware/devtools/stateSanitizer'; +// Model Reducers +import sd1ModelReducer from 'features/system/store/models/sd1ModelSlice'; +import sd2ModelReducer from 'features/system/store/models/sd2ModelSlice'; + +import { LOCALSTORAGE_PREFIX } from './constants'; import { serialize } from './enhancers/reduxRemember/serialize'; import { unserialize } from './enhancers/reduxRemember/unserialize'; -import { LOCALSTORAGE_PREFIX } from './constants'; import { api } from 'services/apiSlice'; const allReducers = { @@ -40,7 +43,8 @@ const allReducers = { gallery: galleryReducer, generation: generationReducer, lightbox: lightboxReducer, - models: modelsReducer, + sd1models: sd1ModelReducer, + sd2models: sd2ModelReducer, nodes: nodesReducer, postprocessing: postprocessingReducer, system: systemReducer, @@ -63,7 +67,6 @@ const rememberedKeys: (keyof typeof allReducers)[] = [ 'gallery', 'generation', 'lightbox', - // 'models', 'nodes', 'postprocessing', 'system', diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx index a1ef69de01..d3d37765f4 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx @@ -1,29 +1,14 @@ -import { Select } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; +import { NativeSelect } from '@mantine/core'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { fieldValueChanged } from 'features/nodes/store/nodesSlice'; import { ModelInputFieldTemplate, ModelInputFieldValue, } from 'features/nodes/types/types'; -import { selectModelsIds } from 'features/system/store/modelSlice'; -import { isEqual } from 'lodash-es'; +import { modelSelector } from 'features/system/components/ModelSelect'; import { ChangeEvent, memo } from 'react'; import { FieldComponentProps } from './types'; -const availableModelsSelector = createSelector( - [selectModelsIds], - (allModelNames) => { - return { allModelNames }; - // return map(modelList, (_, name) => name); - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - const ModelInputFieldComponent = ( props: FieldComponentProps ) => { @@ -31,7 +16,7 @@ const ModelInputFieldComponent = ( const dispatch = useAppDispatch(); - const { allModelNames } = useAppSelector(availableModelsSelector); + const { sd1ModelData, sd2ModelData } = useAppSelector(modelSelector); const handleValueChanged = (e: ChangeEvent) => { dispatch( @@ -44,14 +29,11 @@ const ModelInputFieldComponent = ( }; return ( - + value={field.value || sd1ModelData[0].value} + data={sd1ModelData.concat(sd2ModelData)} + > ); }; diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 2facb65f04..39f38e386c 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,10 +1,11 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; +import { Scheduler } from 'app/constants'; import { configChanged } from 'features/system/store/configSlice'; import { clamp, sortBy } from 'lodash-es'; import { ImageDTO } from 'services/api'; import { imageUrlsReceived } from 'services/thunks/image'; -import { receivedModels } from 'services/thunks/model'; +import { getModels } from 'services/thunks/model'; import { CfgScaleParam, HeightParam, @@ -17,7 +18,6 @@ import { StrengthParam, WidthParam, } from './parameterZodSchemas'; -import { DEFAULT_SCHEDULER_NAME } from 'app/constants'; export interface GenerationState { cfgScale: CfgScaleParam; @@ -220,7 +220,7 @@ export const generationSlice = createSlice({ }, }, extraReducers: (builder) => { - builder.addCase(receivedModels.fulfilled, (state, action) => { + builder.addCase(getModels.fulfilled, (state, action) => { if (!state.model) { const firstModel = sortBy(action.payload, 'name')[0]; state.model = firstModel.name; diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index a38ab150dd..a65c8501dc 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -10,22 +10,42 @@ import IAIMantineSelect, { } from 'common/components/IAIMantineSelect'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { modelSelected } from 'features/parameters/store/generationSlice'; -import { selectModelsAll, selectModelsById } from '../store/modelSlice'; +import { + selectAllSD1Models, + selectByIdSD1Models, +} from '../store/models/sd1ModelSlice'; +import { + selectAllSD2Models, + selectByIdSD2Models, +} from '../store/models/sd2ModelSlice'; -const selector = createSelector( +export const modelSelector = createSelector( [(state: RootState) => state, generationSelector], (state, generation) => { - const selectedModel = selectModelsById(state, generation.model); + let selectedModel = selectByIdSD1Models(state, generation.model); + if (selectedModel === undefined) + selectedModel = selectByIdSD2Models(state, generation.model); - const modelData = selectModelsAll(state) + const sd1ModelData = selectAllSD1Models(state) .map((m) => ({ value: m.name, label: m.name, + group: '1.x Models', })) .sort((a, b) => a.label.localeCompare(b.label)); + + const sd2ModelData = selectAllSD2Models(state) + .map((m) => ({ + value: m.name, + label: m.name, + group: '2.x Models', + })) + .sort((a, b) => a.label.localeCompare(b.label)); + return { selectedModel, - modelData, + sd1ModelData, + sd2ModelData, }; }, { @@ -38,7 +58,9 @@ const selector = createSelector( const ModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const { selectedModel, modelData } = useAppSelector(selector); + const { selectedModel, sd1ModelData, sd2ModelData } = + useAppSelector(modelSelector); + const handleChangeModel = useCallback( (v: string | null) => { if (!v) { @@ -55,7 +77,7 @@ const ModelSelect = () => { label={t('modelManager.model')} value={selectedModel?.name ?? ''} placeholder="Pick one" - data={modelData} + data={sd1ModelData.concat(sd2ModelData)} onChange={handleChangeModel} /> ); diff --git a/invokeai/frontend/web/src/features/system/store/modelSlice.ts b/invokeai/frontend/web/src/features/system/store/modelSlice.ts deleted file mode 100644 index ed38425872..0000000000 --- a/invokeai/frontend/web/src/features/system/store/modelSlice.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { createEntityAdapter } from '@reduxjs/toolkit'; -import { createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { CkptModelInfo, DiffusersModelInfo } from 'services/api'; -import { receivedModels } from 'services/thunks/model'; - -export type Model = (CkptModelInfo | DiffusersModelInfo) & { - name: string; -}; - -export const modelsAdapter = createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), -}); - -export const initialModelsState = modelsAdapter.getInitialState(); - -export type ModelsState = typeof initialModelsState; - -export const modelsSlice = createSlice({ - name: 'models', - initialState: initialModelsState, - reducers: { - modelAdded: modelsAdapter.upsertOne, - }, - extraReducers(builder) { - /** - * Received Models - FULFILLED - */ - builder.addCase(receivedModels.fulfilled, (state, action) => { - const models = action.payload; - modelsAdapter.setAll(state, models); - }); - }, -}); - -export const { - selectAll: selectModelsAll, - selectById: selectModelsById, - selectEntities: selectModelsEntities, - selectIds: selectModelsIds, - selectTotal: selectModelsTotal, -} = modelsAdapter.getSelectors((state) => state.models); - -export const { modelAdded } = modelsSlice.actions; - -export default modelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts new file mode 100644 index 0000000000..9f62fde264 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts @@ -0,0 +1,53 @@ +import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { + StableDiffusion1ModelCheckpointConfig, + StableDiffusion1ModelDiffusersConfig, +} from 'services/api'; + +import { getModels } from 'services/thunks/model'; + +export type SD1ModelType = ( + | StableDiffusion1ModelCheckpointConfig + | StableDiffusion1ModelDiffusersConfig +) & { + name: string; +}; + +export const sd1ModelsAdapter = createEntityAdapter({ + selectId: (model) => model.name, + sortComparer: (a, b) => a.name.localeCompare(b.name), +}); + +export const sd1InitialModelsState = sd1ModelsAdapter.getInitialState(); + +export type SD1ModelState = typeof sd1InitialModelsState; + +export const sd1ModelsSlice = createSlice({ + name: 'sd1models', + initialState: sd1InitialModelsState, + reducers: { + modelAdded: sd1ModelsAdapter.upsertOne, + }, + extraReducers(builder) { + /** + * Received Models - FULFILLED + */ + builder.addCase(getModels.fulfilled, (state, action) => { + if (action.meta.arg.baseModel !== 'sd-1') return; + sd1ModelsAdapter.setAll(state, action.payload); + }); + }, +}); + +export const { + selectAll: selectAllSD1Models, + selectById: selectByIdSD1Models, + selectEntities: selectEntitiesSD1Models, + selectIds: selectIdsSD1Models, + selectTotal: selectTotalSD1Models, +} = sd1ModelsAdapter.getSelectors((state) => state.sd1models); + +export const { modelAdded } = sd1ModelsSlice.actions; + +export default sd1ModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts new file mode 100644 index 0000000000..e8e1f5bedf --- /dev/null +++ b/invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts @@ -0,0 +1,53 @@ +import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { + StableDiffusion2ModelCheckpointConfig, + StableDiffusion2ModelDiffusersConfig, +} from 'services/api'; + +import { getModels } from 'services/thunks/model'; + +export type SD2ModelType = ( + | StableDiffusion2ModelCheckpointConfig + | StableDiffusion2ModelDiffusersConfig +) & { + name: string; +}; + +export const sd2ModelsAdapater = createEntityAdapter({ + selectId: (model) => model.name, + sortComparer: (a, b) => a.name.localeCompare(b.name), +}); + +export const sd2InitialModelsState = sd2ModelsAdapater.getInitialState(); + +export type SD2ModelState = typeof sd2InitialModelsState; + +export const sd2ModelsSlice = createSlice({ + name: 'sd2models', + initialState: sd2InitialModelsState, + reducers: { + modelAdded: sd2ModelsAdapater.upsertOne, + }, + extraReducers(builder) { + /** + * Received Models - FULFILLED + */ + builder.addCase(getModels.fulfilled, (state, action) => { + if (action.meta.arg.baseModel !== 'sd-2') return; + sd2ModelsAdapater.setAll(state, action.payload); + }); + }, +}); + +export const { + selectAll: selectAllSD2Models, + selectById: selectByIdSD2Models, + selectEntities: selectEntitiesSD2Models, + selectIds: selectIdsSD2Models, + selectTotal: selectTotalSD2Models, +} = sd2ModelsAdapater.getSelectors((state) => state.sd2models); + +export const { modelAdded } = sd2ModelsSlice.actions; + +export default sd2ModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts index aa9fb057e1..7b0d78d37e 100644 --- a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts @@ -1,6 +1,9 @@ -import { ModelsState } from './modelSlice'; +import { SD1ModelState } from './models/sd1ModelSlice'; +import { SD2ModelState } from './models/sd2ModelSlice'; /** * Models slice persist denylist */ -export const modelsPersistDenylist: (keyof ModelsState)[] = ['entities', 'ids']; +export const modelsPersistDenylist: + | (keyof SD1ModelState)[] + | (keyof SD2ModelState)[] = ['entities', 'ids']; diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index f86415cf37..fd9b8a0a08 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -1,20 +1,12 @@ import { UseToastOptions } from '@chakra-ui/react'; -import { PayloadAction } from '@reduxjs/toolkit'; -import { createSlice } from '@reduxjs/toolkit'; +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import * as InvokeAI from 'app/types/invokeai'; -import { ProgressImage } from 'services/events/types'; -import { makeToast } from '../../../app/components/Toaster'; -import { isAnySessionRejected, sessionCanceled } from 'services/thunks/session'; -import { receivedModels } from 'services/thunks/model'; -import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice'; -import { LogLevelName } from 'roarr'; import { InvokeLogLevel } from 'app/logging/useLogger'; -import { TFuncKey } from 'i18next'; -import { t } from 'i18next'; import { userInvoked } from 'app/store/actions'; -import { LANGUAGES } from '../components/LanguagePicker'; -import { imageUploaded } from 'services/thunks/image'; +import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice'; +import { TFuncKey, t } from 'i18next'; +import { LogLevelName } from 'roarr'; import { appSocketConnected, appSocketDisconnected, @@ -26,6 +18,12 @@ import { appSocketSubscribed, appSocketUnsubscribed, } from 'services/events/actions'; +import { ProgressImage } from 'services/events/types'; +import { imageUploaded } from 'services/thunks/image'; +import { getModels } from 'services/thunks/model'; +import { isAnySessionRejected, sessionCanceled } from 'services/thunks/session'; +import { makeToast } from '../../../app/components/Toaster'; +import { LANGUAGES } from '../components/LanguagePicker'; export type CancelStrategy = 'immediate' | 'scheduled'; @@ -382,7 +380,7 @@ export const systemSlice = createSlice({ /** * Received available models from the backend */ - builder.addCase(receivedModels.fulfilled, (state) => { + builder.addCase(getModels.fulfilled, (state) => { state.wereModelsReceived = true; }); diff --git a/invokeai/frontend/web/src/services/thunks/model.ts b/invokeai/frontend/web/src/services/thunks/model.ts index 97f2bd8016..4d134439f7 100644 --- a/invokeai/frontend/web/src/services/thunks/model.ts +++ b/invokeai/frontend/web/src/services/thunks/model.ts @@ -1,31 +1,55 @@ import { log } from 'app/logging/useLogger'; import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { Model } from 'features/system/store/modelSlice'; +import { SD1ModelType } from 'features/system/store/models/sd1ModelSlice'; import { reduce, size } from 'lodash-es'; -import { ModelsService } from 'services/api'; +import { BaseModelType, ModelType, ModelsService } from 'services/api'; const models = log.child({ namespace: 'model' }); export const IMAGES_PER_PAGE = 20; -export const receivedModels = createAppAsyncThunk( - 'models/receivedModels', - async (_) => { - const response = await ModelsService.listModels(); +type getModelsArg = { + baseModel: BaseModelType | undefined; + modelType: ModelType | undefined; +}; - const deserializedModels = reduce( - response.models['sd-1']['pipeline'], - (modelsAccumulator, model, modelName) => { - modelsAccumulator[modelName] = { ...model, name: modelName }; +export const getModels = createAppAsyncThunk( + 'models/getModels', + async (arg: getModelsArg) => { + const response = await ModelsService.listModels(arg); - return modelsAccumulator; - }, - {} as Record - ); + let deserializedModels = {}; + + if (arg.baseModel === undefined) return response.models; + if (arg.modelType === undefined) return response.models; + + if (arg.baseModel === 'sd-1') { + deserializedModels = reduce( + response.models[arg.baseModel][arg.modelType], + (modelsAccumulator, model, modelName) => { + modelsAccumulator[modelName] = { ...model, name: modelName }; + return modelsAccumulator; + }, + {} as Record + ); + } + + if (arg.baseModel === 'sd-2') { + deserializedModels = reduce( + response.models[arg.baseModel][arg.modelType], + (modelsAccumulator, model, modelName) => { + modelsAccumulator[modelName] = { ...model, name: modelName }; + return modelsAccumulator; + }, + {} as Record + ); + } models.info( { response }, - `Received ${size(response.models['sd-1']['pipeline'])} models` + `Received ${size(response.models[arg.baseModel][arg.modelType])} ${[ + arg.baseModel, + ]} models` ); return deserializedModels; From ef83a2fffe466e1800bd57b30f14384873010cb4 Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Sat, 17 Jun 2023 22:48:44 +0300 Subject: [PATCH 088/110] Add name, base_mode, type fields to model info --- invokeai/backend/model_management/model_manager.py | 4 +++- invokeai/backend/model_management/models/__init__.py | 8 +++++++- invokeai/backend/model_management/models/base.py | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index cbb319c6ea..e6cab04da7 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -530,6 +530,8 @@ class ModelManager(object): models[cur_base_model][cur_model_type][cur_model_name] = dict( **model_config.dict(exclude_defaults=True), + + # OpenAPIModelInfoBase name=cur_model_name, base_model=cur_base_model, type=cur_model_type, @@ -646,7 +648,7 @@ class ModelManager(object): model_class = MODEL_CLASSES[base_model][model_type] if model_class.save_to_config: # TODO: or exclude_unset better fits here? - data_to_save[model_key] = model_config.dict(exclude_defaults=True) + data_to_save[model_key] = model_config.dict(exclude_defaults=True, exclude={"error"}) yaml_str = OmegaConf.to_yaml(data_to_save) config_file_path = conf_file or self.config_path diff --git a/invokeai/backend/model_management/models/__init__.py b/invokeai/backend/model_management/models/__init__.py index eff71798a5..b22075991e 100644 --- a/invokeai/backend/model_management/models/__init__.py +++ b/invokeai/backend/model_management/models/__init__.py @@ -1,3 +1,4 @@ +from pydantic import BaseModel from .base import BaseModelType, ModelType, SubModelType, ModelBase, ModelConfigBase, ModelVariantType, SchedulerPredictionType, ModelError, SilenceWarnings from .stable_diffusion import StableDiffusion1Model, StableDiffusion2Model from .vae import VaeModel @@ -40,10 +41,15 @@ def _get_all_model_configs(): MODEL_CONFIGS = _get_all_model_configs() OPENAPI_MODEL_CONFIGS = list() +class OpenAPIModelInfoBase(BaseModel): + name: str + base_model: BaseModelType + type: ModelType + for cfg in MODEL_CONFIGS: model_name, cfg_name = cfg.__qualname__.split('.')[-2:] openapi_cfg_name = model_name + cfg_name - name_wrapper = type(openapi_cfg_name, (cfg,), {}) + name_wrapper = type(openapi_cfg_name, (cfg, OpenAPIModelInfoBase), {}) #globals()[name] = value vars()[openapi_cfg_name] = name_wrapper diff --git a/invokeai/backend/model_management/models/base.py b/invokeai/backend/model_management/models/base.py index 3bf0045918..ac43b938b0 100644 --- a/invokeai/backend/model_management/models/base.py +++ b/invokeai/backend/model_management/models/base.py @@ -53,7 +53,7 @@ class ModelConfigBase(BaseModel): format: Optional[str] = Field(None) default: Optional[bool] = Field(False) # do not save to config - error: Optional[ModelError] = Field(None, exclude=True) + error: Optional[ModelError] = Field(None) class Config: use_enum_values = True From d2f3500e1bac419904047c42746bdf6a933dee36 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 07:50:28 +1200 Subject: [PATCH 089/110] chore: Rebuild API - base_model and type added --- .../web/src/services/api/models/ControlNetModelConfig.ts | 5 +++++ .../frontend/web/src/services/api/models/LoraModelConfig.ts | 5 +++++ invokeai/frontend/web/src/services/api/models/ModelsList.ts | 4 ++++ .../api/models/StableDiffusion1ModelCheckpointConfig.ts | 5 +++++ .../api/models/StableDiffusion1ModelDiffusersConfig.ts | 5 +++++ .../api/models/StableDiffusion2ModelCheckpointConfig.ts | 5 +++++ .../api/models/StableDiffusion2ModelDiffusersConfig.ts | 5 +++++ .../src/services/api/models/TextualInversionModelConfig.ts | 5 +++++ .../frontend/web/src/services/api/models/VAEModelConfig.ts | 5 +++++ 9 files changed, 44 insertions(+) diff --git a/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts b/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts index 109fc39b7d..e4f77ba7bf 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts @@ -2,9 +2,14 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; export type ControlNetModelConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: ('checkpoint' | 'diffusers'); diff --git a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts b/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts index a62d1a3f4b..d300e38fd0 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts @@ -2,9 +2,14 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; export type LoRAModelConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: ('lycoris' | 'diffusers'); diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index db41b33048..42d0ddd8f6 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -25,10 +25,14 @@ import type { TextualInversionModelConfig } from './TextualInversionModelConfig' import type { VaeModelConfig } from './VaeModelConfig'; export type ModelsList = { +<<<<<<< HEAD <<<<<<< HEAD models: Record>>; >>>>>>> 76dd749b1 (chore: Rebuild API) ======= models: Record>>; >>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) +======= + models: Record>>; +>>>>>>> 24673fd85 (chore: Rebuild API - base_model and type added) }; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts index 7b3f90cd0a..c9708a0b6f 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts @@ -2,10 +2,15 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; export type StableDiffusion1ModelCheckpointConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: 'checkpoint'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts index ec634b9692..4b6f834216 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts @@ -2,10 +2,15 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; export type StableDiffusion1ModelDiffusersConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts index bcf9801314..27b6879703 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts @@ -2,11 +2,16 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; import type { SchedulerPredictionType } from './SchedulerPredictionType'; export type StableDiffusion2ModelCheckpointConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: 'checkpoint'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts index 3f3142dae3..a2b66d7157 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts @@ -2,11 +2,16 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; import type { SchedulerPredictionType } from './SchedulerPredictionType'; export type StableDiffusion2ModelDiffusersConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts b/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts index 34ef4791bc..7abfbec081 100644 --- a/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts @@ -2,9 +2,14 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; export type TextualInversionModelConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: null; diff --git a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts b/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts index 8d0d2e8304..ad7f70c3d4 100644 --- a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts @@ -2,9 +2,14 @@ /* tslint:disable */ /* eslint-disable */ +import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; +import type { ModelType } from './ModelType'; export type VaeModelConfig = { + name: string; + base_model: BaseModelType; + type: ModelType; path: string; description?: string; format: ('checkpoint' | 'diffusers'); From 727293d722a946d837139ad9a95e44a56ca36ca3 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 08:26:25 +1200 Subject: [PATCH 090/110] fix: 2.1 models breaking generation Co-Authored-By: StAlKeR7779 <7768370+StAlKeR7779@users.noreply.github.com> --- invokeai/backend/model_management/models/base.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/invokeai/backend/model_management/models/base.py b/invokeai/backend/model_management/models/base.py index ac43b938b0..f32e658aa1 100644 --- a/invokeai/backend/model_management/models/base.py +++ b/invokeai/backend/model_management/models/base.py @@ -94,6 +94,11 @@ class ModelBase(metaclass=ABCMeta): def _hf_definition_to_type(self, subtypes: List[str]) -> Type: if len(subtypes) < 2: raise Exception("Invalid subfolder definition!") + if all(t is None for t in subtypes): + return None + elif any(t is None for t in subtypes): + raise Exception(f"Unsupported definition: {subtypes}") + if subtypes[0] in ["diffusers", "transformers"]: res_type = sys.modules[subtypes[0]] subtypes = subtypes[1:] From 4847212d5beede6d79f5f3067be020d52aca8d33 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 08:27:13 +1200 Subject: [PATCH 091/110] feat: Enable 2.x Model Generation in Linear UI --- .../parameters/store/generationSlice.ts | 7 ++++++ .../system/components/ModelSelect.tsx | 24 +++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 39f38e386c..96a6070ad2 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,6 +1,7 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import { Scheduler } from 'app/constants'; +import { ModelLoaderTypes } from 'features/system/components/ModelSelect'; import { configChanged } from 'features/system/store/configSlice'; import { clamp, sortBy } from 'lodash-es'; import { ImageDTO } from 'services/api'; @@ -49,6 +50,7 @@ export interface GenerationState { horizontalSymmetrySteps: number; verticalSymmetrySteps: number; model: ModelParam; + currentModelType: ModelLoaderTypes; shouldUseSeamless: boolean; seamlessXAxis: boolean; seamlessYAxis: boolean; @@ -83,6 +85,7 @@ export const initialGenerationState: GenerationState = { horizontalSymmetrySteps: 0, verticalSymmetrySteps: 0, model: '', + currentModelType: 'sd1_model_loader', shouldUseSeamless: false, seamlessXAxis: true, seamlessYAxis: true, @@ -218,6 +221,9 @@ export const generationSlice = createSlice({ modelSelected: (state, action: PayloadAction) => { state.model = action.payload; }, + setCurrentModelType: (state, action: PayloadAction) => { + state.currentModelType = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(getModels.fulfilled, (state, action) => { @@ -278,6 +284,7 @@ export const { setVerticalSymmetrySteps, initialImageChanged, modelSelected, + setCurrentModelType, setShouldUseNoiseSettings, setSeamless, setSeamlessXAxis, diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index a65c8501dc..bf0775d52e 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -1,6 +1,6 @@ import { createSelector } from '@reduxjs/toolkit'; import { isEqual } from 'lodash-es'; -import { memo, useCallback } from 'react'; +import { memo, useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { RootState } from 'app/store/store'; @@ -9,7 +9,11 @@ import IAIMantineSelect, { IAISelectDataType, } from 'common/components/IAIMantineSelect'; import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { modelSelected } from 'features/parameters/store/generationSlice'; +import { + modelSelected, + setCurrentModelType, +} from 'features/parameters/store/generationSlice'; + import { selectAllSD1Models, selectByIdSD1Models, @@ -55,12 +59,28 @@ export const modelSelector = createSelector( } ); +export type ModelLoaderTypes = 'sd1_model_loader' | 'sd2_model_loader'; + +const MODEL_LOADER_MAP = { + 'sd-1': 'sd1_model_loader', + 'sd-2': 'sd2_model_loader', +}; + const ModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); const { selectedModel, sd1ModelData, sd2ModelData } = useAppSelector(modelSelector); + useEffect(() => { + if (selectedModel) + dispatch( + setCurrentModelType( + MODEL_LOADER_MAP[selectedModel?.base_model] as ModelLoaderTypes + ) + ); + }, [dispatch, selectedModel]); + const handleChangeModel = useCallback( (v: string | null) => { if (!v) { From 604cc1adcdd3c161ba013296265fbca71b534028 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 09:19:13 +1200 Subject: [PATCH 092/110] wip: Move Model Selector to own file --- .../fields/ModelInputFieldComponent.tsx | 10 ++-- .../system/components/ModelSelect.tsx | 57 ++----------------- .../features/system/store/modelSelectors.ts | 53 ++++++++++++++++- 3 files changed, 62 insertions(+), 58 deletions(-) diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx index d3d37765f4..3842e8da3a 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx @@ -5,7 +5,8 @@ import { ModelInputFieldTemplate, ModelInputFieldValue, } from 'features/nodes/types/types'; -import { modelSelector } from 'features/system/components/ModelSelect'; + +import { modelSelector } from 'features/system/store/modelSelectors'; import { ChangeEvent, memo } from 'react'; import { FieldComponentProps } from './types'; @@ -16,7 +17,8 @@ const ModelInputFieldComponent = ( const dispatch = useAppDispatch(); - const { sd1ModelData, sd2ModelData } = useAppSelector(modelSelector); + const { sd1ModelDropDownData, sd2ModelDropdownData } = + useAppSelector(modelSelector); const handleValueChanged = (e: ChangeEvent) => { dispatch( @@ -31,8 +33,8 @@ const ModelInputFieldComponent = ( return ( ); }; diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index bf0775d52e..43de144991 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -1,63 +1,14 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { isEqual } from 'lodash-es'; import { memo, useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIMantineSelect, { - IAISelectDataType, -} from 'common/components/IAIMantineSelect'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; import { modelSelected, setCurrentModelType, } from 'features/parameters/store/generationSlice'; -import { - selectAllSD1Models, - selectByIdSD1Models, -} from '../store/models/sd1ModelSlice'; -import { - selectAllSD2Models, - selectByIdSD2Models, -} from '../store/models/sd2ModelSlice'; - -export const modelSelector = createSelector( - [(state: RootState) => state, generationSelector], - (state, generation) => { - let selectedModel = selectByIdSD1Models(state, generation.model); - if (selectedModel === undefined) - selectedModel = selectByIdSD2Models(state, generation.model); - - const sd1ModelData = selectAllSD1Models(state) - .map((m) => ({ - value: m.name, - label: m.name, - group: '1.x Models', - })) - .sort((a, b) => a.label.localeCompare(b.label)); - - const sd2ModelData = selectAllSD2Models(state) - .map((m) => ({ - value: m.name, - label: m.name, - group: '2.x Models', - })) - .sort((a, b) => a.label.localeCompare(b.label)); - - return { - selectedModel, - sd1ModelData, - sd2ModelData, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); +import { modelSelector } from '../store/modelSelectors'; export type ModelLoaderTypes = 'sd1_model_loader' | 'sd2_model_loader'; @@ -69,7 +20,7 @@ const MODEL_LOADER_MAP = { const ModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const { selectedModel, sd1ModelData, sd2ModelData } = + const { selectedModel, sd1ModelDropDownData, sd2ModelDropdownData } = useAppSelector(modelSelector); useEffect(() => { @@ -97,7 +48,7 @@ const ModelSelect = () => { label={t('modelManager.model')} value={selectedModel?.name ?? ''} placeholder="Pick one" - data={sd1ModelData.concat(sd2ModelData)} + data={sd1ModelDropDownData.concat(sd2ModelDropdownData)} onChange={handleChangeModel} /> ); diff --git a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts b/invokeai/frontend/web/src/features/system/store/modelSelectors.ts index f857bc85bc..6e101da5f5 100644 --- a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts +++ b/invokeai/frontend/web/src/features/system/store/modelSelectors.ts @@ -1,3 +1,54 @@ +import { createSelector } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; +import { IAISelectDataType } from 'common/components/IAIMantineSelect'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { isEqual } from 'lodash-es'; +import { + selectAllSD1Models, + selectByIdSD1Models, +} from './models/sd1ModelSlice'; +import { + selectAllSD2Models, + selectByIdSD2Models, +} from './models/sd2ModelSlice'; -export const modelSelector = (state: RootState) => state.models; +export const modelSelector = createSelector( + [(state: RootState) => state, generationSelector], + (state, generation) => { + let selectedModel = selectByIdSD1Models(state, generation.model); + if (selectedModel === undefined) + selectedModel = selectByIdSD2Models(state, generation.model); + + const sd1Models = selectAllSD1Models(state); + const sd2Models = selectAllSD2Models(state); + + const sd1ModelDropDownData = selectAllSD1Models(state) + .map((m) => ({ + value: m.name, + label: m.name, + group: '1.x Models', + })) + .sort((a, b) => a.label.localeCompare(b.label)); + + const sd2ModelDropdownData = selectAllSD2Models(state) + .map((m) => ({ + value: m.name, + label: m.name, + group: '2.x Models', + })) + .sort((a, b) => a.label.localeCompare(b.label)); + + return { + selectedModel, + sd1Models, + sd2Models, + sd1ModelDropDownData, + sd2ModelDropdownData, + }; + }, + { + memoizeOptions: { + resultEqualityCheck: isEqual, + }, + } +); From 0c3616229e346844a68be393d8f17b5216149eae Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 17:36:23 +1200 Subject: [PATCH 093/110] cleanup: Updated model slice names to be more descriptive Basically updated all slices to be more descriptive in their names. Did so in order to make sure theres good naming scheme available for secondary models. --- .../enhancers/reduxRemember/unserialize.ts | 8 +-- .../listeners/socketio/socketConnected.ts | 7 ++- invokeai/frontend/web/src/app/store/store.ts | 8 +-- .../fields/ModelInputFieldComponent.tsx | 6 +- .../system/components/ModelSelect.tsx | 9 ++- .../features/system/store/modelSelectors.ts | 37 ++++++------ .../system/store/models/sd1ModelSlice.ts | 53 ----------------- .../store/models/sd1PipelineModelSlice.ts | 57 +++++++++++++++++++ .../system/store/models/sd2ModelSlice.ts | 53 ----------------- .../store/models/sd2PipelineModelSlice.ts | 57 +++++++++++++++++++ .../system/store/modelsPersistDenylist.ts | 8 +-- .../frontend/web/src/services/thunks/model.ts | 7 ++- 12 files changed, 164 insertions(+), 146 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts create mode 100644 invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts delete mode 100644 invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts create mode 100644 invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts index 93cc19f832..dc1c25c015 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts @@ -7,8 +7,8 @@ import { initialNodesState } from 'features/nodes/store/nodesSlice'; import { initialGenerationState } from 'features/parameters/store/generationSlice'; import { initialPostprocessingState } from 'features/parameters/store/postprocessingSlice'; import { initialConfigState } from 'features/system/store/configSlice'; -import { sd1InitialModelsState } from 'features/system/store/models/sd1ModelSlice'; -import { sd2InitialModelsState } from 'features/system/store/models/sd2ModelSlice'; +import { sd1InitialPipelineModelsState } from 'features/system/store/models/sd1PipelineModelSlice'; +import { sd2InitialPipelineModelsState } from 'features/system/store/models/sd2PipelineModelSlice'; import { initialSystemState } from 'features/system/store/systemSlice'; import { initialHotkeysState } from 'features/ui/store/hotkeysSlice'; import { initialUIState } from 'features/ui/store/uiSlice'; @@ -22,8 +22,8 @@ const initialStates: { gallery: initialGalleryState, generation: initialGenerationState, lightbox: initialLightboxState, - sd1models: sd1InitialModelsState, - sd2models: sd2InitialModelsState, + sd1pipelinemodels: sd1InitialPipelineModelsState, + sd2pipelinemodels: sd2InitialPipelineModelsState, nodes: initialNodesState, postprocessing: initialPostprocessingState, system: initialSystemState, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index b257b470bd..a88576565a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -15,7 +15,8 @@ export const addSocketConnectedEventListener = () => { moduleLog.debug({ timestamp }, 'Connected'); - const { sd1models, sd2models, nodes, config, images } = getState(); + const { sd1pipelinemodels, sd2pipelinemodels, nodes, config, images } = + getState(); const { disabledTabs } = config; @@ -28,11 +29,11 @@ export const addSocketConnectedEventListener = () => { ); } - if (!sd1models.ids.length) { + if (!sd1pipelinemodels.ids.length) { dispatch(getModels({ baseModel: 'sd-1', modelType: 'pipeline' })); } - if (!sd2models.ids.length) { + if (!sd2pipelinemodels.ids.length) { dispatch(getModels({ baseModel: 'sd-2', modelType: 'pipeline' })); } diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 4ecc9eb9bf..8489de85f0 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -30,8 +30,8 @@ import { actionsDenylist } from './middleware/devtools/actionsDenylist'; import { stateSanitizer } from './middleware/devtools/stateSanitizer'; // Model Reducers -import sd1ModelReducer from 'features/system/store/models/sd1ModelSlice'; -import sd2ModelReducer from 'features/system/store/models/sd2ModelSlice'; +import sd1PipelineModelReducer from 'features/system/store/models/sd1PipelineModelSlice'; +import sd2PipelineModelReducer from 'features/system/store/models/sd2PipelineModelSlice'; import { LOCALSTORAGE_PREFIX } from './constants'; import { serialize } from './enhancers/reduxRemember/serialize'; @@ -43,8 +43,8 @@ const allReducers = { gallery: galleryReducer, generation: generationReducer, lightbox: lightboxReducer, - sd1models: sd1ModelReducer, - sd2models: sd2ModelReducer, + sd1pipelinemodels: sd1PipelineModelReducer, + sd2pipelinemodels: sd2PipelineModelReducer, nodes: nodesReducer, postprocessing: postprocessingReducer, system: systemReducer, diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx index 3842e8da3a..480c8591bb 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx @@ -17,7 +17,7 @@ const ModelInputFieldComponent = ( const dispatch = useAppDispatch(); - const { sd1ModelDropDownData, sd2ModelDropdownData } = + const { sd1PipelineModelDropDownData, sd2PipelineModelDropdownData } = useAppSelector(modelSelector); const handleValueChanged = (e: ChangeEvent) => { @@ -33,8 +33,8 @@ const ModelInputFieldComponent = ( return ( ); }; diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index 43de144991..813bd9fb70 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -20,8 +20,11 @@ const MODEL_LOADER_MAP = { const ModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const { selectedModel, sd1ModelDropDownData, sd2ModelDropdownData } = - useAppSelector(modelSelector); + const { + selectedModel, + sd1PipelineModelDropDownData, + sd2PipelineModelDropdownData, + } = useAppSelector(modelSelector); useEffect(() => { if (selectedModel) @@ -48,7 +51,7 @@ const ModelSelect = () => { label={t('modelManager.model')} value={selectedModel?.name ?? ''} placeholder="Pick one" - data={sd1ModelDropDownData.concat(sd2ModelDropdownData)} + data={sd1PipelineModelDropDownData.concat(sd2PipelineModelDropdownData)} onChange={handleChangeModel} /> ); diff --git a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts b/invokeai/frontend/web/src/features/system/store/modelSelectors.ts index 6e101da5f5..b63c6d256c 100644 --- a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts +++ b/invokeai/frontend/web/src/features/system/store/modelSelectors.ts @@ -3,26 +3,30 @@ import { RootState } from 'app/store/store'; import { IAISelectDataType } from 'common/components/IAIMantineSelect'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { isEqual } from 'lodash-es'; + import { - selectAllSD1Models, - selectByIdSD1Models, -} from './models/sd1ModelSlice'; + selectAllSD1PipelineModels, + selectByIdSD1PipelineModels, +} from './models/sd1PipelineModelSlice'; + import { - selectAllSD2Models, - selectByIdSD2Models, -} from './models/sd2ModelSlice'; + selectAllSD2PipelineModels, + selectByIdSD2PipelineModels, +} from './models/sd2PipelineModelSlice'; export const modelSelector = createSelector( [(state: RootState) => state, generationSelector], (state, generation) => { - let selectedModel = selectByIdSD1Models(state, generation.model); + let selectedModel = selectByIdSD1PipelineModels(state, generation.model); if (selectedModel === undefined) - selectedModel = selectByIdSD2Models(state, generation.model); + selectedModel = selectByIdSD2PipelineModels(state, generation.model); - const sd1Models = selectAllSD1Models(state); - const sd2Models = selectAllSD2Models(state); + const sd1PipelineModels = selectAllSD1PipelineModels(state); + const sd2PipelineModels = selectAllSD2PipelineModels(state); - const sd1ModelDropDownData = selectAllSD1Models(state) + const allPipelineModels = sd1PipelineModels.concat(sd2PipelineModels); + + const sd1PipelineModelDropDownData = selectAllSD1PipelineModels(state) .map((m) => ({ value: m.name, label: m.name, @@ -30,7 +34,7 @@ export const modelSelector = createSelector( })) .sort((a, b) => a.label.localeCompare(b.label)); - const sd2ModelDropdownData = selectAllSD2Models(state) + const sd2PipelineModelDropdownData = selectAllSD2PipelineModels(state) .map((m) => ({ value: m.name, label: m.name, @@ -40,10 +44,11 @@ export const modelSelector = createSelector( return { selectedModel, - sd1Models, - sd2Models, - sd1ModelDropDownData, - sd2ModelDropdownData, + allPipelineModels, + sd1PipelineModels, + sd2PipelineModels, + sd1PipelineModelDropDownData, + sd2PipelineModelDropdownData, }; }, { diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts deleted file mode 100644 index 9f62fde264..0000000000 --- a/invokeai/frontend/web/src/features/system/store/models/sd1ModelSlice.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { - StableDiffusion1ModelCheckpointConfig, - StableDiffusion1ModelDiffusersConfig, -} from 'services/api'; - -import { getModels } from 'services/thunks/model'; - -export type SD1ModelType = ( - | StableDiffusion1ModelCheckpointConfig - | StableDiffusion1ModelDiffusersConfig -) & { - name: string; -}; - -export const sd1ModelsAdapter = createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), -}); - -export const sd1InitialModelsState = sd1ModelsAdapter.getInitialState(); - -export type SD1ModelState = typeof sd1InitialModelsState; - -export const sd1ModelsSlice = createSlice({ - name: 'sd1models', - initialState: sd1InitialModelsState, - reducers: { - modelAdded: sd1ModelsAdapter.upsertOne, - }, - extraReducers(builder) { - /** - * Received Models - FULFILLED - */ - builder.addCase(getModels.fulfilled, (state, action) => { - if (action.meta.arg.baseModel !== 'sd-1') return; - sd1ModelsAdapter.setAll(state, action.payload); - }); - }, -}); - -export const { - selectAll: selectAllSD1Models, - selectById: selectByIdSD1Models, - selectEntities: selectEntitiesSD1Models, - selectIds: selectIdsSD1Models, - selectTotal: selectTotalSD1Models, -} = sd1ModelsAdapter.getSelectors((state) => state.sd1models); - -export const { modelAdded } = sd1ModelsSlice.actions; - -export default sd1ModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts new file mode 100644 index 0000000000..5755b14886 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts @@ -0,0 +1,57 @@ +import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { + StableDiffusion1ModelCheckpointConfig, + StableDiffusion1ModelDiffusersConfig, +} from 'services/api'; + +import { getModels } from 'services/thunks/model'; + +export type SD1PipelineModelType = ( + | StableDiffusion1ModelCheckpointConfig + | StableDiffusion1ModelDiffusersConfig +) & { + name: string; +}; + +export const sd1PipelineModelsAdapter = + createEntityAdapter({ + selectId: (model) => model.name, + sortComparer: (a, b) => a.name.localeCompare(b.name), + }); + +export const sd1InitialPipelineModelsState = + sd1PipelineModelsAdapter.getInitialState(); + +export type SD1PipelineModelState = typeof sd1InitialPipelineModelsState; + +export const sd1PipelineModelsSlice = createSlice({ + name: 'sd1models', + initialState: sd1InitialPipelineModelsState, + reducers: { + modelAdded: sd1PipelineModelsAdapter.upsertOne, + }, + extraReducers(builder) { + /** + * Received Models - FULFILLED + */ + builder.addCase(getModels.fulfilled, (state, action) => { + if (action.meta.arg.baseModel !== 'sd-1') return; + sd1PipelineModelsAdapter.setAll(state, action.payload); + }); + }, +}); + +export const { + selectAll: selectAllSD1PipelineModels, + selectById: selectByIdSD1PipelineModels, + selectEntities: selectEntitiesSD1PipelineModels, + selectIds: selectIdsSD1PipelineModels, + selectTotal: selectTotalSD1PipelineModels, +} = sd1PipelineModelsAdapter.getSelectors( + (state) => state.sd1pipelinemodels +); + +export const { modelAdded } = sd1PipelineModelsSlice.actions; + +export default sd1PipelineModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts deleted file mode 100644 index e8e1f5bedf..0000000000 --- a/invokeai/frontend/web/src/features/system/store/models/sd2ModelSlice.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { - StableDiffusion2ModelCheckpointConfig, - StableDiffusion2ModelDiffusersConfig, -} from 'services/api'; - -import { getModels } from 'services/thunks/model'; - -export type SD2ModelType = ( - | StableDiffusion2ModelCheckpointConfig - | StableDiffusion2ModelDiffusersConfig -) & { - name: string; -}; - -export const sd2ModelsAdapater = createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), -}); - -export const sd2InitialModelsState = sd2ModelsAdapater.getInitialState(); - -export type SD2ModelState = typeof sd2InitialModelsState; - -export const sd2ModelsSlice = createSlice({ - name: 'sd2models', - initialState: sd2InitialModelsState, - reducers: { - modelAdded: sd2ModelsAdapater.upsertOne, - }, - extraReducers(builder) { - /** - * Received Models - FULFILLED - */ - builder.addCase(getModels.fulfilled, (state, action) => { - if (action.meta.arg.baseModel !== 'sd-2') return; - sd2ModelsAdapater.setAll(state, action.payload); - }); - }, -}); - -export const { - selectAll: selectAllSD2Models, - selectById: selectByIdSD2Models, - selectEntities: selectEntitiesSD2Models, - selectIds: selectIdsSD2Models, - selectTotal: selectTotalSD2Models, -} = sd2ModelsAdapater.getSelectors((state) => state.sd2models); - -export const { modelAdded } = sd2ModelsSlice.actions; - -export default sd2ModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts new file mode 100644 index 0000000000..0c307e23cc --- /dev/null +++ b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts @@ -0,0 +1,57 @@ +import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { + StableDiffusion2ModelCheckpointConfig, + StableDiffusion2ModelDiffusersConfig, +} from 'services/api'; + +import { getModels } from 'services/thunks/model'; + +export type SD2PipelineModelType = ( + | StableDiffusion2ModelCheckpointConfig + | StableDiffusion2ModelDiffusersConfig +) & { + name: string; +}; + +export const sd2PipelineModelsAdapater = + createEntityAdapter({ + selectId: (model) => model.name, + sortComparer: (a, b) => a.name.localeCompare(b.name), + }); + +export const sd2InitialPipelineModelsState = + sd2PipelineModelsAdapater.getInitialState(); + +export type SD2PipelineModelState = typeof sd2InitialPipelineModelsState; + +export const sd2PipelineModelsSlice = createSlice({ + name: 'sd2models', + initialState: sd2InitialPipelineModelsState, + reducers: { + modelAdded: sd2PipelineModelsAdapater.upsertOne, + }, + extraReducers(builder) { + /** + * Received Models - FULFILLED + */ + builder.addCase(getModels.fulfilled, (state, action) => { + if (action.meta.arg.baseModel !== 'sd-2') return; + sd2PipelineModelsAdapater.setAll(state, action.payload); + }); + }, +}); + +export const { + selectAll: selectAllSD2PipelineModels, + selectById: selectByIdSD2PipelineModels, + selectEntities: selectEntitiesSD2PipelineModels, + selectIds: selectIdsSD2PipelineModels, + selectTotal: selectTotalSD2PipelineModels, +} = sd2PipelineModelsAdapater.getSelectors( + (state) => state.sd2pipelinemodels +); + +export const { modelAdded } = sd2PipelineModelsSlice.actions; + +export default sd2PipelineModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts index 7b0d78d37e..417a399cf2 100644 --- a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts @@ -1,9 +1,9 @@ -import { SD1ModelState } from './models/sd1ModelSlice'; -import { SD2ModelState } from './models/sd2ModelSlice'; +import { SD1PipelineModelState } from './models/sd1PipelineModelSlice'; +import { SD2PipelineModelState } from './models/sd2PipelineModelSlice'; /** * Models slice persist denylist */ export const modelsPersistDenylist: - | (keyof SD1ModelState)[] - | (keyof SD2ModelState)[] = ['entities', 'ids']; + | (keyof SD1PipelineModelState)[] + | (keyof SD2PipelineModelState)[] = ['entities', 'ids']; diff --git a/invokeai/frontend/web/src/services/thunks/model.ts b/invokeai/frontend/web/src/services/thunks/model.ts index 4d134439f7..039748fa3f 100644 --- a/invokeai/frontend/web/src/services/thunks/model.ts +++ b/invokeai/frontend/web/src/services/thunks/model.ts @@ -1,6 +1,7 @@ import { log } from 'app/logging/useLogger'; import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { SD1ModelType } from 'features/system/store/models/sd1ModelSlice'; +import { SD1PipelineModelType } from 'features/system/store/models/sd1PipelineModelSlice'; +import { SD2PipelineModelType } from 'features/system/store/models/sd2PipelineModelSlice'; import { reduce, size } from 'lodash-es'; import { BaseModelType, ModelType, ModelsService } from 'services/api'; @@ -30,7 +31,7 @@ export const getModels = createAppAsyncThunk( modelsAccumulator[modelName] = { ...model, name: modelName }; return modelsAccumulator; }, - {} as Record + {} as Record ); } @@ -41,7 +42,7 @@ export const getModels = createAppAsyncThunk( modelsAccumulator[modelName] = { ...model, name: modelName }; return modelsAccumulator; }, - {} as Record + {} as Record ); } From 6bdf68dd4cd17e0e4fac0b38a9c5ce5e286dd5aa Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:31:53 +1200 Subject: [PATCH 094/110] feat: Port Schedulers to Mantine --- .../web/src/features/parameters/store/generationSlice.ts | 1 - .../system/components/SettingsModal/SettingsSchedulers.tsx | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 96a6070ad2..d93552809d 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,6 +1,5 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import { Scheduler } from 'app/constants'; import { ModelLoaderTypes } from 'features/system/components/ModelSelect'; import { configChanged } from 'features/system/store/configSlice'; import { clamp, sortBy } from 'lodash-es'; diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx index 2e0b3234c7..a27db43e6b 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx @@ -16,6 +16,7 @@ const data = map(SCHEDULER_NAMES, (s) => ({ export default function SettingsSchedulers() { const dispatch = useAppDispatch(); + const { t } = useTranslation(); const enabledSchedulers = useAppSelector( From e48528bbefc773728faf57b17aec075e6765e07b Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:35:46 +1200 Subject: [PATCH 095/110] revert: getModels to receivedModels --- .../listeners/socketio/socketConnected.ts | 6 +++--- .../web/src/features/parameters/store/generationSlice.ts | 4 ++-- .../features/system/store/models/sd1PipelineModelSlice.ts | 4 ++-- .../features/system/store/models/sd2PipelineModelSlice.ts | 4 ++-- .../frontend/web/src/features/system/store/systemSlice.ts | 4 ++-- invokeai/frontend/web/src/services/thunks/model.ts | 8 ++++---- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index a88576565a..0893066f1f 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -1,7 +1,7 @@ import { log } from 'app/logging/useLogger'; import { appSocketConnected, socketConnected } from 'services/events/actions'; import { receivedPageOfImages } from 'services/thunks/image'; -import { getModels } from 'services/thunks/model'; +import { receivedModels } from 'services/thunks/model'; import { receivedOpenAPISchema } from 'services/thunks/schema'; import { startAppListening } from '../..'; @@ -30,11 +30,11 @@ export const addSocketConnectedEventListener = () => { } if (!sd1pipelinemodels.ids.length) { - dispatch(getModels({ baseModel: 'sd-1', modelType: 'pipeline' })); + dispatch(receivedModels({ baseModel: 'sd-1', modelType: 'pipeline' })); } if (!sd2pipelinemodels.ids.length) { - dispatch(getModels({ baseModel: 'sd-2', modelType: 'pipeline' })); + dispatch(receivedModels({ baseModel: 'sd-2', modelType: 'pipeline' })); } if (!nodes.schema && !disabledTabs.includes('nodes')) { diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index d93552809d..946e9084d8 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -5,7 +5,7 @@ import { configChanged } from 'features/system/store/configSlice'; import { clamp, sortBy } from 'lodash-es'; import { ImageDTO } from 'services/api'; import { imageUrlsReceived } from 'services/thunks/image'; -import { getModels } from 'services/thunks/model'; +import { receivedModels } from 'services/thunks/model'; import { CfgScaleParam, HeightParam, @@ -225,7 +225,7 @@ export const generationSlice = createSlice({ }, }, extraReducers: (builder) => { - builder.addCase(getModels.fulfilled, (state, action) => { + builder.addCase(receivedModels.fulfilled, (state, action) => { if (!state.model) { const firstModel = sortBy(action.payload, 'name')[0]; state.model = firstModel.name; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts index 5755b14886..8c8fbbd4f2 100644 --- a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts @@ -5,7 +5,7 @@ import { StableDiffusion1ModelDiffusersConfig, } from 'services/api'; -import { getModels } from 'services/thunks/model'; +import { receivedModels } from 'services/thunks/model'; export type SD1PipelineModelType = ( | StableDiffusion1ModelCheckpointConfig @@ -35,7 +35,7 @@ export const sd1PipelineModelsSlice = createSlice({ /** * Received Models - FULFILLED */ - builder.addCase(getModels.fulfilled, (state, action) => { + builder.addCase(receivedModels.fulfilled, (state, action) => { if (action.meta.arg.baseModel !== 'sd-1') return; sd1PipelineModelsAdapter.setAll(state, action.payload); }); diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts index 0c307e23cc..fb94fae9d0 100644 --- a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts @@ -5,7 +5,7 @@ import { StableDiffusion2ModelDiffusersConfig, } from 'services/api'; -import { getModels } from 'services/thunks/model'; +import { receivedModels } from 'services/thunks/model'; export type SD2PipelineModelType = ( | StableDiffusion2ModelCheckpointConfig @@ -35,7 +35,7 @@ export const sd2PipelineModelsSlice = createSlice({ /** * Received Models - FULFILLED */ - builder.addCase(getModels.fulfilled, (state, action) => { + builder.addCase(receivedModels.fulfilled, (state, action) => { if (action.meta.arg.baseModel !== 'sd-2') return; sd2PipelineModelsAdapater.setAll(state, action.payload); }); diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index fd9b8a0a08..8a148ca38b 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -20,7 +20,7 @@ import { } from 'services/events/actions'; import { ProgressImage } from 'services/events/types'; import { imageUploaded } from 'services/thunks/image'; -import { getModels } from 'services/thunks/model'; +import { receivedModels } from 'services/thunks/model'; import { isAnySessionRejected, sessionCanceled } from 'services/thunks/session'; import { makeToast } from '../../../app/components/Toaster'; import { LANGUAGES } from '../components/LanguagePicker'; @@ -380,7 +380,7 @@ export const systemSlice = createSlice({ /** * Received available models from the backend */ - builder.addCase(getModels.fulfilled, (state) => { + builder.addCase(receivedModels.fulfilled, (state) => { state.wereModelsReceived = true; }); diff --git a/invokeai/frontend/web/src/services/thunks/model.ts b/invokeai/frontend/web/src/services/thunks/model.ts index 039748fa3f..05766de7b3 100644 --- a/invokeai/frontend/web/src/services/thunks/model.ts +++ b/invokeai/frontend/web/src/services/thunks/model.ts @@ -9,14 +9,14 @@ const models = log.child({ namespace: 'model' }); export const IMAGES_PER_PAGE = 20; -type getModelsArg = { +type receivedModelsArg = { baseModel: BaseModelType | undefined; modelType: ModelType | undefined; }; -export const getModels = createAppAsyncThunk( - 'models/getModels', - async (arg: getModelsArg) => { +export const receivedModels = createAppAsyncThunk( + 'models/receivedModels', + async (arg: receivedModelsArg) => { const response = await ModelsService.listModels(arg); let deserializedModels = {}; From 7033071934d725d4303c4249de3b45c651f9e231 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:38:15 +1200 Subject: [PATCH 096/110] fix: Unserialization key issue --- .../web/src/app/store/enhancers/reduxRemember/unserialize.ts | 4 ++-- .../src/features/system/store/models/sd1PipelineModelSlice.ts | 2 +- .../src/features/system/store/models/sd2PipelineModelSlice.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts index dc1c25c015..649b56316d 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts @@ -22,8 +22,8 @@ const initialStates: { gallery: initialGalleryState, generation: initialGenerationState, lightbox: initialLightboxState, - sd1pipelinemodels: sd1InitialPipelineModelsState, - sd2pipelinemodels: sd2InitialPipelineModelsState, + sd1PipelineModels: sd1InitialPipelineModelsState, + sd2PipelineModels: sd2InitialPipelineModelsState, nodes: initialNodesState, postprocessing: initialPostprocessingState, system: initialSystemState, diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts index 8c8fbbd4f2..a59c29d87a 100644 --- a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts @@ -26,7 +26,7 @@ export const sd1InitialPipelineModelsState = export type SD1PipelineModelState = typeof sd1InitialPipelineModelsState; export const sd1PipelineModelsSlice = createSlice({ - name: 'sd1models', + name: 'sd1PipelineModels', initialState: sd1InitialPipelineModelsState, reducers: { modelAdded: sd1PipelineModelsAdapter.upsertOne, diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts index fb94fae9d0..8e10767a7c 100644 --- a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts @@ -26,7 +26,7 @@ export const sd2InitialPipelineModelsState = export type SD2PipelineModelState = typeof sd2InitialPipelineModelsState; export const sd2PipelineModelsSlice = createSlice({ - name: 'sd2models', + name: 'sd2PipelineModels', initialState: sd2InitialPipelineModelsState, reducers: { modelAdded: sd2PipelineModelsAdapater.upsertOne, From 6256be480c9cb75afa8bf1a22ac4a1a2c3d89dff Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:41:30 +1200 Subject: [PATCH 097/110] fix: Remove type from Model type name --- .../system/store/models/sd1PipelineModelSlice.ts | 11 +++++------ .../system/store/models/sd2PipelineModelSlice.ts | 11 +++++------ invokeai/frontend/web/src/services/thunks/model.ts | 8 ++++---- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts index a59c29d87a..99f1514e6c 100644 --- a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts @@ -7,18 +7,17 @@ import { import { receivedModels } from 'services/thunks/model'; -export type SD1PipelineModelType = ( +export type SD1PipelineModel = ( | StableDiffusion1ModelCheckpointConfig | StableDiffusion1ModelDiffusersConfig ) & { name: string; }; -export const sd1PipelineModelsAdapter = - createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), - }); +export const sd1PipelineModelsAdapter = createEntityAdapter({ + selectId: (model) => model.name, + sortComparer: (a, b) => a.name.localeCompare(b.name), +}); export const sd1InitialPipelineModelsState = sd1PipelineModelsAdapter.getInitialState(); diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts index 8e10767a7c..69ff772222 100644 --- a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts @@ -7,18 +7,17 @@ import { import { receivedModels } from 'services/thunks/model'; -export type SD2PipelineModelType = ( +export type SD2PipelineModel = ( | StableDiffusion2ModelCheckpointConfig | StableDiffusion2ModelDiffusersConfig ) & { name: string; }; -export const sd2PipelineModelsAdapater = - createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), - }); +export const sd2PipelineModelsAdapater = createEntityAdapter({ + selectId: (model) => model.name, + sortComparer: (a, b) => a.name.localeCompare(b.name), +}); export const sd2InitialPipelineModelsState = sd2PipelineModelsAdapater.getInitialState(); diff --git a/invokeai/frontend/web/src/services/thunks/model.ts b/invokeai/frontend/web/src/services/thunks/model.ts index 05766de7b3..619aa4b7b2 100644 --- a/invokeai/frontend/web/src/services/thunks/model.ts +++ b/invokeai/frontend/web/src/services/thunks/model.ts @@ -1,7 +1,7 @@ import { log } from 'app/logging/useLogger'; import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { SD1PipelineModelType } from 'features/system/store/models/sd1PipelineModelSlice'; -import { SD2PipelineModelType } from 'features/system/store/models/sd2PipelineModelSlice'; +import { SD1PipelineModel } from 'features/system/store/models/sd1PipelineModelSlice'; +import { SD2PipelineModel } from 'features/system/store/models/sd2PipelineModelSlice'; import { reduce, size } from 'lodash-es'; import { BaseModelType, ModelType, ModelsService } from 'services/api'; @@ -31,7 +31,7 @@ export const receivedModels = createAppAsyncThunk( modelsAccumulator[modelName] = { ...model, name: modelName }; return modelsAccumulator; }, - {} as Record + {} as Record ); } @@ -42,7 +42,7 @@ export const receivedModels = createAppAsyncThunk( modelsAccumulator[modelName] = { ...model, name: modelName }; return modelsAccumulator; }, - {} as Record + {} as Record ); } From c4c3c96062dc8faafaa3990df99b9ab7703d5acd Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 18 Jun 2023 22:22:56 +1200 Subject: [PATCH 098/110] Revert "feat: Port Schedulers to Mantine" This reverts commit e0c105f413dde29bc242a666b7b270d62ea03908. --- .../web/src/features/parameters/store/generationSlice.ts | 1 + .../system/components/SettingsModal/SettingsSchedulers.tsx | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 946e9084d8..e1de166b5c 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,5 +1,6 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; +import { DEFAULT_SCHEDULER_NAME, Scheduler } from 'app/constants'; import { ModelLoaderTypes } from 'features/system/components/ModelSelect'; import { configChanged } from 'features/system/store/configSlice'; import { clamp, sortBy } from 'lodash-es'; diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx index a27db43e6b..26c11604e1 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx @@ -1,6 +1,5 @@ import { SCHEDULER_LABEL_MAP, SCHEDULER_NAMES } from 'app/constants'; import { RootState } from 'app/store/store'; - import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIMantineMultiSelect from 'common/components/IAIMantineMultiSelect'; import { SchedulerParam } from 'features/parameters/store/parameterZodSchemas'; From 6c987007405c808b7981ed1d30eaeea2d2dcd893 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 19 Jun 2023 23:05:32 +1200 Subject: [PATCH 099/110] fix: Adjust the Schedular select width So the long names do not get cut off. --- .../components/Parameters/Core/ParamSchedulerAndModel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSchedulerAndModel.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSchedulerAndModel.tsx index 65da89b94d..5092893eed 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSchedulerAndModel.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSchedulerAndModel.tsx @@ -6,7 +6,7 @@ import ParamScheduler from './ParamScheduler'; const ParamSchedulerAndModel = () => { return ( - + From d3dec59cc3283ea719aa2198406b0c937b8f69e5 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 19 Jun 2023 23:16:14 +1200 Subject: [PATCH 100/110] tweal: UI colors --- invokeai/frontend/web/src/theme/colors/greenTea.ts | 6 +++--- invokeai/frontend/web/src/theme/colors/invokeAI.ts | 6 +++--- invokeai/frontend/web/src/theme/colors/lightTheme.ts | 2 +- invokeai/frontend/web/src/theme/colors/oceanBlue.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/invokeai/frontend/web/src/theme/colors/greenTea.ts b/invokeai/frontend/web/src/theme/colors/greenTea.ts index ffecbf2ffa..318aecbc61 100644 --- a/invokeai/frontend/web/src/theme/colors/greenTea.ts +++ b/invokeai/frontend/web/src/theme/colors/greenTea.ts @@ -4,8 +4,8 @@ import { generateColorPalette } from '../util/generateColorPalette'; export const greenTeaThemeColors: InvokeAIThemeColors = { base: generateColorPalette(223, 10), baseAlpha: generateColorPalette(223, 10, false, true), - accent: generateColorPalette(155, 80), - accentAlpha: generateColorPalette(155, 80, false, true), + accent: generateColorPalette(160, 60), + accentAlpha: generateColorPalette(160, 60, false, true), working: generateColorPalette(47, 68), workingAlpha: generateColorPalette(47, 68, false, true), warning: generateColorPalette(28, 75), @@ -14,5 +14,5 @@ export const greenTeaThemeColors: InvokeAIThemeColors = { okAlpha: generateColorPalette(122, 49, false, true), error: generateColorPalette(0, 50), errorAlpha: generateColorPalette(0, 50, false, true), - gridLineColor: 'rgba(255, 255, 255, 0.2)', + gridLineColor: 'rgba(255, 255, 255, 0.15)', }; diff --git a/invokeai/frontend/web/src/theme/colors/invokeAI.ts b/invokeai/frontend/web/src/theme/colors/invokeAI.ts index c39b3bed81..82db58bd35 100644 --- a/invokeai/frontend/web/src/theme/colors/invokeAI.ts +++ b/invokeai/frontend/web/src/theme/colors/invokeAI.ts @@ -2,8 +2,8 @@ import { InvokeAIThemeColors } from 'theme/themeTypes'; import { generateColorPalette } from 'theme/util/generateColorPalette'; export const invokeAIThemeColors: InvokeAIThemeColors = { - base: generateColorPalette(225, 15), - baseAlpha: generateColorPalette(225, 15, false, true), + base: generateColorPalette(220, 15), + baseAlpha: generateColorPalette(220, 15, false, true), accent: generateColorPalette(250, 50), accentAlpha: generateColorPalette(250, 50, false, true), working: generateColorPalette(47, 67), @@ -14,5 +14,5 @@ export const invokeAIThemeColors: InvokeAIThemeColors = { okAlpha: generateColorPalette(113, 70, false, true), error: generateColorPalette(0, 76), errorAlpha: generateColorPalette(0, 76, false, true), - gridLineColor: 'rgba(255, 255, 255, 0.2)', + gridLineColor: 'rgba(150, 150, 180, 0.15)', }; diff --git a/invokeai/frontend/web/src/theme/colors/lightTheme.ts b/invokeai/frontend/web/src/theme/colors/lightTheme.ts index 2a7a05bbd2..2fdbd1a769 100644 --- a/invokeai/frontend/web/src/theme/colors/lightTheme.ts +++ b/invokeai/frontend/web/src/theme/colors/lightTheme.ts @@ -14,5 +14,5 @@ export const lightThemeColors: InvokeAIThemeColors = { okAlpha: generateColorPalette(122, 49, true, true), error: generateColorPalette(0, 50, true), errorAlpha: generateColorPalette(0, 50, true, true), - gridLineColor: 'rgba(0, 0, 0, 0.2)', + gridLineColor: 'rgba(0, 0, 0, 0.15)', }; diff --git a/invokeai/frontend/web/src/theme/colors/oceanBlue.ts b/invokeai/frontend/web/src/theme/colors/oceanBlue.ts index adfb8ab288..952e0a5066 100644 --- a/invokeai/frontend/web/src/theme/colors/oceanBlue.ts +++ b/invokeai/frontend/web/src/theme/colors/oceanBlue.ts @@ -14,5 +14,5 @@ export const oceanBlueColors: InvokeAIThemeColors = { okAlpha: generateColorPalette(122, 49, false, true), error: generateColorPalette(0, 100), errorAlpha: generateColorPalette(0, 100, false, true), - gridLineColor: 'rgba(136, 148, 184, 0.2)', + gridLineColor: 'rgba(136, 148, 184, 0.15)', }; From aceadacad48be3e81d59c59f839d6da3a48a74e1 Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Tue, 20 Jun 2023 03:13:10 +0300 Subject: [PATCH 101/110] Remove default model logic --- .../app/services/model_manager_service.py | 24 -------------- .../backend/model_management/model_manager.py | 32 ------------------- .../backend/model_management/models/base.py | 2 -- 3 files changed, 58 deletions(-) diff --git a/invokeai/app/services/model_manager_service.py b/invokeai/app/services/model_manager_service.py index c212ff6a72..8956b55139 100644 --- a/invokeai/app/services/model_manager_service.py +++ b/invokeai/app/services/model_manager_service.py @@ -69,19 +69,6 @@ class ModelManagerServiceBase(ABC): ) -> bool: pass - @abstractmethod - def default_model(self) -> Optional[Tuple[str, BaseModelType, ModelType]]: - """ - Returns the name and typeof the default model, or None - if none is defined. - """ - pass - - @abstractmethod - def set_default_model(self, model_name: str, base_model: BaseModelType, model_type: ModelType): - """Sets the default model to the indicated name.""" - pass - @abstractmethod def model_info(self, model_name: str, base_model: BaseModelType, model_type: ModelType) -> dict: """ @@ -270,17 +257,6 @@ class ModelManagerService(ModelManagerServiceBase): model_type, ) - def default_model(self) -> Optional[Tuple[str, BaseModelType, ModelType]]: - """ - Returns the name of the default model, or None - if none is defined. - """ - return self.mgr.default_model() - - def set_default_model(self, model_name: str, base_model: BaseModelType, model_type: ModelType): - """Sets the default model to the indicated name.""" - self.mgr.set_default_model(model_name, base_model, model_type) - def model_info(self, model_name: str, base_model: BaseModelType, model_type: ModelType) -> dict: """ Given a model name returns a dict-like (OmegaConf) object describing it. diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index e6cab04da7..37798aaf6f 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -445,38 +445,6 @@ class ModelManager(object): _cache = self.cache, ) - def default_model(self) -> Optional[Tuple[str, BaseModelType, ModelType]]: - """ - Returns the name of the default model, or None - if none is defined. - """ - for model_key, model_config in self.models.items(): - if model_config.default: - return self.parse_key(model_key) - - for model_key, _ in self.models.items(): - return self.parse_key(model_key) - else: - return None # TODO: or redo as (None, None, None) - - def set_default_model( - self, - model_name: str, - base_model: BaseModelType, - model_type: ModelType, - ) -> None: - """ - Set the default model. The change will not take - effect until you call model_manager.commit() - """ - - model_key = self.model_key(model_name, base_model, model_type) - if model_key not in self.models: - raise Exception(f"Unknown model: {model_key}") - - for cur_model_key, config in self.models.items(): - config.default = cur_model_key == model_key - def model_info( self, model_name: str, diff --git a/invokeai/backend/model_management/models/base.py b/invokeai/backend/model_management/models/base.py index f32e658aa1..e57ba81c3c 100644 --- a/invokeai/backend/model_management/models/base.py +++ b/invokeai/backend/model_management/models/base.py @@ -48,10 +48,8 @@ class ModelError(str, Enum): class ModelConfigBase(BaseModel): path: str # or Path - #name: str # not included as present in model key description: Optional[str] = Field(None) format: Optional[str] = Field(None) - default: Optional[bool] = Field(False) # do not save to config error: Optional[ModelError] = Field(None) From e4dc9c5a04ab44019c74967ce08e22bb26bcae6f Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Tue, 20 Jun 2023 03:25:08 +0300 Subject: [PATCH 102/110] Rename format to model_format(still named format when work with config) --- .../backend/model_management/model_manager.py | 4 +++ .../backend/model_management/models/base.py | 26 +++++++++---------- .../model_management/models/controlnet.py | 2 +- .../backend/model_management/models/lora.py | 2 +- .../models/stable_diffusion.py | 12 ++++----- .../models/textual_inversion.py | 2 +- .../backend/model_management/models/vae.py | 2 +- 7 files changed, 27 insertions(+), 23 deletions(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 37798aaf6f..9a8c7e64c6 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -266,6 +266,8 @@ class ModelManager(object): for model_key, model_config in config.items(): model_name, base_model, model_type = self.parse_key(model_key) model_class = MODEL_CLASSES[base_model][model_type] + # alias for config file + model_config["model_format"] = model_config.pop("format") self.models[model_key] = model_class.create_config(**model_config) # check config version number and update on disk/RAM if necessary @@ -617,6 +619,8 @@ class ModelManager(object): if model_class.save_to_config: # TODO: or exclude_unset better fits here? data_to_save[model_key] = model_config.dict(exclude_defaults=True, exclude={"error"}) + # alias for config file + data_to_save[model_key]["format"] = data_to_save[model_key].pop("model_format") yaml_str = OmegaConf.to_yaml(data_to_save) config_file_path = conf_file or self.config_path diff --git a/invokeai/backend/model_management/models/base.py b/invokeai/backend/model_management/models/base.py index e57ba81c3c..06cc3db50f 100644 --- a/invokeai/backend/model_management/models/base.py +++ b/invokeai/backend/model_management/models/base.py @@ -49,7 +49,7 @@ class ModelError(str, Enum): class ModelConfigBase(BaseModel): path: str # or Path description: Optional[str] = Field(None) - format: Optional[str] = Field(None) + model_format: Optional[str] = Field(None) # do not save to config error: Optional[ModelError] = Field(None) @@ -125,20 +125,20 @@ class ModelBase(metaclass=ABCMeta): continue fields = inspect.get_annotations(value) - if "format" not in fields: - raise Exception("Invalid config definition - format field not found") + if "model_format" not in fields: + raise Exception("Invalid config definition - model_format field not found") - format_type = typing.get_origin(fields["format"]) + format_type = typing.get_origin(fields["model_format"]) if format_type not in {None, Literal, Union}: - raise Exception(f"Invalid config definition - unknown format type: {fields['format']}") + raise Exception(f"Invalid config definition - unknown format type: {fields['model_format']}") - if format_type is Union and not all(typing.get_origin(v) in {None, Literal} for v in fields["format"].__args__): - raise Exception(f"Invalid config definition - unknown format type: {fields['format']}") + if format_type is Union and not all(typing.get_origin(v) in {None, Literal} for v in fields["model_format"].__args__): + raise Exception(f"Invalid config definition - unknown format type: {fields['model_format']}") if format_type == Union: - f_fields = fields["format"].__args__ + f_fields = fields["model_format"].__args__ else: - f_fields = (fields["format"],) + f_fields = (fields["model_format"],) for field in f_fields: @@ -155,17 +155,17 @@ class ModelBase(metaclass=ABCMeta): @classmethod def create_config(cls, **kwargs) -> ModelConfigBase: - if "format" not in kwargs: - raise Exception("Field 'format' not found in model config") + if "model_format" not in kwargs: + raise Exception("Field 'model_format' not found in model config") configs = cls._get_configs() - return configs[kwargs["format"]](**kwargs) + return configs[kwargs["model_format"]](**kwargs) @classmethod def probe_config(cls, path: str, **kwargs) -> ModelConfigBase: return cls.create_config( path=path, - format=cls.detect_format(path), + model_format=cls.detect_format(path), ) @classmethod diff --git a/invokeai/backend/model_management/models/controlnet.py b/invokeai/backend/model_management/models/controlnet.py index de9926c83e..e452621eba 100644 --- a/invokeai/backend/model_management/models/controlnet.py +++ b/invokeai/backend/model_management/models/controlnet.py @@ -19,7 +19,7 @@ class ControlNetModel(ModelBase): #model_size: int class Config(ModelConfigBase): - format: Union[Literal["checkpoint"], Literal["diffusers"]] + model_format: Union[Literal["checkpoint"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.ControlNet diff --git a/invokeai/backend/model_management/models/lora.py b/invokeai/backend/model_management/models/lora.py index bcf3224ece..2e4309a161 100644 --- a/invokeai/backend/model_management/models/lora.py +++ b/invokeai/backend/model_management/models/lora.py @@ -16,7 +16,7 @@ class LoRAModel(ModelBase): #model_size: int class Config(ModelConfigBase): - format: Union[Literal["lycoris"], Literal["diffusers"]] + model_format: Union[Literal["lycoris"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.Lora diff --git a/invokeai/backend/model_management/models/stable_diffusion.py b/invokeai/backend/model_management/models/stable_diffusion.py index 20aaae23a6..50089b3338 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -23,12 +23,12 @@ from omegaconf import OmegaConf class StableDiffusion1Model(DiffusersModel): class DiffusersConfig(ModelConfigBase): - format: Literal["diffusers"] + model_format: Literal["diffusers"] vae: Optional[str] = Field(None) variant: ModelVariantType class CheckpointConfig(ModelConfigBase): - format: Literal["checkpoint"] + model_format: Literal["checkpoint"] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) variant: ModelVariantType @@ -80,7 +80,7 @@ class StableDiffusion1Model(DiffusersModel): return cls.create_config( path=path, - format=model_format, + model_format=model_format, config=ckpt_config_path, variant=variant, @@ -121,14 +121,14 @@ class StableDiffusion2Model(DiffusersModel): # TODO: check that configs overwriten properly class DiffusersConfig(ModelConfigBase): - format: Literal["diffusers"] + model_format: Literal["diffusers"] vae: Optional[str] = Field(None) variant: ModelVariantType prediction_type: SchedulerPredictionType upcast_attention: bool class CheckpointConfig(ModelConfigBase): - format: Literal["checkpoint"] + model_format: Literal["checkpoint"] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) variant: ModelVariantType @@ -191,7 +191,7 @@ class StableDiffusion2Model(DiffusersModel): return cls.create_config( path=path, - format=model_format, + model_format=model_format, config=ckpt_config_path, variant=variant, diff --git a/invokeai/backend/model_management/models/textual_inversion.py b/invokeai/backend/model_management/models/textual_inversion.py index 66847f53eb..9a032218f0 100644 --- a/invokeai/backend/model_management/models/textual_inversion.py +++ b/invokeai/backend/model_management/models/textual_inversion.py @@ -16,7 +16,7 @@ class TextualInversionModel(ModelBase): #model_size: int class Config(ModelConfigBase): - format: None + model_format: None def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.TextualInversion diff --git a/invokeai/backend/model_management/models/vae.py b/invokeai/backend/model_management/models/vae.py index b78617869a..e86bc00ecd 100644 --- a/invokeai/backend/model_management/models/vae.py +++ b/invokeai/backend/model_management/models/vae.py @@ -24,7 +24,7 @@ class VaeModel(ModelBase): #model_size: int class Config(ModelConfigBase): - format: Union[Literal["checkpoint"], Literal["diffusers"]] + model_format: Union[Literal["checkpoint"], Literal["diffusers"]] def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.Vae From da566b59e88b5862ece7f4a5a3ecddc94bc44441 Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Tue, 20 Jun 2023 03:30:09 +0300 Subject: [PATCH 103/110] Update model format field to use enums --- .../backend/model_management/models/base.py | 34 ++++++++----------- .../model_management/models/controlnet.py | 13 ++++--- .../backend/model_management/models/lora.py | 13 ++++--- .../models/stable_diffusion.py | 31 ++++++++++------- .../backend/model_management/models/vae.py | 13 ++++--- 5 files changed, 60 insertions(+), 44 deletions(-) diff --git a/invokeai/backend/model_management/models/base.py b/invokeai/backend/model_management/models/base.py index 06cc3db50f..ef354ecc07 100644 --- a/invokeai/backend/model_management/models/base.py +++ b/invokeai/backend/model_management/models/base.py @@ -125,30 +125,24 @@ class ModelBase(metaclass=ABCMeta): continue fields = inspect.get_annotations(value) - if "model_format" not in fields: - raise Exception("Invalid config definition - model_format field not found") + try: + field = fields["model_format"] + except: + raise Exception(f"Invalid config definition - format field not found({cls.__qualname__})") - format_type = typing.get_origin(fields["model_format"]) - if format_type not in {None, Literal, Union}: - raise Exception(f"Invalid config definition - unknown format type: {fields['model_format']}") + if isinstance(field, type) and issubclass(field, str) and issubclass(field, Enum): + for model_format in field: + configs[model_format.value] = value - if format_type is Union and not all(typing.get_origin(v) in {None, Literal} for v in fields["model_format"].__args__): - raise Exception(f"Invalid config definition - unknown format type: {fields['model_format']}") + elif typing.get_origin(field) is Literal and all(isinstance(arg, str) and isinstance(arg, Enum) for arg in field.__args__): + for model_format in field.__args__: + configs[model_format.value] = value + + elif field is None: + configs[None] = value - if format_type == Union: - f_fields = fields["model_format"].__args__ else: - f_fields = (fields["model_format"],) - - - for field in f_fields: - if field is None: - format_name = None - else: - format_name = field.__args__[0] - - configs[format_name] = value # TODO: error when override(multiple)? - + raise Exception(f"Unsupported format definition in {cls.__qualname__}") cls.__configs = configs return cls.__configs diff --git a/invokeai/backend/model_management/models/controlnet.py b/invokeai/backend/model_management/models/controlnet.py index e452621eba..9563f87afd 100644 --- a/invokeai/backend/model_management/models/controlnet.py +++ b/invokeai/backend/model_management/models/controlnet.py @@ -1,5 +1,6 @@ import os import torch +from enum import Enum from pathlib import Path from typing import Optional, Union, Literal from .base import ( @@ -14,12 +15,16 @@ from .base import ( classproperty, ) +class ControlNetModelFormat(str, Enum): + Checkpoint = "checkpoint" + Diffusers = "diffusers" + class ControlNetModel(ModelBase): #model_class: Type #model_size: int class Config(ModelConfigBase): - model_format: Union[Literal["checkpoint"], Literal["diffusers"]] + model_format: ControlNetModelFormat def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.ControlNet @@ -69,9 +74,9 @@ class ControlNetModel(ModelBase): @classmethod def detect_format(cls, path: str): if os.path.isdir(path): - return "diffusers" + return ControlNetModelFormat.Diffusers else: - return "checkpoint" + return ControlNetModelFormat.Checkpoint @classmethod def convert_if_required( @@ -81,7 +86,7 @@ class ControlNetModel(ModelBase): config: ModelConfigBase, # empty config or config of parent model base_model: BaseModelType, ) -> str: - if cls.detect_format(model_path) != "diffusers": + if cls.detect_format(model_path) != ControlNetModelFormat.Diffusers: raise NotImplementedError("Checkpoint controlnet models currently unsupported") else: return model_path diff --git a/invokeai/backend/model_management/models/lora.py b/invokeai/backend/model_management/models/lora.py index 2e4309a161..59feacde06 100644 --- a/invokeai/backend/model_management/models/lora.py +++ b/invokeai/backend/model_management/models/lora.py @@ -1,5 +1,6 @@ import os import torch +from enum import Enum from typing import Optional, Union, Literal from .base import ( ModelBase, @@ -12,11 +13,15 @@ from .base import ( # TODO: naming from ..lora import LoRAModel as LoRAModelRaw +class LoRAModelFormat(str, Enum): + LyCORIS = "lycoris" + Diffusers = "diffusers" + class LoRAModel(ModelBase): #model_size: int class Config(ModelConfigBase): - model_format: Union[Literal["lycoris"], Literal["diffusers"]] + model_format: LoRAModelFormat # TODO: def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.Lora @@ -52,9 +57,9 @@ class LoRAModel(ModelBase): @classmethod def detect_format(cls, path: str): if os.path.isdir(path): - return "diffusers" + return LoRAModelFormat.Diffusers else: - return "lycoris" + return LoRAModelFormat.LyCORIS @classmethod def convert_if_required( @@ -64,7 +69,7 @@ class LoRAModel(ModelBase): config: ModelConfigBase, base_model: BaseModelType, ) -> str: - if cls.detect_format(model_path) == "diffusers": + if cls.detect_format(model_path) == LoRAModelFormat.Diffusers: # TODO: add diffusers lora when it stabilizes a bit raise NotImplementedError("Diffusers lora not supported") else: diff --git a/invokeai/backend/model_management/models/stable_diffusion.py b/invokeai/backend/model_management/models/stable_diffusion.py index 50089b3338..f169326571 100644 --- a/invokeai/backend/model_management/models/stable_diffusion.py +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -1,5 +1,6 @@ import os import json +from enum import Enum from pydantic import Field from pathlib import Path from typing import Literal, Optional, Union @@ -19,16 +20,19 @@ from .base import ( from invokeai.app.services.config import InvokeAIAppConfig from omegaconf import OmegaConf +class StableDiffusion1ModelFormat(str, Enum): + Checkpoint = "checkpoint" + Diffusers = "diffusers" class StableDiffusion1Model(DiffusersModel): class DiffusersConfig(ModelConfigBase): - model_format: Literal["diffusers"] + model_format: Literal[StableDiffusion1ModelFormat.Diffusers] vae: Optional[str] = Field(None) variant: ModelVariantType class CheckpointConfig(ModelConfigBase): - model_format: Literal["checkpoint"] + model_format: Literal[StableDiffusion1ModelFormat.Checkpoint] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) variant: ModelVariantType @@ -47,7 +51,7 @@ class StableDiffusion1Model(DiffusersModel): def probe_config(cls, path: str, **kwargs): model_format = cls.detect_format(path) ckpt_config_path = kwargs.get("config", None) - if model_format == "checkpoint": + if model_format == StableDiffusion1ModelFormat.Checkpoint: if ckpt_config_path: ckpt_config = OmegaConf.load(ckpt_config_path) ckpt_config["model"]["params"]["unet_config"]["params"]["in_channels"] @@ -57,7 +61,7 @@ class StableDiffusion1Model(DiffusersModel): checkpoint = checkpoint.get('state_dict', checkpoint) in_channels = checkpoint["model.diffusion_model.input_blocks.0.0.weight"].shape[1] - elif model_format == "diffusers": + elif model_format == StableDiffusion1ModelFormat.Diffusers: unet_config_path = os.path.join(path, "unet", "config.json") if os.path.exists(unet_config_path): with open(unet_config_path, "r") as f: @@ -93,9 +97,9 @@ class StableDiffusion1Model(DiffusersModel): @classmethod def detect_format(cls, model_path: str): if os.path.isdir(model_path): - return "diffusers" + return StableDiffusion1ModelFormat.Diffusers else: - return "checkpoint" + return StableDiffusion1ModelFormat.Checkpoint @classmethod def convert_if_required( @@ -116,19 +120,22 @@ class StableDiffusion1Model(DiffusersModel): else: return model_path +class StableDiffusion2ModelFormat(str, Enum): + Checkpoint = "checkpoint" + Diffusers = "diffusers" class StableDiffusion2Model(DiffusersModel): # TODO: check that configs overwriten properly class DiffusersConfig(ModelConfigBase): - model_format: Literal["diffusers"] + model_format: Literal[StableDiffusion2ModelFormat.Diffusers] vae: Optional[str] = Field(None) variant: ModelVariantType prediction_type: SchedulerPredictionType upcast_attention: bool class CheckpointConfig(ModelConfigBase): - model_format: Literal["checkpoint"] + model_format: Literal[StableDiffusion2ModelFormat.Checkpoint] vae: Optional[str] = Field(None) config: Optional[str] = Field(None) variant: ModelVariantType @@ -149,7 +156,7 @@ class StableDiffusion2Model(DiffusersModel): def probe_config(cls, path: str, **kwargs): model_format = cls.detect_format(path) ckpt_config_path = kwargs.get("config", None) - if model_format == "checkpoint": + if model_format == StableDiffusion2ModelFormat.Checkpoint: if ckpt_config_path: ckpt_config = OmegaConf.load(ckpt_config_path) ckpt_config["model"]["params"]["unet_config"]["params"]["in_channels"] @@ -159,7 +166,7 @@ class StableDiffusion2Model(DiffusersModel): checkpoint = checkpoint.get('state_dict', checkpoint) in_channels = checkpoint["model.diffusion_model.input_blocks.0.0.weight"].shape[1] - elif model_format == "diffusers": + elif model_format == StableDiffusion2ModelFormat.Diffusers: unet_config_path = os.path.join(path, "unet", "config.json") if os.path.exists(unet_config_path): with open(unet_config_path, "r") as f: @@ -206,9 +213,9 @@ class StableDiffusion2Model(DiffusersModel): @classmethod def detect_format(cls, model_path: str): if os.path.isdir(model_path): - return "diffusers" + return StableDiffusion2ModelFormat.Diffusers else: - return "checkpoint" + return StableDiffusion2ModelFormat.Checkpoint @classmethod def convert_if_required( diff --git a/invokeai/backend/model_management/models/vae.py b/invokeai/backend/model_management/models/vae.py index e86bc00ecd..76133b074d 100644 --- a/invokeai/backend/model_management/models/vae.py +++ b/invokeai/backend/model_management/models/vae.py @@ -1,6 +1,7 @@ import os import torch import safetensors +from enum import Enum from pathlib import Path from typing import Optional, Union, Literal from .base import ( @@ -19,12 +20,16 @@ from invokeai.app.services.config import InvokeAIAppConfig from diffusers.utils import is_safetensors_available from omegaconf import OmegaConf +class VaeModelFormat(str, Enum): + Checkpoint = "checkpoint" + Diffusers = "diffusers" + class VaeModel(ModelBase): #vae_class: Type #model_size: int class Config(ModelConfigBase): - model_format: Union[Literal["checkpoint"], Literal["diffusers"]] + model_format: VaeModelFormat def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): assert model_type == ModelType.Vae @@ -71,9 +76,9 @@ class VaeModel(ModelBase): @classmethod def detect_format(cls, path: str): if os.path.isdir(path): - return "diffusers" + return VaeModelFormat.Diffusers else: - return "checkpoint" + return VaeModelFormat.Checkpoint @classmethod def convert_if_required( @@ -83,7 +88,7 @@ class VaeModel(ModelBase): config: ModelConfigBase, # empty config or config of parent model base_model: BaseModelType, ) -> str: - if cls.detect_format(model_path) != "diffusers": + if cls.detect_format(model_path) == VaeModelFormat.Checkpoint: return _convert_vae_ckpt_and_cache( weights_path=model_path, output_path=output_path, From 21245a0fb2de5f865faa8dc14c444fdf2eb026da Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Tue, 20 Jun 2023 03:44:58 +0300 Subject: [PATCH 104/110] Set model type to const value in openapi schema, add model format enums to model schema(as they not not referenced in case of Literal definition) --- invokeai/app/api_app.py | 16 +++++ .../model_management/models/__init__.py | 71 ++++++++++++++----- 2 files changed, 71 insertions(+), 16 deletions(-) diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index 22b4efec74..e14c58bab7 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -120,6 +120,22 @@ def custom_openapi(): invoker_schema["output"] = outputs_ref + from invokeai.backend.model_management.models import get_model_config_enums + for model_config_format_enum in set(get_model_config_enums()): + name = model_config_format_enum.__qualname__ + + if name in openapi_schema["components"]["schemas"]: + # print(f"Config with name {name} already defined") + continue + + # "BaseModelType":{"title":"BaseModelType","description":"An enumeration.","enum":["sd-1","sd-2"],"type":"string"} + openapi_schema["components"]["schemas"][name] = dict( + title=name, + description="An enumeration.", + type="string", + enum=list(v.value for v in model_config_format_enum), + ) + app.openapi_schema = openapi_schema return app.openapi_schema diff --git a/invokeai/backend/model_management/models/__init__.py b/invokeai/backend/model_management/models/__init__.py index b22075991e..6975d45f93 100644 --- a/invokeai/backend/model_management/models/__init__.py +++ b/invokeai/backend/model_management/models/__init__.py @@ -1,4 +1,7 @@ +import inspect +from enum import Enum from pydantic import BaseModel +from typing import Literal, get_origin from .base import BaseModelType, ModelType, SubModelType, ModelBase, ModelConfigBase, ModelVariantType, SchedulerPredictionType, ModelError, SilenceWarnings from .stable_diffusion import StableDiffusion1Model, StableDiffusion2Model from .vae import VaeModel @@ -30,15 +33,7 @@ MODEL_CLASSES = { #}, } -def _get_all_model_configs(): - configs = set() - for models in MODEL_CLASSES.values(): - for _, model in models.items(): - configs.update(model._get_configs().values()) - configs.discard(None) - return list(configs) - -MODEL_CONFIGS = _get_all_model_configs() +MODEL_CONFIGS = list() OPENAPI_MODEL_CONFIGS = list() class OpenAPIModelInfoBase(BaseModel): @@ -46,11 +41,55 @@ class OpenAPIModelInfoBase(BaseModel): base_model: BaseModelType type: ModelType -for cfg in MODEL_CONFIGS: - model_name, cfg_name = cfg.__qualname__.split('.')[-2:] - openapi_cfg_name = model_name + cfg_name - name_wrapper = type(openapi_cfg_name, (cfg, OpenAPIModelInfoBase), {}) - #globals()[name] = value - vars()[openapi_cfg_name] = name_wrapper - OPENAPI_MODEL_CONFIGS.append(name_wrapper) +for base_model, models in MODEL_CLASSES.items(): + for model_type, model_class in models.items(): + model_configs = set(model_class._get_configs().values()) + model_configs.discard(None) + MODEL_CONFIGS.extend(model_configs) + + for cfg in model_configs: + model_name, cfg_name = cfg.__qualname__.split('.')[-2:] + openapi_cfg_name = model_name + cfg_name + if openapi_cfg_name in vars(): + continue + + api_wrapper = type(openapi_cfg_name, (cfg, OpenAPIModelInfoBase), dict( + __annotations__ = dict( + type=Literal[model_type.value], + ), + )) + + #globals()[openapi_cfg_name] = api_wrapper + vars()[openapi_cfg_name] = api_wrapper + OPENAPI_MODEL_CONFIGS.append(api_wrapper) + +def get_model_config_enums(): + enums = list() + + for model_config in MODEL_CONFIGS: + fields = inspect.get_annotations(model_config) + try: + field = fields["model_format"] + except: + raise Exception("format field not found") + + # model_format: None + # model_format: SomeModelFormat + # model_format: Literal[SomeModelFormat.Diffusers] + # model_format: Literal[SomeModelFormat.Diffusers, SomeModelFormat.Checkpoint] + + if isinstance(field, type) and issubclass(field, str) and issubclass(field, Enum): + enums.append(field) + + elif get_origin(field) is Literal and all(isinstance(arg, str) and isinstance(arg, Enum) for arg in field.__args__): + enums.append(type(field.__args__[0])) + + elif field is None: + pass + + else: + raise Exception(f"Unsupported format definition in {model_configs.__qualname__}") + + return enums + From b937b7da011a34d9d0d5fbfc8f3d77f2e85afc73 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:34:12 +1000 Subject: [PATCH 105/110] feat(models): update model manager service & route to return list of models --- invokeai/app/api/routers/models.py | 7 +++---- .../app/services/model_manager_service.py | 19 ++++--------------- .../backend/model_management/model_manager.py | 16 ++++++---------- 3 files changed, 13 insertions(+), 29 deletions(-) diff --git a/invokeai/app/api/routers/models.py b/invokeai/app/api/routers/models.py index 0abcc19dcf..50d645eb57 100644 --- a/invokeai/app/api/routers/models.py +++ b/invokeai/app/api/routers/models.py @@ -62,8 +62,7 @@ class ConvertedModelResponse(BaseModel): info: DiffusersModelInfo = Field(description="The converted model info") class ModelsList(BaseModel): - models: Dict[BaseModelType, Dict[ModelType, Dict[str, MODEL_CONFIGS]]] # TODO: debug/discuss with frontend - #models: dict[SDModelType, dict[str, Annotated[Union[(DiffusersModelInfo,CkptModelInfo,SafetensorsModelInfo)], Field(discriminator="format")]]] + models: list[MODEL_CONFIGS] @models_router.get( @@ -72,10 +71,10 @@ class ModelsList(BaseModel): responses={200: {"model": ModelsList }}, ) async def list_models( - base_model: BaseModelType = Query( + base_model: Optional[BaseModelType] = Query( default=None, description="Base model" ), - model_type: ModelType = Query( + model_type: Optional[ModelType] = Query( default=None, description="The type of model to get" ), ) -> ModelsList: diff --git a/invokeai/app/services/model_manager_service.py b/invokeai/app/services/model_manager_service.py index 8956b55139..8b46b17ad0 100644 --- a/invokeai/app/services/model_manager_service.py +++ b/invokeai/app/services/model_manager_service.py @@ -5,7 +5,7 @@ from __future__ import annotations import torch from abc import ABC, abstractmethod from pathlib import Path -from typing import Union, Callable, List, Tuple, types, TYPE_CHECKING +from typing import Optional, Union, Callable, List, Tuple, types, TYPE_CHECKING from dataclasses import dataclass from invokeai.backend.model_management.model_manager import ( @@ -273,21 +273,10 @@ class ModelManagerService(ModelManagerServiceBase): self, base_model: Optional[BaseModelType] = None, model_type: Optional[ModelType] = None - ) -> dict: + ) -> list[dict]: + # ) -> dict: """ - Return a dict of models in the format: - { model_type1: - { model_name1: {'status': 'active'|'cached'|'not loaded', - 'model_name' : name, - 'model_type' : SDModelType, - 'description': description, - 'format': 'folder'|'safetensors'|'ckpt' - }, - model_name2: { etc } - }, - model_type2: - { model_name_n: etc - } + Return a list of models. """ return self.mgr.list_models(base_model, model_type) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 9a8c7e64c6..f9a66a87dd 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -473,9 +473,9 @@ class ModelManager(object): self, base_model: Optional[BaseModelType] = None, model_type: Optional[ModelType] = None, - ) -> Dict[str, Dict[str, str]]: + ) -> list[dict]: """ - Return a dict of models, in format [base_model][model_type][model_name] + Return a list of models. Please use model_manager.models() to get all the model names, model_manager.model_info('model-name') to get the stanza for the model @@ -483,7 +483,7 @@ class ModelManager(object): object derived from models.yaml """ - models = dict() + models = [] for model_key in sorted(self.models, key=str.casefold): model_config = self.models[model_key] @@ -493,20 +493,16 @@ class ModelManager(object): if model_type is not None and cur_model_type != model_type: continue - if cur_base_model not in models: - models[cur_base_model] = dict() - if cur_model_type not in models[cur_base_model]: - models[cur_base_model][cur_model_type] = dict() - - models[cur_base_model][cur_model_type][cur_model_name] = dict( + model_dict = dict( **model_config.dict(exclude_defaults=True), - # OpenAPIModelInfoBase name=cur_model_name, base_model=cur_base_model, type=cur_model_type, ) + models.append(model_dict) + return models def print_models(self) -> None: From 42a59aa147754294b6aec01b42ae83773709ce5a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:36:05 +1000 Subject: [PATCH 106/110] feat(nodes): add `sd_model_loader` node Loads any pipeline model. Also introduced is `PipelineModelField`, which includes a model name and base model. --- invokeai/app/invocations/model.py | 111 ++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/invokeai/app/invocations/model.py b/invokeai/app/invocations/model.py index 9d77cadf8c..48b15c2e4e 100644 --- a/invokeai/app/invocations/model.py +++ b/invokeai/app/invocations/model.py @@ -43,6 +43,117 @@ class ModelLoaderOutput(BaseInvocationOutput): #fmt: on +class PipelineModelField(BaseModel): + """Pipeline model field""" + + model_name: str = Field(description="Name of the model") + base_model: BaseModelType = Field(description="Base model") + + +class SDModelLoaderInvocation(BaseInvocation): + """Loading submodels of selected model.""" + + type: Literal["sd_model_loader"] = "sd_model_loader" + + model: PipelineModelField = Field(description="The model to load") + # TODO: precision? + + # Schema customisation + class Config(InvocationConfig): + schema_extra = { + "ui": { + "tags": ["model", "loader"], + "type_hints": { + "model": "model" + } + }, + } + + def invoke(self, context: InvocationContext) -> ModelLoaderOutput: + + base_model = self.model.base_model + model_name = self.model.model_name + model_type = ModelType.Pipeline + + # TODO: not found exceptions + if not context.services.model_manager.model_exists( + model_name=model_name, + base_model=base_model, + model_type=model_type, + ): + raise Exception(f"Unknown {base_model} {model_type} model: {model_name}") + + """ + if not context.services.model_manager.model_exists( + model_name=self.model_name, + model_type=SDModelType.Diffusers, + submodel=SDModelType.Tokenizer, + ): + raise Exception( + f"Failed to find tokenizer submodel in {self.model_name}! Check if model corrupted" + ) + + if not context.services.model_manager.model_exists( + model_name=self.model_name, + model_type=SDModelType.Diffusers, + submodel=SDModelType.TextEncoder, + ): + raise Exception( + f"Failed to find text_encoder submodel in {self.model_name}! Check if model corrupted" + ) + + if not context.services.model_manager.model_exists( + model_name=self.model_name, + model_type=SDModelType.Diffusers, + submodel=SDModelType.UNet, + ): + raise Exception( + f"Failed to find unet submodel from {self.model_name}! Check if model corrupted" + ) + """ + + + return ModelLoaderOutput( + unet=UNetField( + unet=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.UNet, + ), + scheduler=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.Scheduler, + ), + loras=[], + ), + clip=ClipField( + tokenizer=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.Tokenizer, + ), + text_encoder=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.TextEncoder, + ), + loras=[], + ), + vae=VaeField( + vae=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.Vae, + ), + ) + ) + class SD1ModelLoaderInvocation(BaseInvocation): """Loading submodels of selected model.""" From 3722cdf5d6521d18f4efe5242d765a6f1a90d994 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:36:20 +1000 Subject: [PATCH 107/110] chore(ui): regen api client --- .../frontend/web/src/services/api/index.ts | 35 +-- .../src/services/api/models/AddInvocation.ts | 1 + .../services/api/models/Body_upload_image.ts | 1 + .../models/CannyImageProcessorInvocation.ts | 1 + .../src/services/api/models/CkptModelInfo.ts | 1 + .../web/src/services/api/models/ClipField.ts | 3 - .../services/api/models/CollectInvocation.ts | 1 + .../api/models/CollectInvocationOutput.ts | 1 + .../web/src/services/api/models/ColorField.ts | 1 + .../services/api/models/CompelInvocation.ts | 1 + .../src/services/api/models/CompelOutput.ts | 1 + .../services/api/models/ConditioningField.ts | 1 + .../ContentShuffleImageProcessorInvocation.ts | 1 + .../src/services/api/models/ControlField.ts | 1 + .../api/models/ControlNetInvocation.ts | 1 + .../api/models/ControlNetModelConfig.ts | 8 +- .../api/models/ControlNetModelFormat.ts | 8 + .../src/services/api/models/ControlOutput.ts | 1 + .../services/api/models/CreateModelRequest.ts | 1 + .../api/models/CvInpaintInvocation.ts | 1 + .../services/api/models/DiffusersModelInfo.ts | 1 + .../services/api/models/DivideInvocation.ts | 1 + .../api/models/DynamicPromptInvocation.ts | 1 + .../web/src/services/api/models/Edge.ts | 1 + .../src/services/api/models/EdgeConnection.ts | 1 + .../api/models/FloatCollectionOutput.ts | 1 + .../api/models/FloatLinearRangeInvocation.ts | 1 + .../src/services/api/models/FloatOutput.ts | 1 + .../web/src/services/api/models/Graph.ts | 8 +- .../api/models/GraphExecutionState.ts | 5 +- .../services/api/models/GraphInvocation.ts | 1 + .../api/models/GraphInvocationOutput.ts | 1 + .../api/models/HTTPValidationError.ts | 1 + .../api/models/HedImageProcessorInvocation.ts | 1 + .../api/models/ImageBlurInvocation.ts | 1 + .../api/models/ImageChannelInvocation.ts | 1 + .../api/models/ImageConvertInvocation.ts | 1 + .../api/models/ImageCropInvocation.ts | 1 + .../web/src/services/api/models/ImageDTO.ts | 1 + .../web/src/services/api/models/ImageField.ts | 1 + .../api/models/ImageInverseLerpInvocation.ts | 1 + .../api/models/ImageLerpInvocation.ts | 1 + .../src/services/api/models/ImageMetadata.ts | 1 + .../api/models/ImageMultiplyInvocation.ts | 1 + .../src/services/api/models/ImageOutput.ts | 1 + .../api/models/ImagePasteInvocation.ts | 1 + .../api/models/ImageProcessorInvocation.ts | 1 + .../services/api/models/ImageRecordChanges.ts | 1 + .../api/models/ImageResizeInvocation.ts | 1 + .../api/models/ImageScaleInvocation.ts | 1 + .../api/models/ImageToImageInvocation.ts | 76 ------ .../api/models/ImageToLatentsInvocation.ts | 1 + .../src/services/api/models/ImageUrlsDTO.ts | 1 + .../api/models/InfillColorInvocation.ts | 1 + .../api/models/InfillPatchMatchInvocation.ts | 1 + .../api/models/InfillTileInvocation.ts | 1 + .../services/api/models/InpaintInvocation.ts | 1 + .../api/models/IntCollectionOutput.ts | 1 + .../web/src/services/api/models/IntOutput.ts | 1 + .../services/api/models/IterateInvocation.ts | 1 + .../api/models/IterateInvocationOutput.ts | 1 + .../src/services/api/models/LatentsField.ts | 1 + .../src/services/api/models/LatentsOutput.ts | 1 + .../api/models/LatentsToImageInvocation.ts | 1 + .../api/models/LatentsToLatentsInvocation.ts | 1 + .../LineartAnimeImageProcessorInvocation.ts | 1 + .../models/LineartImageProcessorInvocation.ts | 1 + ...{LoraModelConfig.ts => LoRAModelConfig.ts} | 8 +- .../services/api/models/LoRAModelFormat.ts | 8 + .../api/models/LoadImageInvocation.ts | 1 + .../web/src/services/api/models/LoraInfo.ts | 3 - .../api/models/LoraLoaderInvocation.ts | 3 - .../services/api/models/LoraLoaderOutput.ts | 3 - .../api/models/MaskFromAlphaInvocation.ts | 1 + .../web/src/services/api/models/MaskOutput.ts | 1 + .../MediapipeFaceProcessorInvocation.ts | 1 + .../MidasDepthImageProcessorInvocation.ts | 1 + .../models/MlsdImageProcessorInvocation.ts | 1 + .../web/src/services/api/models/ModelInfo.ts | 3 - .../services/api/models/ModelLoaderOutput.ts | 3 - .../web/src/services/api/models/ModelsList.ts | 25 +- .../services/api/models/MultiplyInvocation.ts | 1 + .../services/api/models/NoiseInvocation.ts | 1 + .../src/services/api/models/NoiseOutput.ts | 1 + .../NormalbaeImageProcessorInvocation.ts | 1 + .../OffsetPaginatedResults_ImageDTO_.ts | 1 + .../OpenposeImageProcessorInvocation.ts | 1 + .../PaginatedResults_GraphExecutionState_.ts | 1 + .../api/models/ParamFloatInvocation.ts | 1 + .../services/api/models/ParamIntInvocation.ts | 1 + .../models/PidiImageProcessorInvocation.ts | 1 + .../services/api/models/PipelineModelField.ts | 20 ++ .../api/models/PromptCollectionOutput.ts | 1 + .../src/services/api/models/PromptOutput.ts | 1 + .../api/models/RandomIntInvocation.ts | 1 + .../api/models/RandomRangeInvocation.ts | 1 + .../services/api/models/RangeInvocation.ts | 1 + .../api/models/RangeOfSizeInvocation.ts | 1 + .../api/models/ResizeLatentsInvocation.ts | 1 + .../api/models/RestoreFaceInvocation.ts | 1 + .../api/models/SD1ModelLoaderInvocation.ts | 3 - .../api/models/SD2ModelLoaderInvocation.ts | 3 - .../api/models/SDModelLoaderInvocation.ts | 25 ++ .../api/models/ScaleLatentsInvocation.ts | 1 + .../api/models/ShowImageInvocation.ts | 1 + .../StableDiffusion1ModelCheckpointConfig.ts | 7 +- .../StableDiffusion1ModelDiffusersConfig.ts | 7 +- .../api/models/StableDiffusion1ModelFormat.ts | 8 + .../StableDiffusion2ModelCheckpointConfig.ts | 7 +- .../StableDiffusion2ModelDiffusersConfig.ts | 7 +- .../api/models/StableDiffusion2ModelFormat.ts | 8 + .../api/models/StepParamEasingInvocation.ts | 1 + .../services/api/models/SubtractInvocation.ts | 1 + .../api/models/TextToImageInvocation.ts | 64 ----- .../api/models/TextToLatentsInvocation.ts | 1 + .../api/models/TextualInversionModelConfig.ts | 7 +- .../web/src/services/api/models/UNetField.ts | 3 - .../services/api/models/UpscaleInvocation.ts | 1 + .../web/src/services/api/models/VaeField.ts | 3 - .../{VAEModelConfig.ts => VaeModelConfig.ts} | 8 +- .../src/services/api/models/VaeModelFormat.ts | 8 + .../web/src/services/api/models/VaeRepo.ts | 1 + .../services/api/models/ValidationError.ts | 1 + .../ZoeDepthImageProcessorInvocation.ts | 1 + ...ls__controlnet__ControlNetModel__Config.ts | 14 -- ...gement__models__lora__LoRAModel__Config.ts | 14 -- ...StableDiffusion1Model__CheckpointConfig.ts | 18 -- ..._StableDiffusion1Model__DiffusersConfig.ts | 17 -- ...StableDiffusion2Model__CheckpointConfig.ts | 21 -- ..._StableDiffusion2Model__DiffusersConfig.ts | 20 -- ...nversion__TextualInversionModel__Config.ts | 14 -- ...nagement__models__vae__VaeModel__Config.ts | 14 -- .../services/api/services/ImagesService.ts | 156 +++++------- .../services/api/services/ModelsService.ts | 31 +-- .../services/api/services/SessionsService.ts | 224 ++++++++---------- 135 files changed, 387 insertions(+), 636 deletions(-) create mode 100644 invokeai/frontend/web/src/services/api/models/ControlNetModelFormat.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts rename invokeai/frontend/web/src/services/api/models/{LoraModelConfig.ts => LoRAModelConfig.ts} (71%) create mode 100644 invokeai/frontend/web/src/services/api/models/LoRAModelFormat.ts create mode 100644 invokeai/frontend/web/src/services/api/models/PipelineModelField.ts create mode 100644 invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelFormat.ts create mode 100644 invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelFormat.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts rename invokeai/frontend/web/src/services/api/models/{VAEModelConfig.ts => VaeModelConfig.ts} (71%) create mode 100644 invokeai/frontend/web/src/services/api/models/VaeModelFormat.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__controlnet__ControlNetModel__Config.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__lora__LoRAModel__Config.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__vae__VaeModel__Config.ts diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index a738a9aafd..3c143ecf6e 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -8,13 +8,10 @@ export type { OpenAPIConfig } from './core/OpenAPI'; export type { AddInvocation } from './models/AddInvocation'; export type { BaseModelType } from './models/BaseModelType'; -<<<<<<< HEAD export type { BoardChanges } from './models/BoardChanges'; export type { BoardDTO } from './models/BoardDTO'; export type { Body_create_board_image } from './models/Body_create_board_image'; export type { Body_remove_board_image } from './models/Body_remove_board_image'; -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) export type { Body_upload_image } from './models/Body_upload_image'; export type { CannyImageProcessorInvocation } from './models/CannyImageProcessorInvocation'; export type { CkptModelInfo } from './models/CkptModelInfo'; @@ -29,6 +26,7 @@ export type { ContentShuffleImageProcessorInvocation } from './models/ContentShu export type { ControlField } from './models/ControlField'; export type { ControlNetInvocation } from './models/ControlNetInvocation'; export type { ControlNetModelConfig } from './models/ControlNetModelConfig'; +export type { ControlNetModelFormat } from './models/ControlNetModelFormat'; export type { ControlOutput } from './models/ControlOutput'; export type { CreateModelRequest } from './models/CreateModelRequest'; export type { CvInpaintInvocation } from './models/CvInpaintInvocation'; @@ -71,14 +69,6 @@ export type { InfillTileInvocation } from './models/InfillTileInvocation'; export type { InpaintInvocation } from './models/InpaintInvocation'; export type { IntCollectionOutput } from './models/IntCollectionOutput'; export type { IntOutput } from './models/IntOutput'; -export type { invokeai__backend__model_management__models__controlnet__ControlNetModel__Config } from './models/invokeai__backend__model_management__models__controlnet__ControlNetModel__Config'; -export type { invokeai__backend__model_management__models__lora__LoRAModel__Config } from './models/invokeai__backend__model_management__models__lora__LoRAModel__Config'; -export type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig } from './models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig'; -export type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig } from './models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig'; -export type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig } from './models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig'; -export type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig } from './models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig'; -export type { invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config } from './models/invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config'; -export type { invokeai__backend__model_management__models__vae__VaeModel__Config } from './models/invokeai__backend__model_management__models__vae__VaeModel__Config'; export type { IterateInvocation } from './models/IterateInvocation'; export type { IterateInvocationOutput } from './models/IterateInvocationOutput'; export type { LatentsField } from './models/LatentsField'; @@ -91,14 +81,8 @@ export type { LoadImageInvocation } from './models/LoadImageInvocation'; export type { LoraInfo } from './models/LoraInfo'; export type { LoraLoaderInvocation } from './models/LoraLoaderInvocation'; export type { LoraLoaderOutput } from './models/LoraLoaderOutput'; -<<<<<<< HEAD -<<<<<<< HEAD -======= -export type { LoraModelConfig } from './models/LoraModelConfig'; ->>>>>>> 76dd749b1 (chore: Rebuild API) -======= export type { LoRAModelConfig } from './models/LoRAModelConfig'; ->>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) +export type { LoRAModelFormat } from './models/LoRAModelFormat'; export type { MaskFromAlphaInvocation } from './models/MaskFromAlphaInvocation'; export type { MaskOutput } from './models/MaskOutput'; export type { MediapipeFaceProcessorInvocation } from './models/MediapipeFaceProcessorInvocation'; @@ -121,6 +105,7 @@ export type { PaginatedResults_GraphExecutionState_ } from './models/PaginatedRe export type { ParamFloatInvocation } from './models/ParamFloatInvocation'; export type { ParamIntInvocation } from './models/ParamIntInvocation'; export type { PidiImageProcessorInvocation } from './models/PidiImageProcessorInvocation'; +export type { PipelineModelField } from './models/PipelineModelField'; export type { PromptCollectionOutput } from './models/PromptCollectionOutput'; export type { PromptOutput } from './models/PromptOutput'; export type { RandomIntInvocation } from './models/RandomIntInvocation'; @@ -134,30 +119,24 @@ export type { ScaleLatentsInvocation } from './models/ScaleLatentsInvocation'; export type { SchedulerPredictionType } from './models/SchedulerPredictionType'; export type { SD1ModelLoaderInvocation } from './models/SD1ModelLoaderInvocation'; export type { SD2ModelLoaderInvocation } from './models/SD2ModelLoaderInvocation'; +export type { SDModelLoaderInvocation } from './models/SDModelLoaderInvocation'; export type { ShowImageInvocation } from './models/ShowImageInvocation'; export type { StableDiffusion1ModelCheckpointConfig } from './models/StableDiffusion1ModelCheckpointConfig'; export type { StableDiffusion1ModelDiffusersConfig } from './models/StableDiffusion1ModelDiffusersConfig'; +export type { StableDiffusion1ModelFormat } from './models/StableDiffusion1ModelFormat'; export type { StableDiffusion2ModelCheckpointConfig } from './models/StableDiffusion2ModelCheckpointConfig'; export type { StableDiffusion2ModelDiffusersConfig } from './models/StableDiffusion2ModelDiffusersConfig'; +export type { StableDiffusion2ModelFormat } from './models/StableDiffusion2ModelFormat'; export type { StepParamEasingInvocation } from './models/StepParamEasingInvocation'; export type { SubModelType } from './models/SubModelType'; export type { SubtractInvocation } from './models/SubtractInvocation'; export type { TextToLatentsInvocation } from './models/TextToLatentsInvocation'; -<<<<<<< HEAD -export type { UNetField } from './models/UNetField'; -export type { UpscaleInvocation } from './models/UpscaleInvocation'; -export type { VaeField } from './models/VaeField'; -======= export type { TextualInversionModelConfig } from './models/TextualInversionModelConfig'; export type { UNetField } from './models/UNetField'; export type { UpscaleInvocation } from './models/UpscaleInvocation'; export type { VaeField } from './models/VaeField'; -<<<<<<< HEAD -export type { VAEModelConfig } from './models/VAEModelConfig'; ->>>>>>> 76dd749b1 (chore: Rebuild API) -======= export type { VaeModelConfig } from './models/VaeModelConfig'; ->>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) +export type { VaeModelFormat } from './models/VaeModelFormat'; export type { VaeRepo } from './models/VaeRepo'; export type { ValidationError } from './models/ValidationError'; export type { ZoeDepthImageProcessorInvocation } from './models/ZoeDepthImageProcessorInvocation'; diff --git a/invokeai/frontend/web/src/services/api/models/AddInvocation.ts b/invokeai/frontend/web/src/services/api/models/AddInvocation.ts index b7c1c88187..e9671a918f 100644 --- a/invokeai/frontend/web/src/services/api/models/AddInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/AddInvocation.ts @@ -24,3 +24,4 @@ export type AddInvocation = { */ 'b'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts b/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts index fd26ed49e0..b81146d3ab 100644 --- a/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts +++ b/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts @@ -5,3 +5,4 @@ export type Body_upload_image = { file: Blob; }; + diff --git a/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts index 3f1a5b3d46..d5203867ac 100644 --- a/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts @@ -30,3 +30,4 @@ export type CannyImageProcessorInvocation = { */ high_threshold?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts b/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts index 464474736f..cfa4357725 100644 --- a/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts @@ -37,3 +37,4 @@ export type CkptModelInfo = { */ height?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ClipField.ts b/invokeai/frontend/web/src/services/api/models/ClipField.ts index f267fe85d9..f9ef2cc683 100644 --- a/invokeai/frontend/web/src/services/api/models/ClipField.ts +++ b/invokeai/frontend/web/src/services/api/models/ClipField.ts @@ -19,7 +19,4 @@ export type ClipField = { */ loras: Array; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts b/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts index a0fe38613c..f190ab7073 100644 --- a/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts @@ -24,3 +24,4 @@ export type CollectInvocation = { */ collection?: Array; }; + diff --git a/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts index 62c785f374..a5976242ea 100644 --- a/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts @@ -12,3 +12,4 @@ export type CollectInvocationOutput = { */ collection: Array; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ColorField.ts b/invokeai/frontend/web/src/services/api/models/ColorField.ts index 25167433d4..e0a609ec12 100644 --- a/invokeai/frontend/web/src/services/api/models/ColorField.ts +++ b/invokeai/frontend/web/src/services/api/models/ColorField.ts @@ -20,3 +20,4 @@ export type ColorField = { */ 'a': number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts b/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts index 642e11023b..dd381ef22c 100644 --- a/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts @@ -26,3 +26,4 @@ export type CompelInvocation = { */ clip?: ClipField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/CompelOutput.ts b/invokeai/frontend/web/src/services/api/models/CompelOutput.ts index b42ab73c74..94f1fcb282 100644 --- a/invokeai/frontend/web/src/services/api/models/CompelOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/CompelOutput.ts @@ -14,3 +14,4 @@ export type CompelOutput = { */ conditioning?: ConditioningField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ConditioningField.ts b/invokeai/frontend/web/src/services/api/models/ConditioningField.ts index da227dd4f9..7e53a63b42 100644 --- a/invokeai/frontend/web/src/services/api/models/ConditioningField.ts +++ b/invokeai/frontend/web/src/services/api/models/ConditioningField.ts @@ -8,3 +8,4 @@ export type ConditioningField = { */ conditioning_name: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts index 43f6100db8..e3f67ec9be 100644 --- a/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts @@ -42,3 +42,4 @@ export type ContentShuffleImageProcessorInvocation = { */ 'f'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ControlField.ts b/invokeai/frontend/web/src/services/api/models/ControlField.ts index 8de332b82d..0479684d2c 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlField.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlField.ts @@ -26,3 +26,4 @@ export type ControlField = { */ end_step_percent: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts b/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts index 31e22a1bba..42268b8295 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts @@ -38,3 +38,4 @@ export type ControlNetInvocation = { */ end_step_percent?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts b/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts index e4f77ba7bf..60e2958f5c 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlNetModelConfig.ts @@ -3,16 +3,16 @@ /* eslint-disable */ import type { BaseModelType } from './BaseModelType'; +import type { ControlNetModelFormat } from './ControlNetModelFormat'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; export type ControlNetModelConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'controlnet'; path: string; description?: string; - format: ('checkpoint' | 'diffusers'); - default?: boolean; + model_format: ControlNetModelFormat; error?: ModelError; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ControlNetModelFormat.ts b/invokeai/frontend/web/src/services/api/models/ControlNetModelFormat.ts new file mode 100644 index 0000000000..500b3e8f8c --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ControlNetModelFormat.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type ControlNetModelFormat = 'checkpoint' | 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/ControlOutput.ts b/invokeai/frontend/web/src/services/api/models/ControlOutput.ts index 625d8f670d..a3cc5530c1 100644 --- a/invokeai/frontend/web/src/services/api/models/ControlOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/ControlOutput.ts @@ -14,3 +14,4 @@ export type ControlOutput = { */ control?: ControlField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts b/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts index 80976f126f..0b0f52b8fe 100644 --- a/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts +++ b/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts @@ -15,3 +15,4 @@ export type CreateModelRequest = { */ info: (CkptModelInfo | DiffusersModelInfo); }; + diff --git a/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts index 4f1c33483e..874df93c30 100644 --- a/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts @@ -26,3 +26,4 @@ export type CvInpaintInvocation = { */ mask?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts b/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts index ea175e351a..4e722ddb80 100644 --- a/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts @@ -31,3 +31,4 @@ export type DiffusersModelInfo = { */ path?: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts b/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts index 5b37dea710..fd5b3475ae 100644 --- a/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts @@ -24,3 +24,4 @@ export type DivideInvocation = { */ 'b'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts b/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts index 79dc958d0f..f7323a489b 100644 --- a/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/DynamicPromptInvocation.ts @@ -28,3 +28,4 @@ export type DynamicPromptInvocation = { */ combinatorial?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/Edge.ts b/invokeai/frontend/web/src/services/api/models/Edge.ts index e72108f74a..bba275cb26 100644 --- a/invokeai/frontend/web/src/services/api/models/Edge.ts +++ b/invokeai/frontend/web/src/services/api/models/Edge.ts @@ -14,3 +14,4 @@ export type Edge = { */ destination: EdgeConnection; }; + diff --git a/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts b/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts index ab4c6d354b..ecbddccd76 100644 --- a/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts +++ b/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts @@ -12,3 +12,4 @@ export type EdgeConnection = { */ field: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts b/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts index fb9e4164e6..a3f08247a4 100644 --- a/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/FloatCollectionOutput.ts @@ -12,3 +12,4 @@ export type FloatCollectionOutput = { */ collection?: Array; }; + diff --git a/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts index a9e67d8135..e0fd4a1caa 100644 --- a/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/FloatLinearRangeInvocation.ts @@ -28,3 +28,4 @@ export type FloatLinearRangeInvocation = { */ steps?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/FloatOutput.ts b/invokeai/frontend/web/src/services/api/models/FloatOutput.ts index db2784ed9f..2331936b30 100644 --- a/invokeai/frontend/web/src/services/api/models/FloatOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/FloatOutput.ts @@ -12,3 +12,4 @@ export type FloatOutput = { */ param?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index 3f51247701..7976c08abb 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -58,6 +58,7 @@ import type { RestoreFaceInvocation } from './RestoreFaceInvocation'; import type { ScaleLatentsInvocation } from './ScaleLatentsInvocation'; import type { SD1ModelLoaderInvocation } from './SD1ModelLoaderInvocation'; import type { SD2ModelLoaderInvocation } from './SD2ModelLoaderInvocation'; +import type { SDModelLoaderInvocation } from './SDModelLoaderInvocation'; import type { ShowImageInvocation } from './ShowImageInvocation'; import type { StepParamEasingInvocation } from './StepParamEasingInvocation'; import type { SubtractInvocation } from './SubtractInvocation'; @@ -73,13 +74,10 @@ export type Graph = { /** * The nodes in this graph */ -<<<<<<< HEAD - nodes?: Record; -======= - nodes?: Record; ->>>>>>> 76dd749b1 (chore: Rebuild API) + nodes?: Record; /** * The connections between nodes and their fields in this graph */ edges?: Array; }; + diff --git a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts index 24ec8c9d6d..602e7a2ebc 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts @@ -48,11 +48,7 @@ export type GraphExecutionState = { /** * The results of node executions */ -<<<<<<< HEAD results: Record; -======= - results: Record; ->>>>>>> 76dd749b1 (chore: Rebuild API) /** * Errors raised when executing nodes */ @@ -66,3 +62,4 @@ export type GraphExecutionState = { */ source_prepared_mapping: Record>; }; + diff --git a/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts b/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts index a7e3d6c948..8512faae74 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts @@ -22,3 +22,4 @@ export type GraphInvocation = { */ graph?: Graph; }; + diff --git a/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts index 219a4a675d..af0aae3edb 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts @@ -8,3 +8,4 @@ export type GraphInvocationOutput = { type: 'graph_output'; }; + diff --git a/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts b/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts index 69908c3bba..5e13adc4e5 100644 --- a/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts +++ b/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts @@ -7,3 +7,4 @@ import type { ValidationError } from './ValidationError'; export type HTTPValidationError = { detail?: Array; }; + diff --git a/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts index 387e8c8634..1132012c5a 100644 --- a/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/HedImageProcessorInvocation.ts @@ -34,3 +34,4 @@ export type HedImageProcessorInvocation = { */ scribble?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts index 6466efcd82..3ba86d8fab 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts @@ -30,3 +30,4 @@ export type ImageBlurInvocation = { */ blur_type?: 'gaussian' | 'box'; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts index d6abae5eae..47bfd4110f 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts @@ -26,3 +26,4 @@ export type ImageChannelInvocation = { */ channel?: 'A' | 'R' | 'G' | 'B'; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts index d303c7ce79..4bd59d03b0 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts @@ -26,3 +26,4 @@ export type ImageConvertInvocation = { */ mode?: 'L' | 'RGB' | 'RGBA' | 'CMYK' | 'YCbCr' | 'LAB' | 'HSV' | 'I' | 'F'; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts index e29e177aa7..5207ebbf6d 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts @@ -38,3 +38,4 @@ export type ImageCropInvocation = { */ height?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageDTO.ts b/invokeai/frontend/web/src/services/api/models/ImageDTO.ts index 1e0ea0648f..4e273e8854 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageDTO.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageDTO.ts @@ -71,3 +71,4 @@ export type ImageDTO = { */ board_id?: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageField.ts b/invokeai/frontend/web/src/services/api/models/ImageField.ts index c4c67a0674..baf3bf2b54 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageField.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageField.ts @@ -11,3 +11,4 @@ export type ImageField = { */ image_name: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts index 63220c8047..0347d4dc38 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts @@ -30,3 +30,4 @@ export type ImageInverseLerpInvocation = { */ max?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts index 444c7e6467..388c86061c 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts @@ -30,3 +30,4 @@ export type ImageLerpInvocation = { */ max?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts b/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts index 8aecdddc4f..0b2af78799 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts @@ -78,3 +78,4 @@ export type ImageMetadata = { */ extra?: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts index 724061fce8..751ee49158 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts @@ -26,3 +26,4 @@ export type ImageMultiplyInvocation = { */ image2?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageOutput.ts b/invokeai/frontend/web/src/services/api/models/ImageOutput.ts index c030632926..d7db0c11de 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageOutput.ts @@ -22,3 +22,4 @@ export type ImageOutput = { */ height: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts index 5af28452b6..c883b9a5d8 100644 --- a/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts @@ -38,3 +38,4 @@ export type ImagePasteInvocation = { */ 'y'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts index e0058a78ca..0d995c4e68 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts @@ -22,3 +22,4 @@ export type ImageProcessorInvocation = { */ image?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts b/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts index 209b6d8e88..e597cd907d 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts @@ -26,3 +26,4 @@ export type ImageRecordChanges = { */ is_intermediate?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts index f277516ccd..3b096c83b7 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageResizeInvocation.ts @@ -34,3 +34,4 @@ export type ImageResizeInvocation = { */ resample_mode?: 'nearest' | 'box' | 'bilinear' | 'hamming' | 'bicubic' | 'lanczos'; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts index 709e1cc66a..bf4da28a4a 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageScaleInvocation.ts @@ -30,3 +30,4 @@ export type ImageScaleInvocation = { */ resample_mode?: 'nearest' | 'box' | 'bilinear' | 'hamming' | 'bicubic' | 'lanczos'; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts deleted file mode 100644 index faa9d164a8..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Generates an image using img2img. - */ -export type ImageToImageInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'img2img'; - /** - * The prompt to generate an image from - */ - prompt?: string; - /** - * The seed to use (omit for random) - */ - seed?: number; - /** - * The number of steps to use to generate the image - */ - steps?: number; - /** - * The width of the resulting image - */ - width?: number; - /** - * The height of the resulting image - */ - height?: number; - /** - * The Classifier-Free Guidance, higher values may result in a result closer to the prompt - */ - cfg_scale?: number; - /** - * The scheduler to use - */ - scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; - /** - * The model to use (currently ignored) - */ - model?: string; - /** - * Whether or not to produce progress images during generation - */ - progress_images?: boolean; - /** - * The control model to use - */ - control_model?: string; - /** - * The processed control image - */ - control_image?: ImageField; - /** - * The input image - */ - image?: ImageField; - /** - * The strength of the original image - */ - strength?: number; - /** - * Whether or not the result should be fit to the aspect ratio of the input image - */ - fit?: boolean; -}; diff --git a/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts index 65df90351a..ace0ed8e3c 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts @@ -31,3 +31,4 @@ export type ImageToLatentsInvocation = { */ tiled?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts b/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts index ad45d2047e..1e0ff322e8 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts @@ -19,3 +19,4 @@ export type ImageUrlsDTO = { */ thumbnail_url: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts index 6d60bbe226..3e637b299c 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts @@ -27,3 +27,4 @@ export type InfillColorInvocation = { */ color?: ColorField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts index bf6e8012b7..325bfe2080 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts @@ -22,3 +22,4 @@ export type InfillPatchMatchInvocation = { */ image?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts index 551e00da16..dfb1cbc61d 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts @@ -30,3 +30,4 @@ export type InfillTileInvocation = { */ seed?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts index 5e7d3f4b92..8fb9ad3d54 100644 --- a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts @@ -118,3 +118,4 @@ export type InpaintInvocation = { */ inpaint_replace?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts b/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts index 1e60ee8009..93a115f980 100644 --- a/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts @@ -12,3 +12,4 @@ export type IntCollectionOutput = { */ collection?: Array; }; + diff --git a/invokeai/frontend/web/src/services/api/models/IntOutput.ts b/invokeai/frontend/web/src/services/api/models/IntOutput.ts index 58655d0858..eeea6c68b4 100644 --- a/invokeai/frontend/web/src/services/api/models/IntOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/IntOutput.ts @@ -12,3 +12,4 @@ export type IntOutput = { */ 'a'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts b/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts index b6a70156c3..15bf92dfea 100644 --- a/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts @@ -24,3 +24,4 @@ export type IterateInvocation = { */ index?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts index 2eeffd05fd..ce8d9f8c4b 100644 --- a/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts @@ -12,3 +12,4 @@ export type IterateInvocationOutput = { */ item: any; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LatentsField.ts b/invokeai/frontend/web/src/services/api/models/LatentsField.ts index e7446e9cb3..bc6a525f7c 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsField.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsField.ts @@ -11,3 +11,4 @@ export type LatentsField = { */ latents_name: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts b/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts index edf388dbf6..3e9c2f60e4 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts @@ -22,3 +22,4 @@ export type LatentsOutput = { */ height: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts index 1235962d8f..865eeff554 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts @@ -31,3 +31,4 @@ export type LatentsToImageInvocation = { */ tiled?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts index 4f0f49d961..4273115963 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts @@ -61,3 +61,4 @@ export type LatentsToLatentsInvocation = { */ strength?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts index 7c655480c7..5d239536d5 100644 --- a/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts @@ -30,3 +30,4 @@ export type LineartAnimeImageProcessorInvocation = { */ image_resolution?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts index af3a527f3a..17720e689b 100644 --- a/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts @@ -34,3 +34,4 @@ export type LineartImageProcessorInvocation = { */ coarse?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts b/invokeai/frontend/web/src/services/api/models/LoRAModelConfig.ts similarity index 71% rename from invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts rename to invokeai/frontend/web/src/services/api/models/LoRAModelConfig.ts index d300e38fd0..184a266169 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/LoRAModelConfig.ts @@ -3,16 +3,16 @@ /* eslint-disable */ import type { BaseModelType } from './BaseModelType'; +import type { LoRAModelFormat } from './LoRAModelFormat'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; export type LoRAModelConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'lora'; path: string; description?: string; - format: ('lycoris' | 'diffusers'); - default?: boolean; + model_format: LoRAModelFormat; error?: ModelError; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LoRAModelFormat.ts b/invokeai/frontend/web/src/services/api/models/LoRAModelFormat.ts new file mode 100644 index 0000000000..829f8a7c57 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/LoRAModelFormat.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type LoRAModelFormat = 'lycoris' | 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts index 469e7200ad..f20d983f9b 100644 --- a/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts @@ -22,3 +22,4 @@ export type LoadImageInvocation = { */ image?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/LoraInfo.ts b/invokeai/frontend/web/src/services/api/models/LoraInfo.ts index d4499530ac..1a575d4147 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraInfo.ts @@ -28,7 +28,4 @@ export type LoraInfo = { */ weight: number; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts index b448a7a8ad..b93281c5a7 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraLoaderInvocation.ts @@ -35,7 +35,4 @@ export type LoraLoaderInvocation = { */ clip?: ClipField; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts b/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts index 54e02d49e5..1fed1ebc58 100644 --- a/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/LoraLoaderOutput.ts @@ -19,7 +19,4 @@ export type LoraLoaderOutput = { */ clip?: ClipField; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts b/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts index a031c0e05f..e3693f6d98 100644 --- a/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts @@ -26,3 +26,4 @@ export type MaskFromAlphaInvocation = { */ invert?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/MaskOutput.ts b/invokeai/frontend/web/src/services/api/models/MaskOutput.ts index 05d2b36d58..d4594fe6e9 100644 --- a/invokeai/frontend/web/src/services/api/models/MaskOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/MaskOutput.ts @@ -22,3 +22,4 @@ export type MaskOutput = { */ height?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts index 76e89422e9..aa7b966b4b 100644 --- a/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MediapipeFaceProcessorInvocation.ts @@ -30,3 +30,4 @@ export type MediapipeFaceProcessorInvocation = { */ min_confidence?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts index 14cf26f075..bd274228db 100644 --- a/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts @@ -30,3 +30,4 @@ export type MidasDepthImageProcessorInvocation = { */ bg_th?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts index b2a15b5861..0e81c9a4b8 100644 --- a/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts @@ -38,3 +38,4 @@ export type MlsdImageProcessorInvocation = { */ thr_d?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ModelInfo.ts b/invokeai/frontend/web/src/services/api/models/ModelInfo.ts index d11bb523c7..e87799d142 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelInfo.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelInfo.ts @@ -24,7 +24,4 @@ export type ModelInfo = { */ submodel?: SubModelType; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts b/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts index 2599d650cb..5b5b51e71f 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelLoaderOutput.ts @@ -24,7 +24,4 @@ export type ModelLoaderOutput = { */ vae?: VaeField; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index 42d0ddd8f6..01575b990c 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -2,19 +2,6 @@ /* tslint:disable */ /* eslint-disable */ -<<<<<<< HEAD -import type { invokeai__backend__model_management__models__controlnet__ControlNetModel__Config } from './invokeai__backend__model_management__models__controlnet__ControlNetModel__Config'; -import type { invokeai__backend__model_management__models__lora__LoRAModel__Config } from './invokeai__backend__model_management__models__lora__LoRAModel__Config'; -import type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig } from './invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig'; -import type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig } from './invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig'; -import type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig } from './invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig'; -import type { invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig } from './invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig'; -import type { invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config } from './invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config'; -import type { invokeai__backend__model_management__models__vae__VaeModel__Config } from './invokeai__backend__model_management__models__vae__VaeModel__Config'; - -export type ModelsList = { - models: Record>>; -======= import type { ControlNetModelConfig } from './ControlNetModelConfig'; import type { LoRAModelConfig } from './LoRAModelConfig'; import type { StableDiffusion1ModelCheckpointConfig } from './StableDiffusion1ModelCheckpointConfig'; @@ -25,14 +12,6 @@ import type { TextualInversionModelConfig } from './TextualInversionModelConfig' import type { VaeModelConfig } from './VaeModelConfig'; export type ModelsList = { -<<<<<<< HEAD -<<<<<<< HEAD - models: Record>>; ->>>>>>> 76dd749b1 (chore: Rebuild API) -======= - models: Record>>; ->>>>>>> 0f3b7d2b3 (chore: Rebuild API with new Model API names) -======= - models: Record>>; ->>>>>>> 24673fd85 (chore: Rebuild API - base_model and type added) + models: Array<(StableDiffusion1ModelDiffusersConfig | StableDiffusion1ModelCheckpointConfig | VaeModelConfig | LoRAModelConfig | ControlNetModelConfig | TextualInversionModelConfig | StableDiffusion2ModelDiffusersConfig | StableDiffusion2ModelCheckpointConfig)>; }; + diff --git a/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts b/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts index 6a3b17feea..9fd716f33d 100644 --- a/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts @@ -24,3 +24,4 @@ export type MultiplyInvocation = { */ 'b'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts b/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts index 22846f5345..239a24bfe5 100644 --- a/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts @@ -28,3 +28,4 @@ export type NoiseInvocation = { */ height?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts b/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts index cb1b13ef25..f1832d7aa2 100644 --- a/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts @@ -22,3 +22,4 @@ export type NoiseOutput = { */ height: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts index 29fcebf567..400068171e 100644 --- a/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts @@ -30,3 +30,4 @@ export type NormalbaeImageProcessorInvocation = { */ image_resolution?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts index 2b22b247b4..3408bea6db 100644 --- a/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts +++ b/invokeai/frontend/web/src/services/api/models/OffsetPaginatedResults_ImageDTO_.ts @@ -25,3 +25,4 @@ export type OffsetPaginatedResults_ImageDTO_ = { */ total: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts index 80c136546d..982ce8ade7 100644 --- a/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts @@ -34,3 +34,4 @@ export type OpenposeImageProcessorInvocation = { */ image_resolution?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts b/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts index cb5914f677..dd9f50cd4a 100644 --- a/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts +++ b/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts @@ -29,3 +29,4 @@ export type PaginatedResults_GraphExecutionState_ = { */ total: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts b/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts index 4e9087237b..87c01f847f 100644 --- a/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ParamFloatInvocation.ts @@ -20,3 +20,4 @@ export type ParamFloatInvocation = { */ param?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts b/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts index f47c3b8f01..7a45d0a0ac 100644 --- a/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts @@ -20,3 +20,4 @@ export type ParamIntInvocation = { */ 'a'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts index 96433218f7..91c9dc0ce5 100644 --- a/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts @@ -38,3 +38,4 @@ export type PidiImageProcessorInvocation = { */ scribble?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/PipelineModelField.ts b/invokeai/frontend/web/src/services/api/models/PipelineModelField.ts new file mode 100644 index 0000000000..c2f1c07fbf --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/PipelineModelField.ts @@ -0,0 +1,20 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { BaseModelType } from './BaseModelType'; + +/** + * Pipeline model field + */ +export type PipelineModelField = { + /** + * Name of the model + */ + model_name: string; + /** + * Base model + */ + base_model: BaseModelType; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts b/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts index ffab4ca09a..4444ab4d33 100644 --- a/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/PromptCollectionOutput.ts @@ -16,3 +16,4 @@ export type PromptCollectionOutput = { */ count: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/PromptOutput.ts b/invokeai/frontend/web/src/services/api/models/PromptOutput.ts index f9dcfd1b08..5bca3f3037 100644 --- a/invokeai/frontend/web/src/services/api/models/PromptOutput.ts +++ b/invokeai/frontend/web/src/services/api/models/PromptOutput.ts @@ -12,3 +12,4 @@ export type PromptOutput = { */ prompt: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts b/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts index c4fa84cc8e..a2f7c2f02a 100644 --- a/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts @@ -24,3 +24,4 @@ export type RandomIntInvocation = { */ high?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts index 5625324a1e..925511578d 100644 --- a/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts @@ -32,3 +32,4 @@ export type RandomRangeInvocation = { */ seed?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts index 5292d32156..3681602a95 100644 --- a/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts @@ -28,3 +28,4 @@ export type RangeInvocation = { */ step?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts index d97826099a..7dfac68d39 100644 --- a/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts @@ -28,3 +28,4 @@ export type RangeOfSizeInvocation = { */ step?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts index 500514f3c9..9a7b6c61e4 100644 --- a/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts @@ -38,3 +38,4 @@ export type ResizeLatentsInvocation = { */ antialias?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts b/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts index 5cfc165e23..0bacb5d805 100644 --- a/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts @@ -26,3 +26,4 @@ export type RestoreFaceInvocation = { */ strength?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts index 0f287be7bb..9a8a23077a 100644 --- a/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts @@ -20,7 +20,4 @@ export type SD1ModelLoaderInvocation = { */ model_name?: string; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts index 5afc63a387..f477c11a8d 100644 --- a/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts @@ -20,7 +20,4 @@ export type SD2ModelLoaderInvocation = { */ model_name?: string; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts new file mode 100644 index 0000000000..3086c59cf0 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts @@ -0,0 +1,25 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { PipelineModelField } from './PipelineModelField'; + +/** + * Loading submodels of selected model. + */ +export type SDModelLoaderInvocation = { + /** + * The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Whether or not this node is an intermediate node. + */ + is_intermediate?: boolean; + type?: 'sd_model_loader'; + /** + * The model to load + */ + model: PipelineModelField; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts index a65308dcba..506b21e540 100644 --- a/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts @@ -34,3 +34,4 @@ export type ScaleLatentsInvocation = { */ antialias?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts index c6bceda651..1b73055584 100644 --- a/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts @@ -22,3 +22,4 @@ export type ShowImageInvocation = { */ image?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts index c9708a0b6f..be7077cc53 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelCheckpointConfig.ts @@ -4,19 +4,18 @@ import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; export type StableDiffusion1ModelCheckpointConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'pipeline'; path: string; description?: string; - format: 'checkpoint'; - default?: boolean; + model_format: 'checkpoint'; error?: ModelError; vae?: string; config?: string; variant: ModelVariantType; }; + diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts index 4b6f834216..befe014605 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelDiffusersConfig.ts @@ -4,18 +4,17 @@ import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; export type StableDiffusion1ModelDiffusersConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'pipeline'; path: string; description?: string; - format: 'diffusers'; - default?: boolean; + model_format: 'diffusers'; error?: ModelError; vae?: string; variant: ModelVariantType; }; + diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelFormat.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelFormat.ts new file mode 100644 index 0000000000..01b50c2fc0 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion1ModelFormat.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type StableDiffusion1ModelFormat = 'checkpoint' | 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts index 27b6879703..dadd7cac9b 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelCheckpointConfig.ts @@ -4,18 +4,16 @@ import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; import type { SchedulerPredictionType } from './SchedulerPredictionType'; export type StableDiffusion2ModelCheckpointConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'pipeline'; path: string; description?: string; - format: 'checkpoint'; - default?: boolean; + model_format: 'checkpoint'; error?: ModelError; vae?: string; config?: string; @@ -23,3 +21,4 @@ export type StableDiffusion2ModelCheckpointConfig = { prediction_type: SchedulerPredictionType; upcast_attention: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts index a2b66d7157..1e4a34c5dc 100644 --- a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelDiffusersConfig.ts @@ -4,21 +4,20 @@ import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; import type { ModelVariantType } from './ModelVariantType'; import type { SchedulerPredictionType } from './SchedulerPredictionType'; export type StableDiffusion2ModelDiffusersConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'pipeline'; path: string; description?: string; - format: 'diffusers'; - default?: boolean; + model_format: 'diffusers'; error?: ModelError; vae?: string; variant: ModelVariantType; prediction_type: SchedulerPredictionType; upcast_attention: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelFormat.ts b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelFormat.ts new file mode 100644 index 0000000000..7e7b895231 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/StableDiffusion2ModelFormat.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type StableDiffusion2ModelFormat = 'checkpoint' | 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts b/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts index dca4fa8e82..2cff38b3e5 100644 --- a/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/StepParamEasingInvocation.ts @@ -56,3 +56,4 @@ export type StepParamEasingInvocation = { */ show_easing_plot?: boolean; }; + diff --git a/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts b/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts index a1b8ca5628..23334bd891 100644 --- a/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts @@ -24,3 +24,4 @@ export type SubtractInvocation = { */ 'b'?: number; }; + diff --git a/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts deleted file mode 100644 index 26d6117573..0000000000 --- a/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Generates an image using text2img. - */ -export type TextToImageInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'txt2img'; - /** - * The prompt to generate an image from - */ - prompt?: string; - /** - * The seed to use (omit for random) - */ - seed?: number; - /** - * The number of steps to use to generate the image - */ - steps?: number; - /** - * The width of the resulting image - */ - width?: number; - /** - * The height of the resulting image - */ - height?: number; - /** - * The Classifier-Free Guidance, higher values may result in a result closer to the prompt - */ - cfg_scale?: number; - /** - * The scheduler to use - */ - scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; - /** - * The model to use (currently ignored) - */ - model?: string; - /** - * Whether or not to produce progress images during generation - */ - progress_images?: boolean; - /** - * The control model to use - */ - control_model?: string; - /** - * The processed control image - */ - control_image?: ImageField; -}; diff --git a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts index b0b8ec5fc1..cf8229b1f7 100644 --- a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts @@ -53,3 +53,4 @@ export type TextToLatentsInvocation = { */ control?: (ControlField | Array); }; + diff --git a/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts b/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts index 7abfbec081..97d6aa7ffa 100644 --- a/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/TextualInversionModelConfig.ts @@ -4,15 +4,14 @@ import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; export type TextualInversionModelConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'embedding'; path: string; description?: string; - format: null; - default?: boolean; + model_format: null; error?: ModelError; }; + diff --git a/invokeai/frontend/web/src/services/api/models/UNetField.ts b/invokeai/frontend/web/src/services/api/models/UNetField.ts index f0f247c860..ad3b1ddb5b 100644 --- a/invokeai/frontend/web/src/services/api/models/UNetField.ts +++ b/invokeai/frontend/web/src/services/api/models/UNetField.ts @@ -19,7 +19,4 @@ export type UNetField = { */ loras: Array; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts b/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts index 3b42906e39..d0aca63964 100644 --- a/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts @@ -30,3 +30,4 @@ export type UpscaleInvocation = { */ level?: 2 | 4; }; + diff --git a/invokeai/frontend/web/src/services/api/models/VaeField.ts b/invokeai/frontend/web/src/services/api/models/VaeField.ts index 8d3b6f4e53..bfe2793887 100644 --- a/invokeai/frontend/web/src/services/api/models/VaeField.ts +++ b/invokeai/frontend/web/src/services/api/models/VaeField.ts @@ -10,7 +10,4 @@ export type VaeField = { */ vae: ModelInfo; }; -<<<<<<< HEAD -======= ->>>>>>> 76dd749b1 (chore: Rebuild API) diff --git a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts b/invokeai/frontend/web/src/services/api/models/VaeModelConfig.ts similarity index 71% rename from invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts rename to invokeai/frontend/web/src/services/api/models/VaeModelConfig.ts index ad7f70c3d4..a73ee0aa32 100644 --- a/invokeai/frontend/web/src/services/api/models/VAEModelConfig.ts +++ b/invokeai/frontend/web/src/services/api/models/VaeModelConfig.ts @@ -4,15 +4,15 @@ import type { BaseModelType } from './BaseModelType'; import type { ModelError } from './ModelError'; -import type { ModelType } from './ModelType'; +import type { VaeModelFormat } from './VaeModelFormat'; export type VaeModelConfig = { name: string; base_model: BaseModelType; - type: ModelType; + type: 'vae'; path: string; description?: string; - format: ('checkpoint' | 'diffusers'); - default?: boolean; + model_format: VaeModelFormat; error?: ModelError; }; + diff --git a/invokeai/frontend/web/src/services/api/models/VaeModelFormat.ts b/invokeai/frontend/web/src/services/api/models/VaeModelFormat.ts new file mode 100644 index 0000000000..497f81d16f --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/VaeModelFormat.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * An enumeration. + */ +export type VaeModelFormat = 'checkpoint' | 'diffusers'; diff --git a/invokeai/frontend/web/src/services/api/models/VaeRepo.ts b/invokeai/frontend/web/src/services/api/models/VaeRepo.ts index cb6e33c199..0e233626c6 100644 --- a/invokeai/frontend/web/src/services/api/models/VaeRepo.ts +++ b/invokeai/frontend/web/src/services/api/models/VaeRepo.ts @@ -16,3 +16,4 @@ export type VaeRepo = { */ subfolder?: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ValidationError.ts b/invokeai/frontend/web/src/services/api/models/ValidationError.ts index 92697e1d74..14e1fdecd0 100644 --- a/invokeai/frontend/web/src/services/api/models/ValidationError.ts +++ b/invokeai/frontend/web/src/services/api/models/ValidationError.ts @@ -7,3 +7,4 @@ export type ValidationError = { msg: string; type: string; }; + diff --git a/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts index 0dbc99c9e3..6caded8f04 100644 --- a/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ZoeDepthImageProcessorInvocation.ts @@ -22,3 +22,4 @@ export type ZoeDepthImageProcessorInvocation = { */ image?: ImageField; }; + diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__controlnet__ControlNetModel__Config.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__controlnet__ControlNetModel__Config.ts deleted file mode 100644 index f8decdb341..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__controlnet__ControlNetModel__Config.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; - -export type invokeai__backend__model_management__models__controlnet__ControlNetModel__Config = { - path: string; - description?: string; - format: ('checkpoint' | 'diffusers'); - default?: boolean; - error?: ModelError; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__lora__LoRAModel__Config.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__lora__LoRAModel__Config.ts deleted file mode 100644 index 614749a2c5..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__lora__LoRAModel__Config.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; - -export type invokeai__backend__model_management__models__lora__LoRAModel__Config = { - path: string; - description?: string; - format: ('lycoris' | 'diffusers'); - default?: boolean; - error?: ModelError; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig.ts deleted file mode 100644 index 6bdcb87dd4..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; -import type { ModelVariantType } from './ModelVariantType'; - -export type invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__CheckpointConfig = { - path: string; - description?: string; - format: 'checkpoint'; - default?: boolean; - error?: ModelError; - vae?: string; - config?: string; - variant: ModelVariantType; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig.ts deleted file mode 100644 index c88e042178..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; -import type { ModelVariantType } from './ModelVariantType'; - -export type invokeai__backend__model_management__models__stable_diffusion__StableDiffusion1Model__DiffusersConfig = { - path: string; - description?: string; - format: 'diffusers'; - default?: boolean; - error?: ModelError; - vae?: string; - variant: ModelVariantType; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig.ts deleted file mode 100644 index ec2ae4a845..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; -import type { ModelVariantType } from './ModelVariantType'; -import type { SchedulerPredictionType } from './SchedulerPredictionType'; - -export type invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__CheckpointConfig = { - path: string; - description?: string; - format: 'checkpoint'; - default?: boolean; - error?: ModelError; - vae?: string; - config?: string; - variant: ModelVariantType; - prediction_type: SchedulerPredictionType; - upcast_attention: boolean; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig.ts deleted file mode 100644 index 67b897d9d9..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; -import type { ModelVariantType } from './ModelVariantType'; -import type { SchedulerPredictionType } from './SchedulerPredictionType'; - -export type invokeai__backend__model_management__models__stable_diffusion__StableDiffusion2Model__DiffusersConfig = { - path: string; - description?: string; - format: 'diffusers'; - default?: boolean; - error?: ModelError; - vae?: string; - variant: ModelVariantType; - prediction_type: SchedulerPredictionType; - upcast_attention: boolean; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config.ts deleted file mode 100644 index f23d5002e3..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; - -export type invokeai__backend__model_management__models__textual_inversion__TextualInversionModel__Config = { - path: string; - description?: string; - format: null; - default?: boolean; - error?: ModelError; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__vae__VaeModel__Config.ts b/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__vae__VaeModel__Config.ts deleted file mode 100644 index d9314a6063..0000000000 --- a/invokeai/frontend/web/src/services/api/models/invokeai__backend__model_management__models__vae__VaeModel__Config.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ModelError } from './ModelError'; - -export type invokeai__backend__model_management__models__vae__VaeModel__Config = { - path: string; - description?: string; - format: ('checkpoint' | 'diffusers'); - default?: boolean; - error?: ModelError; -}; - diff --git a/invokeai/frontend/web/src/services/api/services/ImagesService.ts b/invokeai/frontend/web/src/services/api/services/ImagesService.ts index f372d4fa87..bfdef887a0 100644 --- a/invokeai/frontend/web/src/services/api/services/ImagesService.ts +++ b/invokeai/frontend/web/src/services/api/services/ImagesService.ts @@ -22,7 +22,6 @@ export class ImagesService { * @throws ApiError */ public static listImagesWithMetadata({ -<<<<<<< HEAD imageOrigin, categories, isIntermediate, @@ -55,35 +54,6 @@ export class ImagesService { */ limit?: number, }): CancelablePromise { -======= -imageOrigin, -categories, -isIntermediate, -offset, -limit = 10, -}: { -/** - * The origin of images to list - */ -imageOrigin?: ResourceOrigin, -/** - * The categories of image to include - */ -categories?: Array, -/** - * Whether to list intermediate images - */ -isIntermediate?: boolean, -/** - * The page offset - */ -offset?: number, -/** - * The number of images per page - */ -limit?: number, -}): CancelablePromise { ->>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/', @@ -108,25 +78,25 @@ limit?: number, * @throws ApiError */ public static uploadImage({ -imageCategory, -isIntermediate, -formData, -sessionId, -}: { -/** - * The category of the image - */ -imageCategory: ImageCategory, -/** - * Whether this is an intermediate image - */ -isIntermediate: boolean, -formData: Body_upload_image, -/** - * The session ID associated with this upload, if any - */ -sessionId?: string, -}): CancelablePromise { + imageCategory, + isIntermediate, + formData, + sessionId, + }: { + /** + * The category of the image + */ + imageCategory: ImageCategory, + /** + * Whether this is an intermediate image + */ + isIntermediate: boolean, + formData: Body_upload_image, + /** + * The session ID associated with this upload, if any + */ + sessionId?: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/images/', @@ -151,13 +121,13 @@ sessionId?: string, * @throws ApiError */ public static getImageFull({ -imageName, -}: { -/** - * The name of full-resolution image file to get - */ -imageName: string, -}): CancelablePromise { + imageName, + }: { + /** + * The name of full-resolution image file to get + */ + imageName: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}', @@ -178,13 +148,13 @@ imageName: string, * @throws ApiError */ public static deleteImage({ -imageName, -}: { -/** - * The name of the image to delete - */ -imageName: string, -}): CancelablePromise { + imageName, + }: { + /** + * The name of the image to delete + */ + imageName: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/images/{image_name}', @@ -204,15 +174,15 @@ imageName: string, * @throws ApiError */ public static updateImage({ -imageName, -requestBody, -}: { -/** - * The name of the image to update - */ -imageName: string, -requestBody: ImageRecordChanges, -}): CancelablePromise { + imageName, + requestBody, + }: { + /** + * The name of the image to update + */ + imageName: string, + requestBody: ImageRecordChanges, + }): CancelablePromise { return __request(OpenAPI, { method: 'PATCH', url: '/api/v1/images/{image_name}', @@ -234,13 +204,13 @@ requestBody: ImageRecordChanges, * @throws ApiError */ public static getImageMetadata({ -imageName, -}: { -/** - * The name of image to get - */ -imageName: string, -}): CancelablePromise { + imageName, + }: { + /** + * The name of image to get + */ + imageName: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}/metadata', @@ -260,13 +230,13 @@ imageName: string, * @throws ApiError */ public static getImageThumbnail({ -imageName, -}: { -/** - * The name of thumbnail image file to get - */ -imageName: string, -}): CancelablePromise { + imageName, + }: { + /** + * The name of thumbnail image file to get + */ + imageName: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}/thumbnail', @@ -287,13 +257,13 @@ imageName: string, * @throws ApiError */ public static getImageUrls({ -imageName, -}: { -/** - * The name of the image whose URL to get - */ -imageName: string, -}): CancelablePromise { + imageName, + }: { + /** + * The name of the image whose URL to get + */ + imageName: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/images/{image_name}/urls', diff --git a/invokeai/frontend/web/src/services/api/services/ModelsService.ts b/invokeai/frontend/web/src/services/api/services/ModelsService.ts index 248f4a352e..54580ce204 100644 --- a/invokeai/frontend/web/src/services/api/services/ModelsService.ts +++ b/invokeai/frontend/web/src/services/api/services/ModelsService.ts @@ -19,7 +19,6 @@ export class ModelsService { * @throws ApiError */ public static listModels({ -<<<<<<< HEAD baseModel, modelType, }: { @@ -32,20 +31,6 @@ export class ModelsService { */ modelType?: ModelType, }): CancelablePromise { -======= -baseModel, -modelType, -}: { -/** - * Base model - */ -baseModel?: BaseModelType, -/** - * The type of model to get - */ -modelType?: ModelType, -}): CancelablePromise { ->>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'GET', url: '/api/v1/models/', @@ -66,10 +51,10 @@ modelType?: ModelType, * @throws ApiError */ public static updateModel({ -requestBody, -}: { -requestBody: CreateModelRequest, -}): CancelablePromise { + requestBody, + }: { + requestBody: CreateModelRequest, + }): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/models/', @@ -88,10 +73,10 @@ requestBody: CreateModelRequest, * @throws ApiError */ public static delModel({ -modelName, -}: { -modelName: string, -}): CancelablePromise { + modelName, + }: { + modelName: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/models/{model_name}', diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index 937cff9c05..2c6ca91319 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -60,6 +60,7 @@ import type { RestoreFaceInvocation } from '../models/RestoreFaceInvocation'; import type { ScaleLatentsInvocation } from '../models/ScaleLatentsInvocation'; import type { SD1ModelLoaderInvocation } from '../models/SD1ModelLoaderInvocation'; import type { SD2ModelLoaderInvocation } from '../models/SD2ModelLoaderInvocation'; +import type { SDModelLoaderInvocation } from '../models/SDModelLoaderInvocation'; import type { ShowImageInvocation } from '../models/ShowImageInvocation'; import type { StepParamEasingInvocation } from '../models/StepParamEasingInvocation'; import type { SubtractInvocation } from '../models/SubtractInvocation'; @@ -80,23 +81,23 @@ export class SessionsService { * @throws ApiError */ public static listSessions({ -page, -perPage = 10, -query = '', -}: { -/** - * The page of results to get - */ -page?: number, -/** - * The number of results per page - */ -perPage?: number, -/** - * The query string to search for - */ -query?: string, -}): CancelablePromise { + page, + perPage = 10, + query = '', + }: { + /** + * The page of results to get + */ + page?: number, + /** + * The number of results per page + */ + perPage?: number, + /** + * The query string to search for + */ + query?: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/sessions/', @@ -118,10 +119,10 @@ query?: string, * @throws ApiError */ public static createSession({ -requestBody, -}: { -requestBody?: Graph, -}): CancelablePromise { + requestBody, + }: { + requestBody?: Graph, + }): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/sessions/', @@ -141,13 +142,13 @@ requestBody?: Graph, * @throws ApiError */ public static getSession({ -sessionId, -}: { -/** - * The id of the session to get - */ -sessionId: string, -}): CancelablePromise { + sessionId, + }: { + /** + * The id of the session to get + */ + sessionId: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/sessions/{session_id}', @@ -168,7 +169,6 @@ sessionId: string, * @throws ApiError */ public static addNode({ -<<<<<<< HEAD sessionId, requestBody, }: { @@ -176,19 +176,8 @@ sessionId: string, * The id of the session */ sessionId: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SDModelLoaderInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { -======= -sessionId, -requestBody, -}: { -/** - * The id of the session - */ -sessionId: string, -requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | CompelInvocation | LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CvInpaintInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | DynamicPromptInvocation | RestoreFaceInvocation | UpscaleInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | ImageToImageInvocation | LatentsToLatentsInvocation | InpaintInvocation), -}): CancelablePromise { ->>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'POST', url: '/api/v1/sessions/{session_id}/nodes', @@ -212,7 +201,6 @@ requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | * @throws ApiError */ public static updateNode({ -<<<<<<< HEAD sessionId, nodePath, requestBody, @@ -225,24 +213,8 @@ requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | * The path to the node in the graph */ nodePath: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SDModelLoaderInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { -======= -sessionId, -nodePath, -requestBody, -}: { -/** - * The id of the session - */ -sessionId: string, -/** - * The path to the node in the graph - */ -nodePath: string, -requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | CompelInvocation | LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | CvInpaintInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | DynamicPromptInvocation | RestoreFaceInvocation | UpscaleInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | ImageToImageInvocation | LatentsToLatentsInvocation | InpaintInvocation), -}): CancelablePromise { ->>>>>>> 76dd749b1 (chore: Rebuild API) return __request(OpenAPI, { method: 'PUT', url: '/api/v1/sessions/{session_id}/nodes/{node_path}', @@ -267,18 +239,18 @@ requestBody: (RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | * @throws ApiError */ public static deleteNode({ -sessionId, -nodePath, -}: { -/** - * The id of the session - */ -sessionId: string, -/** - * The path to the node to delete - */ -nodePath: string, -}): CancelablePromise { + sessionId, + nodePath, + }: { + /** + * The id of the session + */ + sessionId: string, + /** + * The path to the node to delete + */ + nodePath: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/sessions/{session_id}/nodes/{node_path}', @@ -301,15 +273,15 @@ nodePath: string, * @throws ApiError */ public static addEdge({ -sessionId, -requestBody, -}: { -/** - * The id of the session - */ -sessionId: string, -requestBody: Edge, -}): CancelablePromise { + sessionId, + requestBody, + }: { + /** + * The id of the session + */ + sessionId: string, + requestBody: Edge, + }): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/sessions/{session_id}/edges', @@ -333,33 +305,33 @@ requestBody: Edge, * @throws ApiError */ public static deleteEdge({ -sessionId, -fromNodeId, -fromField, -toNodeId, -toField, -}: { -/** - * The id of the session - */ -sessionId: string, -/** - * The id of the node the edge is coming from - */ -fromNodeId: string, -/** - * The field of the node the edge is coming from - */ -fromField: string, -/** - * The id of the node the edge is going to - */ -toNodeId: string, -/** - * The field of the node the edge is going to - */ -toField: string, -}): CancelablePromise { + sessionId, + fromNodeId, + fromField, + toNodeId, + toField, + }: { + /** + * The id of the session + */ + sessionId: string, + /** + * The id of the node the edge is coming from + */ + fromNodeId: string, + /** + * The field of the node the edge is coming from + */ + fromField: string, + /** + * The id of the node the edge is going to + */ + toNodeId: string, + /** + * The field of the node the edge is going to + */ + toField: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/sessions/{session_id}/edges/{from_node_id}/{from_field}/{to_node_id}/{to_field}', @@ -385,18 +357,18 @@ toField: string, * @throws ApiError */ public static invokeSession({ -sessionId, -all = false, -}: { -/** - * The id of the session to invoke - */ -sessionId: string, -/** - * Whether or not to invoke all remaining invocations - */ -all?: boolean, -}): CancelablePromise { + sessionId, + all = false, + }: { + /** + * The id of the session to invoke + */ + sessionId: string, + /** + * Whether or not to invoke all remaining invocations + */ + all?: boolean, + }): CancelablePromise { return __request(OpenAPI, { method: 'PUT', url: '/api/v1/sessions/{session_id}/invoke', @@ -421,13 +393,13 @@ all?: boolean, * @throws ApiError */ public static cancelSessionInvoke({ -sessionId, -}: { -/** - * The id of the session to cancel - */ -sessionId: string, -}): CancelablePromise { + sessionId, + }: { + /** + * The id of the session to cancel + */ + sessionId: string, + }): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/sessions/{session_id}/invoke', From 1bc170727badcf5d2d1cd74fe7115f80682cb6cc Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:47:58 +1000 Subject: [PATCH 108/110] tidy(nodes): rename `sd_model_loader` to `pipeline_model_loader` this is more accurate bc it can do eg kandinsky also --- invokeai/app/invocations/model.py | 211 +----------------------------- 1 file changed, 3 insertions(+), 208 deletions(-) diff --git a/invokeai/app/invocations/model.py b/invokeai/app/invocations/model.py index 48b15c2e4e..b77aa5dafd 100644 --- a/invokeai/app/invocations/model.py +++ b/invokeai/app/invocations/model.py @@ -50,10 +50,10 @@ class PipelineModelField(BaseModel): base_model: BaseModelType = Field(description="Base model") -class SDModelLoaderInvocation(BaseInvocation): - """Loading submodels of selected model.""" +class PipelineModelLoaderInvocation(BaseInvocation): + """Loads a pipeline model, outputting its submodels.""" - type: Literal["sd_model_loader"] = "sd_model_loader" + type: Literal["pipeline_model_loader"] = "pipeline_model_loader" model: PipelineModelField = Field(description="The model to load") # TODO: precision? @@ -154,211 +154,6 @@ class SDModelLoaderInvocation(BaseInvocation): ) ) -class SD1ModelLoaderInvocation(BaseInvocation): - """Loading submodels of selected model.""" - - type: Literal["sd1_model_loader"] = "sd1_model_loader" - - model_name: str = Field(default="", description="Model to load") - # TODO: precision? - - # Schema customisation - class Config(InvocationConfig): - schema_extra = { - "ui": { - "tags": ["model", "loader"], - "type_hints": { - "model_name": "model" # TODO: rename to model_name? - } - }, - } - - def invoke(self, context: InvocationContext) -> ModelLoaderOutput: - - base_model = BaseModelType.StableDiffusion1 # TODO: - - # TODO: not found exceptions - if not context.services.model_manager.model_exists( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - ): - raise Exception(f"Unkown model name: {self.model_name}!") - - """ - if not context.services.model_manager.model_exists( - model_name=self.model_name, - model_type=SDModelType.Diffusers, - submodel=SDModelType.Tokenizer, - ): - raise Exception( - f"Failed to find tokenizer submodel in {self.model_name}! Check if model corrupted" - ) - - if not context.services.model_manager.model_exists( - model_name=self.model_name, - model_type=SDModelType.Diffusers, - submodel=SDModelType.TextEncoder, - ): - raise Exception( - f"Failed to find text_encoder submodel in {self.model_name}! Check if model corrupted" - ) - - if not context.services.model_manager.model_exists( - model_name=self.model_name, - model_type=SDModelType.Diffusers, - submodel=SDModelType.UNet, - ): - raise Exception( - f"Failed to find unet submodel from {self.model_name}! Check if model corrupted" - ) - """ - - - return ModelLoaderOutput( - unet=UNetField( - unet=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.UNet, - ), - scheduler=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.Scheduler, - ), - loras=[], - ), - clip=ClipField( - tokenizer=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.Tokenizer, - ), - text_encoder=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.TextEncoder, - ), - loras=[], - ), - vae=VaeField( - vae=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.Vae, - ), - ) - ) - -# TODO: optimize(less code copy) -class SD2ModelLoaderInvocation(BaseInvocation): - """Loading submodels of selected model.""" - - type: Literal["sd2_model_loader"] = "sd2_model_loader" - - model_name: str = Field(default="", description="Model to load") - # TODO: precision? - - # Schema customisation - class Config(InvocationConfig): - schema_extra = { - "ui": { - "tags": ["model", "loader"], - "type_hints": { - "model_name": "model" # TODO: rename to model_name? - } - }, - } - - def invoke(self, context: InvocationContext) -> ModelLoaderOutput: - - base_model = BaseModelType.StableDiffusion2 # TODO: - - # TODO: not found exceptions - if not context.services.model_manager.model_exists( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - ): - raise Exception(f"Unkown model name: {self.model_name}!") - - """ - if not context.services.model_manager.model_exists( - model_name=self.model_name, - model_type=SDModelType.Diffusers, - submodel=SDModelType.Tokenizer, - ): - raise Exception( - f"Failed to find tokenizer submodel in {self.model_name}! Check if model corrupted" - ) - - if not context.services.model_manager.model_exists( - model_name=self.model_name, - model_type=SDModelType.Diffusers, - submodel=SDModelType.TextEncoder, - ): - raise Exception( - f"Failed to find text_encoder submodel in {self.model_name}! Check if model corrupted" - ) - - if not context.services.model_manager.model_exists( - model_name=self.model_name, - model_type=SDModelType.Diffusers, - submodel=SDModelType.UNet, - ): - raise Exception( - f"Failed to find unet submodel from {self.model_name}! Check if model corrupted" - ) - """ - - - return ModelLoaderOutput( - unet=UNetField( - unet=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.UNet, - ), - scheduler=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.Scheduler, - ), - loras=[], - ), - clip=ClipField( - tokenizer=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.Tokenizer, - ), - text_encoder=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.TextEncoder, - ), - loras=[], - ), - vae=VaeField( - vae=ModelInfo( - model_name=self.model_name, - base_model=base_model, - model_type=ModelType.Pipeline, - submodel=SubModelType.Vae, - ), - ) - ) - class LoraLoaderOutput(BaseInvocationOutput): """Model loader output""" From 2a178f5a25190e2a4026d0ddb314d4de4b4a606a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:48:13 +1000 Subject: [PATCH 109/110] chore(ui): regen api client --- .../frontend/web/src/services/api/index.ts | 4 +--- .../web/src/services/api/models/Graph.ts | 6 ++--- .../web/src/services/api/models/ModelsList.ts | 2 +- ...on.ts => PipelineModelLoaderInvocation.ts} | 6 ++--- .../api/models/SD1ModelLoaderInvocation.ts | 23 ------------------- .../api/models/SD2ModelLoaderInvocation.ts | 23 ------------------- .../services/api/services/SessionsService.ts | 8 +++---- 7 files changed, 10 insertions(+), 62 deletions(-) rename invokeai/frontend/web/src/services/api/models/{SDModelLoaderInvocation.ts => PipelineModelLoaderInvocation.ts} (74%) delete mode 100644 invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index 3c143ecf6e..8ce42494e5 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -106,6 +106,7 @@ export type { ParamFloatInvocation } from './models/ParamFloatInvocation'; export type { ParamIntInvocation } from './models/ParamIntInvocation'; export type { PidiImageProcessorInvocation } from './models/PidiImageProcessorInvocation'; export type { PipelineModelField } from './models/PipelineModelField'; +export type { PipelineModelLoaderInvocation } from './models/PipelineModelLoaderInvocation'; export type { PromptCollectionOutput } from './models/PromptCollectionOutput'; export type { PromptOutput } from './models/PromptOutput'; export type { RandomIntInvocation } from './models/RandomIntInvocation'; @@ -117,9 +118,6 @@ export type { ResourceOrigin } from './models/ResourceOrigin'; export type { RestoreFaceInvocation } from './models/RestoreFaceInvocation'; export type { ScaleLatentsInvocation } from './models/ScaleLatentsInvocation'; export type { SchedulerPredictionType } from './models/SchedulerPredictionType'; -export type { SD1ModelLoaderInvocation } from './models/SD1ModelLoaderInvocation'; -export type { SD2ModelLoaderInvocation } from './models/SD2ModelLoaderInvocation'; -export type { SDModelLoaderInvocation } from './models/SDModelLoaderInvocation'; export type { ShowImageInvocation } from './models/ShowImageInvocation'; export type { StableDiffusion1ModelCheckpointConfig } from './models/StableDiffusion1ModelCheckpointConfig'; export type { StableDiffusion1ModelDiffusersConfig } from './models/StableDiffusion1ModelDiffusersConfig'; diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index 7976c08abb..5fba3d8311 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -49,6 +49,7 @@ import type { OpenposeImageProcessorInvocation } from './OpenposeImageProcessorI import type { ParamFloatInvocation } from './ParamFloatInvocation'; import type { ParamIntInvocation } from './ParamIntInvocation'; import type { PidiImageProcessorInvocation } from './PidiImageProcessorInvocation'; +import type { PipelineModelLoaderInvocation } from './PipelineModelLoaderInvocation'; import type { RandomIntInvocation } from './RandomIntInvocation'; import type { RandomRangeInvocation } from './RandomRangeInvocation'; import type { RangeInvocation } from './RangeInvocation'; @@ -56,9 +57,6 @@ import type { RangeOfSizeInvocation } from './RangeOfSizeInvocation'; import type { ResizeLatentsInvocation } from './ResizeLatentsInvocation'; import type { RestoreFaceInvocation } from './RestoreFaceInvocation'; import type { ScaleLatentsInvocation } from './ScaleLatentsInvocation'; -import type { SD1ModelLoaderInvocation } from './SD1ModelLoaderInvocation'; -import type { SD2ModelLoaderInvocation } from './SD2ModelLoaderInvocation'; -import type { SDModelLoaderInvocation } from './SDModelLoaderInvocation'; import type { ShowImageInvocation } from './ShowImageInvocation'; import type { StepParamEasingInvocation } from './StepParamEasingInvocation'; import type { SubtractInvocation } from './SubtractInvocation'; @@ -74,7 +72,7 @@ export type Graph = { /** * The nodes in this graph */ - nodes?: Record; + nodes?: Record; /** * The connections between nodes and their fields in this graph */ diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts index 01575b990c..9186db3e29 100644 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ b/invokeai/frontend/web/src/services/api/models/ModelsList.ts @@ -12,6 +12,6 @@ import type { TextualInversionModelConfig } from './TextualInversionModelConfig' import type { VaeModelConfig } from './VaeModelConfig'; export type ModelsList = { - models: Array<(StableDiffusion1ModelDiffusersConfig | StableDiffusion1ModelCheckpointConfig | VaeModelConfig | LoRAModelConfig | ControlNetModelConfig | TextualInversionModelConfig | StableDiffusion2ModelDiffusersConfig | StableDiffusion2ModelCheckpointConfig)>; + models: Array<(StableDiffusion1ModelCheckpointConfig | StableDiffusion1ModelDiffusersConfig | VaeModelConfig | LoRAModelConfig | ControlNetModelConfig | TextualInversionModelConfig | StableDiffusion2ModelCheckpointConfig | StableDiffusion2ModelDiffusersConfig)>; }; diff --git a/invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/PipelineModelLoaderInvocation.ts similarity index 74% rename from invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts rename to invokeai/frontend/web/src/services/api/models/PipelineModelLoaderInvocation.ts index 3086c59cf0..b8cdb27acf 100644 --- a/invokeai/frontend/web/src/services/api/models/SDModelLoaderInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/PipelineModelLoaderInvocation.ts @@ -5,9 +5,9 @@ import type { PipelineModelField } from './PipelineModelField'; /** - * Loading submodels of selected model. + * Loads a pipeline model, outputting its submodels. */ -export type SDModelLoaderInvocation = { +export type PipelineModelLoaderInvocation = { /** * The id of this node. Must be unique among all nodes. */ @@ -16,7 +16,7 @@ export type SDModelLoaderInvocation = { * Whether or not this node is an intermediate node. */ is_intermediate?: boolean; - type?: 'sd_model_loader'; + type?: 'pipeline_model_loader'; /** * The model to load */ diff --git a/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts deleted file mode 100644 index 9a8a23077a..0000000000 --- a/invokeai/frontend/web/src/services/api/models/SD1ModelLoaderInvocation.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Loading submodels of selected model. - */ -export type SD1ModelLoaderInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'sd1_model_loader'; - /** - * Model to load - */ - model_name?: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts b/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts deleted file mode 100644 index f477c11a8d..0000000000 --- a/invokeai/frontend/web/src/services/api/models/SD2ModelLoaderInvocation.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Loading submodels of selected model. - */ -export type SD2ModelLoaderInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'sd2_model_loader'; - /** - * Model to load - */ - model_name?: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index 2c6ca91319..51a36caad1 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -51,6 +51,7 @@ import type { PaginatedResults_GraphExecutionState_ } from '../models/PaginatedR import type { ParamFloatInvocation } from '../models/ParamFloatInvocation'; import type { ParamIntInvocation } from '../models/ParamIntInvocation'; import type { PidiImageProcessorInvocation } from '../models/PidiImageProcessorInvocation'; +import type { PipelineModelLoaderInvocation } from '../models/PipelineModelLoaderInvocation'; import type { RandomIntInvocation } from '../models/RandomIntInvocation'; import type { RandomRangeInvocation } from '../models/RandomRangeInvocation'; import type { RangeInvocation } from '../models/RangeInvocation'; @@ -58,9 +59,6 @@ import type { RangeOfSizeInvocation } from '../models/RangeOfSizeInvocation'; import type { ResizeLatentsInvocation } from '../models/ResizeLatentsInvocation'; import type { RestoreFaceInvocation } from '../models/RestoreFaceInvocation'; import type { ScaleLatentsInvocation } from '../models/ScaleLatentsInvocation'; -import type { SD1ModelLoaderInvocation } from '../models/SD1ModelLoaderInvocation'; -import type { SD2ModelLoaderInvocation } from '../models/SD2ModelLoaderInvocation'; -import type { SDModelLoaderInvocation } from '../models/SDModelLoaderInvocation'; import type { ShowImageInvocation } from '../models/ShowImageInvocation'; import type { StepParamEasingInvocation } from '../models/StepParamEasingInvocation'; import type { SubtractInvocation } from '../models/SubtractInvocation'; @@ -176,7 +174,7 @@ export class SessionsService { * The id of the session */ sessionId: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SDModelLoaderInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | PipelineModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -213,7 +211,7 @@ export class SessionsService { * The path to the node in the graph */ nodePath: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | SDModelLoaderInvocation | SD1ModelLoaderInvocation | SD2ModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageResizeInvocation | ImageScaleInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | ControlNetInvocation | ImageProcessorInvocation | PipelineModelLoaderInvocation | LoraLoaderInvocation | DynamicPromptInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | ParamFloatInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | FloatLinearRangeInvocation | StepParamEasingInvocation | UpscaleInvocation | RestoreFaceInvocation | InpaintInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | CannyImageProcessorInvocation | HedImageProcessorInvocation | LineartImageProcessorInvocation | LineartAnimeImageProcessorInvocation | OpenposeImageProcessorInvocation | MidasDepthImageProcessorInvocation | NormalbaeImageProcessorInvocation | MlsdImageProcessorInvocation | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation | ZoeDepthImageProcessorInvocation | MediapipeFaceProcessorInvocation | LatentsToLatentsInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'PUT', From 339e7ce2136c8e2bbe0a707b9e22451e76c05421 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:48:57 +1000 Subject: [PATCH 110/110] feat(ui): initial implementation of model loading - Update model listing code to use `rtk-query` - Update all graph generation to use new `pipeline_model_loader` node --- .../frontend/web/src/app/components/App.tsx | 13 +++ .../enhancers/reduxRemember/serialize.ts | 3 - .../enhancers/reduxRemember/unserialize.ts | 4 - .../listeners/socketio/socketConnected.ts | 12 +-- invokeai/frontend/web/src/app/store/store.ts | 9 +- .../fields/ModelInputFieldComponent.tsx | 98 +++++++++++++++---- .../src/features/nodes/store/nodesSlice.ts | 15 --- .../buildCanvasImageToImageGraph.ts | 9 +- .../graphBuilders/buildCanvasInpaintGraph.ts | 9 +- .../buildCanvasTextToImageGraph.ts | 9 +- .../buildLinearImageToImageGraph.ts | 9 +- .../buildLinearTextToImageGraph.ts | 15 ++- .../util/graphBuilders/buildNodesGraph.ts | 9 +- .../nodes/util/graphBuilders/constants.ts | 2 +- .../nodes/util/modelIdToPipelineModelField.ts | 18 ++++ .../parameters/store/generationSlice.ts | 29 +----- .../parameters/store/parameterZodSchemas.ts | 14 +++ .../system/components/ModelSelect.tsx | 85 +++++++++++----- .../system/hooks/useIsApplicationReady.ts | 11 +-- .../features/system/store/modelSelectors.ts | 59 ----------- .../store/models/sd1PipelineModelSlice.ts | 56 ----------- .../store/models/sd2PipelineModelSlice.ts | 56 ----------- .../system/store/modelsPersistDenylist.ts | 9 -- .../src/features/system/store/systemSlice.ts | 8 -- .../frontend/web/src/services/apiSlice.ts | 48 ++++++++- .../frontend/web/src/services/thunks/model.ts | 58 ----------- 26 files changed, 281 insertions(+), 386 deletions(-) create mode 100644 invokeai/frontend/web/src/features/nodes/util/modelIdToPipelineModelField.ts delete mode 100644 invokeai/frontend/web/src/features/system/store/modelSelectors.ts delete mode 100644 invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts delete mode 100644 invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts delete mode 100644 invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts delete mode 100644 invokeai/frontend/web/src/services/thunks/model.ts diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index a11d8d048c..55fcc97745 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -24,6 +24,7 @@ import Toaster from './Toaster'; import DeleteImageModal from 'features/gallery/components/DeleteImageModal'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal'; +import { useListModelsQuery } from 'services/apiSlice'; const DEFAULT_CONFIG = {}; @@ -46,6 +47,18 @@ const App = ({ const isApplicationReady = useIsApplicationReady(); + const { data: pipelineModels } = useListModelsQuery({ + model_type: 'pipeline', + }); + const { data: controlnetModels } = useListModelsQuery({ + model_type: 'controlnet', + }); + const { data: vaeModels } = useListModelsQuery({ model_type: 'vae' }); + const { data: loraModels } = useListModelsQuery({ model_type: 'lora' }); + const { data: embeddingModels } = useListModelsQuery({ + model_type: 'embedding', + }); + const [loadingOverridden, setLoadingOverridden] = useState(false); const dispatch = useAppDispatch(); diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts index e498ecb749..cb18d48301 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts @@ -5,7 +5,6 @@ import { lightboxPersistDenylist } from 'features/lightbox/store/lightboxPersist import { nodesPersistDenylist } from 'features/nodes/store/nodesPersistDenylist'; import { generationPersistDenylist } from 'features/parameters/store/generationPersistDenylist'; import { postprocessingPersistDenylist } from 'features/parameters/store/postprocessingPersistDenylist'; -import { modelsPersistDenylist } from 'features/system/store/modelsPersistDenylist'; import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist'; import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist'; import { omit } from 'lodash-es'; @@ -18,8 +17,6 @@ const serializationDenylist: { gallery: galleryPersistDenylist, generation: generationPersistDenylist, lightbox: lightboxPersistDenylist, - sd1models: modelsPersistDenylist, - sd2models: modelsPersistDenylist, nodes: nodesPersistDenylist, postprocessing: postprocessingPersistDenylist, system: systemPersistDenylist, diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts index 649b56316d..8f40b0bb59 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts @@ -7,8 +7,6 @@ import { initialNodesState } from 'features/nodes/store/nodesSlice'; import { initialGenerationState } from 'features/parameters/store/generationSlice'; import { initialPostprocessingState } from 'features/parameters/store/postprocessingSlice'; import { initialConfigState } from 'features/system/store/configSlice'; -import { sd1InitialPipelineModelsState } from 'features/system/store/models/sd1PipelineModelSlice'; -import { sd2InitialPipelineModelsState } from 'features/system/store/models/sd2PipelineModelSlice'; import { initialSystemState } from 'features/system/store/systemSlice'; import { initialHotkeysState } from 'features/ui/store/hotkeysSlice'; import { initialUIState } from 'features/ui/store/uiSlice'; @@ -22,8 +20,6 @@ const initialStates: { gallery: initialGalleryState, generation: initialGenerationState, lightbox: initialLightboxState, - sd1PipelineModels: sd1InitialPipelineModelsState, - sd2PipelineModels: sd2InitialPipelineModelsState, nodes: initialNodesState, postprocessing: initialPostprocessingState, system: initialSystemState, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index 0893066f1f..bf54e63836 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -1,7 +1,6 @@ import { log } from 'app/logging/useLogger'; import { appSocketConnected, socketConnected } from 'services/events/actions'; import { receivedPageOfImages } from 'services/thunks/image'; -import { receivedModels } from 'services/thunks/model'; import { receivedOpenAPISchema } from 'services/thunks/schema'; import { startAppListening } from '../..'; @@ -15,8 +14,7 @@ export const addSocketConnectedEventListener = () => { moduleLog.debug({ timestamp }, 'Connected'); - const { sd1pipelinemodels, sd2pipelinemodels, nodes, config, images } = - getState(); + const { nodes, config, images } = getState(); const { disabledTabs } = config; @@ -29,14 +27,6 @@ export const addSocketConnectedEventListener = () => { ); } - if (!sd1pipelinemodels.ids.length) { - dispatch(receivedModels({ baseModel: 'sd-1', modelType: 'pipeline' })); - } - - if (!sd2pipelinemodels.ids.length) { - dispatch(receivedModels({ baseModel: 'sd-2', modelType: 'pipeline' })); - } - if (!nodes.schema && !disabledTabs.includes('nodes')) { dispatch(receivedOpenAPISchema()); } diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 8489de85f0..57a97168a3 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -28,11 +28,6 @@ import { listenerMiddleware } from './middleware/listenerMiddleware'; import { actionSanitizer } from './middleware/devtools/actionSanitizer'; import { actionsDenylist } from './middleware/devtools/actionsDenylist'; import { stateSanitizer } from './middleware/devtools/stateSanitizer'; - -// Model Reducers -import sd1PipelineModelReducer from 'features/system/store/models/sd1PipelineModelSlice'; -import sd2PipelineModelReducer from 'features/system/store/models/sd2PipelineModelSlice'; - import { LOCALSTORAGE_PREFIX } from './constants'; import { serialize } from './enhancers/reduxRemember/serialize'; import { unserialize } from './enhancers/reduxRemember/unserialize'; @@ -43,8 +38,6 @@ const allReducers = { gallery: galleryReducer, generation: generationReducer, lightbox: lightboxReducer, - sd1pipelinemodels: sd1PipelineModelReducer, - sd2pipelinemodels: sd2PipelineModelReducer, nodes: nodesReducer, postprocessing: postprocessingReducer, system: systemReducer, @@ -54,8 +47,8 @@ const allReducers = { images: imagesReducer, controlNet: controlNetReducer, boards: boardsReducer, - [api.reducerPath]: api.reducer, // session: sessionReducer, + [api.reducerPath]: api.reducer, }; const rootReducer = combineReducers(allReducers); diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx index 480c8591bb..c274f23f26 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx @@ -1,14 +1,18 @@ -import { NativeSelect } from '@mantine/core'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { SelectItem } from '@mantine/core'; +import { useAppDispatch } from 'app/store/storeHooks'; import { fieldValueChanged } from 'features/nodes/store/nodesSlice'; import { ModelInputFieldTemplate, ModelInputFieldValue, } from 'features/nodes/types/types'; -import { modelSelector } from 'features/system/store/modelSelectors'; -import { ChangeEvent, memo } from 'react'; +import { memo, useCallback, useEffect, useMemo } from 'react'; import { FieldComponentProps } from './types'; +import { forEach, isString } from 'lodash-es'; +import { MODEL_TYPE_MAP as BASE_MODEL_NAME_MAP } from 'features/system/components/ModelSelect'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; +import { useTranslation } from 'react-i18next'; +import { useListModelsQuery } from 'services/apiSlice'; const ModelInputFieldComponent = ( props: FieldComponentProps @@ -16,26 +20,82 @@ const ModelInputFieldComponent = ( const { nodeId, field } = props; const dispatch = useAppDispatch(); + const { t } = useTranslation(); - const { sd1PipelineModelDropDownData, sd2PipelineModelDropdownData } = - useAppSelector(modelSelector); + const { data: pipelineModels } = useListModelsQuery({ + model_type: 'pipeline', + }); - const handleValueChanged = (e: ChangeEvent) => { - dispatch( - fieldValueChanged({ - nodeId, - fieldName: field.name, - value: e.target.value, - }) - ); - }; + const data = useMemo(() => { + if (!pipelineModels) { + return []; + } + + const data: SelectItem[] = []; + + forEach(pipelineModels.entities, (model, id) => { + if (!model) { + return; + } + + data.push({ + value: id, + label: model.name, + group: BASE_MODEL_NAME_MAP[model.base_model], + }); + }); + + return data; + }, [pipelineModels]); + + const selectedModel = useMemo( + () => pipelineModels?.entities[field.value ?? pipelineModels.ids[0]], + [pipelineModels?.entities, pipelineModels?.ids, field.value] + ); + + const handleValueChanged = useCallback( + (v: string | null) => { + if (!v) { + return; + } + + dispatch( + fieldValueChanged({ + nodeId, + fieldName: field.name, + value: v, + }) + ); + }, + [dispatch, field.name, nodeId] + ); + + useEffect(() => { + if (field.value && pipelineModels?.ids.includes(field.value)) { + return; + } + + const firstModel = pipelineModels?.ids[0]; + + if (!isString(firstModel)) { + return; + } + + handleValueChanged(firstModel); + }, [field.value, handleValueChanged, pipelineModels?.ids]); return ( - + /> ); }; diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts index 5425d1cfd5..341f0c467b 100644 --- a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts +++ b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts @@ -101,21 +101,6 @@ const nodesSlice = createSlice({ builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => { state.schema = action.payload; }); - - builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - const { image_name, image_url, thumbnail_url } = action.payload; - - state.nodes.forEach((node) => { - forEach(node.data.inputs, (input) => { - if (input.type === 'image') { - if (input.value?.image_name === image_name) { - input.value.image_url = image_url; - input.value.thumbnail_url = thumbnail_url; - } - } - }); - }); - }); }, }); diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts index efaeaddff2..ccdc3e0a27 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts @@ -23,6 +23,7 @@ import { } from './constants'; import { set } from 'lodash-es'; import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; const moduleLog = log.child({ namespace: 'nodes' }); @@ -36,7 +37,7 @@ export const buildCanvasImageToImageGraph = ( const { positivePrompt, negativePrompt, - model: model_name, + model: modelId, cfgScale: cfg_scale, scheduler, steps, @@ -49,6 +50,8 @@ export const buildCanvasImageToImageGraph = ( // The bounding box determines width and height, not the width and height params const { width, height } = state.canvas.boundingBoxDimensions; + const model = modelIdToPipelineModelField(modelId); + /** * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the * full graph here as a template. Then use the parameters from app state and set friendlier node @@ -85,9 +88,9 @@ export const buildCanvasImageToImageGraph = ( id: NOISE, }, [MODEL_LOADER]: { - type: 'sd1_model_loader', + type: 'pipeline_model_loader', id: MODEL_LOADER, - model_name, + model, }, [LATENTS_TO_IMAGE]: { type: 'l2i', diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts index 785e1d2fdb..9ffe85b3c9 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts @@ -17,6 +17,7 @@ import { INPAINT_GRAPH, INPAINT, } from './constants'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; const moduleLog = log.child({ namespace: 'nodes' }); @@ -31,7 +32,7 @@ export const buildCanvasInpaintGraph = ( const { positivePrompt, negativePrompt, - model: model_name, + model: modelId, cfgScale: cfg_scale, scheduler, steps, @@ -54,6 +55,8 @@ export const buildCanvasInpaintGraph = ( // We may need to set the inpaint width and height to scale the image const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas; + const model = modelIdToPipelineModelField(modelId); + const graph: NonNullableGraph = { id: INPAINT_GRAPH, nodes: { @@ -99,9 +102,9 @@ export const buildCanvasInpaintGraph = ( prompt: negativePrompt, }, [MODEL_LOADER]: { - type: 'sd1_model_loader', + type: 'pipeline_model_loader', id: MODEL_LOADER, - model_name, + model, }, [RANGE_OF_SIZE]: { type: 'range_of_size', diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts index ca0e56e849..920cb5bf02 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts @@ -14,6 +14,7 @@ import { TEXT_TO_LATENTS, } from './constants'; import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; /** * Builds the Canvas tab's Text to Image graph. @@ -24,7 +25,7 @@ export const buildCanvasTextToImageGraph = ( const { positivePrompt, negativePrompt, - model: model_name, + model: modelId, cfgScale: cfg_scale, scheduler, steps, @@ -36,6 +37,8 @@ export const buildCanvasTextToImageGraph = ( // The bounding box determines width and height, not the width and height params const { width, height } = state.canvas.boundingBoxDimensions; + const model = modelIdToPipelineModelField(modelId); + /** * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the * full graph here as a template. Then use the parameters from app state and set friendlier node @@ -80,9 +83,9 @@ export const buildCanvasTextToImageGraph = ( steps, }, [MODEL_LOADER]: { - type: 'sd1_model_loader', + type: 'pipeline_model_loader', id: MODEL_LOADER, - model_name, + model, }, [LATENTS_TO_IMAGE]: { type: 'l2i', diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts index 78a6623a16..8425ac043a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts @@ -22,6 +22,7 @@ import { } from './constants'; import { set } from 'lodash-es'; import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; const moduleLog = log.child({ namespace: 'nodes' }); @@ -34,7 +35,7 @@ export const buildLinearImageToImageGraph = ( const { positivePrompt, negativePrompt, - model: model_name, + model: modelId, cfgScale: cfg_scale, scheduler, steps, @@ -62,6 +63,8 @@ export const buildLinearImageToImageGraph = ( throw new Error('No initial image found in state'); } + const model = modelIdToPipelineModelField(modelId); + // copy-pasted graph from node editor, filled in with state values & friendly node ids const graph: NonNullableGraph = { id: IMAGE_TO_IMAGE_GRAPH, @@ -89,9 +92,9 @@ export const buildLinearImageToImageGraph = ( id: NOISE, }, [MODEL_LOADER]: { - type: 'sd1_model_loader', + type: 'pipeline_model_loader', id: MODEL_LOADER, - model_name, + model, }, [LATENTS_TO_IMAGE]: { type: 'l2i', diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts index c179a89504..973acdfb77 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts @@ -1,6 +1,10 @@ import { RootState } from 'app/store/store'; import { NonNullableGraph } from 'features/nodes/types/types'; -import { RandomIntInvocation, RangeOfSizeInvocation } from 'services/api'; +import { + BaseModelType, + RandomIntInvocation, + RangeOfSizeInvocation, +} from 'services/api'; import { ITERATE, LATENTS_TO_IMAGE, @@ -14,6 +18,7 @@ import { TEXT_TO_LATENTS, } from './constants'; import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; type TextToImageGraphOverrides = { width: number; @@ -27,7 +32,7 @@ export const buildLinearTextToImageGraph = ( const { positivePrompt, negativePrompt, - model: model_name, + model: modelId, cfgScale: cfg_scale, scheduler, steps, @@ -38,6 +43,8 @@ export const buildLinearTextToImageGraph = ( shouldRandomizeSeed, } = state.generation; + const model = modelIdToPipelineModelField(modelId); + /** * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the * full graph here as a template. Then use the parameters from app state and set friendlier node @@ -82,9 +89,9 @@ export const buildLinearTextToImageGraph = ( steps, }, [MODEL_LOADER]: { - type: 'sd1_model_loader', + type: 'pipeline_model_loader', id: MODEL_LOADER, - model_name, + model, }, [LATENTS_TO_IMAGE]: { type: 'l2i', diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts index 6a700d4813..072b1a53fd 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts @@ -1,9 +1,10 @@ import { Graph } from 'services/api'; import { v4 as uuidv4 } from 'uuid'; -import { cloneDeep, forEach, omit, reduce, values } from 'lodash-es'; +import { cloneDeep, omit, reduce } from 'lodash-es'; import { RootState } from 'app/store/store'; import { InputFieldValue } from 'features/nodes/types/types'; import { AnyInvocation } from 'services/events/types'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; /** * We need to do special handling for some fields @@ -24,6 +25,12 @@ export const parseFieldValue = (field: InputFieldValue) => { } } + if (field.type === 'model') { + if (field.value) { + return modelIdToPipelineModelField(field.value); + } + } + return field.value; }; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts index 39e0080d11..7d4469bc41 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts @@ -7,7 +7,7 @@ export const NOISE = 'noise'; export const RANDOM_INT = 'rand_int'; export const RANGE_OF_SIZE = 'range_of_size'; export const ITERATE = 'iterate'; -export const MODEL_LOADER = 'model_loader'; +export const MODEL_LOADER = 'pipeline_model_loader'; export const IMAGE_TO_LATENTS = 'image_to_latents'; export const LATENTS_TO_LATENTS = 'latents_to_latents'; export const RESIZE = 'resize_image'; diff --git a/invokeai/frontend/web/src/features/nodes/util/modelIdToPipelineModelField.ts b/invokeai/frontend/web/src/features/nodes/util/modelIdToPipelineModelField.ts new file mode 100644 index 0000000000..bbcd8d9bc6 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/modelIdToPipelineModelField.ts @@ -0,0 +1,18 @@ +import { BaseModelType, PipelineModelField } from 'services/api'; + +/** + * Crudely converts a model id to a pipeline model field + * TODO: Make better + */ +export const modelIdToPipelineModelField = ( + modelId: string +): PipelineModelField => { + const [base_model, model_type, model_name] = modelId.split('/'); + + const field: PipelineModelField = { + base_model: base_model as BaseModelType, + model_name, + }; + + return field; +}; diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index e1de166b5c..e7dcbf0d83 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,12 +1,9 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import { DEFAULT_SCHEDULER_NAME, Scheduler } from 'app/constants'; -import { ModelLoaderTypes } from 'features/system/components/ModelSelect'; +import { DEFAULT_SCHEDULER_NAME } from 'app/constants'; import { configChanged } from 'features/system/store/configSlice'; -import { clamp, sortBy } from 'lodash-es'; +import { clamp } from 'lodash-es'; import { ImageDTO } from 'services/api'; -import { imageUrlsReceived } from 'services/thunks/image'; -import { receivedModels } from 'services/thunks/model'; import { CfgScaleParam, HeightParam, @@ -50,7 +47,6 @@ export interface GenerationState { horizontalSymmetrySteps: number; verticalSymmetrySteps: number; model: ModelParam; - currentModelType: ModelLoaderTypes; shouldUseSeamless: boolean; seamlessXAxis: boolean; seamlessYAxis: boolean; @@ -85,7 +81,6 @@ export const initialGenerationState: GenerationState = { horizontalSymmetrySteps: 0, verticalSymmetrySteps: 0, model: '', - currentModelType: 'sd1_model_loader', shouldUseSeamless: false, seamlessXAxis: true, seamlessYAxis: true, @@ -221,33 +216,14 @@ export const generationSlice = createSlice({ modelSelected: (state, action: PayloadAction) => { state.model = action.payload; }, - setCurrentModelType: (state, action: PayloadAction) => { - state.currentModelType = action.payload; - }, }, extraReducers: (builder) => { - builder.addCase(receivedModels.fulfilled, (state, action) => { - if (!state.model) { - const firstModel = sortBy(action.payload, 'name')[0]; - state.model = firstModel.name; - } - }); - builder.addCase(configChanged, (state, action) => { const defaultModel = action.payload.sd?.defaultModel; if (defaultModel && !state.model) { state.model = defaultModel; } }); - - // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - // const { image_name, image_url, thumbnail_url } = action.payload; - - // if (state.initialImage?.image_name === image_name) { - // state.initialImage.image_url = image_url; - // state.initialImage.thumbnail_url = thumbnail_url; - // } - // }); }, }); @@ -284,7 +260,6 @@ export const { setVerticalSymmetrySteps, initialImageChanged, modelSelected, - setCurrentModelType, setShouldUseNoiseSettings, setSeamless, setSeamlessXAxis, diff --git a/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts b/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts index 61567d3fb8..48eb309e7d 100644 --- a/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts +++ b/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts @@ -154,3 +154,17 @@ export type StrengthParam = z.infer; */ export const isValidStrength = (val: unknown): val is StrengthParam => zStrength.safeParse(val).success; + +// /** +// * Zod schema for BaseModelType +// */ +// export const zBaseModelType = z.enum(['sd-1', 'sd-2']); +// /** +// * Type alias for base model type, inferred from its zod schema. Should be identical to the type alias from OpenAPI. +// */ +// export type BaseModelType = z.infer; +// /** +// * Validates/type-guards a value as a base model type +// */ +// export const isValidBaseModelType = (val: unknown): val is BaseModelType => +// zBaseModelType.safeParse(val).success; diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index 813bd9fb70..43de14d507 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -1,39 +1,58 @@ -import { memo, useCallback, useEffect } from 'react'; +import { memo, useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIMantineSelect from 'common/components/IAIMantineSelect'; -import { - modelSelected, - setCurrentModelType, -} from 'features/parameters/store/generationSlice'; +import { modelSelected } from 'features/parameters/store/generationSlice'; -import { modelSelector } from '../store/modelSelectors'; +import { forEach, isString } from 'lodash-es'; +import { SelectItem } from '@mantine/core'; +import { RootState } from 'app/store/store'; +import { useListModelsQuery } from 'services/apiSlice'; -export type ModelLoaderTypes = 'sd1_model_loader' | 'sd2_model_loader'; - -const MODEL_LOADER_MAP = { - 'sd-1': 'sd1_model_loader', - 'sd-2': 'sd2_model_loader', +export const MODEL_TYPE_MAP = { + 'sd-1': 'Stable Diffusion 1.x', + 'sd-2': 'Stable Diffusion 2.x', }; const ModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const { - selectedModel, - sd1PipelineModelDropDownData, - sd2PipelineModelDropdownData, - } = useAppSelector(modelSelector); - useEffect(() => { - if (selectedModel) - dispatch( - setCurrentModelType( - MODEL_LOADER_MAP[selectedModel?.base_model] as ModelLoaderTypes - ) - ); - }, [dispatch, selectedModel]); + const selectedModelId = useAppSelector( + (state: RootState) => state.generation.model + ); + + const { data: pipelineModels } = useListModelsQuery({ + model_type: 'pipeline', + }); + + const data = useMemo(() => { + if (!pipelineModels) { + return []; + } + + const data: SelectItem[] = []; + + forEach(pipelineModels.entities, (model, id) => { + if (!model) { + return; + } + + data.push({ + value: id, + label: model.name, + group: MODEL_TYPE_MAP[model.base_model], + }); + }); + + return data; + }, [pipelineModels]); + + const selectedModel = useMemo( + () => pipelineModels?.entities[selectedModelId], + [pipelineModels?.entities, selectedModelId] + ); const handleChangeModel = useCallback( (v: string | null) => { @@ -45,13 +64,27 @@ const ModelSelect = () => { [dispatch] ); + useEffect(() => { + if (selectedModelId && pipelineModels?.ids.includes(selectedModelId)) { + return; + } + + const firstModel = pipelineModels?.ids[0]; + + if (!isString(firstModel)) { + return; + } + + handleChangeModel(firstModel); + }, [handleChangeModel, pipelineModels?.ids, selectedModelId]); + return ( ); diff --git a/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts b/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts index 193420e29c..8ba5731a5b 100644 --- a/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts +++ b/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts @@ -7,13 +7,12 @@ import { systemSelector } from '../store/systemSelectors'; const isApplicationReadySelector = createSelector( [systemSelector, configSelector], (system, config) => { - const { wereModelsReceived, wasSchemaParsed } = system; + const { wasSchemaParsed } = system; const { disabledTabs } = config; return { disabledTabs, - wereModelsReceived, wasSchemaParsed, }; } @@ -23,21 +22,17 @@ const isApplicationReadySelector = createSelector( * Checks if the application is ready to be used, i.e. if the initial startup process is finished. */ export const useIsApplicationReady = () => { - const { disabledTabs, wereModelsReceived, wasSchemaParsed } = useAppSelector( + const { disabledTabs, wasSchemaParsed } = useAppSelector( isApplicationReadySelector ); const isApplicationReady = useMemo(() => { - if (!wereModelsReceived) { - return false; - } - if (!disabledTabs.includes('nodes') && !wasSchemaParsed) { return false; } return true; - }, [disabledTabs, wereModelsReceived, wasSchemaParsed]); + }, [disabledTabs, wasSchemaParsed]); return isApplicationReady; }; diff --git a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts b/invokeai/frontend/web/src/features/system/store/modelSelectors.ts deleted file mode 100644 index b63c6d256c..0000000000 --- a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { IAISelectDataType } from 'common/components/IAIMantineSelect'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { isEqual } from 'lodash-es'; - -import { - selectAllSD1PipelineModels, - selectByIdSD1PipelineModels, -} from './models/sd1PipelineModelSlice'; - -import { - selectAllSD2PipelineModels, - selectByIdSD2PipelineModels, -} from './models/sd2PipelineModelSlice'; - -export const modelSelector = createSelector( - [(state: RootState) => state, generationSelector], - (state, generation) => { - let selectedModel = selectByIdSD1PipelineModels(state, generation.model); - if (selectedModel === undefined) - selectedModel = selectByIdSD2PipelineModels(state, generation.model); - - const sd1PipelineModels = selectAllSD1PipelineModels(state); - const sd2PipelineModels = selectAllSD2PipelineModels(state); - - const allPipelineModels = sd1PipelineModels.concat(sd2PipelineModels); - - const sd1PipelineModelDropDownData = selectAllSD1PipelineModels(state) - .map((m) => ({ - value: m.name, - label: m.name, - group: '1.x Models', - })) - .sort((a, b) => a.label.localeCompare(b.label)); - - const sd2PipelineModelDropdownData = selectAllSD2PipelineModels(state) - .map((m) => ({ - value: m.name, - label: m.name, - group: '2.x Models', - })) - .sort((a, b) => a.label.localeCompare(b.label)); - - return { - selectedModel, - allPipelineModels, - sd1PipelineModels, - sd2PipelineModels, - sd1PipelineModelDropDownData, - sd2PipelineModelDropdownData, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); diff --git a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts deleted file mode 100644 index 99f1514e6c..0000000000 --- a/invokeai/frontend/web/src/features/system/store/models/sd1PipelineModelSlice.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { - StableDiffusion1ModelCheckpointConfig, - StableDiffusion1ModelDiffusersConfig, -} from 'services/api'; - -import { receivedModels } from 'services/thunks/model'; - -export type SD1PipelineModel = ( - | StableDiffusion1ModelCheckpointConfig - | StableDiffusion1ModelDiffusersConfig -) & { - name: string; -}; - -export const sd1PipelineModelsAdapter = createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), -}); - -export const sd1InitialPipelineModelsState = - sd1PipelineModelsAdapter.getInitialState(); - -export type SD1PipelineModelState = typeof sd1InitialPipelineModelsState; - -export const sd1PipelineModelsSlice = createSlice({ - name: 'sd1PipelineModels', - initialState: sd1InitialPipelineModelsState, - reducers: { - modelAdded: sd1PipelineModelsAdapter.upsertOne, - }, - extraReducers(builder) { - /** - * Received Models - FULFILLED - */ - builder.addCase(receivedModels.fulfilled, (state, action) => { - if (action.meta.arg.baseModel !== 'sd-1') return; - sd1PipelineModelsAdapter.setAll(state, action.payload); - }); - }, -}); - -export const { - selectAll: selectAllSD1PipelineModels, - selectById: selectByIdSD1PipelineModels, - selectEntities: selectEntitiesSD1PipelineModels, - selectIds: selectIdsSD1PipelineModels, - selectTotal: selectTotalSD1PipelineModels, -} = sd1PipelineModelsAdapter.getSelectors( - (state) => state.sd1pipelinemodels -); - -export const { modelAdded } = sd1PipelineModelsSlice.actions; - -export default sd1PipelineModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts b/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts deleted file mode 100644 index 69ff772222..0000000000 --- a/invokeai/frontend/web/src/features/system/store/models/sd2PipelineModelSlice.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { - StableDiffusion2ModelCheckpointConfig, - StableDiffusion2ModelDiffusersConfig, -} from 'services/api'; - -import { receivedModels } from 'services/thunks/model'; - -export type SD2PipelineModel = ( - | StableDiffusion2ModelCheckpointConfig - | StableDiffusion2ModelDiffusersConfig -) & { - name: string; -}; - -export const sd2PipelineModelsAdapater = createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), -}); - -export const sd2InitialPipelineModelsState = - sd2PipelineModelsAdapater.getInitialState(); - -export type SD2PipelineModelState = typeof sd2InitialPipelineModelsState; - -export const sd2PipelineModelsSlice = createSlice({ - name: 'sd2PipelineModels', - initialState: sd2InitialPipelineModelsState, - reducers: { - modelAdded: sd2PipelineModelsAdapater.upsertOne, - }, - extraReducers(builder) { - /** - * Received Models - FULFILLED - */ - builder.addCase(receivedModels.fulfilled, (state, action) => { - if (action.meta.arg.baseModel !== 'sd-2') return; - sd2PipelineModelsAdapater.setAll(state, action.payload); - }); - }, -}); - -export const { - selectAll: selectAllSD2PipelineModels, - selectById: selectByIdSD2PipelineModels, - selectEntities: selectEntitiesSD2PipelineModels, - selectIds: selectIdsSD2PipelineModels, - selectTotal: selectTotalSD2PipelineModels, -} = sd2PipelineModelsAdapater.getSelectors( - (state) => state.sd2pipelinemodels -); - -export const { modelAdded } = sd2PipelineModelsSlice.actions; - -export default sd2PipelineModelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts deleted file mode 100644 index 417a399cf2..0000000000 --- a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { SD1PipelineModelState } from './models/sd1PipelineModelSlice'; -import { SD2PipelineModelState } from './models/sd2PipelineModelSlice'; - -/** - * Models slice persist denylist - */ -export const modelsPersistDenylist: - | (keyof SD1PipelineModelState)[] - | (keyof SD2PipelineModelState)[] = ['entities', 'ids']; diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 8a148ca38b..688f69c1f7 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -20,7 +20,6 @@ import { } from 'services/events/actions'; import { ProgressImage } from 'services/events/types'; import { imageUploaded } from 'services/thunks/image'; -import { receivedModels } from 'services/thunks/model'; import { isAnySessionRejected, sessionCanceled } from 'services/thunks/session'; import { makeToast } from '../../../app/components/Toaster'; import { LANGUAGES } from '../components/LanguagePicker'; @@ -377,13 +376,6 @@ export const systemSlice = createSlice({ ); }); - /** - * Received available models from the backend - */ - builder.addCase(receivedModels.fulfilled, (state) => { - state.wereModelsReceived = true; - }); - /** * OpenAPI schema was parsed */ diff --git a/invokeai/frontend/web/src/services/apiSlice.ts b/invokeai/frontend/web/src/services/apiSlice.ts index 2d42931b0b..e2d765dd90 100644 --- a/invokeai/frontend/web/src/services/apiSlice.ts +++ b/invokeai/frontend/web/src/services/apiSlice.ts @@ -13,23 +13,68 @@ import { TagTypesFrom, TagTypesFromApi, } from '@reduxjs/toolkit/dist/query/endpointDefinitions'; +import { EntityState, createEntityAdapter } from '@reduxjs/toolkit'; +import { BaseModelType } from './api/models/BaseModelType'; +import { ModelType } from './api/models/ModelType'; +import { ModelsList } from './api/models/ModelsList'; +import { keyBy } from 'lodash-es'; type ListBoardsArg = { offset: number; limit: number }; type UpdateBoardArg = { board_id: string; changes: BoardChanges }; type AddImageToBoardArg = { board_id: string; image_name: string }; type RemoveImageFromBoardArg = { board_id: string; image_name: string }; type ListBoardImagesArg = { board_id: string; offset: number; limit: number }; +type ListModelsArg = { base_model?: BaseModelType; model_type?: ModelType }; -const tagTypes = ['Board', 'Image']; +type ModelConfig = ModelsList['models'][number]; + +const tagTypes = ['Board', 'Image', 'Model']; type ApiFullTagDescription = FullTagDescription<(typeof tagTypes)[number]>; const LIST = 'LIST'; +const modelsAdapter = createEntityAdapter({ + selectId: (model) => getModelId(model), + sortComparer: (a, b) => a.name.localeCompare(b.name), +}); + +const getModelId = ({ base_model, type, name }: ModelConfig) => + `${base_model}/${type}/${name}`; + export const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5173/api/v1/' }), reducerPath: 'api', tagTypes, endpoints: (build) => ({ + /** + * Models Queries + */ + + listModels: build.query, ListModelsArg>({ + query: (arg) => ({ url: 'models/', params: arg }), + providesTags: (result, error, arg) => { + // any list of boards + const tags: ApiFullTagDescription[] = [{ id: 'Model', type: LIST }]; + + if (result) { + // and individual tags for each board + tags.push( + ...result.ids.map((id) => ({ + type: 'Model' as const, + id, + })) + ); + } + + return tags; + }, + transformResponse: (response: ModelsList, meta, arg) => { + return modelsAdapter.addMany( + modelsAdapter.getInitialState(), + keyBy(response.models, getModelId) + ); + }, + }), /** * Boards Queries */ @@ -174,4 +219,5 @@ export const { useRemoveImageFromBoardMutation, useListBoardImagesQuery, useGetImageDTOQuery, + useListModelsQuery, } = api; diff --git a/invokeai/frontend/web/src/services/thunks/model.ts b/invokeai/frontend/web/src/services/thunks/model.ts deleted file mode 100644 index 619aa4b7b2..0000000000 --- a/invokeai/frontend/web/src/services/thunks/model.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { log } from 'app/logging/useLogger'; -import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { SD1PipelineModel } from 'features/system/store/models/sd1PipelineModelSlice'; -import { SD2PipelineModel } from 'features/system/store/models/sd2PipelineModelSlice'; -import { reduce, size } from 'lodash-es'; -import { BaseModelType, ModelType, ModelsService } from 'services/api'; - -const models = log.child({ namespace: 'model' }); - -export const IMAGES_PER_PAGE = 20; - -type receivedModelsArg = { - baseModel: BaseModelType | undefined; - modelType: ModelType | undefined; -}; - -export const receivedModels = createAppAsyncThunk( - 'models/receivedModels', - async (arg: receivedModelsArg) => { - const response = await ModelsService.listModels(arg); - - let deserializedModels = {}; - - if (arg.baseModel === undefined) return response.models; - if (arg.modelType === undefined) return response.models; - - if (arg.baseModel === 'sd-1') { - deserializedModels = reduce( - response.models[arg.baseModel][arg.modelType], - (modelsAccumulator, model, modelName) => { - modelsAccumulator[modelName] = { ...model, name: modelName }; - return modelsAccumulator; - }, - {} as Record - ); - } - - if (arg.baseModel === 'sd-2') { - deserializedModels = reduce( - response.models[arg.baseModel][arg.modelType], - (modelsAccumulator, model, modelName) => { - modelsAccumulator[modelName] = { ...model, name: modelName }; - return modelsAccumulator; - }, - {} as Record - ); - } - - models.info( - { response }, - `Received ${size(response.models[arg.baseModel][arg.modelType])} ${[ - arg.baseModel, - ]} models` - ); - - return deserializedModels; - } -);