From 690331b8c0ae3b60f157a0e12f324a30e62b232d Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sat, 15 Jul 2023 17:33:09 +1200 Subject: [PATCH 01/34] chore: Regen Schema --- .../frontend/web/src/services/api/schema.d.ts | 295 ++++++++++++++---- 1 file changed, 230 insertions(+), 65 deletions(-) diff --git a/invokeai/frontend/web/src/services/api/schema.d.ts b/invokeai/frontend/web/src/services/api/schema.d.ts index acbed14eac..2ae5109f4f 100644 --- a/invokeai/frontend/web/src/services/api/schema.d.ts +++ b/invokeai/frontend/web/src/services/api/schema.d.ts @@ -75,11 +75,6 @@ export type paths = { * @description Gets a list of models */ get: operations["list_models"]; - /** - * Import Model - * @description Add a model using its local path, repo_id, or remote URL - */ - post: operations["import_model"]; }; "/api/v1/models/{base_model}/{model_type}/{model_name}": { /** @@ -93,13 +88,53 @@ export type paths = { */ patch: operations["update_model"]; }; + "/api/v1/models/import": { + /** + * Import Model + * @description Add a model using its local path, repo_id, or remote URL. Model characteristics will be probed and configured automatically + */ + post: operations["import_model"]; + }; + "/api/v1/models/add": { + /** + * Add Model + * @description Add a model using the configuration information appropriate for its type. Only local models can be added by path + */ + post: operations["add_model"]; + }; + "/api/v1/models/rename/{base_model}/{model_type}/{model_name}": { + /** + * Rename Model + * @description Rename a model + */ + post: operations["rename_model"]; + }; "/api/v1/models/convert/{base_model}/{model_type}/{model_name}": { /** * Convert Model - * @description Convert a checkpoint model into a diffusers model + * @description Convert a checkpoint model into a diffusers model, optionally saving to the indicated destination directory, or `models` if none. */ put: operations["convert_model"]; }; + "/api/v1/models/search": { + /** Search For Models */ + get: operations["search_for_models"]; + }; + "/api/v1/models/ckpt_confs": { + /** + * List Ckpt Configs + * @description Return a list of the legacy checkpoint configuration files stored in `ROOT/configs/stable-diffusion`, relative to ROOT. + */ + get: operations["list_ckpt_configs"]; + }; + "/api/v1/models/sync": { + /** + * Sync To Config + * @description Call after making changes to models.yaml, autoimport directories or models directory to synchronize + * in-memory data structures with disk data structures. + */ + get: operations["sync_to_config"]; + }; "/api/v1/models/merge/{base_model}": { /** * Merge Models @@ -397,6 +432,11 @@ export type components = { * @default false */ force?: boolean; + /** + * Merge Dest Directory + * @description Save the merged model to the designated directory (with 'merged_model_name' appended) + */ + merge_dest_directory?: string; }; /** Body_remove_board_image */ Body_remove_board_image: { @@ -1174,7 +1214,7 @@ export type components = { * @description The nodes in this graph */ nodes?: { - [key: string]: (components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]) | undefined; + [key: string]: (components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]) | undefined; }; /** * Edges @@ -1217,7 +1257,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: (components["schemas"]["ImageOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["MetadataAccumulatorOutput"] | components["schemas"]["PromptOutput"] | components["schemas"]["PromptCollectionOutput"] | components["schemas"]["CompelOutput"] | components["schemas"]["ClipSkipInvocationOutput"] | components["schemas"]["IntOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["IntCollectionOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]) | undefined; + [key: string]: (components["schemas"]["ImageOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["MetadataAccumulatorOutput"] | components["schemas"]["IntCollectionOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["CompelOutput"] | components["schemas"]["ClipSkipInvocationOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["IntOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["PromptOutput"] | components["schemas"]["PromptCollectionOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]) | undefined; }; /** * Errors @@ -1923,12 +1963,12 @@ export type components = { * Width * @description The width to resize to (px) */ - width: number; + width?: number; /** * Height * @description The height to resize to (px) */ - height: number; + height?: number; /** * Resample Mode * @description The resampling mode @@ -3290,7 +3330,7 @@ export type components = { /** ModelsList */ ModelsList: { /** Models */ - models: (components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"])[]; + models: (components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"])[]; }; /** * MultiplyInvocation @@ -3910,14 +3950,16 @@ export type components = { latents?: components["schemas"]["LatentsField"]; /** * Width - * @description The width to resize to (px) + * @description The width to resize to (px) + * @default 512 */ - width: number; + width?: number; /** * Height - * @description The height to resize to (px) + * @description The height to resize to (px) + * @default 512 */ - height: number; + height?: number; /** * Mode * @description The interpolation mode @@ -4605,18 +4647,18 @@ export type components = { */ image?: components["schemas"]["ImageField"]; }; - /** - * StableDiffusion2ModelFormat - * @description An enumeration. - * @enum {string} - */ - StableDiffusion2ModelFormat: "checkpoint" | "diffusers"; /** * StableDiffusion1ModelFormat * @description An enumeration. * @enum {string} */ StableDiffusion1ModelFormat: "checkpoint" | "diffusers"; + /** + * StableDiffusion2ModelFormat + * @description An enumeration. + * @enum {string} + */ + StableDiffusion2ModelFormat: "checkpoint" | "diffusers"; }; responses: never; parameters: never; @@ -4727,7 +4769,7 @@ export type operations = { }; requestBody: { content: { - "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; + "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; }; }; responses: { @@ -4764,7 +4806,7 @@ export type operations = { }; requestBody: { content: { - "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; + "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; }; }; responses: { @@ -4983,37 +5025,6 @@ export type operations = { }; }; }; - /** - * Import Model - * @description Add a model using its local path, repo_id, or remote URL - */ - import_model: { - requestBody: { - content: { - "application/json": components["schemas"]["Body_import_model"]; - }; - }; - responses: { - /** @description The model imported successfully */ - 201: { - content: { - "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"]; - }; - }; - /** @description The model could not be found */ - 404: never; - /** @description There is already a model corresponding to this path or repo_id */ - 409: never; - /** @description Validation Error */ - 422: { - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - /** @description The model appeared to import successfully, but could not be found in the model manager */ - 424: never; - }; - }; /** * Delete Model * @description Delete Model @@ -5030,12 +5041,6 @@ export type operations = { }; }; responses: { - /** @description Successful Response */ - 200: { - content: { - "application/json": unknown; - }; - }; /** @description Model deleted successfully */ 204: never; /** @description Model not found */ @@ -5065,14 +5070,14 @@ export type operations = { }; requestBody: { content: { - "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"]; + "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; }; }; responses: { /** @description The model was updated successfully */ 200: { content: { - "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"]; + "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; }; }; /** @description Bad request */ @@ -5087,12 +5092,118 @@ export type operations = { }; }; }; + /** + * Import Model + * @description Add a model using its local path, repo_id, or remote URL. Model characteristics will be probed and configured automatically + */ + import_model: { + requestBody: { + content: { + "application/json": components["schemas"]["Body_import_model"]; + }; + }; + responses: { + /** @description The model imported successfully */ + 201: { + content: { + "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; + }; + }; + /** @description The model could not be found */ + 404: never; + /** @description There is already a model corresponding to this path or repo_id */ + 409: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + /** @description The model appeared to import successfully, but could not be found in the model manager */ + 424: never; + }; + }; + /** + * Add Model + * @description Add a model using the configuration information appropriate for its type. Only local models can be added by path + */ + add_model: { + requestBody: { + content: { + "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; + }; + }; + responses: { + /** @description The model added successfully */ + 201: { + content: { + "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; + }; + }; + /** @description The model could not be found */ + 404: never; + /** @description There is already a model corresponding to this path or repo_id */ + 409: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + /** @description The model appeared to add successfully, but could not be found in the model manager */ + 424: never; + }; + }; + /** + * Rename Model + * @description Rename a model + */ + rename_model: { + parameters: { + query?: { + /** @description new model name */ + new_name?: string; + /** @description new model base */ + new_base?: components["schemas"]["BaseModelType"]; + }; + path: { + /** @description Base model */ + base_model: components["schemas"]["BaseModelType"]; + /** @description The type of model */ + model_type: components["schemas"]["ModelType"]; + /** @description current model name */ + model_name: string; + }; + }; + responses: { + /** @description The model was renamed successfully */ + 201: { + content: { + "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; + }; + }; + /** @description The model could not be found */ + 404: never; + /** @description There is already a model corresponding to the new name */ + 409: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; /** * Convert Model - * @description Convert a checkpoint model into a diffusers model + * @description Convert a checkpoint model into a diffusers model, optionally saving to the indicated destination directory, or `models` if none. */ convert_model: { parameters: { + query?: { + /** @description Save the converted model to the designated directory */ + convert_dest_directory?: string; + }; path: { /** @description Base model */ base_model: components["schemas"]["BaseModelType"]; @@ -5106,7 +5217,7 @@ export type operations = { /** @description Model converted successfully */ 200: { content: { - "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"]; + "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; }; }; /** @description Bad request */ @@ -5121,6 +5232,60 @@ export type operations = { }; }; }; + /** Search For Models */ + search_for_models: { + parameters: { + query: { + /** @description Directory path to search for models */ + search_path: string; + }; + }; + responses: { + /** @description Directory searched successfully */ + 200: { + content: { + "application/json": (string)[]; + }; + }; + /** @description Invalid directory path */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * List Ckpt Configs + * @description Return a list of the legacy checkpoint configuration files stored in `ROOT/configs/stable-diffusion`, relative to ROOT. + */ + list_ckpt_configs: { + responses: { + /** @description paths retrieved successfully */ + 200: { + content: { + "application/json": (string)[]; + }; + }; + }; + }; + /** + * Sync To Config + * @description Call after making changes to models.yaml, autoimport directories or models directory to synchronize + * in-memory data structures with disk data structures. + */ + sync_to_config: { + responses: { + /** @description synchronization successful */ + 201: { + content: { + "application/json": unknown; + }; + }; + }; + }; /** * Merge Models * @description Convert a checkpoint model into a diffusers model @@ -5141,7 +5306,7 @@ export type operations = { /** @description Model converted successfully */ 200: { content: { - "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"]; + "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; }; }; /** @description Incompatible models */ From 79ca0d0d025e2f4118bff5ae3c84dfacb4bfdf08 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sat, 15 Jul 2023 17:33:44 +1200 Subject: [PATCH 02/34] feat: Allow user to pick where to saved merged model --- .../tabs/ModelManager/subpanels/MergeModelsPanel.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx index b2d88802c9..bd03092085 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx @@ -125,9 +125,9 @@ export default function MergeModelsPanel() { mergedModelName !== '' ? mergedModelName : models_names.join('-'), alpha: modelMergeAlpha, interp: modelMergeInterp, - // model_merge_save_path: - // modelMergeSaveLocType === 'root' ? null : modelMergeCustomSaveLoc, force: modelMergeForce, + merge_dest_directory: + modelMergeSaveLocType === 'root' ? undefined : modelMergeCustomSaveLoc, }; mergeModels({ @@ -288,7 +288,7 @@ export default function MergeModelsPanel() { - {/* setModelMergeCustomSaveLoc(e.target.value)} /> )} - */} + Date: Sat, 15 Jul 2023 18:48:18 +1200 Subject: [PATCH 03/34] feat: Handle toasts for Model Delete --- invokeai/frontend/web/public/locales/en.json | 4 ++- .../ModelManagerPanel/ModelListItem.tsx | 34 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index a8f52742d4..ae1778101d 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -399,6 +399,8 @@ "deleteModel": "Delete Model", "deleteConfig": "Delete Config", "deleteMsg1": "Are you sure you want to delete this model from InvokeAI?", + "modelDeleted": "Model Deleted", + "modelDeleteFailed": "Failed to delete model", "deleteMsg2": "This WILL delete the model from disk if it is in the InvokeAI root folder. If you are using a custom location, then the model WILL NOT be deleted from disk.", "formMessageDiffusersModelLocation": "Diffusers Model Location", "formMessageDiffusersModelLocationDesc": "Please enter at least one.", @@ -408,7 +410,7 @@ "convertToDiffusers": "Convert To Diffusers", "convertToDiffusersHelpText1": "This model will be converted to the 🧨 Diffusers format.", "convertToDiffusersHelpText2": "This process will replace your Model Manager entry with the Diffusers version of the same model.", - "convertToDiffusersHelpText3": "Your checkpoint file on the disk will NOT be deleted or modified in anyway. You can add your checkpoint to the Model Manager again if you want to.", + "convertToDiffusersHelpText3": "Your checkpoint file on disk WILL be deleted if it is in InvokeAI root folder. If it is in a custom location, then it WILL NOT be deleted.", "convertToDiffusersHelpText4": "This is a one time process only. It might take around 30s-60s depending on the specifications of your computer.", "convertToDiffusersHelpText5": "Please make sure you have enough disk space. Models generally vary between 2GB-7GB in size.", "convertToDiffusersHelpText6": "Do you wish to convert this model?", diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelListItem.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelListItem.tsx index aaa2e1bce4..3d2126dce7 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelListItem.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelListItem.tsx @@ -1,10 +1,12 @@ import { DeleteIcon } from '@chakra-ui/icons'; import { Flex, Text, Tooltip } from '@chakra-ui/react'; -import { useAppSelector } from 'app/store/storeHooks'; +import { makeToast } from 'app/components/Toaster'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIAlertDialog from 'common/components/IAIAlertDialog'; import IAIButton from 'common/components/IAIButton'; import IAIIconButton from 'common/components/IAIIconButton'; import { selectIsBusy } from 'features/system/store/systemSelectors'; +import { addToast } from 'features/system/store/systemSlice'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -21,6 +23,7 @@ type ModelListItemProps = { export default function ModelListItem(props: ModelListItemProps) { const isBusy = useAppSelector(selectIsBusy); const { t } = useTranslation(); + const dispatch = useAppDispatch(); const [deleteMainModel] = useDeleteMainModelsMutation(); const { model, isSelected, setSelectedModelId } = props; @@ -30,9 +33,34 @@ export default function ModelListItem(props: ModelListItemProps) { }, [model.id, setSelectedModelId]); const handleModelDelete = useCallback(() => { - deleteMainModel(model); + deleteMainModel(model) + .unwrap() + .then((_) => { + dispatch( + addToast( + makeToast({ + title: `${t('modelManager.modelDeleted')}: ${model.model_name}`, + status: 'success', + }) + ) + ); + }) + .catch((error) => { + if (error) { + dispatch( + addToast( + makeToast({ + title: `${t('modelManager.modelDeleteFailed')}: ${ + model.model_name + }`, + status: 'success', + }) + ) + ); + } + }); setSelectedModelId(undefined); - }, [deleteMainModel, model, setSelectedModelId]); + }, [deleteMainModel, model, setSelectedModelId, dispatch, t]); return ( From 9769b4866115ea3a498cf2b0cc7d3a762b825732 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sat, 15 Jul 2023 19:17:16 +1200 Subject: [PATCH 04/34] feat: Add Custom location support for model conversion --- invokeai/app/api/routers/models.py | 241 +++++++++--------- invokeai/frontend/web/public/locales/en.json | 2 + .../ModelManagerPanel/ModelConvert.tsx | 79 ++++-- .../web/src/services/api/endpoints/models.ts | 5 +- .../frontend/web/src/services/api/schema.d.ts | 17 +- .../frontend/web/src/services/api/types.d.ts | 2 + 6 files changed, 204 insertions(+), 142 deletions(-) diff --git a/invokeai/app/api/routers/models.py b/invokeai/app/api/routers/models.py index c298114cbc..12b7f991f4 100644 --- a/invokeai/app/api/routers/models.py +++ b/invokeai/app/api/routers/models.py @@ -2,7 +2,7 @@ import pathlib -from typing import Literal, List, Optional, Union +from typing import List, Literal, Optional, Union from fastapi import Body, Path, Query, Response from fastapi.routing import APIRouter @@ -10,11 +10,10 @@ from pydantic import BaseModel, parse_obj_as from starlette.exceptions import HTTPException from invokeai.backend import BaseModelType, ModelType -from invokeai.backend.model_management.models import ( - OPENAPI_MODEL_CONFIGS, - SchedulerPredictionType, -) from invokeai.backend.model_management import MergeInterpolationMethod +from invokeai.backend.model_management.models import (OPENAPI_MODEL_CONFIGS, + SchedulerPredictionType) + from ..dependencies import ApiDependencies models_router = APIRouter(prefix="/v1/models", tags=["models"]) @@ -25,32 +24,37 @@ ConvertModelResponse = Union[tuple(OPENAPI_MODEL_CONFIGS)] MergeModelResponse = Union[tuple(OPENAPI_MODEL_CONFIGS)] ImportModelAttributes = Union[tuple(OPENAPI_MODEL_CONFIGS)] + class ModelsList(BaseModel): models: list[Union[tuple(OPENAPI_MODEL_CONFIGS)]] + @models_router.get( "/", operation_id="list_models", - responses={200: {"model": ModelsList }}, + responses={200: {"model": ModelsList}}, ) async def list_models( base_model: Optional[BaseModelType] = Query(default=None, description="Base model"), model_type: Optional[ModelType] = Query(default=None, description="The type of model to get"), ) -> ModelsList: """Gets a list of models""" - models_raw = ApiDependencies.invoker.services.model_manager.list_models(base_model, model_type) - models = parse_obj_as(ModelsList, { "models": models_raw }) + models_raw = ApiDependencies.invoker.services.model_manager.list_models( + base_model, + model_type) + models = parse_obj_as(ModelsList, {"models": models_raw}) return models + @models_router.patch( "/{base_model}/{model_type}/{model_name}", operation_id="update_model", - responses={200: {"description" : "The model was updated successfully"}, - 404: {"description" : "The model could not be found"}, - 400: {"description" : "Bad request"} + responses={200: {"description": "The model was updated successfully"}, + 404: {"description": "The model could not be found"}, + 400: {"description": "Bad request"} }, - status_code = 200, - response_model = UpdateModelResponse, + status_code=200, + response_model=UpdateModelResponse, ) async def update_model( base_model: BaseModelType = Path(description="Base model"), @@ -79,40 +83,41 @@ async def update_model( return model_response + @models_router.post( "/import", operation_id="import_model", - responses= { - 201: {"description" : "The model imported successfully"}, - 404: {"description" : "The model could not be found"}, - 424: {"description" : "The model appeared to import successfully, but could not be found in the model manager"}, - 409: {"description" : "There is already a model corresponding to this path or repo_id"}, + responses={ + 201: {"description": "The model imported successfully"}, + 404: {"description": "The model could not be found"}, + 424: {"description": "The model appeared to import successfully, but could not be found in the model manager"}, + 409: {"description": "There is already a model corresponding to this path or repo_id"}, }, status_code=201, response_model=ImportModelResponse ) async def import_model( location: str = Body(description="A model path, repo_id or URL to import"), - prediction_type: Optional[Literal['v_prediction','epsilon','sample']] = \ - Body(description='Prediction type for SDv2 checkpoint files', default="v_prediction"), + prediction_type: Optional[Literal['v_prediction', 'epsilon', 'sample']] = + Body(description='Prediction type for SDv2 checkpoint files', default="v_prediction"), ) -> ImportModelResponse: """ Add a model using its local path, repo_id, or remote URL. Model characteristics will be probed and configured automatically """ - + items_to_import = {location} - prediction_types = { x.value: x for x in SchedulerPredictionType } + prediction_types = {x.value: x for x in SchedulerPredictionType} logger = ApiDependencies.invoker.services.logger try: installed_models = ApiDependencies.invoker.services.model_manager.heuristic_import( - items_to_import = items_to_import, - prediction_type_helper = lambda x: prediction_types.get(prediction_type) + items_to_import=items_to_import, + prediction_type_helper=lambda x: prediction_types.get(prediction_type) ) info = installed_models.get(location) if not info: logger.error("Import failed") raise HTTPException(status_code=424) - + logger.info(f'Successfully imported {location}, got {info}') model_raw = ApiDependencies.invoker.services.model_manager.list_model( model_name=info.name, @@ -120,22 +125,23 @@ async def import_model( model_type=info.model_type ) return parse_obj_as(ImportModelResponse, model_raw) - + except KeyError as e: logger.error(str(e)) raise HTTPException(status_code=404, detail=str(e)) except ValueError as e: logger.error(str(e)) raise HTTPException(status_code=409, detail=str(e)) - + + @models_router.post( "/add", operation_id="add_model", - responses= { - 201: {"description" : "The model added successfully"}, - 404: {"description" : "The model could not be found"}, - 424: {"description" : "The model appeared to add successfully, but could not be found in the model manager"}, - 409: {"description" : "There is already a model corresponding to this path or repo_id"}, + responses={ + 201: {"description": "The model added successfully"}, + 404: {"description": "The model could not be found"}, + 424: {"description": "The model appeared to add successfully, but could not be found in the model manager"}, + 409: {"description": "There is already a model corresponding to this path or repo_id"}, }, status_code=201, response_model=ImportModelResponse @@ -144,7 +150,7 @@ async def add_model( info: Union[tuple(OPENAPI_MODEL_CONFIGS)] = Body(description="Model configuration"), ) -> ImportModelResponse: """ Add a model using the configuration information appropriate for its type. Only local models can be added by path""" - + logger = ApiDependencies.invoker.services.logger try: @@ -152,7 +158,7 @@ async def add_model( info.model_name, info.base_model, info.model_type, - model_attributes = info.dict() + model_attributes=info.dict() ) logger.info(f'Successfully added {info.model_name}') model_raw = ApiDependencies.invoker.services.model_manager.list_model( @@ -168,13 +174,14 @@ async def add_model( logger.error(str(e)) raise HTTPException(status_code=409, detail=str(e)) + @models_router.post( "/rename/{base_model}/{model_type}/{model_name}", operation_id="rename_model", - responses= { - 201: {"description" : "The model was renamed successfully"}, - 404: {"description" : "The model could not be found"}, - 409: {"description" : "There is already a model corresponding to the new name"}, + responses={ + 201: {"description": "The model was renamed successfully"}, + 404: {"description": "The model could not be found"}, + 409: {"description": "There is already a model corresponding to the new name"}, }, status_code=201, response_model=ImportModelResponse @@ -187,16 +194,16 @@ async def rename_model( new_base: Optional[BaseModelType] = Query(description="new model base", default=None), ) -> ImportModelResponse: """ Rename a model""" - + logger = ApiDependencies.invoker.services.logger try: result = ApiDependencies.invoker.services.model_manager.rename_model( - base_model = base_model, - model_type = model_type, - model_name = model_name, - new_name = new_name, - new_base = new_base, + base_model=base_model, + model_type=model_type, + model_name=model_name, + new_name=new_name, + new_base=new_base, ) logger.debug(result) logger.info(f'Successfully renamed {model_name}=>{new_name}') @@ -212,16 +219,17 @@ async def rename_model( except ValueError as e: logger.error(str(e)) raise HTTPException(status_code=409, detail=str(e)) - + + @models_router.delete( "/{base_model}/{model_type}/{model_name}", operation_id="del_model", responses={ - 204: { "description": "Model deleted successfully" }, - 404: { "description": "Model not found" } + 204: {"description": "Model deleted successfully"}, + 404: {"description": "Model not found"} }, - status_code = 204, - response_model = None, + status_code=204, + response_model=None, ) async def delete_model( base_model: BaseModelType = Path(description="Base model"), @@ -230,142 +238,145 @@ async def delete_model( ) -> Response: """Delete Model""" logger = ApiDependencies.invoker.services.logger - + try: - ApiDependencies.invoker.services.model_manager.del_model(model_name, - base_model = base_model, - model_type = model_type - ) + ApiDependencies.invoker.services.model_manager.del_model( + model_name, base_model=base_model, model_type=model_type) logger.info(f"Deleted model: {model_name}") return Response(status_code=204) except KeyError: logger.error(f"Model not found: {model_name}") - raise HTTPException(status_code=404, detail=f"Model '{model_name}' not found") + raise HTTPException( + status_code=404, detail=f"Model '{model_name}' not found") + @models_router.put( "/convert/{base_model}/{model_type}/{model_name}", operation_id="convert_model", responses={ - 200: { "description": "Model converted successfully" }, - 400: {"description" : "Bad request" }, - 404: { "description": "Model not found" }, + 200: {"description": "Model converted successfully"}, + 400: {"description": "Bad request"}, + 404: {"description": "Model not found"}, }, - status_code = 200, - response_model = ConvertModelResponse, + status_code=200, + response_model=ConvertModelResponse, ) async def convert_model( base_model: BaseModelType = Path(description="Base model"), model_type: ModelType = Path(description="The type of model"), model_name: str = Path(description="model name"), - convert_dest_directory: Optional[str] = Query(default=None, description="Save the converted model to the designated directory"), + convert_dest_directory: Optional[str] = Body(description="Save the converted model to the designated directory", default=None, embed=True) ) -> ConvertModelResponse: """Convert a checkpoint model into a diffusers model, optionally saving to the indicated destination directory, or `models` if none.""" logger = ApiDependencies.invoker.services.logger try: logger.info(f"Converting model: {model_name}") - dest = pathlib.Path(convert_dest_directory) if convert_dest_directory else None - ApiDependencies.invoker.services.model_manager.convert_model(model_name, - base_model = base_model, - model_type = model_type, - convert_dest_directory = dest, - ) - model_raw = ApiDependencies.invoker.services.model_manager.list_model(model_name, - base_model = base_model, - model_type = model_type) + dest = pathlib.Path( + convert_dest_directory) if convert_dest_directory else None + ApiDependencies.invoker.services.model_manager.convert_model( + model_name, base_model=base_model, model_type=model_type, + convert_dest_directory=dest,) + model_raw = ApiDependencies.invoker.services.model_manager.list_model( + model_name, base_model=base_model, model_type=model_type) response = parse_obj_as(ConvertModelResponse, model_raw) except KeyError: - raise HTTPException(status_code=404, detail=f"Model '{model_name}' not found") + raise HTTPException( + status_code=404, detail=f"Model '{model_name}' not found") except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) return response + @models_router.get( "/search", operation_id="search_for_models", responses={ - 200: { "description": "Directory searched successfully" }, - 404: { "description": "Invalid directory path" }, + 200: {"description": "Directory searched successfully"}, + 404: {"description": "Invalid directory path"}, }, - status_code = 200, - response_model = List[pathlib.Path] + status_code=200, + response_model=List[pathlib.Path] ) async def search_for_models( search_path: pathlib.Path = Query(description="Directory path to search for models") -)->List[pathlib.Path]: +) -> List[pathlib.Path]: if not search_path.is_dir(): - raise HTTPException(status_code=404, detail=f"The search path '{search_path}' does not exist or is not directory") - return ApiDependencies.invoker.services.model_manager.search_for_models([search_path]) + raise HTTPException( + status_code=404, + detail=f"The search path '{search_path}' does not exist or is not directory") + return ApiDependencies.invoker.services.model_manager.search_for_models([ + search_path]) + @models_router.get( "/ckpt_confs", operation_id="list_ckpt_configs", responses={ - 200: { "description" : "paths retrieved successfully" }, + 200: {"description": "paths retrieved successfully"}, }, - status_code = 200, - response_model = List[pathlib.Path] + status_code=200, + response_model=List[pathlib.Path] ) async def list_ckpt_configs( -)->List[pathlib.Path]: +) -> List[pathlib.Path]: """Return a list of the legacy checkpoint configuration files stored in `ROOT/configs/stable-diffusion`, relative to ROOT.""" return ApiDependencies.invoker.services.model_manager.list_checkpoint_configs() - - + + @models_router.get( "/sync", operation_id="sync_to_config", responses={ - 201: { "description": "synchronization successful" }, + 201: {"description": "synchronization successful"}, }, - status_code = 201, - response_model = None + status_code=201, + response_model=None ) async def sync_to_config( -)->None: +) -> None: """Call after making changes to models.yaml, autoimport directories or models directory to synchronize in-memory data structures with disk data structures.""" return ApiDependencies.invoker.services.model_manager.sync_to_config() - + + @models_router.put( "/merge/{base_model}", operation_id="merge_models", responses={ - 200: { "description": "Model converted successfully" }, - 400: { "description": "Incompatible models" }, - 404: { "description": "One or more models not found" }, + 200: {"description": "Model converted successfully"}, + 400: {"description": "Incompatible models"}, + 404: {"description": "One or more models not found"}, }, - status_code = 200, - response_model = MergeModelResponse, + status_code=200, + response_model=MergeModelResponse, ) async def merge_models( - base_model: BaseModelType = Path(description="Base model"), - model_names: List[str] = Body(description="model name", min_items=2, max_items=3), - merged_model_name: Optional[str] = Body(description="Name of destination model"), - alpha: Optional[float] = Body(description="Alpha weighting strength to apply to 2d and 3d models", default=0.5), + base_model: BaseModelType = Path(description="Base model"), + model_names: List[str] = Body(description="model name", min_items=2, max_items=3), + merged_model_name: Optional[str] = Body(description="Name of destination model"), + alpha: Optional[float] = Body(description="Alpha weighting strength to apply to 2d and 3d models", default=0.5), interp: Optional[MergeInterpolationMethod] = Body(description="Interpolation method"), - force: Optional[bool] = Body(description="Force merging of models created with different versions of diffusers", default=False), - merge_dest_directory: Optional[str] = Body(description="Save the merged model to the designated directory (with 'merged_model_name' appended)", default=None) + force: Optional[bool] = Body(description="Force merging of models created with different versions of diffusers", default=False), + merge_dest_directory: Optional[str] = Body(description="Save the merged model to the designated directory (with 'merged_model_name' appended)", default=None) ) -> MergeModelResponse: """Convert a checkpoint model into a diffusers model""" logger = ApiDependencies.invoker.services.logger try: - logger.info(f"Merging models: {model_names} into {merge_dest_directory or ''}/{merged_model_name}") - dest = pathlib.Path(merge_dest_directory) if merge_dest_directory else None - result = ApiDependencies.invoker.services.model_manager.merge_models(model_names, - base_model, - merged_model_name=merged_model_name or "+".join(model_names), - alpha=alpha, - interp=interp, - force=force, - merge_dest_directory = dest - ) - model_raw = ApiDependencies.invoker.services.model_manager.list_model(result.name, - base_model = base_model, - model_type = ModelType.Main, - ) + logger.info( + f"Merging models: {model_names} into {merge_dest_directory or ''}/{merged_model_name}") + dest = pathlib.Path( + merge_dest_directory) if merge_dest_directory else None + result = ApiDependencies.invoker.services.model_manager.merge_models( + model_names, base_model, + merged_model_name=merged_model_name or "+".join(model_names), + alpha=alpha, interp=interp, force=force, merge_dest_directory=dest) + model_raw = ApiDependencies.invoker.services.model_manager.list_model( + result.name, base_model=base_model, model_type=ModelType.Main, ) response = parse_obj_as(ConvertModelResponse, model_raw) except KeyError: - raise HTTPException(status_code=404, detail=f"One or more of the models '{model_names}' not found") + raise HTTPException( + status_code=404, + detail=f"One or more of the models '{model_names}' not found") except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) return response diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index ae1778101d..36cf1d7af6 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -415,6 +415,8 @@ "convertToDiffusersHelpText5": "Please make sure you have enough disk space. Models generally vary between 2GB-7GB in size.", "convertToDiffusersHelpText6": "Do you wish to convert this model?", "convertToDiffusersSaveLocation": "Save Location", + "noCustomLocationProvided": "No Custom Location Provided", + "convertingModelBegin": "Converting Model. Please wait.", "v1": "v1", "v2_base": "v2 (512px)", "v2_768": "v2 (768px)", diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx index 922fdacee7..9c7130f2ad 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx @@ -1,9 +1,18 @@ -import { Flex, ListItem, Text, UnorderedList } from '@chakra-ui/react'; -// import { convertToDiffusers } from 'app/socketio/actions'; +import { + Flex, + ListItem, + Radio, + RadioGroup, + Text, + Tooltip, + UnorderedList, +} from '@chakra-ui/react'; import { makeToast } from 'app/components/Toaster'; +// import { convertToDiffusers } from 'app/socketio/actions'; import { useAppDispatch } from 'app/store/storeHooks'; import IAIAlertDialog from 'common/components/IAIAlertDialog'; import IAIButton from 'common/components/IAIButton'; +import IAIInput from 'common/components/IAIInput'; import { addToast } from 'features/system/store/systemSlice'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -15,6 +24,8 @@ interface ModelConvertProps { model: CheckpointModelConfig; } +type SaveLocation = 'InvokeAIRoot' | 'Custom'; + export default function ModelConvert(props: ModelConvertProps) { const { model } = props; @@ -23,22 +34,51 @@ export default function ModelConvert(props: ModelConvertProps) { const [convertModel, { isLoading }] = useConvertMainModelsMutation(); - const [saveLocation, setSaveLocation] = useState('same'); + const [saveLocation, setSaveLocation] = + useState('InvokeAIRoot'); const [customSaveLocation, setCustomSaveLocation] = useState(''); useEffect(() => { - setSaveLocation('same'); + setSaveLocation('InvokeAIRoot'); }, [model]); const modelConvertCancelHandler = () => { - setSaveLocation('same'); + setSaveLocation('InvokeAIRoot'); }; const modelConvertHandler = () => { const responseBody = { base_model: model.base_model, model_name: model.model_name, + body: { + convert_dest_directory: + saveLocation === 'Custom' ? customSaveLocation : undefined, + }, }; + + if (saveLocation === 'Custom' && customSaveLocation === '') { + dispatch( + addToast( + makeToast({ + title: t('modelManager.noCustomLocationProvided'), + status: 'error', + }) + ) + ); + return; + } + + dispatch( + addToast( + makeToast({ + title: `${t('modelManager.convertingModelBegin')}: ${ + model.model_name + }`, + status: 'success', + }) + ) + ); + convertModel(responseBody) .unwrap() .then((_) => { @@ -94,35 +134,30 @@ export default function ModelConvert(props: ModelConvertProps) { {t('modelManager.convertToDiffusersHelpText6')} - {/* + {t('modelManager.convertToDiffusersSaveLocation')} - setSaveLocation(v)}> + setSaveLocation(v as SaveLocation)} + > - - - {t('modelManager.sameFolder')} - - - - + {t('modelManager.invokeRoot')} - - + {t('modelManager.custom')} - */} - - {/* {saveLocation === 'custom' && ( + + {saveLocation === 'Custom' && ( {t('modelManager.customSaveLocation')} @@ -130,13 +165,13 @@ export default function ModelConvert(props: ModelConvertProps) { { - if (e.target.value !== '') - setCustomSaveLocation(e.target.value); + setCustomSaveLocation(e.target.value); }} width="full" /> - )} */} + )} + ); } diff --git a/invokeai/frontend/web/src/services/api/endpoints/models.ts b/invokeai/frontend/web/src/services/api/endpoints/models.ts index c86ad91100..79e685313e 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/models.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/models.ts @@ -5,6 +5,7 @@ import { BaseModelType, CheckpointModelConfig, ControlNetModelConfig, + ConvertModelConfig, DiffusersModelConfig, LoRAModelConfig, MainModelConfig, @@ -62,6 +63,7 @@ type DeleteMainModelResponse = void; type ConvertMainModelArg = { base_model: BaseModelType; model_name: string; + body: ConvertModelConfig; }; type ConvertMainModelResponse = @@ -176,10 +178,11 @@ export const modelsApi = api.injectEndpoints({ ConvertMainModelResponse, ConvertMainModelArg >({ - query: ({ base_model, model_name }) => { + query: ({ base_model, model_name, body }) => { return { url: `models/convert/${base_model}/main/${model_name}`, method: 'PUT', + body: body, }; }, invalidatesTags: [{ type: 'MainModel', id: LIST_TAG }], diff --git a/invokeai/frontend/web/src/services/api/schema.d.ts b/invokeai/frontend/web/src/services/api/schema.d.ts index 2ae5109f4f..610e9fa05e 100644 --- a/invokeai/frontend/web/src/services/api/schema.d.ts +++ b/invokeai/frontend/web/src/services/api/schema.d.ts @@ -378,6 +378,14 @@ export type components = { */ image_count: number; }; + /** Body_convert_model */ + Body_convert_model: { + /** + * Convert Dest Directory + * @description Save the converted model to the designated directory + */ + convert_dest_directory?: string; + }; /** Body_create_board_image */ Body_create_board_image: { /** @@ -5200,10 +5208,6 @@ export type operations = { */ convert_model: { parameters: { - query?: { - /** @description Save the converted model to the designated directory */ - convert_dest_directory?: string; - }; path: { /** @description Base model */ base_model: components["schemas"]["BaseModelType"]; @@ -5213,6 +5217,11 @@ export type operations = { model_name: string; }; }; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_convert_model"]; + }; + }; responses: { /** @description Model converted successfully */ 200: { diff --git a/invokeai/frontend/web/src/services/api/types.d.ts b/invokeai/frontend/web/src/services/api/types.d.ts index fcbbd1a6a0..57258fb19b 100644 --- a/invokeai/frontend/web/src/services/api/types.d.ts +++ b/invokeai/frontend/web/src/services/api/types.d.ts @@ -55,7 +55,9 @@ export type AnyModelConfig = | ControlNetModelConfig | TextualInversionModelConfig | MainModelConfig; + export type MergeModelConfig = components['schemas']['Body_merge_models']; +export type ConvertModelConfig = components['schemas']['Body_convert_model']; // Graphs export type Graph = components['schemas']['Graph']; From 558c26d78ffc48c57a05e76138c476088ef6bac1 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sat, 15 Jul 2023 22:22:22 +1200 Subject: [PATCH 05/34] feat: Create Model Manager Store --- invokeai/frontend/web/src/app/store/store.ts | 3 +++ .../src/features/system/store/systemSlice.ts | 22 +++--------------- .../ModelManager/store/modelManagerSlice.ts | 23 +++++++++++++++++++ .../store/modelmanagerSelectors.tsx | 3 +++ 4 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelManagerSlice.ts create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelmanagerSelectors.tsx diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 2bafd21a74..4725a2f921 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -21,6 +21,7 @@ import generationReducer from 'features/parameters/store/generationSlice'; import postprocessingReducer from 'features/parameters/store/postprocessingSlice'; import configReducer from 'features/system/store/configSlice'; import systemReducer from 'features/system/store/systemSlice'; +import modelmanagerReducer from 'features/ui/components/tabs/ModelManager/store/modelManagerSlice'; import hotkeysReducer from 'features/ui/store/hotkeysSlice'; import uiReducer from 'features/ui/store/uiSlice'; @@ -49,6 +50,7 @@ const allReducers = { dynamicPrompts: dynamicPromptsReducer, imageDeletion: imageDeletionReducer, lora: loraReducer, + modelmanager: modelmanagerReducer, [api.reducerPath]: api.reducer, }; @@ -67,6 +69,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [ 'controlNet', 'dynamicPrompts', 'lora', + 'modelmanager', ]; export const store = configureStore({ diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 4d723378ba..875ae7a693 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -1,11 +1,10 @@ import { UseToastOptions } from '@chakra-ui/react'; import { PayloadAction, createSlice } from '@reduxjs/toolkit'; -import * as InvokeAI from 'app/types/invokeai'; import { InvokeLogLevel } from 'app/logging/useLogger'; import { userInvoked } from 'app/store/actions'; import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice'; -import { TFuncKey, t } from 'i18next'; +import { t } from 'i18next'; import { LogLevelName } from 'roarr'; import { imageUploaded } from 'services/api/thunks/image'; import { @@ -44,8 +43,6 @@ export interface SystemState { isCancelable: boolean; enableImageDebugging: boolean; toastQueue: UseToastOptions[]; - searchFolder: string | null; - foundModels: InvokeAI.FoundModel[] | null; /** * The current progress image */ @@ -79,7 +76,7 @@ export interface SystemState { */ consoleLogLevel: InvokeLogLevel; shouldLogToConsole: boolean; - statusTranslationKey: TFuncKey; + statusTranslationKey: any; /** * When a session is canceled, its ID is stored here until a new session is created. */ @@ -106,8 +103,6 @@ export const initialSystemState: SystemState = { isCancelable: true, enableImageDebugging: false, toastQueue: [], - searchFolder: null, - foundModels: null, progressImage: null, shouldAntialiasProgressImage: false, sessionId: null, @@ -132,7 +127,7 @@ export const systemSlice = createSlice({ setIsProcessing: (state, action: PayloadAction) => { state.isProcessing = action.payload; }, - setCurrentStatus: (state, action: PayloadAction) => { + setCurrentStatus: (state, action: any) => { state.statusTranslationKey = action.payload; }, setShouldConfirmOnDelete: (state, action: PayloadAction) => { @@ -153,15 +148,6 @@ export const systemSlice = createSlice({ clearToastQueue: (state) => { state.toastQueue = []; }, - setSearchFolder: (state, action: PayloadAction) => { - state.searchFolder = action.payload; - }, - setFoundModels: ( - state, - action: PayloadAction - ) => { - state.foundModels = action.payload; - }, /** * A cancel was scheduled */ @@ -426,8 +412,6 @@ export const { setEnableImageDebugging, addToast, clearToastQueue, - setSearchFolder, - setFoundModels, cancelScheduled, scheduledCancelAborted, cancelTypeChanged, diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelManagerSlice.ts b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelManagerSlice.ts new file mode 100644 index 0000000000..c71407824e --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelManagerSlice.ts @@ -0,0 +1,23 @@ +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; + +type ModelManagerState = { + searchFolder: string | null; +}; + +const initialModelManagerState: ModelManagerState = { + searchFolder: null, +}; + +export const modelManagerSlice = createSlice({ + name: 'modelmanager', + initialState: initialModelManagerState, + reducers: { + setSearchFolder: (state, action: PayloadAction) => { + state.searchFolder = action.payload; + }, + }, +}); + +export const { setSearchFolder } = modelManagerSlice.actions; + +export default modelManagerSlice.reducer; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelmanagerSelectors.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelmanagerSelectors.tsx new file mode 100644 index 0000000000..593282760a --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelmanagerSelectors.tsx @@ -0,0 +1,3 @@ +import { RootState } from 'app/store/store'; + +export const modelmanagerSelector = (state: RootState) => state.modelmanager; From 4a2f34f77fda055361827efdde3aeae7a0fb24c4 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sat, 15 Jul 2023 22:23:00 +1200 Subject: [PATCH 06/34] wip: Model Search Going to rework the whole thing. The old system is convoluted and too difficult to plug back. --- .../AddModelsPanel/FoundModelsList.tsx | 34 ++ .../AddModelsPanel/SearchFolderForm.tsx | 114 +++++ .../subpanels/AddModelsPanel/SearchModels.tsx | 429 +---------------- .../AddModelsPanel/SearchModelsOld.tsx | 430 ++++++++++++++++++ .../web/src/services/api/endpoints/models.ts | 17 +- .../frontend/web/src/services/api/schema.d.ts | 12 +- .../frontend/web/src/services/api/types.d.ts | 1 + 7 files changed, 609 insertions(+), 428 deletions(-) create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModelsOld.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx new file mode 100644 index 0000000000..af862d005d --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx @@ -0,0 +1,34 @@ +import { Flex } from '@chakra-ui/react'; +import { RootState } from 'app/store/store'; +import { useAppSelector } from 'app/store/storeHooks'; +import { useGetModelsInFolderQuery } from 'services/api/endpoints/models'; + +export default function FoundModelsList() { + const searchFolder = useAppSelector( + (state: RootState) => state.modelmanager.searchFolder + ); + + const { data: foundModels } = useGetModelsInFolderQuery({ + search_path: searchFolder ? searchFolder : '', + }); + + console.log(foundModels); + + const renderFoundModels = () => { + if (!searchFolder) return; + + if (!foundModels || foundModels.length === 0) { + return No Models Found; + } + + return ( + + {foundModels.map((model) => ( + {model} + ))} + + ); + }; + + return renderFoundModels(); +} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx new file mode 100644 index 0000000000..10d0f51665 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx @@ -0,0 +1,114 @@ +import { Flex, Text } from '@chakra-ui/react'; +import { useForm } from '@mantine/form'; +import { RootState } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIIconButton from 'common/components/IAIIconButton'; +import IAIInput from 'common/components/IAIInput'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FaSearch, FaTrash } from 'react-icons/fa'; +import { setSearchFolder } from '../../store/modelManagerSlice'; + +type SearchFolderForm = { + folder: string; +}; + +function SearchFolderForm() { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + + const searchFolder = useAppSelector( + (state: RootState) => state.modelmanager.searchFolder + ); + + const searchFolderForm = useForm({ + initialValues: { + folder: '', + }, + }); + + const searchFolderFormSubmitHandler = useCallback( + (values: SearchFolderForm) => { + dispatch(setSearchFolder(values.folder)); + }, + [dispatch] + ); + + return ( + + searchFolderFormSubmitHandler(values) + )} + style={{ width: '100%' }} + > + + + + Search Folder + + {!searchFolder ? ( + + ) : ( + + {searchFolder} + + )} + + + + } + fontSize={18} + size="sm" + type="submit" + /> + } + size="sm" + onClick={() => dispatch(setSearchFolder(null))} + isDisabled={!searchFolder} + colorScheme="red" + /> + + + + ); +} + +export default memo(SearchFolderForm); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx index 3381cb85d3..e3e48c7e6b 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx @@ -1,430 +1,17 @@ -import IAIButton from 'common/components/IAIButton'; -import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; -import IAIIconButton from 'common/components/IAIIconButton'; -import React from 'react'; - -import { - Badge, - Flex, - FormControl, - HStack, - Radio, - RadioGroup, - Spacer, - Text, -} from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { systemSelector } from 'features/system/store/systemSelectors'; +import { Flex } from '@chakra-ui/react'; +import { useAppDispatch } from 'app/store/storeHooks'; import { useTranslation } from 'react-i18next'; - -import { FaSearch, FaTrash } from 'react-icons/fa'; - -// import { addNewModel, searchForModels } from 'app/socketio/actions'; -import { - setFoundModels, - setSearchFolder, -} from 'features/system/store/systemSlice'; -import { setShouldShowExistingModelsInSearch } from 'features/ui/store/uiSlice'; - -import type { FoundModel } from 'app/types/invokeai'; -import type { RootState } from 'app/store/store'; -import IAIInput from 'common/components/IAIInput'; -import { Field, Formik } from 'formik'; -import { forEach, remove } from 'lodash-es'; -import type { ChangeEvent, ReactNode } from 'react'; -import IAIForm from 'common/components/IAIForm'; - -const existingModelsSelector = createSelector([systemSelector], (system) => { - const { model_list } = system; - - const existingModels: string[] = []; - - forEach(model_list, (value) => { - existingModels.push(value.weights); - }); - - return existingModels; -}); - -interface SearchModelEntry { - model: FoundModel; - modelsToAdd: string[]; - setModelsToAdd: React.Dispatch>; -} - -function SearchModelEntry({ - model, - modelsToAdd, - setModelsToAdd, -}: SearchModelEntry) { - const { t } = useTranslation(); - const existingModels = useAppSelector(existingModelsSelector); - - const foundModelsChangeHandler = (e: ChangeEvent) => { - if (!modelsToAdd.includes(e.target.value)) { - setModelsToAdd([...modelsToAdd, e.target.value]); - } else { - setModelsToAdd(remove(modelsToAdd, (v) => v !== e.target.value)); - } - }; - - return ( - - - {model.name}} - isChecked={modelsToAdd.includes(model.name)} - isDisabled={existingModels.includes(model.location)} - onChange={foundModelsChangeHandler} - > - {existingModels.includes(model.location) && ( - {t('modelManager.modelExists')} - )} - - - {model.location} - - - ); -} +import FoundModelsList from './FoundModelsList'; +import SearchFolderForm from './SearchFolderForm'; export default function SearchModels() { const dispatch = useAppDispatch(); - const { t } = useTranslation(); - const searchFolder = useAppSelector( - (state: RootState) => state.system.searchFolder - ); - - const foundModels = useAppSelector( - (state: RootState) => state.system.foundModels - ); - - const existingModels = useAppSelector(existingModelsSelector); - - const shouldShowExistingModelsInSearch = useAppSelector( - (state: RootState) => state.ui.shouldShowExistingModelsInSearch - ); - - const isProcessing = useAppSelector( - (state: RootState) => state.system.isProcessing - ); - - const [modelsToAdd, setModelsToAdd] = React.useState([]); - const [modelType, setModelType] = React.useState('v1'); - const [pathToConfig, setPathToConfig] = React.useState(''); - - const resetSearchModelHandler = () => { - dispatch(setSearchFolder(null)); - dispatch(setFoundModels(null)); - setModelsToAdd([]); - }; - - const findModelsHandler = (values: { checkpointFolder: string }) => { - dispatch(searchForModels(values.checkpointFolder)); - }; - - const addAllToSelected = () => { - setModelsToAdd([]); - if (foundModels) { - foundModels.forEach((model) => { - if (!existingModels.includes(model.location)) { - setModelsToAdd((currentModels) => { - return [...currentModels, model.name]; - }); - } - }); - } - }; - - const removeAllFromSelected = () => { - setModelsToAdd([]); - }; - - const addSelectedModels = () => { - const modelsToBeAdded = foundModels?.filter((foundModel) => - modelsToAdd.includes(foundModel.name) - ); - - const configFiles = { - v1: 'configs/stable-diffusion/v1-inference.yaml', - v2_base: 'configs/stable-diffusion/v2-inference-v.yaml', - v2_768: 'configs/stable-diffusion/v2-inference-v.yaml', - inpainting: 'configs/stable-diffusion/v1-inpainting-inference.yaml', - custom: pathToConfig, - }; - - modelsToBeAdded?.forEach((model) => { - const modelFormat = { - name: model.name, - description: '', - config: configFiles[modelType as keyof typeof configFiles], - weights: model.location, - vae: '', - width: 512, - height: 512, - default: false, - format: 'ckpt', - }; - dispatch(addNewModel(modelFormat)); - }); - setModelsToAdd([]); - }; - - const renderFoundModels = () => { - const newFoundModels: ReactNode[] = []; - const existingFoundModels: ReactNode[] = []; - - if (foundModels) { - foundModels.forEach((model, index) => { - if (existingModels.includes(model.location)) { - existingFoundModels.push( - - ); - } else { - newFoundModels.push( - - ); - } - }); - } - - return ( - - {newFoundModels} - {shouldShowExistingModelsInSearch && existingFoundModels} - - ); - }; - return ( - <> - {searchFolder ? ( - - - - {t('modelManager.checkpointFolder')} - - {searchFolder} - - - } - fontSize={18} - disabled={isProcessing} - onClick={() => dispatch(searchForModels(searchFolder))} - /> - } - onClick={resetSearchModelHandler} - /> - - ) : ( - { - findModelsHandler(values); - }} - > - {({ handleSubmit }) => ( - - - - - - } - aria-label={t('modelManager.findModels')} - tooltip={t('modelManager.findModels')} - type="submit" - disabled={isProcessing} - px={8} - > - {t('modelManager.findModels')} - - - - )} - - )} - {foundModels && ( - - - - {t('modelManager.modelsFound')}: {foundModels.length} - - - {t('modelManager.selected')}: {modelsToAdd.length} - - - - - - {t('modelManager.selectAll')} - - - {t('modelManager.deselectAll')} - - - dispatch( - setShouldShowExistingModelsInSearch( - !shouldShowExistingModelsInSearch - ) - ) - } - /> - - - - {t('modelManager.addSelected')} - - - - - - - {t('modelManager.pickModelType')} - - setModelType(v)} - defaultValue="v1" - name="model_type" - > - - - {t('modelManager.v1')} - - - {t('modelManager.v2_base')} - - - {t('modelManager.v2_768')} - - - {t('modelManager.inpainting')} - - - {t('modelManager.customConfig')} - - - - - - {modelType === 'custom' && ( - - - {t('modelManager.pathToCustomConfig')} - - { - if (e.target.value !== '') setPathToConfig(e.target.value); - }} - width="full" - /> - - )} - - - - {foundModels.length > 0 ? ( - renderFoundModels() - ) : ( - - {t('modelManager.noModelsFound')} - - )} - - - )} - > + + + + ); } diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModelsOld.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModelsOld.tsx new file mode 100644 index 0000000000..3381cb85d3 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModelsOld.tsx @@ -0,0 +1,430 @@ +import IAIButton from 'common/components/IAIButton'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; +import IAIIconButton from 'common/components/IAIIconButton'; +import React from 'react'; + +import { + Badge, + Flex, + FormControl, + HStack, + Radio, + RadioGroup, + Spacer, + Text, +} from '@chakra-ui/react'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { systemSelector } from 'features/system/store/systemSelectors'; +import { useTranslation } from 'react-i18next'; + +import { FaSearch, FaTrash } from 'react-icons/fa'; + +// import { addNewModel, searchForModels } from 'app/socketio/actions'; +import { + setFoundModels, + setSearchFolder, +} from 'features/system/store/systemSlice'; +import { setShouldShowExistingModelsInSearch } from 'features/ui/store/uiSlice'; + +import type { FoundModel } from 'app/types/invokeai'; +import type { RootState } from 'app/store/store'; +import IAIInput from 'common/components/IAIInput'; +import { Field, Formik } from 'formik'; +import { forEach, remove } from 'lodash-es'; +import type { ChangeEvent, ReactNode } from 'react'; +import IAIForm from 'common/components/IAIForm'; + +const existingModelsSelector = createSelector([systemSelector], (system) => { + const { model_list } = system; + + const existingModels: string[] = []; + + forEach(model_list, (value) => { + existingModels.push(value.weights); + }); + + return existingModels; +}); + +interface SearchModelEntry { + model: FoundModel; + modelsToAdd: string[]; + setModelsToAdd: React.Dispatch>; +} + +function SearchModelEntry({ + model, + modelsToAdd, + setModelsToAdd, +}: SearchModelEntry) { + const { t } = useTranslation(); + const existingModels = useAppSelector(existingModelsSelector); + + const foundModelsChangeHandler = (e: ChangeEvent) => { + if (!modelsToAdd.includes(e.target.value)) { + setModelsToAdd([...modelsToAdd, e.target.value]); + } else { + setModelsToAdd(remove(modelsToAdd, (v) => v !== e.target.value)); + } + }; + + return ( + + + {model.name}} + isChecked={modelsToAdd.includes(model.name)} + isDisabled={existingModels.includes(model.location)} + onChange={foundModelsChangeHandler} + > + {existingModels.includes(model.location) && ( + {t('modelManager.modelExists')} + )} + + + {model.location} + + + ); +} + +export default function SearchModels() { + const dispatch = useAppDispatch(); + + const { t } = useTranslation(); + + const searchFolder = useAppSelector( + (state: RootState) => state.system.searchFolder + ); + + const foundModels = useAppSelector( + (state: RootState) => state.system.foundModels + ); + + const existingModels = useAppSelector(existingModelsSelector); + + const shouldShowExistingModelsInSearch = useAppSelector( + (state: RootState) => state.ui.shouldShowExistingModelsInSearch + ); + + const isProcessing = useAppSelector( + (state: RootState) => state.system.isProcessing + ); + + const [modelsToAdd, setModelsToAdd] = React.useState([]); + const [modelType, setModelType] = React.useState('v1'); + const [pathToConfig, setPathToConfig] = React.useState(''); + + const resetSearchModelHandler = () => { + dispatch(setSearchFolder(null)); + dispatch(setFoundModels(null)); + setModelsToAdd([]); + }; + + const findModelsHandler = (values: { checkpointFolder: string }) => { + dispatch(searchForModels(values.checkpointFolder)); + }; + + const addAllToSelected = () => { + setModelsToAdd([]); + if (foundModels) { + foundModels.forEach((model) => { + if (!existingModels.includes(model.location)) { + setModelsToAdd((currentModels) => { + return [...currentModels, model.name]; + }); + } + }); + } + }; + + const removeAllFromSelected = () => { + setModelsToAdd([]); + }; + + const addSelectedModels = () => { + const modelsToBeAdded = foundModels?.filter((foundModel) => + modelsToAdd.includes(foundModel.name) + ); + + const configFiles = { + v1: 'configs/stable-diffusion/v1-inference.yaml', + v2_base: 'configs/stable-diffusion/v2-inference-v.yaml', + v2_768: 'configs/stable-diffusion/v2-inference-v.yaml', + inpainting: 'configs/stable-diffusion/v1-inpainting-inference.yaml', + custom: pathToConfig, + }; + + modelsToBeAdded?.forEach((model) => { + const modelFormat = { + name: model.name, + description: '', + config: configFiles[modelType as keyof typeof configFiles], + weights: model.location, + vae: '', + width: 512, + height: 512, + default: false, + format: 'ckpt', + }; + dispatch(addNewModel(modelFormat)); + }); + setModelsToAdd([]); + }; + + const renderFoundModels = () => { + const newFoundModels: ReactNode[] = []; + const existingFoundModels: ReactNode[] = []; + + if (foundModels) { + foundModels.forEach((model, index) => { + if (existingModels.includes(model.location)) { + existingFoundModels.push( + + ); + } else { + newFoundModels.push( + + ); + } + }); + } + + return ( + + {newFoundModels} + {shouldShowExistingModelsInSearch && existingFoundModels} + + ); + }; + + return ( + <> + {searchFolder ? ( + + + + {t('modelManager.checkpointFolder')} + + {searchFolder} + + + } + fontSize={18} + disabled={isProcessing} + onClick={() => dispatch(searchForModels(searchFolder))} + /> + } + onClick={resetSearchModelHandler} + /> + + ) : ( + { + findModelsHandler(values); + }} + > + {({ handleSubmit }) => ( + + + + + + } + aria-label={t('modelManager.findModels')} + tooltip={t('modelManager.findModels')} + type="submit" + disabled={isProcessing} + px={8} + > + {t('modelManager.findModels')} + + + + )} + + )} + {foundModels && ( + + + + {t('modelManager.modelsFound')}: {foundModels.length} + + + {t('modelManager.selected')}: {modelsToAdd.length} + + + + + + {t('modelManager.selectAll')} + + + {t('modelManager.deselectAll')} + + + dispatch( + setShouldShowExistingModelsInSearch( + !shouldShowExistingModelsInSearch + ) + ) + } + /> + + + + {t('modelManager.addSelected')} + + + + + + + {t('modelManager.pickModelType')} + + setModelType(v)} + defaultValue="v1" + name="model_type" + > + + + {t('modelManager.v1')} + + + {t('modelManager.v2_base')} + + + {t('modelManager.v2_768')} + + + {t('modelManager.inpainting')} + + + {t('modelManager.customConfig')} + + + + + + {modelType === 'custom' && ( + + + {t('modelManager.pathToCustomConfig')} + + { + if (e.target.value !== '') setPathToConfig(e.target.value); + }} + width="full" + /> + + )} + + + + {foundModels.length > 0 ? ( + renderFoundModels() + ) : ( + + {t('modelManager.noModelsFound')} + + )} + + + )} + > + ); +} diff --git a/invokeai/frontend/web/src/services/api/endpoints/models.ts b/invokeai/frontend/web/src/services/api/endpoints/models.ts index 79e685313e..a838a82f46 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/models.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/models.ts @@ -14,8 +14,9 @@ import { VaeModelConfig, } from 'services/api/types'; +import queryString from 'query-string'; import { ApiFullTagDescription, LIST_TAG, api } from '..'; -import { paths } from '../schema'; +import { operations, paths } from '../schema'; export type DiffusersModelConfigEntity = DiffusersModelConfig & { id: string }; export type CheckpointModelConfigEntity = CheckpointModelConfig & { @@ -77,6 +78,11 @@ type MergeMainModelArg = { type MergeMainModelResponse = paths['/api/v1/models/merge/{base_model}']['put']['responses']['200']['content']['application/json']; +type SearchFolderResponse = + paths['/api/v1/models/search']['get']['responses']['200']['content']['application/json']; + +type SearchFolderArg = operations['search_for_models']['parameters']['query']; + const mainModelsAdapter = createEntityAdapter({ sortComparer: (a, b) => a.model_name.localeCompare(b.model_name), }); @@ -331,6 +337,14 @@ export const modelsApi = api.injectEndpoints({ ); }, }), + getModelsInFolder: build.query({ + query: (arg) => { + const folderQueryStr = queryString.stringify(arg, {}); + return { + url: `/models/search?${folderQueryStr}`, + }; + }, + }), }), }); @@ -344,4 +358,5 @@ export const { useDeleteMainModelsMutation, useConvertMainModelsMutation, useMergeMainModelsMutation, + useGetModelsInFolderQuery, } = modelsApi; diff --git a/invokeai/frontend/web/src/services/api/schema.d.ts b/invokeai/frontend/web/src/services/api/schema.d.ts index 610e9fa05e..892ed289c1 100644 --- a/invokeai/frontend/web/src/services/api/schema.d.ts +++ b/invokeai/frontend/web/src/services/api/schema.d.ts @@ -4655,18 +4655,18 @@ export type components = { */ image?: components["schemas"]["ImageField"]; }; - /** - * StableDiffusion1ModelFormat - * @description An enumeration. - * @enum {string} - */ - StableDiffusion1ModelFormat: "checkpoint" | "diffusers"; /** * StableDiffusion2ModelFormat * @description An enumeration. * @enum {string} */ StableDiffusion2ModelFormat: "checkpoint" | "diffusers"; + /** + * StableDiffusion1ModelFormat + * @description An enumeration. + * @enum {string} + */ + StableDiffusion1ModelFormat: "checkpoint" | "diffusers"; }; responses: never; parameters: never; diff --git a/invokeai/frontend/web/src/services/api/types.d.ts b/invokeai/frontend/web/src/services/api/types.d.ts index 57258fb19b..c945b7de78 100644 --- a/invokeai/frontend/web/src/services/api/types.d.ts +++ b/invokeai/frontend/web/src/services/api/types.d.ts @@ -58,6 +58,7 @@ export type AnyModelConfig = export type MergeModelConfig = components['schemas']['Body_merge_models']; export type ConvertModelConfig = components['schemas']['Body_convert_model']; +export type SearchFolderConfig = components['schemas']; // Graphs export type Graph = components['schemas']['Graph']; From e1c0ca1ab2663e518d9b91c9013c510af5cdd2ce Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 16 Jul 2023 01:36:00 +1200 Subject: [PATCH 07/34] feat: Add Auto Import Model --- invokeai/frontend/web/public/locales/en.json | 3 +- .../tabs/ModelManager/ModelManagerTab.tsx | 8 +- .../ModelManager/subpanels/AddModelsPanel.tsx | 42 ++- .../AddModelsPanel/AddDiffusersModel.tsx | 259 ------------------ .../subpanels/AddModelsPanel/AddModels.tsx | 110 ++++++++ .../AddModelsPanel/FoundModelsList.tsx | 6 +- ...{AddCheckpointModel.tsx => ScanModels.tsx} | 2 +- .../subpanels/AddModelsPanel/SearchModels.tsx | 4 +- .../web/src/features/ui/store/uiTypes.ts | 3 - .../web/src/services/api/endpoints/models.ts | 22 ++ .../frontend/web/src/services/api/types.d.ts | 2 +- 11 files changed, 167 insertions(+), 294 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddDiffusersModel.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx rename invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/{AddCheckpointModel.tsx => ScanModels.tsx} (99%) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 36cf1d7af6..ed2899cfcb 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -454,7 +454,8 @@ "none": "none", "addDifference": "Add Difference", "pickModelType": "Pick Model Type", - "selectModel": "Select Model" + "selectModel": "Select Model", + "importModels": "Import Models" }, "parameters": { "general": "General", diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx index 9aced0dda8..6f9a836902 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx @@ -5,7 +5,7 @@ import AddModelsPanel from './subpanels/AddModelsPanel'; import MergeModelsPanel from './subpanels/MergeModelsPanel'; import ModelManagerPanel from './subpanels/ModelManagerPanel'; -type ModelManagerTabName = 'modelManager' | 'addModels' | 'mergeModels'; +type ModelManagerTabName = 'modelManager' | 'importModels' | 'mergeModels'; type ModelManagerTabInfo = { id: ModelManagerTabName; @@ -20,8 +20,8 @@ const tabs: ModelManagerTabInfo[] = [ content: , }, { - id: 'addModels', - label: i18n.t('modelManager.addModel'), + id: 'importModels', + label: i18n.t('modelManager.importModels'), content: , }, { @@ -46,7 +46,7 @@ const ModelManagerTab = () => { ))} - + {tabs.map((tab) => ( {tab.content} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx index 32fafe9ae1..d868dc6c80 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx @@ -1,43 +1,39 @@ -import { Divider, Flex, useColorMode } from '@chakra-ui/react'; -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { ButtonGroup, Divider, Flex } from '@chakra-ui/react'; import IAIButton from 'common/components/IAIButton'; -import { setAddNewModelUIOption } from 'features/ui/store/uiSlice'; +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import AddCheckpointModel from './AddModelsPanel/AddCheckpointModel'; -import AddDiffusersModel from './AddModelsPanel/AddDiffusersModel'; +import AddModels from './AddModelsPanel/AddModels'; +import ScanModels from './AddModelsPanel/ScanModels'; + +type AddModelTabs = 'add' | 'scan'; export default function AddModelsPanel() { - const addNewModelUIOption = useAppSelector( - (state: RootState) => state.ui.addNewModelUIOption - ); - - const { colorMode } = useColorMode(); - - const dispatch = useAppDispatch(); + const [addModelTab, setAddModelTab] = useState('add'); const { t } = useTranslation(); return ( - + dispatch(setAddNewModelUIOption('ckpt'))} - isChecked={addNewModelUIOption == 'ckpt'} + onClick={() => setAddModelTab('add')} + isChecked={addModelTab == 'add'} + size="sm" > - {t('modelManager.addCheckpointModel')} + {t('modelManager.addModel')} dispatch(setAddNewModelUIOption('diffusers'))} - isChecked={addNewModelUIOption == 'diffusers'} + onClick={() => setAddModelTab('scan')} + isChecked={addModelTab == 'scan'} + size="sm" > - {t('modelManager.addDiffuserModel')} + {t('modelManager.scanForModels')} - + - {addNewModelUIOption == 'ckpt' && } - {addNewModelUIOption == 'diffusers' && } + {addModelTab == 'add' && } + {addModelTab == 'scan' && } ); } diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddDiffusersModel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddDiffusersModel.tsx deleted file mode 100644 index c871a0ede5..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddDiffusersModel.tsx +++ /dev/null @@ -1,259 +0,0 @@ -import { - Flex, - FormControl, - FormErrorMessage, - FormHelperText, - FormLabel, - Text, - VStack, -} from '@chakra-ui/react'; -import { InvokeDiffusersModelConfigProps } from 'app/types/invokeai'; -// import { addNewModel } from 'app/socketio/actions'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIButton from 'common/components/IAIButton'; -import IAIInput from 'common/components/IAIInput'; -import { setAddNewModelUIOption } from 'features/ui/store/uiSlice'; -import { Field, Formik } from 'formik'; -import { useTranslation } from 'react-i18next'; - -import type { RootState } from 'app/store/store'; -import IAIForm from 'common/components/IAIForm'; -import { IAIFormItemWrapper } from 'common/components/IAIForms/IAIFormItemWrapper'; - -export default function AddDiffusersModel() { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - const isProcessing = useAppSelector( - (state: RootState) => state.system.isProcessing - ); - - function hasWhiteSpace(s: string) { - return /\s/.test(s); - } - - function baseValidation(value: string) { - let error; - if (hasWhiteSpace(value)) error = t('modelManager.cannotUseSpaces'); - return error; - } - - const addModelFormValues: InvokeDiffusersModelConfigProps = { - name: '', - description: '', - repo_id: '', - path: '', - format: 'diffusers', - default: false, - vae: { - repo_id: '', - path: '', - }, - }; - - const addModelFormSubmitHandler = ( - values: InvokeDiffusersModelConfigProps - ) => { - const diffusersModelToAdd = values; - - if (values.path === '') delete diffusersModelToAdd.path; - if (values.repo_id === '') delete diffusersModelToAdd.repo_id; - if (values.vae.path === '') delete diffusersModelToAdd.vae.path; - if (values.vae.repo_id === '') delete diffusersModelToAdd.vae.repo_id; - - dispatch(addNewModel(diffusersModelToAdd)); - dispatch(setAddNewModelUIOption(null)); - }; - - return ( - - - {({ handleSubmit, errors, touched }) => ( - - - - {/* Name */} - - - {t('modelManager.name')} - - - - {!!errors.name && touched.name ? ( - {errors.name} - ) : ( - - {t('modelManager.nameValidationMsg')} - - )} - - - - - - {/* Description */} - - - {t('modelManager.description')} - - - - {!!errors.description && touched.description ? ( - {errors.description} - ) : ( - - {t('modelManager.descriptionValidationMsg')} - - )} - - - - - - - {t('modelManager.formMessageDiffusersModelLocation')} - - - {t('modelManager.formMessageDiffusersModelLocationDesc')} - - - {/* Path */} - - - {t('modelManager.modelLocation')} - - - - {!!errors.path && touched.path ? ( - {errors.path} - ) : ( - - {t('modelManager.modelLocationValidationMsg')} - - )} - - - - {/* Repo ID */} - - - {t('modelManager.repo_id')} - - - - {!!errors.repo_id && touched.repo_id ? ( - {errors.repo_id} - ) : ( - - {t('modelManager.repoIDValidationMsg')} - - )} - - - - - - {/* VAE Path */} - - {t('modelManager.formMessageDiffusersVAELocation')} - - - {t('modelManager.formMessageDiffusersVAELocationDesc')} - - - - {t('modelManager.vaeLocation')} - - - - {!!errors.vae?.path && touched.vae?.path ? ( - {errors.vae?.path} - ) : ( - - {t('modelManager.vaeLocationValidationMsg')} - - )} - - - - {/* VAE Repo ID */} - - - {t('modelManager.vaeRepoID')} - - - - {!!errors.vae?.repo_id && touched.vae?.repo_id ? ( - {errors.vae?.repo_id} - ) : ( - - {t('modelManager.vaeRepoIDValidationMsg')} - - )} - - - - - - {t('modelManager.addModel')} - - - - )} - - - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx new file mode 100644 index 0000000000..c6517d38a7 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx @@ -0,0 +1,110 @@ +import { Flex } from '@chakra-ui/react'; +// import { addNewModel } from 'app/socketio/actions'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useTranslation } from 'react-i18next'; + +import { SelectItem } from '@mantine/core'; +import { useForm } from '@mantine/form'; +import { makeToast } from 'app/components/Toaster'; +import { RootState } from 'app/store/store'; +import IAIButton from 'common/components/IAIButton'; +import IAIMantineTextInput from 'common/components/IAIMantineInput'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; +import { addToast } from 'features/system/store/systemSlice'; +import { useImportMainModelsMutation } from 'services/api/endpoints/models'; + +const predictionSelectData: SelectItem[] = [ + { label: 'None', value: 'none' }, + { label: 'v_prediction', value: 'v_prediction' }, + { label: 'epsilon', value: 'epsilon' }, + { label: 'sample', value: 'sample' }, +]; + +type ExtendedImportModelConfig = { + location: string; + prediction_type?: 'v_prediction' | 'epsilon' | 'sample' | 'none' | undefined; +}; + +export default function AddModels() { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + + const isProcessing = useAppSelector( + (state: RootState) => state.system.isProcessing + ); + + const [importMainModel, { isLoading }] = useImportMainModelsMutation(); + + const addModelForm = useForm({ + initialValues: { + location: '', + prediction_type: undefined, + }, + }); + + const handleAddModelSubmit = (values: ExtendedImportModelConfig) => { + const importModelResponseBody = { + location: values.location, + prediction_type: + values.prediction_type === 'none' ? undefined : values.prediction_type, + }; + + importMainModel({ body: importModelResponseBody }) + .unwrap() + .then((_) => { + dispatch( + addToast( + makeToast({ + title: 'Model Added', + status: 'success', + }) + ) + ); + addModelForm.reset(); + }) + .catch((error) => { + if (error) { + console.log(error); + dispatch( + addToast( + makeToast({ + title: `${error.data.detail} `, + status: 'error', + }) + ) + ); + } + }); + }; + + return ( + handleAddModelSubmit(v))}> + + + + + {t('modelManager.addModel')} + + + + ); +} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx index af862d005d..69dbd20746 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx @@ -22,7 +22,11 @@ export default function FoundModelsList() { } return ( - + {foundModels.map((model) => ( {model} ))} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddCheckpointModel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx similarity index 99% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddCheckpointModel.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx index 75e2017bb8..20f4330658 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddCheckpointModel.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx @@ -33,7 +33,7 @@ import SearchModels from './SearchModels'; const MIN_MODEL_SIZE = 64; const MAX_MODEL_SIZE = 2048; -export default function AddCheckpointModel() { +export default function ScanModels() { const dispatch = useAppDispatch(); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx index e3e48c7e6b..be2d9ea20d 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx @@ -11,7 +11,9 @@ export default function SearchModels() { return ( - + + + ); } diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts index 4c72bd6239..325e8e898f 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts @@ -1,7 +1,5 @@ import { SchedulerParam } from 'features/parameters/types/parameterSchemas'; -export type AddNewModelType = 'ckpt' | 'diffusers' | null; - export type Coordinates = { x: number; y: number; @@ -22,7 +20,6 @@ export interface UIState { shouldUseCanvasBetaLayout: boolean; shouldShowExistingModelsInSearch: boolean; shouldUseSliders: boolean; - addNewModelUIOption: AddNewModelType; shouldHidePreview: boolean; shouldPinGallery: boolean; shouldShowGallery: boolean; diff --git a/invokeai/frontend/web/src/services/api/endpoints/models.ts b/invokeai/frontend/web/src/services/api/endpoints/models.ts index a838a82f46..2a5e5547b3 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/models.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/models.ts @@ -7,6 +7,7 @@ import { ControlNetModelConfig, ConvertModelConfig, DiffusersModelConfig, + ImportModelConfig, LoRAModelConfig, MainModelConfig, MergeModelConfig, @@ -78,6 +79,13 @@ type MergeMainModelArg = { type MergeMainModelResponse = paths['/api/v1/models/merge/{base_model}']['put']['responses']['200']['content']['application/json']; +type ImportMainModelArg = { + body: ImportModelConfig; +}; + +type ImportMainModelResponse = + paths['/api/v1/models/import']['post']['responses']['201']['content']['application/json']; + type SearchFolderResponse = paths['/api/v1/models/search']['get']['responses']['200']['content']['application/json']; @@ -168,6 +176,19 @@ export const modelsApi = api.injectEndpoints({ }, invalidatesTags: [{ type: 'MainModel', id: LIST_TAG }], }), + importMainModels: build.mutation< + ImportMainModelResponse, + ImportMainModelArg + >({ + query: ({ body }) => { + return { + url: `models/import`, + method: 'POST', + body: body, + }; + }, + invalidatesTags: [{ type: 'MainModel', id: LIST_TAG }], + }), deleteMainModels: build.mutation< DeleteMainModelResponse, DeleteMainModelArg @@ -356,6 +377,7 @@ export const { useGetVaeModelsQuery, useUpdateMainModelsMutation, useDeleteMainModelsMutation, + useImportMainModelsMutation, useConvertMainModelsMutation, useMergeMainModelsMutation, useGetModelsInFolderQuery, diff --git a/invokeai/frontend/web/src/services/api/types.d.ts b/invokeai/frontend/web/src/services/api/types.d.ts index 8519228c5e..6812a62925 100644 --- a/invokeai/frontend/web/src/services/api/types.d.ts +++ b/invokeai/frontend/web/src/services/api/types.d.ts @@ -60,7 +60,7 @@ export type AnyModelConfig = export type MergeModelConfig = components['schemas']['Body_merge_models']; export type ConvertModelConfig = components['schemas']['Body_convert_model']; -export type SearchFolderConfig = components['schemas']; +export type ImportModelConfig = components['schemas']['Body_import_model']; // Graphs export type Graph = components['schemas']['Graph']; From b1e16aa3db1330656df594759c49befbcd25cd53 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 16 Jul 2023 01:41:32 +1200 Subject: [PATCH 08/34] fix: placeholder text for Add model input --- .../tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx | 3 ++- .../ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx index c6517d38a7..e4d6ffb663 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx @@ -88,11 +88,12 @@ export default function AddModels() { > { if (!searchFolder) return; From cd033f4ead1a9eb4be35cff6118c16860d1accfa Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 16 Jul 2023 01:57:42 +1200 Subject: [PATCH 09/34] fix: Refine some UI --- .../ModelManager/subpanels/AddModelsPanel.tsx | 2 + .../subpanels/AddModelsPanel/AddModels.tsx | 136 +++++------------- .../AddModelsPanel/AutoAddModels.tsx | 108 ++++++++++++++ 3 files changed, 146 insertions(+), 100 deletions(-) create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AutoAddModels.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx index d868dc6c80..cac6c7a136 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx @@ -18,6 +18,7 @@ export default function AddModelsPanel() { onClick={() => setAddModelTab('add')} isChecked={addModelTab == 'add'} size="sm" + width="100%" > {t('modelManager.addModel')} @@ -25,6 +26,7 @@ export default function AddModelsPanel() { onClick={() => setAddModelTab('scan')} isChecked={addModelTab == 'scan'} size="sm" + width="100%" > {t('modelManager.scanForModels')} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx index e4d6ffb663..e667c17d3e 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx @@ -1,111 +1,47 @@ -import { Flex } from '@chakra-ui/react'; -// import { addNewModel } from 'app/socketio/actions'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { useTranslation } from 'react-i18next'; - -import { SelectItem } from '@mantine/core'; -import { useForm } from '@mantine/form'; -import { makeToast } from 'app/components/Toaster'; -import { RootState } from 'app/store/store'; +import { ButtonGroup, Flex } from '@chakra-ui/react'; import IAIButton from 'common/components/IAIButton'; -import IAIMantineTextInput from 'common/components/IAIMantineInput'; -import IAIMantineSelect from 'common/components/IAIMantineSelect'; -import { addToast } from 'features/system/store/systemSlice'; -import { useImportMainModelsMutation } from 'services/api/endpoints/models'; - -const predictionSelectData: SelectItem[] = [ - { label: 'None', value: 'none' }, - { label: 'v_prediction', value: 'v_prediction' }, - { label: 'epsilon', value: 'epsilon' }, - { label: 'sample', value: 'sample' }, -]; - -type ExtendedImportModelConfig = { - location: string; - prediction_type?: 'v_prediction' | 'epsilon' | 'sample' | 'none' | undefined; -}; +import { useState } from 'react'; +import AutoAddModels from './AutoAddModels'; export default function AddModels() { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - const isProcessing = useAppSelector( - (state: RootState) => state.system.isProcessing + const [addModelMode, setAddModelMode] = useState<'simple' | 'advanced'>( + 'simple' ); - - const [importMainModel, { isLoading }] = useImportMainModelsMutation(); - - const addModelForm = useForm({ - initialValues: { - location: '', - prediction_type: undefined, - }, - }); - - const handleAddModelSubmit = (values: ExtendedImportModelConfig) => { - const importModelResponseBody = { - location: values.location, - prediction_type: - values.prediction_type === 'none' ? undefined : values.prediction_type, - }; - - importMainModel({ body: importModelResponseBody }) - .unwrap() - .then((_) => { - dispatch( - addToast( - makeToast({ - title: 'Model Added', - status: 'success', - }) - ) - ); - addModelForm.reset(); - }) - .catch((error) => { - if (error) { - console.log(error); - dispatch( - addToast( - makeToast({ - title: `${error.data.detail} `, - status: 'error', - }) - ) - ); - } - }); - }; - return ( - handleAddModelSubmit(v))}> - - - + + setAddModelMode('simple')} > - {t('modelManager.addModel')} + Simple + setAddModelMode('advanced')} + > + Advanced + + + + {addModelMode === 'simple' && } + {addModelMode === 'advanced' && null} - + ); } diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AutoAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AutoAddModels.tsx new file mode 100644 index 0000000000..55a33ca1b8 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AutoAddModels.tsx @@ -0,0 +1,108 @@ +import { Flex } from '@chakra-ui/react'; +// import { addNewModel } from 'app/socketio/actions'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useTranslation } from 'react-i18next'; + +import { SelectItem } from '@mantine/core'; +import { useForm } from '@mantine/form'; +import { makeToast } from 'app/components/Toaster'; +import { RootState } from 'app/store/store'; +import IAIButton from 'common/components/IAIButton'; +import IAIMantineTextInput from 'common/components/IAIMantineInput'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; +import { addToast } from 'features/system/store/systemSlice'; +import { useImportMainModelsMutation } from 'services/api/endpoints/models'; + +const predictionSelectData: SelectItem[] = [ + { label: 'None', value: 'none' }, + { label: 'v_prediction', value: 'v_prediction' }, + { label: 'epsilon', value: 'epsilon' }, + { label: 'sample', value: 'sample' }, +]; + +type ExtendedImportModelConfig = { + location: string; + prediction_type?: 'v_prediction' | 'epsilon' | 'sample' | 'none' | undefined; +}; + +export default function AutoAddModels() { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + + const isProcessing = useAppSelector( + (state: RootState) => state.system.isProcessing + ); + + const [importMainModel, { isLoading }] = useImportMainModelsMutation(); + + const addModelForm = useForm({ + initialValues: { + location: '', + prediction_type: undefined, + }, + }); + + const handleAddModelSubmit = (values: ExtendedImportModelConfig) => { + const importModelResponseBody = { + location: values.location, + prediction_type: + values.prediction_type === 'none' ? undefined : values.prediction_type, + }; + + importMainModel({ body: importModelResponseBody }) + .unwrap() + .then((_) => { + dispatch( + addToast( + makeToast({ + title: 'Model Added', + status: 'success', + }) + ) + ); + addModelForm.reset(); + }) + .catch((error) => { + if (error) { + console.log(error); + dispatch( + addToast( + makeToast({ + title: `${error.data.detail} `, + status: 'error', + }) + ) + ); + } + }); + }; + + return ( + handleAddModelSubmit(v))} + style={{ width: '100%' }} + > + + + + + {t('modelManager.addModel')} + + + + ); +} From 2e0370d84536ea2e7b897b1133e4ff76e932700b Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 16 Jul 2023 14:07:26 +1200 Subject: [PATCH 10/34] feat: Extract BaseModel and ModelVariant Select's For reusability --- .../common/components/IAIMantineSelect.tsx | 2 +- .../ModelManagerPanel/CheckpointModelEdit.tsx | 24 ++++------------ .../ModelManagerPanel/DiffusersModelEdit.tsx | 28 ++++--------------- .../subpanels/shared/BaseModelSelect.tsx | 26 +++++++++++++++++ .../subpanels/shared/ModelVariantSelect.tsx | 26 +++++++++++++++++ 5 files changed, 63 insertions(+), 43 deletions(-) create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/shared/BaseModelSelect.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/shared/ModelVariantSelect.tsx diff --git a/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx b/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx index fa9b0b6719..537ccf86be 100644 --- a/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx +++ b/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx @@ -9,7 +9,7 @@ export type IAISelectDataType = { tooltip?: string; }; -type IAISelectProps = Omit & { +export type IAISelectProps = Omit & { tooltip?: string; inputRef?: RefObject; label?: string; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx index 2cdaea904f..6078ba4a68 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx @@ -4,7 +4,6 @@ import { makeToast } from 'app/components/Toaster'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import IAIMantineTextInput from 'common/components/IAIMantineInput'; -import IAIMantineSelect from 'common/components/IAIMantineSelect'; import { MODEL_TYPE_MAP } from 'features/parameters/types/constants'; import { selectIsBusy } from 'features/system/store/systemSelectors'; import { addToast } from 'features/system/store/systemSlice'; @@ -15,19 +14,10 @@ import { useUpdateMainModelsMutation, } from 'services/api/endpoints/models'; import { CheckpointModelConfig } from 'services/api/types'; +import BaseModelSelect from '../shared/BaseModelSelect'; +import ModelVariantSelect from '../shared/ModelVariantSelect'; import ModelConvert from './ModelConvert'; -const baseModelSelectData = [ - { value: 'sd-1', label: MODEL_TYPE_MAP['sd-1'] }, - { value: 'sd-2', label: MODEL_TYPE_MAP['sd-2'] }, -]; - -const variantSelectData = [ - { value: 'normal', label: 'Normal' }, - { value: 'inpaint', label: 'Inpaint' }, - { value: 'depth', label: 'Depth' }, -]; - type CheckpointModelEditProps = { model: CheckpointModelConfigEntity; }; @@ -80,7 +70,7 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) { ) ); }) - .catch((error) => { + .catch((_) => { checkpointEditForm.reset(); dispatch( addToast( @@ -132,14 +122,10 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) { label={t('modelManager.description')} {...checkpointEditForm.getInputProps('description')} /> - - { + .catch((_) => { diffusersEditForm.reset(); dispatch( addToast( @@ -122,16 +112,8 @@ export default function DiffusersModelEdit(props: DiffusersModelEditProps) { label={t('modelManager.description')} {...diffusersEditForm.getInputProps('description')} /> - - + + ; + +export default function BaseModelSelect(props: BaseModelSelectProps) { + const { ...rest } = props; + const { t } = useTranslation(); + + return ( + + ); +} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/shared/ModelVariantSelect.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/shared/ModelVariantSelect.tsx new file mode 100644 index 0000000000..24f295fb73 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/shared/ModelVariantSelect.tsx @@ -0,0 +1,26 @@ +import IAIMantineSelect, { + IAISelectDataType, + IAISelectProps, +} from 'common/components/IAIMantineSelect'; +import { useTranslation } from 'react-i18next'; + +const variantSelectData: IAISelectDataType[] = [ + { value: 'normal', label: 'Normal' }, + { value: 'inpaint', label: 'Inpaint' }, + { value: 'depth', label: 'Depth' }, +]; + +type VariantSelectProps = Omit; + +export default function ModelVariantSelect(props: VariantSelectProps) { + const { ...rest } = props; + const { t } = useTranslation(); + + return ( + + ); +} From 421fcb761b3fdab093a797706cfe59b84af55b60 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 16 Jul 2023 14:20:27 +1200 Subject: [PATCH 11/34] feat: Manual Add Diffusers Model --- .../subpanels/AddModelsPanel/AddModels.tsx | 3 +- .../AddModelsPanel/ManualAddCheckpoint.tsx | 20 ++++ .../AddModelsPanel/ManualAddDiffusers.tsx | 102 ++++++++++++++++++ .../AddModelsPanel/ManualAddModels.tsx | 46 ++++++++ .../web/src/services/api/endpoints/models.ts | 18 ++++ 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddDiffusers.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddModels.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx index e667c17d3e..4e1f3d8240 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx @@ -2,6 +2,7 @@ import { ButtonGroup, Flex } from '@chakra-ui/react'; import IAIButton from 'common/components/IAIButton'; import { useState } from 'react'; import AutoAddModels from './AutoAddModels'; +import ManualAddModels from './ManualAddModels'; export default function AddModels() { const [addModelMode, setAddModelMode] = useState<'simple' | 'advanced'>( @@ -40,7 +41,7 @@ export default function AddModels() { }} > {addModelMode === 'simple' && } - {addModelMode === 'advanced' && null} + {addModelMode === 'advanced' && } ); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx new file mode 100644 index 0000000000..efee656d81 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx @@ -0,0 +1,20 @@ +import { useForm } from '@mantine/form'; +import { CheckpointModelConfig } from 'services/api/types'; + +export default function ManualAddCheckpoint() { + const manualAddCheckpointForm = useForm({ + initialValues: { + model_name: '', + base_model: 'sd-1', + model_type: 'main', + path: '', + description: '', + model_format: 'checkpoint', + error: undefined, + vae: '', + variant: 'normal', + config: '', + }, + }); + return ManualAddCheckpoint; +} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddDiffusers.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddDiffusers.tsx new file mode 100644 index 0000000000..1e51006dcd --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddDiffusers.tsx @@ -0,0 +1,102 @@ +import { Flex } from '@chakra-ui/react'; +import { useForm } from '@mantine/form'; +import { makeToast } from 'app/components/Toaster'; +import { useAppDispatch } from 'app/store/storeHooks'; +import IAIButton from 'common/components/IAIButton'; +import IAIMantineTextInput from 'common/components/IAIMantineInput'; +import { addToast } from 'features/system/store/systemSlice'; +import { useTranslation } from 'react-i18next'; +import { useAddMainModelsMutation } from 'services/api/endpoints/models'; +import { DiffusersModelConfig } from 'services/api/types'; +import BaseModelSelect from '../shared/BaseModelSelect'; +import ModelVariantSelect from '../shared/ModelVariantSelect'; + +export default function ManualAddDiffusers() { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [addMainModel] = useAddMainModelsMutation(); + + const manualAddDiffusersForm = useForm({ + initialValues: { + model_name: '', + base_model: 'sd-1', + model_type: 'main', + path: '', + description: '', + model_format: 'diffusers', + error: undefined, + vae: '', + variant: 'normal', + }, + }); + const manualAddDiffusersFormHandler = (values: DiffusersModelConfig) => { + console.log(values); + addMainModel({ + body: values, + }) + .unwrap() + .then((_) => { + dispatch( + addToast( + makeToast({ + title: `Model Added: ${values.model_name}`, + status: 'success', + }) + ) + ); + manualAddDiffusersForm.reset(); + }) + .catch((error) => { + if (error) { + dispatch( + addToast( + makeToast({ + title: 'Model Add Failed', + status: 'error', + }) + ) + ); + } + }); + }; + + return ( + + manualAddDiffusersFormHandler(v) + )} + style={{ width: '100%' }} + > + + + + + + + + + {t('modelManager.addModel')} + + + + ); +} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddModels.tsx new file mode 100644 index 0000000000..2783e4e6b3 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddModels.tsx @@ -0,0 +1,46 @@ +import { Flex } from '@chakra-ui/react'; +import { SelectItem } from '@mantine/core'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; +import { useState } from 'react'; +import ManualAddCheckpoint from './ManualAddCheckpoint'; +import ManualAddDiffusers from './ManualAddDiffusers'; + +const manualAddModeData: SelectItem[] = [ + { label: 'Diffusers', value: 'diffusers' }, + { label: 'Checkpoint / Safetensors', value: 'checkpoint' }, +]; + +type ManualAddMode = 'diffusers' | 'checkpoint'; + +export default function ManualAddModels() { + const [manualAddMode, setManualAddMode] = + useState('diffusers'); + + return ( + + { + if (!v) return; + setManualAddMode(v as ManualAddMode); + }} + /> + + + {manualAddMode === 'diffusers' && } + {manualAddMode === 'checkpoint' && } + + + ); +} diff --git a/invokeai/frontend/web/src/services/api/endpoints/models.ts b/invokeai/frontend/web/src/services/api/endpoints/models.ts index 2a5e5547b3..e0177ca8d1 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/models.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/models.ts @@ -86,6 +86,13 @@ type ImportMainModelArg = { type ImportMainModelResponse = paths['/api/v1/models/import']['post']['responses']['201']['content']['application/json']; +type AddMainModelArg = { + body: MainModelConfig; +}; + +type AddMainModelResponse = + paths['/api/v1/models/add']['post']['responses']['201']['content']['application/json']; + type SearchFolderResponse = paths['/api/v1/models/search']['get']['responses']['200']['content']['application/json']; @@ -189,6 +196,16 @@ export const modelsApi = api.injectEndpoints({ }, invalidatesTags: [{ type: 'MainModel', id: LIST_TAG }], }), + addMainModels: build.mutation({ + query: ({ body }) => { + return { + url: `models/add`, + method: 'POST', + body: body, + }; + }, + invalidatesTags: [{ type: 'MainModel', id: LIST_TAG }], + }), deleteMainModels: build.mutation< DeleteMainModelResponse, DeleteMainModelArg @@ -378,6 +395,7 @@ export const { useUpdateMainModelsMutation, useDeleteMainModelsMutation, useImportMainModelsMutation, + useAddMainModelsMutation, useConvertMainModelsMutation, useMergeMainModelsMutation, useGetModelsInFolderQuery, From d93d42af4aaea7eca4bc43525e6428311b2bb55f Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 16 Jul 2023 15:21:49 +1200 Subject: [PATCH 12/34] feat: Add Manual Checkpoint / Safetensor Models --- .../src/common/components/IAIMantineInput.tsx | 1 + .../subpanels/AddModelsPanel/AddModels.tsx | 2 +- .../AddModelsPanel/ManualAddCheckpoint.tsx | 111 +++++++++++++++++- .../AddModelsPanel/ManualAddDiffusers.tsx | 2 +- .../shared/CheckpointConfigsSelect.tsx | 22 ++++ .../web/src/services/api/endpoints/models.ts | 11 ++ 6 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/shared/CheckpointConfigsSelect.tsx diff --git a/invokeai/frontend/web/src/common/components/IAIMantineInput.tsx b/invokeai/frontend/web/src/common/components/IAIMantineInput.tsx index d60f6614df..afe8b9442b 100644 --- a/invokeai/frontend/web/src/common/components/IAIMantineInput.tsx +++ b/invokeai/frontend/web/src/common/components/IAIMantineInput.tsx @@ -36,6 +36,7 @@ export default function IAIMantineTextInput(props: IAIMantineTextInputProps) { label: { color: mode(base700, base300)(colorMode), fontWeight: 'normal', + marginBottom: 4, }, })} {...rest} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx index 4e1f3d8240..fdb890152b 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx @@ -13,7 +13,7 @@ export default function AddModels() { flexDirection="column" width="100%" overflow="scroll" - maxHeight={window.innerHeight - 270} + maxHeight={window.innerHeight - 250} gap={4} > diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx index efee656d81..f63085aaaa 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx @@ -1,7 +1,23 @@ +import { Flex } from '@chakra-ui/react'; import { useForm } from '@mantine/form'; +import { makeToast } from 'app/components/Toaster'; +import { useAppDispatch } from 'app/store/storeHooks'; +import IAIButton from 'common/components/IAIButton'; +import IAIMantineTextInput from 'common/components/IAIMantineInput'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; +import { addToast } from 'features/system/store/systemSlice'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useAddMainModelsMutation } from 'services/api/endpoints/models'; import { CheckpointModelConfig } from 'services/api/types'; +import BaseModelSelect from '../shared/BaseModelSelect'; +import CheckpointConfigsSelect from '../shared/CheckpointConfigsSelect'; +import ModelVariantSelect from '../shared/ModelVariantSelect'; export default function ManualAddCheckpoint() { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const manualAddCheckpointForm = useForm({ initialValues: { model_name: '', @@ -13,8 +29,99 @@ export default function ManualAddCheckpoint() { error: undefined, vae: '', variant: 'normal', - config: '', + config: 'configs\\stable-diffusion\\v1-inference.yaml', }, }); - return ManualAddCheckpoint; + + const [addMainModel] = useAddMainModelsMutation(); + + const [useCustomConfig, setUseCustomConfig] = useState(false); + + const manualAddCheckpointFormHandler = (values: CheckpointModelConfig) => { + addMainModel({ + body: values, + }) + .unwrap() + .then((_) => { + dispatch( + addToast( + makeToast({ + title: `Model Added: ${values.model_name}`, + status: 'success', + }) + ) + ); + manualAddCheckpointForm.reset(); + }) + .catch((error) => { + if (error) { + dispatch( + addToast( + makeToast({ + title: 'Model Add Failed', + status: 'error', + }) + ) + ); + } + }); + }; + + return ( + + manualAddCheckpointFormHandler(v) + )} + style={{ width: '100%' }} + > + + + + + + + + + {!useCustomConfig ? ( + + ) : ( + + )} + setUseCustomConfig(!useCustomConfig)} + label="Use Custom Config" + /> + + {t('modelManager.addModel')} + + + + + ); } diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddDiffusers.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddDiffusers.tsx index 1e51006dcd..a4b6870a54 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddDiffusers.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddDiffusers.tsx @@ -31,7 +31,6 @@ export default function ManualAddDiffusers() { }, }); const manualAddDiffusersFormHandler = (values: DiffusersModelConfig) => { - console.log(values); addMainModel({ body: values, }) @@ -80,6 +79,7 @@ export default function ManualAddDiffusers() { ; + +export default function CheckpointConfigsSelect( + props: CheckpointConfigSelectProps +) { + const { data: availableCheckpointConfigs } = useGetCheckpointConfigsQuery(); + const { ...rest } = props; + + return ( + + ); +} diff --git a/invokeai/frontend/web/src/services/api/endpoints/models.ts b/invokeai/frontend/web/src/services/api/endpoints/models.ts index e0177ca8d1..d9cbaf69dd 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/models.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/models.ts @@ -96,6 +96,9 @@ type AddMainModelResponse = type SearchFolderResponse = paths['/api/v1/models/search']['get']['responses']['200']['content']['application/json']; +type CheckpointConfigsResponse = + paths['/api/v1/models/ckpt_confs']['get']['responses']['200']['content']['application/json']; + type SearchFolderArg = operations['search_for_models']['parameters']['query']; const mainModelsAdapter = createEntityAdapter({ @@ -383,6 +386,13 @@ export const modelsApi = api.injectEndpoints({ }; }, }), + getCheckpointConfigs: build.query({ + query: () => { + return { + url: `/models/ckpt_confs`, + }; + }, + }), }), }); @@ -399,4 +409,5 @@ export const { useConvertMainModelsMutation, useMergeMainModelsMutation, useGetModelsInFolderQuery, + useGetCheckpointConfigsQuery, } = modelsApi; From 5b047baeb0ada35c9212d32a6de161e3785da059 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 16 Jul 2023 15:29:01 +1200 Subject: [PATCH 13/34] fix: Mantine Required icon being on new line --- .../frontend/web/src/common/components/IAIMantineSelect.tsx | 4 ++-- .../subpanels/AddModelsPanel/ManualAddCheckpoint.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx b/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx index 537ccf86be..46b5fc95f6 100644 --- a/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx +++ b/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx @@ -16,7 +16,7 @@ export type IAISelectProps = Omit & { }; const IAIMantineSelect = (props: IAISelectProps) => { - const { tooltip, inputRef, label, disabled, ...rest } = props; + const { tooltip, inputRef, label, disabled, required, ...rest } = props; const styles = useMantineSelectStyles(); @@ -25,7 +25,7 @@ const IAIMantineSelect = (props: IAISelectProps) => { + {label} ) : undefined diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx index f63085aaaa..8c745804da 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx @@ -102,6 +102,7 @@ export default function ManualAddCheckpoint() { {!useCustomConfig ? ( From 5351171d0e9d1e22e42f356b51c0d1a1e4914893 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 16 Jul 2023 15:29:25 +1200 Subject: [PATCH 14/34] cleanup: Scan Models component (to begin anew) --- .../subpanels/AddModelsPanel/ScanModels.tsx | 341 +----------------- .../subpanels/AddModelsPanel/SearchModels.tsx | 19 - 2 files changed, 10 insertions(+), 350 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx index 20f4330658..d989463d75 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx @@ -1,337 +1,16 @@ -import { - Flex, - FormControl, - FormErrorMessage, - FormHelperText, - FormLabel, - HStack, - Text, - VStack, -} from '@chakra-ui/react'; - -import IAIButton from 'common/components/IAIButton'; -import IAIInput from 'common/components/IAIInput'; -import IAINumberInput from 'common/components/IAINumberInput'; -import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; -import React from 'react'; - -// import { addNewModel } from 'app/socketio/actions'; - -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; - -import { Field, Formik } from 'formik'; -import { useTranslation } from 'react-i18next'; - -import type { RootState } from 'app/store/store'; -import type { InvokeModelConfigProps } from 'app/types/invokeai'; -import IAIForm from 'common/components/IAIForm'; -import { IAIFormItemWrapper } from 'common/components/IAIForms/IAIFormItemWrapper'; -import { setAddNewModelUIOption } from 'features/ui/store/uiSlice'; -import type { FieldInputProps, FormikProps } from 'formik'; -import SearchModels from './SearchModels'; - -const MIN_MODEL_SIZE = 64; -const MAX_MODEL_SIZE = 2048; +import { Flex } from '@chakra-ui/react'; +import FoundModelsList from './FoundModelsList'; +import SearchFolderForm from './SearchFolderForm'; export default function ScanModels() { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - const isProcessing = useAppSelector( - (state: RootState) => state.system.isProcessing - ); - - function hasWhiteSpace(s: string) { - return /\s/.test(s); - } - - function baseValidation(value: string) { - let error; - if (hasWhiteSpace(value)) error = t('modelManager.cannotUseSpaces'); - return error; - } - - const addModelFormValues: InvokeModelConfigProps = { - name: '', - description: '', - config: 'configs/stable-diffusion/v1-inference.yaml', - weights: '', - vae: '', - width: 512, - height: 512, - format: 'ckpt', - default: false, - }; - - const addModelFormSubmitHandler = (values: InvokeModelConfigProps) => { - dispatch(addNewModel(values)); - dispatch(setAddNewModelUIOption(null)); - }; - - const [addManually, setAddmanually] = React.useState(false); - return ( - - - setAddmanually(!addManually)} - /> - setAddmanually(!addManually)} - /> + + + + - - {addManually ? ( - - {({ handleSubmit, errors, touched }) => ( - - - - {t('modelManager.manual')} - - {/* Name */} - - - - {t('modelManager.name')} - - - - {!!errors.name && touched.name ? ( - {errors.name} - ) : ( - - {t('modelManager.nameValidationMsg')} - - )} - - - - - {/* Description */} - - - - {t('modelManager.description')} - - - - {!!errors.description && touched.description ? ( - - {errors.description} - - ) : ( - - {t('modelManager.descriptionValidationMsg')} - - )} - - - - - {/* Config */} - - - - {t('modelManager.config')} - - - - {!!errors.config && touched.config ? ( - {errors.config} - ) : ( - - {t('modelManager.configValidationMsg')} - - )} - - - - - {/* Weights */} - - - - {t('modelManager.modelLocation')} - - - - {!!errors.weights && touched.weights ? ( - {errors.weights} - ) : ( - - {t('modelManager.modelLocationValidationMsg')} - - )} - - - - - {/* VAE */} - - - - {t('modelManager.vaeLocation')} - - - - {!!errors.vae && touched.vae ? ( - {errors.vae} - ) : ( - - {t('modelManager.vaeLocationValidationMsg')} - - )} - - - - - - {/* Width */} - - - - {t('modelManager.width')} - - - - {({ - field, - form, - }: { - field: FieldInputProps; - form: FormikProps; - }) => ( - - form.setFieldValue(field.name, Number(value)) - } - /> - )} - - - {!!errors.width && touched.width ? ( - {errors.width} - ) : ( - - {t('modelManager.widthValidationMsg')} - - )} - - - - - {/* Height */} - - - - {t('modelManager.height')} - - - - {({ - field, - form, - }: { - field: FieldInputProps; - form: FormikProps; - }) => ( - - form.setFieldValue(field.name, Number(value)) - } - /> - )} - - - {!!errors.height && touched.height ? ( - {errors.height} - ) : ( - - {t('modelManager.heightValidationMsg')} - - )} - - - - - - - {t('modelManager.addModel')} - - - - )} - - ) : ( - - )} - + ); } diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx deleted file mode 100644 index be2d9ea20d..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import { useAppDispatch } from 'app/store/storeHooks'; -import { useTranslation } from 'react-i18next'; -import FoundModelsList from './FoundModelsList'; -import SearchFolderForm from './SearchFolderForm'; - -export default function SearchModels() { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - return ( - - - - - - - ); -} From 92029e69c61b5a89b698eac3fd7d64de97b53566 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 16 Jul 2023 15:48:44 +1200 Subject: [PATCH 15/34] feat: Update Checkpoint Model Edit to use config picker --- .../components/ImageGrid/GalleryImageGrid.tsx | 4 +- .../ModelManagerPanel/CheckpointModelEdit.tsx | 42 ++++++++++++++++--- .../ModelManagerPanel/DiffusersModelEdit.tsx | 11 ++++- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx index 8b44b39ae9..7811e5f1c4 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx @@ -16,14 +16,13 @@ import { ASSETS_CATEGORIES, IMAGE_CATEGORIES, IMAGE_LIMIT, - selectImagesAll, } from 'features/gallery//store/gallerySlice'; import { selectFilteredImages } from 'features/gallery/store/gallerySelectors'; import { VirtuosoGrid } from 'react-virtuoso'; import { receivedPageOfImages } from 'services/api/thunks/image'; +import { useListBoardImagesQuery } from '../../../../services/api/endpoints/boardImages'; import ImageGridItemContainer from './ImageGridItemContainer'; import ImageGridListContainer from './ImageGridListContainer'; -import { useListBoardImagesQuery } from '../../../../services/api/endpoints/boardImages'; const selector = createSelector( [stateSelector, selectFilteredImages], @@ -180,7 +179,6 @@ const GalleryImageGrid = () => { ); } - console.log({ selectedBoardId }); if (status !== 'rejected') { return ( diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx index 6078ba4a68..960fa1cbf5 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx @@ -4,17 +4,20 @@ import { makeToast } from 'app/components/Toaster'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import IAIMantineTextInput from 'common/components/IAIMantineInput'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import { MODEL_TYPE_MAP } from 'features/parameters/types/constants'; import { selectIsBusy } from 'features/system/store/systemSelectors'; import { addToast } from 'features/system/store/systemSlice'; -import { useCallback } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { CheckpointModelConfigEntity, + useGetCheckpointConfigsQuery, useUpdateMainModelsMutation, } from 'services/api/endpoints/models'; import { CheckpointModelConfig } from 'services/api/types'; import BaseModelSelect from '../shared/BaseModelSelect'; +import CheckpointConfigsSelect from '../shared/CheckpointConfigsSelect'; import ModelVariantSelect from '../shared/ModelVariantSelect'; import ModelConvert from './ModelConvert'; @@ -28,6 +31,15 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) { const { model } = props; const [updateMainModel, { isLoading }] = useUpdateMainModelsMutation(); + const { data: availableCheckpointConfigs } = useGetCheckpointConfigsQuery(); + + const [useCustomConfig, setUseCustomConfig] = useState(false); + + useEffect(() => { + if (!availableCheckpointConfigs?.includes(model.config)) { + setUseCustomConfig(true); + } + }, [availableCheckpointConfigs, model.config]); const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -123,12 +135,15 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) { {...checkpointEditForm.getInputProps('description')} /> @@ -136,10 +151,27 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) { label={t('modelManager.vaeLocation')} {...checkpointEditForm.getInputProps('vae')} /> - + + + {!useCustomConfig ? ( + + ) : ( + + )} + setUseCustomConfig(!useCustomConfig)} + label="Use Custom Config" + /> + + - - + + From dabd2bf301b4e2b31688331bd2e9f05e58b03087 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 16 Jul 2023 16:15:53 +1200 Subject: [PATCH 16/34] fix: Readd model name to edit forms Will be needed when we implement changing name and base model type. --- .../subpanels/ModelManagerPanel/CheckpointModelEdit.tsx | 4 ++++ .../subpanels/ModelManagerPanel/DiffusersModelEdit.tsx | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx index 960fa1cbf5..46817a299b 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx @@ -130,6 +130,10 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) { )} > + { @@ -108,6 +109,10 @@ export default function DiffusersModelEdit(props: DiffusersModelEditProps) { )} > + Date: Sun, 16 Jul 2023 16:14:28 -0400 Subject: [PATCH 17/34] add missing exception name --- invokeai/backend/model_management/models/vae.py | 1 + 1 file changed, 1 insertion(+) diff --git a/invokeai/backend/model_management/models/vae.py b/invokeai/backend/model_management/models/vae.py index 2a5b7cff24..f740615509 100644 --- a/invokeai/backend/model_management/models/vae.py +++ b/invokeai/backend/model_management/models/vae.py @@ -16,6 +16,7 @@ from .base import ( calc_model_size_by_data, classproperty, InvalidModelException, + ModelNotFoundException, ) from invokeai.app.services.config import InvokeAIAppConfig from diffusers.utils import is_safetensors_available From 641b90cc3febd9a4e63fd1fefc23788eb3f73416 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 17 Jul 2023 13:50:35 +1200 Subject: [PATCH 18/34] chore: regen types --- .../frontend/web/src/services/api/schema.d.ts | 107 +++--------------- 1 file changed, 14 insertions(+), 93 deletions(-) diff --git a/invokeai/frontend/web/src/services/api/schema.d.ts b/invokeai/frontend/web/src/services/api/schema.d.ts index c658357016..59cd8fcbbb 100644 --- a/invokeai/frontend/web/src/services/api/schema.d.ts +++ b/invokeai/frontend/web/src/services/api/schema.d.ts @@ -84,7 +84,7 @@ export type paths = { delete: operations["del_model"]; /** * Update Model - * @description Add Model + * @description Update model contents with a new config. If the model name or base fields are changed, then the model is renamed. */ patch: operations["update_model"]; }; @@ -102,13 +102,6 @@ export type paths = { */ post: operations["add_model"]; }; - "/api/v1/models/rename/{base_model}/{model_type}/{model_name}": { - /** - * Rename Model - * @description Rename a model - */ - post: operations["rename_model"]; - }; "/api/v1/models/convert/{base_model}/{model_type}/{model_name}": { /** * Convert Model @@ -1226,7 +1219,7 @@ export type components = { * @description The nodes in this graph */ nodes?: { - [key: string]: (components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["RealESRGANInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]) | undefined; + [key: string]: (components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RealESRGANInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]) | undefined; }; /** * Edges @@ -1269,7 +1262,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: (components["schemas"]["ImageOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["MetadataAccumulatorOutput"] | components["schemas"]["PromptOutput"] | components["schemas"]["PromptCollectionOutput"] | components["schemas"]["CompelOutput"] | components["schemas"]["ClipSkipInvocationOutput"] | components["schemas"]["IntOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["IntCollectionOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]) | undefined; + [key: string]: (components["schemas"]["ImageOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["MetadataAccumulatorOutput"] | components["schemas"]["IntCollectionOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["CompelOutput"] | components["schemas"]["ClipSkipInvocationOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["IntOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["PromptOutput"] | components["schemas"]["PromptCollectionOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]) | undefined; }; /** * Errors @@ -4031,40 +4024,6 @@ export type components = { * @enum {string} */ ResourceOrigin: "internal" | "external"; - /** - * RestoreFaceInvocation - * @description Restores faces in an image. - */ - RestoreFaceInvocation: { - /** - * Id - * @description The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Is Intermediate - * @description Whether or not this node is an intermediate node. - * @default false - */ - is_intermediate?: boolean; - /** - * Type - * @default restore_face - * @enum {string} - */ - type?: "restore_face"; - /** - * Image - * @description The input image - */ - image?: components["schemas"]["ImageField"]; - /** - * Strength - * @description The strength of the restoration - * @default 0.75 - */ - strength?: number; - }; /** * ScaleLatentsInvocation * @description Scales latents by a given factor. @@ -4653,18 +4612,18 @@ export type components = { */ image?: components["schemas"]["ImageField"]; }; - /** - * StableDiffusion2ModelFormat - * @description An enumeration. - * @enum {string} - */ - StableDiffusion2ModelFormat: "checkpoint" | "diffusers"; /** * StableDiffusion1ModelFormat * @description An enumeration. * @enum {string} */ StableDiffusion1ModelFormat: "checkpoint" | "diffusers"; + /** + * StableDiffusion2ModelFormat + * @description An enumeration. + * @enum {string} + */ + StableDiffusion2ModelFormat: "checkpoint" | "diffusers"; }; responses: never; parameters: never; @@ -4775,7 +4734,7 @@ export type operations = { }; requestBody: { content: { - "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["RealESRGANInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; + "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RealESRGANInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; }; }; responses: { @@ -4812,7 +4771,7 @@ export type operations = { }; requestBody: { content: { - "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["RealESRGANInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; + "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RealESRGANInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; }; }; responses: { @@ -5061,7 +5020,7 @@ export type operations = { }; /** * Update Model - * @description Add Model + * @description Update model contents with a new config. If the model name or base fields are changed, then the model is renamed. */ update_model: { parameters: { @@ -5090,6 +5049,8 @@ export type operations = { 400: never; /** @description The model could not be found */ 404: never; + /** @description There is already a model corresponding to the new name */ + 409: never; /** @description Validation Error */ 422: { content: { @@ -5160,46 +5121,6 @@ export type operations = { 424: never; }; }; - /** - * Rename Model - * @description Rename a model - */ - rename_model: { - parameters: { - query?: { - /** @description new model name */ - new_name?: string; - /** @description new model base */ - new_base?: components["schemas"]["BaseModelType"]; - }; - path: { - /** @description Base model */ - base_model: components["schemas"]["BaseModelType"]; - /** @description The type of model */ - model_type: components["schemas"]["ModelType"]; - /** @description current model name */ - model_name: string; - }; - }; - responses: { - /** @description The model was renamed successfully */ - 201: { - content: { - "application/json": components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; - }; - }; - /** @description The model could not be found */ - 404: never; - /** @description There is already a model corresponding to the new name */ - 409: never; - /** @description Validation Error */ - 422: { - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; /** * Convert Model * @description Convert a checkpoint model into a diffusers model, optionally saving to the indicated destination directory, or `models` if none. From 540f40c293dbf7b23104c9b962ae4fe36b9c2552 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 17 Jul 2023 13:58:11 +1200 Subject: [PATCH 19/34] fix: Better file and component naming for Add Models --- .../subpanels/AddModelsPanel/AddModels.tsx | 8 +++--- ...eckpoint.tsx => AdvancedAddCheckpoint.tsx} | 28 +++++++++---------- ...Diffusers.tsx => AdvancedAddDiffusers.tsx} | 24 ++++++++-------- ...ualAddModels.tsx => AdvancedAddModels.tsx} | 20 ++++++------- ...{AutoAddModels.tsx => SimpleAddModels.tsx} | 2 +- 5 files changed, 41 insertions(+), 41 deletions(-) rename invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/{ManualAddCheckpoint.tsx => AdvancedAddCheckpoint.tsx} (77%) rename invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/{ManualAddDiffusers.tsx => AdvancedAddDiffusers.tsx} (76%) rename invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/{ManualAddModels.tsx => AdvancedAddModels.tsx} (60%) rename invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/{AutoAddModels.tsx => SimpleAddModels.tsx} (98%) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx index fdb890152b..0fa46ec57f 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx @@ -1,8 +1,8 @@ import { ButtonGroup, Flex } from '@chakra-ui/react'; import IAIButton from 'common/components/IAIButton'; import { useState } from 'react'; -import AutoAddModels from './AutoAddModels'; -import ManualAddModels from './ManualAddModels'; +import AdvancedAddModels from './AdvancedAddModels'; +import SimpleAddModels from './SimpleAddModels'; export default function AddModels() { const [addModelMode, setAddModelMode] = useState<'simple' | 'advanced'>( @@ -40,8 +40,8 @@ export default function AddModels() { _dark: { background: 'base.800' }, }} > - {addModelMode === 'simple' && } - {addModelMode === 'advanced' && } + {addModelMode === 'simple' && } + {addModelMode === 'advanced' && } ); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx similarity index 77% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx index 8c745804da..d1dcf3819a 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddCheckpoint.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx @@ -14,11 +14,11 @@ import BaseModelSelect from '../shared/BaseModelSelect'; import CheckpointConfigsSelect from '../shared/CheckpointConfigsSelect'; import ModelVariantSelect from '../shared/ModelVariantSelect'; -export default function ManualAddCheckpoint() { +export default function AdvancedAddCheckpoint() { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const manualAddCheckpointForm = useForm({ + const advancedAddCheckpointForm = useForm({ initialValues: { model_name: '', base_model: 'sd-1', @@ -37,7 +37,7 @@ export default function ManualAddCheckpoint() { const [useCustomConfig, setUseCustomConfig] = useState(false); - const manualAddCheckpointFormHandler = (values: CheckpointModelConfig) => { + const advancedAddCheckpointFormHandler = (values: CheckpointModelConfig) => { addMainModel({ body: values, }) @@ -51,7 +51,7 @@ export default function ManualAddCheckpoint() { }) ) ); - manualAddCheckpointForm.reset(); + advancedAddCheckpointForm.reset(); }) .catch((error) => { if (error) { @@ -69,8 +69,8 @@ export default function ManualAddCheckpoint() { return ( - manualAddCheckpointFormHandler(v) + onSubmit={advancedAddCheckpointForm.onSubmit((v) => + advancedAddCheckpointFormHandler(v) )} style={{ width: '100%' }} > @@ -78,39 +78,39 @@ export default function ManualAddCheckpoint() { {!useCustomConfig ? ( ) : ( )} ({ + const advancedAddDiffusersForm = useForm({ initialValues: { model_name: '', base_model: 'sd-1', @@ -30,7 +30,7 @@ export default function ManualAddDiffusers() { variant: 'normal', }, }); - const manualAddDiffusersFormHandler = (values: DiffusersModelConfig) => { + const advancedAddDiffusersFormHandler = (values: DiffusersModelConfig) => { addMainModel({ body: values, }) @@ -44,7 +44,7 @@ export default function ManualAddDiffusers() { }) ) ); - manualAddDiffusersForm.reset(); + advancedAddDiffusersForm.reset(); }) .catch((error) => { if (error) { @@ -62,8 +62,8 @@ export default function ManualAddDiffusers() { return ( - manualAddDiffusersFormHandler(v) + onSubmit={advancedAddDiffusersForm.onSubmit((v) => + advancedAddDiffusersFormHandler(v) )} style={{ width: '100%' }} > @@ -71,27 +71,27 @@ export default function ManualAddDiffusers() { {t('modelManager.addModel')} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx similarity index 60% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddModels.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx index 2783e4e6b3..18d576c843 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ManualAddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx @@ -2,29 +2,29 @@ import { Flex } from '@chakra-ui/react'; import { SelectItem } from '@mantine/core'; import IAIMantineSelect from 'common/components/IAIMantineSelect'; import { useState } from 'react'; -import ManualAddCheckpoint from './ManualAddCheckpoint'; -import ManualAddDiffusers from './ManualAddDiffusers'; +import AdvancedAddCheckpoint from './AdvancedAddCheckpoint'; +import AdvancedAddDiffusers from './AdvancedAddDiffusers'; -const manualAddModeData: SelectItem[] = [ +const advancedAddModeData: SelectItem[] = [ { label: 'Diffusers', value: 'diffusers' }, { label: 'Checkpoint / Safetensors', value: 'checkpoint' }, ]; type ManualAddMode = 'diffusers' | 'checkpoint'; -export default function ManualAddModels() { - const [manualAddMode, setManualAddMode] = +export default function AdvancedAddModels() { + const [advancedAddMode, setAdvancedAddMode] = useState('diffusers'); return ( { if (!v) return; - setManualAddMode(v as ManualAddMode); + setAdvancedAddMode(v as ManualAddMode); }} /> @@ -38,8 +38,8 @@ export default function ManualAddModels() { }, }} > - {manualAddMode === 'diffusers' && } - {manualAddMode === 'checkpoint' && } + {advancedAddMode === 'diffusers' && } + {advancedAddMode === 'checkpoint' && } ); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AutoAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx similarity index 98% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AutoAddModels.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx index 55a33ca1b8..8afd17d70d 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AutoAddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx @@ -25,7 +25,7 @@ type ExtendedImportModelConfig = { prediction_type?: 'v_prediction' | 'epsilon' | 'sample' | 'none' | undefined; }; -export default function AutoAddModels() { +export default function SimpleAddModels() { const dispatch = useAppDispatch(); const { t } = useTranslation(); From 38e6e3b36bdbc9e1c595c5e7f3ba6845c3d56e94 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:07:38 +1200 Subject: [PATCH 20/34] feat: Add Quick Add To Scan Model --- .../AddModelsPanel/FoundModelsList.tsx | 123 +++++++++++++++++- .../subpanels/AddModelsPanel/ScanModels.tsx | 6 +- .../AddModelsPanel/SearchFolderForm.tsx | 2 +- 3 files changed, 121 insertions(+), 10 deletions(-) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx index c23d2bf9c8..58914fe04f 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx @@ -1,17 +1,72 @@ -import { Flex } from '@chakra-ui/react'; +import { Flex, Text } from '@chakra-ui/react'; +import { makeToast } from 'app/components/Toaster'; import { RootState } from 'app/store/store'; -import { useAppSelector } from 'app/store/storeHooks'; -import { useGetModelsInFolderQuery } from 'services/api/endpoints/models'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIButton from 'common/components/IAIButton'; +import { addToast } from 'features/system/store/systemSlice'; +import { difference, map, values } from 'lodash-es'; +import { MouseEvent, useCallback } from 'react'; +import { + useGetMainModelsQuery, + useGetModelsInFolderQuery, + useImportMainModelsMutation, +} from 'services/api/endpoints/models'; export default function FoundModelsList() { const searchFolder = useAppSelector( (state: RootState) => state.modelmanager.searchFolder ); + // Get all model paths from a given directory const { data: foundModels } = useGetModelsInFolderQuery({ search_path: searchFolder ? searchFolder : '', }); + // Get paths of models that are already installed + const { data: installedModels } = useGetMainModelsQuery(); + const installedModelValues = values(installedModels?.entities); + const installedModelPaths = map(installedModelValues, 'path'); + + // Only select models those that aren't already installed to Invoke + const notInstalledModels = difference(foundModels, installedModelPaths); + + const [importMainModel, { isLoading }] = useImportMainModelsMutation(); + const dispatch = useAppDispatch(); + + const quickAddHandler = useCallback( + (e: MouseEvent) => { + importMainModel({ + body: { + location: e.currentTarget.id, + }, + }) + .unwrap() + .then((_) => { + dispatch( + addToast( + makeToast({ + title: 'Added Model', + status: 'success', + }) + ) + ); + }) + .catch((error) => { + if (error) { + dispatch( + addToast( + makeToast({ + title: 'Faile To Add Model', + status: 'error', + }) + ) + ); + } + }); + }, + [dispatch, importMainModel] + ); + const renderFoundModels = () => { if (!searchFolder) return; @@ -23,10 +78,68 @@ export default function FoundModelsList() { - {foundModels.map((model) => ( - {model} + + + Found Models: {foundModels.length} + + + Not Installed: {notInstalledModels.length} + + + + {notInstalledModels.map((model) => ( + + + + {model.split('\\').slice(-1)[0]} + + + {model} + + + + + Quick Add + + Advanced + + ))} ); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx index d989463d75..e95264d2de 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx @@ -4,11 +4,9 @@ import SearchFolderForm from './SearchFolderForm'; export default function ScanModels() { return ( - + - + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx index 10d0f51665..6f17b62c66 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx @@ -47,7 +47,7 @@ function SearchFolderForm() { gap: 2, p: 4, borderRadius: 4, - background: 'base.400', + background: 'base.300', alignItems: 'center', _dark: { background: 'base.800', From cbd5be73d22eb0cf737754fdc2b65c69924a3eca Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:44:01 +1200 Subject: [PATCH 21/34] feat: Add Scan Models Advanced Add --- .../ModelManager/store/modelManagerSlice.ts | 8 ++- .../AddModelsPanel/AdvancedAddCheckpoint.tsx | 19 ++++++- .../AddModelsPanel/FoundModelsList.tsx | 8 ++- .../AddModelsPanel/ScanAdvancedAddModels.tsx | 56 +++++++++++++++++++ .../subpanels/AddModelsPanel/ScanModels.tsx | 17 +++++- 5 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelManagerSlice.ts b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelManagerSlice.ts index c71407824e..30f0c5c41b 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelManagerSlice.ts +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelManagerSlice.ts @@ -2,10 +2,12 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; type ModelManagerState = { searchFolder: string | null; + advancedAddScanModel: string | null; }; const initialModelManagerState: ModelManagerState = { searchFolder: null, + advancedAddScanModel: null, }; export const modelManagerSlice = createSlice({ @@ -15,9 +17,13 @@ export const modelManagerSlice = createSlice({ setSearchFolder: (state, action: PayloadAction) => { state.searchFolder = action.payload; }, + setAdvancedAddScanModel: (state, action: PayloadAction) => { + state.advancedAddScanModel = action.payload; + }, }, }); -export const { setSearchFolder } = modelManagerSlice.actions; +export const { setSearchFolder, setAdvancedAddScanModel } = + modelManagerSlice.actions; export default modelManagerSlice.reducer; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx index d1dcf3819a..a3f7c404e2 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx @@ -10,20 +10,28 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useAddMainModelsMutation } from 'services/api/endpoints/models'; import { CheckpointModelConfig } from 'services/api/types'; +import { setAdvancedAddScanModel } from '../../store/modelManagerSlice'; import BaseModelSelect from '../shared/BaseModelSelect'; import CheckpointConfigsSelect from '../shared/CheckpointConfigsSelect'; import ModelVariantSelect from '../shared/ModelVariantSelect'; -export default function AdvancedAddCheckpoint() { +type AdvancedAddCheckpointProps = { + model_path?: string; +}; + +export default function AdvancedAddCheckpoint( + props: AdvancedAddCheckpointProps +) { const { t } = useTranslation(); const dispatch = useAppDispatch(); + const { model_path } = props; const advancedAddCheckpointForm = useForm({ initialValues: { - model_name: '', + model_name: model_path ? model_path.split('\\').splice(-1)[0] : '', base_model: 'sd-1', model_type: 'main', - path: '', + path: model_path ? model_path : '', description: '', model_format: 'checkpoint', error: undefined, @@ -52,6 +60,11 @@ export default function AdvancedAddCheckpoint() { ) ); advancedAddCheckpointForm.reset(); + + // Close Advanced Panel in Scan Models tab + if (model_path) { + dispatch(setAdvancedAddScanModel(null)); + } }) .catch((error) => { if (error) { diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx index 58914fe04f..8393ecdee2 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx @@ -11,6 +11,7 @@ import { useGetModelsInFolderQuery, useImportMainModelsMutation, } from 'services/api/endpoints/models'; +import { setAdvancedAddScanModel } from '../../store/modelManagerSlice'; export default function FoundModelsList() { const searchFolder = useAppSelector( @@ -137,7 +138,12 @@ export default function FoundModelsList() { > Quick Add - Advanced + dispatch(setAdvancedAddScanModel(model))} + isLoading={isLoading} + > + Advanced + ))} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx new file mode 100644 index 0000000000..65454c3363 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx @@ -0,0 +1,56 @@ +import { Box, Flex, Text } from '@chakra-ui/react'; +import { RootState } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIIconButton from 'common/components/IAIIconButton'; +import { motion } from 'framer-motion'; +import { FaTimes } from 'react-icons/fa'; +import { setAdvancedAddScanModel } from '../../store/modelManagerSlice'; +import AdvancedAddCheckpoint from './AdvancedAddCheckpoint'; + +export default function ScanAdvancedAddModels() { + const advancedAddScanModel = useAppSelector( + (state: RootState) => state.modelmanager.advancedAddScanModel + ); + + const dispatch = useAppDispatch(); + + return ( + advancedAddScanModel && ( + + + + Add Checkpoint Model + + } + aria-label="Close Advanced" + onClick={() => dispatch(setAdvancedAddScanModel(null))} + size="sm" + /> + + + + ) + ); +} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx index e95264d2de..c7b4da9479 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx @@ -1,13 +1,24 @@ import { Flex } from '@chakra-ui/react'; import FoundModelsList from './FoundModelsList'; +import ScanAdvancedAddModels from './ScanAdvancedAddModels'; import SearchFolderForm from './SearchFolderForm'; export default function ScanModels() { return ( - + - - + + + + + ); From 98e6a5671430068869137f48a524bd38625daecb Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 17 Jul 2023 17:09:41 +1200 Subject: [PATCH 22/34] fix: Model Manager jank / bugs / refinement --- .../tabs/ModelManager/ModelManagerTab.tsx | 4 +-- .../AddModelsPanel/AdvancedAddCheckpoint.tsx | 4 ++- .../AddModelsPanel/FoundModelsList.tsx | 25 ++++++++++++++++--- .../AddModelsPanel/SearchFolderForm.tsx | 4 +-- ...dModelsPanel.tsx => ImportModelsPanel.tsx} | 6 ++--- 5 files changed, 31 insertions(+), 12 deletions(-) rename invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/{AddModelsPanel.tsx => ImportModelsPanel.tsx} (89%) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx index 6f9a836902..d397058795 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx @@ -1,7 +1,7 @@ import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react'; import i18n from 'i18n'; import { ReactNode, memo } from 'react'; -import AddModelsPanel from './subpanels/AddModelsPanel'; +import ImportModelsPanel from './subpanels/ImportModelsPanel'; import MergeModelsPanel from './subpanels/MergeModelsPanel'; import ModelManagerPanel from './subpanels/ModelManagerPanel'; @@ -22,7 +22,7 @@ const tabs: ModelManagerTabInfo[] = [ { id: 'importModels', label: i18n.t('modelManager.importModels'), - content: , + content: , }, { id: 'mergeModels', diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx index a3f7c404e2..ededadaa06 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx @@ -28,7 +28,9 @@ export default function AdvancedAddCheckpoint( const advancedAddCheckpointForm = useForm({ initialValues: { - model_name: model_path ? model_path.split('\\').splice(-1)[0] : '', + model_name: model_path + ? model_path.split('\\').splice(-1)[0].split('.')[0] + : '', base_model: 'sd-1', model_type: 'main', path: model_path ? model_path : '', diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx index 8393ecdee2..ad245703c9 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx @@ -36,6 +36,7 @@ export default function FoundModelsList() { const quickAddHandler = useCallback( (e: MouseEvent) => { + const model_name = e.currentTarget.id.split('\\').splice(-1)[0]; importMainModel({ body: { location: e.currentTarget.id, @@ -46,7 +47,7 @@ export default function FoundModelsList() { dispatch( addToast( makeToast({ - title: 'Added Model', + title: `Added Model: ${model_name}`, status: 'success', }) ) @@ -72,7 +73,24 @@ export default function FoundModelsList() { if (!searchFolder) return; if (!foundModels || foundModels.length === 0) { - return No Models Found; + return ( + + No Models Found + + ); } return ( @@ -81,6 +99,7 @@ export default function FoundModelsList() { flexDirection: 'column', gap: 2, w: '100%', + minW: '50%', }} > @@ -114,7 +133,7 @@ export default function FoundModelsList() { }} key={model} > - + {model.split('\\').slice(-1)[0]} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx index 6f17b62c66..dd0aa818c8 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx @@ -6,7 +6,7 @@ import IAIIconButton from 'common/components/IAIIconButton'; import IAIInput from 'common/components/IAIInput'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { FaSearch, FaTrash } from 'react-icons/fa'; +import { FaSearch, FaSync, FaTrash } from 'react-icons/fa'; import { setSearchFolder } from '../../store/modelManagerSlice'; type SearchFolderForm = { @@ -91,7 +91,7 @@ function SearchFolderForm() { } + icon={!searchFolder ? : } fontSize={18} size="sm" type="submit" diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ImportModelsPanel.tsx similarity index 89% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ImportModelsPanel.tsx index cac6c7a136..b0a8c124eb 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ImportModelsPanel.tsx @@ -1,4 +1,4 @@ -import { ButtonGroup, Divider, Flex } from '@chakra-ui/react'; +import { ButtonGroup, Flex } from '@chakra-ui/react'; import IAIButton from 'common/components/IAIButton'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -7,7 +7,7 @@ import ScanModels from './AddModelsPanel/ScanModels'; type AddModelTabs = 'add' | 'scan'; -export default function AddModelsPanel() { +export default function ImportModelsPanel() { const [addModelTab, setAddModelTab] = useState('add'); const { t } = useTranslation(); @@ -32,8 +32,6 @@ export default function AddModelsPanel() { - - {addModelTab == 'add' && } {addModelTab == 'scan' && } From 41e7b008fb97e10f8cf2a354b5d1f7ddf838806a Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 17 Jul 2023 17:32:34 +1200 Subject: [PATCH 23/34] feat: Add search to Scanned Models --- .../AddModelsPanel/FoundModelsList.tsx | 65 +++++++++++++++---- .../web/src/services/api/endpoints/models.ts | 2 +- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx index ad245703c9..09e0cf72dc 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx @@ -3,10 +3,13 @@ import { makeToast } from 'app/components/Toaster'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; +import IAIInput from 'common/components/IAIInput'; import { addToast } from 'features/system/store/systemSlice'; -import { difference, map, values } from 'lodash-es'; -import { MouseEvent, useCallback } from 'react'; +import { difference, forEach, map, values } from 'lodash-es'; +import { ChangeEvent, MouseEvent, useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { + SearchFolderResponse, useGetMainModelsQuery, useGetModelsInFolderQuery, useImportMainModelsMutation, @@ -17,22 +20,35 @@ export default function FoundModelsList() { const searchFolder = useAppSelector( (state: RootState) => state.modelmanager.searchFolder ); - - // Get all model paths from a given directory - const { data: foundModels } = useGetModelsInFolderQuery({ - search_path: searchFolder ? searchFolder : '', - }); + const [nameFilter, setNameFilter] = useState(''); // Get paths of models that are already installed const { data: installedModels } = useGetMainModelsQuery(); - const installedModelValues = values(installedModels?.entities); - const installedModelPaths = map(installedModelValues, 'path'); - // Only select models those that aren't already installed to Invoke - const notInstalledModels = difference(foundModels, installedModelPaths); + // Get all model paths from a given directory + const { foundModels, notInstalledModels, filteredModels } = + useGetModelsInFolderQuery( + { + search_path: searchFolder ? searchFolder : '', + }, + { + selectFromResult: ({ data }) => { + const installedModelValues = values(installedModels?.entities); + const installedModelPaths = map(installedModelValues, 'path'); + // Only select models those that aren't already installed to Invoke + const notInstalledModels = difference(data, installedModelPaths); + return { + foundModels: data, + notInstalledModels: notInstalledModels, + filteredModels: foundModelsFilter(notInstalledModels, nameFilter), + }; + }, + } + ); const [importMainModel, { isLoading }] = useImportMainModelsMutation(); const dispatch = useAppDispatch(); + const { t } = useTranslation(); const quickAddHandler = useCallback( (e: MouseEvent) => { @@ -69,6 +85,10 @@ export default function FoundModelsList() { [dispatch, importMainModel] ); + const handleSearchFilter = useCallback((e: ChangeEvent) => { + setNameFilter(e.target.value); + }, []); + const renderFoundModels = () => { if (!searchFolder) return; @@ -102,6 +122,10 @@ export default function FoundModelsList() { minW: '50%', }} > + - {notInstalledModels.map((model) => ( + {filteredModels.map((model) => ( { + const filteredModels: SearchFolderResponse = []; + forEach(data, (model) => { + if (!model) { + return; + } + + if (model.includes(nameFilter)) { + filteredModels.push(model); + } + }); + return filteredModels; +}; diff --git a/invokeai/frontend/web/src/services/api/endpoints/models.ts b/invokeai/frontend/web/src/services/api/endpoints/models.ts index d9cbaf69dd..5ca3362fd6 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/models.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/models.ts @@ -93,7 +93,7 @@ type AddMainModelArg = { type AddMainModelResponse = paths['/api/v1/models/add']['post']['responses']['201']['content']['application/json']; -type SearchFolderResponse = +export type SearchFolderResponse = paths['/api/v1/models/search']['get']['responses']['200']['content']['application/json']; type CheckpointConfigsResponse = From f398fe4136e40c1d5788a9d35367d63153eade56 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 17 Jul 2023 17:59:05 +1200 Subject: [PATCH 24/34] fix: Merge models not respecting save directory --- .../subpanels/ModelManagerPanel/ModelConvert.tsx | 2 +- invokeai/frontend/web/src/services/api/endpoints/models.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx index 9c7130f2ad..741afba025 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx @@ -50,7 +50,7 @@ export default function ModelConvert(props: ModelConvertProps) { const responseBody = { base_model: model.base_model, model_name: model.model_name, - body: { + params: { convert_dest_directory: saveLocation === 'Custom' ? customSaveLocation : undefined, }, diff --git a/invokeai/frontend/web/src/services/api/endpoints/models.ts b/invokeai/frontend/web/src/services/api/endpoints/models.ts index 5ca3362fd6..015ba6ce2c 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/models.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/models.ts @@ -65,7 +65,7 @@ type DeleteMainModelResponse = void; type ConvertMainModelArg = { base_model: BaseModelType; model_name: string; - body: ConvertModelConfig; + params: ConvertModelConfig; }; type ConvertMainModelResponse = @@ -225,11 +225,11 @@ export const modelsApi = api.injectEndpoints({ ConvertMainModelResponse, ConvertMainModelArg >({ - query: ({ base_model, model_name, body }) => { + query: ({ base_model, model_name, params }) => { return { url: `models/convert/${base_model}/main/${model_name}`, method: 'PUT', - body: body, + params: params, }; }, invalidatesTags: [{ type: 'MainModel', id: LIST_TAG }], From cfdaa30d441b1efe8a2c8b16efd6b001a306999c Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 17 Jul 2023 19:40:08 +1200 Subject: [PATCH 25/34] feat: Scan models add to differentiate between ckpt and diffusers --- .../AddModelsPanel/AdvancedAddDiffusers.tsx | 12 +++-- .../AddModelsPanel/AdvancedAddModels.tsx | 4 +- .../AddModelsPanel/ScanAdvancedAddModels.tsx | 51 +++++++++++++++++-- .../AddModelsPanel/SearchFolderForm.tsx | 10 +++- 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx index d7b1561912..7c0bcf0ab1 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx @@ -11,18 +11,23 @@ import { DiffusersModelConfig } from 'services/api/types'; import BaseModelSelect from '../shared/BaseModelSelect'; import ModelVariantSelect from '../shared/ModelVariantSelect'; -export default function AdvancedAddDiffusers() { +type AdvancedAddDiffusersProps = { + model_path?: string; +}; + +export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) { const { t } = useTranslation(); const dispatch = useAppDispatch(); + const { model_path } = props; const [addMainModel] = useAddMainModelsMutation(); const advancedAddDiffusersForm = useForm({ initialValues: { - model_name: '', + model_name: model_path ? model_path.split('\\').splice(-1)[0] : '', base_model: 'sd-1', model_type: 'main', - path: '', + path: model_path ? model_path : '', description: '', model_format: 'diffusers', error: undefined, @@ -30,6 +35,7 @@ export default function AdvancedAddDiffusers() { variant: 'normal', }, }); + const advancedAddDiffusersFormHandler = (values: DiffusersModelConfig) => { addMainModel({ body: values, diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx index 18d576c843..88e83fadc8 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx @@ -5,12 +5,12 @@ import { useState } from 'react'; import AdvancedAddCheckpoint from './AdvancedAddCheckpoint'; import AdvancedAddDiffusers from './AdvancedAddDiffusers'; -const advancedAddModeData: SelectItem[] = [ +export const advancedAddModeData: SelectItem[] = [ { label: 'Diffusers', value: 'diffusers' }, { label: 'Checkpoint / Safetensors', value: 'checkpoint' }, ]; -type ManualAddMode = 'diffusers' | 'checkpoint'; +export type ManualAddMode = 'diffusers' | 'checkpoint'; export default function AdvancedAddModels() { const [advancedAddMode, setAdvancedAddMode] = diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx index 65454c3363..e5b89c7bbf 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx @@ -2,16 +2,36 @@ import { Box, Flex, Text } from '@chakra-ui/react'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIIconButton from 'common/components/IAIIconButton'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; import { motion } from 'framer-motion'; +import { useEffect, useState } from 'react'; import { FaTimes } from 'react-icons/fa'; import { setAdvancedAddScanModel } from '../../store/modelManagerSlice'; import AdvancedAddCheckpoint from './AdvancedAddCheckpoint'; +import AdvancedAddDiffusers from './AdvancedAddDiffusers'; +import { ManualAddMode, advancedAddModeData } from './AdvancedAddModels'; export default function ScanAdvancedAddModels() { const advancedAddScanModel = useAppSelector( (state: RootState) => state.modelmanager.advancedAddScanModel ); + const [advancedAddMode, setAdvancedAddMode] = + useState('diffusers'); + + const [isCheckpoint, setIsCheckpoint] = useState( + advancedAddScanModel && + ['.ckpt', '.safetensors', '.pth', '.pt'].some((ext) => + advancedAddScanModel.endsWith(ext) + ) + ); + + useEffect(() => { + isCheckpoint + ? setAdvancedAddMode('checkpoint') + : setAdvancedAddMode('diffusers'); + }, [setAdvancedAddMode, isCheckpoint]); + const dispatch = useAppDispatch(); return ( @@ -37,7 +57,9 @@ export default function ScanAdvancedAddModels() { > - Add Checkpoint Model + {isCheckpoint || advancedAddMode === 'checkpoint' + ? 'Add Checkpoint Model' + : 'Add Diffusers Model'} } @@ -46,10 +68,31 @@ export default function ScanAdvancedAddModels() { size="sm" /> - { + if (!v) return; + setAdvancedAddMode(v as ManualAddMode); + if (v === 'checkpoint') { + setIsCheckpoint(true); + } else { + setIsCheckpoint(false); + } + }} /> + {isCheckpoint ? ( + + ) : ( + + )} ) ); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx index dd0aa818c8..255307f6f4 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx @@ -7,7 +7,10 @@ import IAIInput from 'common/components/IAIInput'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { FaSearch, FaSync, FaTrash } from 'react-icons/fa'; -import { setSearchFolder } from '../../store/modelManagerSlice'; +import { + setAdvancedAddScanModel, + setSearchFolder, +} from '../../store/modelManagerSlice'; type SearchFolderForm = { folder: string; @@ -101,7 +104,10 @@ function SearchFolderForm() { tooltip={t('modelManager.clearCheckpointFolder')} icon={} size="sm" - onClick={() => dispatch(setSearchFolder(null))} + onClick={() => { + dispatch(setSearchFolder(null)); + dispatch(setAdvancedAddScanModel(null)); + }} isDisabled={!searchFolder} colorScheme="red" /> From 107ca6bf4798757f2ad6b3111a709bf28746c698 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Mon, 17 Jul 2023 07:26:05 -0400 Subject: [PATCH 26/34] expose model paths as absolute to web models API --- invokeai/backend/model_management/model_manager.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index c62f42b88d..3a4f9c1e66 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -568,6 +568,9 @@ class ModelManager(object): model_type=cur_model_type, ) + # expose paths as absolute + if path := model_dict.get('path'): + model_dict['path'] = str(self.app_config.root_path / path) models.append(model_dict) return models @@ -635,6 +638,11 @@ class ModelManager(object): The returned dict has the same format as the dict returned by model_info(). """ + # relativize paths as they go in - this makes it easier to move the root directory around + self.logger.debug(model_attributes) + if path := model_attributes.get('path'): + if Path(path).is_relative_to(self.app_config.root_path): + model_attributes['path'] = str(Path(path).relative_to(self.app_config.root_path)) model_class = MODEL_CLASSES[base_model][model_type] model_config = model_class.create_config(**model_attributes) From 3fba262c94bc4bfe7ddb1687f391606af20a069a Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Mon, 17 Jul 2023 07:29:26 -0400 Subject: [PATCH 27/34] expose paths as absolute to web api --- invokeai/backend/model_management/model_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 3a4f9c1e66..290eeed88c 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -568,7 +568,7 @@ class ModelManager(object): model_type=cur_model_type, ) - # expose paths as absolute + # expose paths as absolute to help web UI if path := model_dict.get('path'): model_dict['path'] = str(self.app_config.root_path / path) models.append(model_dict) From 0ea8d3c30c25a67945be2b4e1fbc50b15bfbed80 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Mon, 17 Jul 2023 07:50:06 -0400 Subject: [PATCH 28/34] prevent crash on rename operation on models in models directory --- invokeai/backend/model_management/model_manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 290eeed88c..30eecbd26f 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -639,7 +639,6 @@ class ModelManager(object): model_info(). """ # relativize paths as they go in - this makes it easier to move the root directory around - self.logger.debug(model_attributes) if path := model_attributes.get('path'): if Path(path).is_relative_to(self.app_config.root_path): model_attributes['path'] = str(Path(path).relative_to(self.app_config.root_path)) @@ -708,7 +707,7 @@ class ModelManager(object): # if this is a model file/directory that we manage ourselves, we need to move it if old_path.is_relative_to(self.app_config.models_path): - new_path = self.app_config.root_path / 'models' / new_base.value / model_type.value / new_name + new_path = self.app_config.root_path / 'models' / BaseModelType(new_base).value / ModelType(model_type).value / new_name move(old_path, new_path) model_cfg.path = str(new_path.relative_to(self.app_config.root_path)) From 0712294c1791eab17c42c9acb1520e5889f0afac Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Tue, 18 Jul 2023 00:29:20 +1200 Subject: [PATCH 29/34] fix: Model Manager light mode color fixes --- .../AddModelsPanel/SearchFolderForm.tsx | 7 +++-- .../subpanels/MergeModelsPanel.tsx | 26 +++++++++---------- .../web/src/theme/components/button.ts | 2 +- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx index 255307f6f4..811b65426b 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx @@ -50,7 +50,7 @@ function SearchFolderForm() { gap: 2, p: 4, borderRadius: 4, - background: 'base.300', + background: 'base.200', alignItems: 'center', _dark: { background: 'base.800', @@ -60,6 +60,7 @@ function SearchFolderForm() { diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx index bd03092085..e6b7b728bf 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx @@ -1,11 +1,4 @@ -import { - Flex, - Radio, - RadioGroup, - Text, - Tooltip, - useColorMode, -} from '@chakra-ui/react'; +import { Flex, Radio, RadioGroup, Text, Tooltip } from '@chakra-ui/react'; import { makeToast } from 'app/components/Toaster'; import { useAppDispatch } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; @@ -23,7 +16,6 @@ import { useMergeMainModelsMutation, } from 'services/api/endpoints/models'; import { BaseModelType, MergeModelConfig } from 'services/api/types'; -import { mode } from 'theme/util/mode'; const baseModelTypeSelectData = [ { label: 'Stable Diffusion 1', value: 'sd-1' }, @@ -38,7 +30,6 @@ type MergeInterpolationMethods = export default function MergeModelsPanel() { const { t } = useTranslation(); - const { colorMode } = useColorMode(); const dispatch = useAppDispatch(); const { data } = useGetMainModelsQuery(); @@ -227,7 +218,10 @@ export default function MergeModelsPanel() { padding: 4, borderRadius: 'base', gap: 4, - bg: mode('base.100', 'base.800')(colorMode), + bg: 'base.200', + _dark: { + bg: 'base.800', + }, }} > @@ -294,7 +291,10 @@ export default function MergeModelsPanel() { padding: 4, borderRadius: 'base', gap: 4, - bg: 'base.900', + bg: 'base.200', + _dark: { + bg: 'base.900', + }, }} > diff --git a/invokeai/frontend/web/src/theme/components/button.ts b/invokeai/frontend/web/src/theme/components/button.ts index a59b9df826..3a1a56a1e9 100644 --- a/invokeai/frontend/web/src/theme/components/button.ts +++ b/invokeai/frontend/web/src/theme/components/button.ts @@ -16,7 +16,7 @@ const invokeAI = defineStyle((props) => { }; return { - bg: mode('base.200', 'base.600')(props), + bg: mode('base.250', 'base.600')(props), color: mode('base.850', 'base.100')(props), borderRadius: 'base', svg: { From 08854b6d68203a0c6958977b27abd693c474d316 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Mon, 17 Jul 2023 10:00:28 -0400 Subject: [PATCH 30/34] keep model path consistent with model manager key in model update api --- invokeai/app/api/routers/models.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/invokeai/app/api/routers/models.py b/invokeai/app/api/routers/models.py index 923a3767a3..683b12fbd5 100644 --- a/invokeai/app/api/routers/models.py +++ b/invokeai/app/api/routers/models.py @@ -63,20 +63,35 @@ async def update_model( ) -> UpdateModelResponse: """ Update model contents with a new config. If the model name or base fields are changed, then the model is renamed. """ logger = ApiDependencies.invoker.services.logger + + try: + previous_info = ApiDependencies.invoker.services.model_manager.list_model( + model_name=model_name, + base_model=base_model, + model_type=model_type, + ) + # rename operation requested if info.model_name != model_name or info.base_model != base_model: - result = ApiDependencies.invoker.services.model_manager.rename_model( + ApiDependencies.invoker.services.model_manager.rename_model( base_model = base_model, model_type = model_type, model_name = model_name, new_name = info.model_name, new_base = info.base_model, ) - logger.debug(f'renaming result = {result}') logger.info(f'Successfully renamed {base_model}/{model_name}=>{info.base_model}/{info.model_name}') + # update information to support an update of attributes model_name = info.model_name base_model = info.base_model + new_info = ApiDependencies.invoker.services.model_manager.list_model( + model_name=model_name, + base_model=base_model, + model_type=model_type, + ) + if new_info.get('path') != previous_info.get('path'): # model manager moved model path during rename - don't overwrite it + info.path = new_info.get('path') ApiDependencies.invoker.services.model_manager.update_model( model_name=model_name, From 337399ff7c81c62b23a6b52f4fae77d5a8da9885 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Tue, 18 Jul 2023 11:57:45 +1200 Subject: [PATCH 31/34] fix: Add API tags for Scanned Models --- .../web/src/services/api/endpoints/models.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/invokeai/frontend/web/src/services/api/endpoints/models.ts b/invokeai/frontend/web/src/services/api/endpoints/models.ts index 015ba6ce2c..27e9aefcdb 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/models.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/models.ts @@ -385,6 +385,21 @@ export const modelsApi = api.injectEndpoints({ url: `/models/search?${folderQueryStr}`, }; }, + providesTags: (result, error, arg) => { + const tags: ApiFullTagDescription[] = [ + { type: 'ScannedModels', id: LIST_TAG }, + ]; + + if (result) { + tags.push( + ...result.map((id) => ({ + type: 'ScannedModels' as const, + id, + })) + ); + } + return tags; + }, }), getCheckpointConfigs: build.query({ query: () => { From 72c1a8db08946ce337acb61d56063331e1a3e911 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Tue, 18 Jul 2023 11:58:04 +1200 Subject: [PATCH 32/34] fix: Diffusers Model edit form not closing on Scan Add --- .../subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx index 7c0bcf0ab1..ce8da9289b 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx @@ -8,6 +8,7 @@ import { addToast } from 'features/system/store/systemSlice'; import { useTranslation } from 'react-i18next'; import { useAddMainModelsMutation } from 'services/api/endpoints/models'; import { DiffusersModelConfig } from 'services/api/types'; +import { setAdvancedAddScanModel } from '../../store/modelManagerSlice'; import BaseModelSelect from '../shared/BaseModelSelect'; import ModelVariantSelect from '../shared/ModelVariantSelect'; @@ -51,6 +52,10 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) { ) ); advancedAddDiffusersForm.reset(); + // Close Advanced Panel in Scan Models tab + if (model_path) { + dispatch(setAdvancedAddScanModel(null)); + } }) .catch((error) => { if (error) { From 715e3217d0d504c1a1e4b136b574f4769c40f1a8 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:14:35 +1200 Subject: [PATCH 33/34] feat: Improve Scanned / Model Lists layout - Now inside ScrollArea - Now displays installed models --- .../web/src/common/components/IAIInput.tsx | 18 ++- .../AddModelsPanel/FoundModelsList.tsx | 144 +++++++++++------- .../AddModelsPanel/ScanAdvancedAddModels.tsx | 4 +- .../subpanels/AddModelsPanel/ScanModels.tsx | 2 +- .../AddModelsPanel/SearchFolderForm.tsx | 7 +- .../subpanels/ModelManagerPanel/ModelList.tsx | 10 +- 6 files changed, 118 insertions(+), 67 deletions(-) diff --git a/invokeai/frontend/web/src/common/components/IAIInput.tsx b/invokeai/frontend/web/src/common/components/IAIInput.tsx index d114fc5968..31dac20998 100644 --- a/invokeai/frontend/web/src/common/components/IAIInput.tsx +++ b/invokeai/frontend/web/src/common/components/IAIInput.tsx @@ -8,19 +8,34 @@ import { import { useAppDispatch } from 'app/store/storeHooks'; import { stopPastePropagation } from 'common/util/stopPastePropagation'; import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice'; -import { ChangeEvent, KeyboardEvent, memo, useCallback } from 'react'; +import { + CSSProperties, + ChangeEvent, + KeyboardEvent, + memo, + useCallback, +} from 'react'; interface IAIInputProps extends InputProps { label?: string; + labelPos?: 'top' | 'side'; value?: string; size?: string; onChange?: (e: ChangeEvent) => void; formControlProps?: Omit; } +const labelPosVerticalStyle: CSSProperties = { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: 10, +}; + const IAIInput = (props: IAIInputProps) => { const { label = '', + labelPos = 'top', isDisabled = false, isInvalid, formControlProps, @@ -51,6 +66,7 @@ const IAIInput = (props: IAIInputProps) => { isInvalid={isInvalid} isDisabled={isDisabled} {...formControlProps} + style={labelPos === 'side' ? labelPosVerticalStyle : undefined} > {label !== '' && {label}} { + return models.map((model) => { + return ( + + + + {model.split('\\').slice(-1)[0]} + + + {model} + + + {showActions ? ( + + + Quick Add + + dispatch(setAdvancedAddScanModel(model))} + isLoading={isLoading} + > + Advanced + + + ) : ( + + Installed + + )} + + ); + }); + }; + const renderFoundModels = () => { if (!searchFolder) return; @@ -125,8 +203,12 @@ export default function FoundModelsList() { + + Models Found: {foundModels.length} + - Found Models: {foundModels.length} - - - Not Installed: {notInstalledModels.length} + Not Installed: {filteredModels.length} - {filteredModels.map((model) => ( - - - - {model.split('\\').slice(-1)[0]} - - - {model} - - - - - Quick Add - - dispatch(setAdvancedAddScanModel(model))} - isLoading={isLoading} - > - Advanced - - + + + {renderModels({ models: filteredModels })} + {renderModels({ models: alreadyInstalled, showActions: false })} - ))} + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx index e5b89c7bbf..d49e429306 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx @@ -43,8 +43,8 @@ export default function ScanAdvancedAddModels() { sx={{ display: 'flex', flexDirection: 'column', - minWidth: '50%', - maxHeight: window.innerHeight - 330, + minWidth: '40%', + maxHeight: window.innerHeight - 300, overflow: 'scroll', p: 4, gap: 4, diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx index c7b4da9479..fc1b6b321f 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx @@ -10,7 +10,7 @@ export default function ScanModels() { @@ -67,7 +62,7 @@ function SearchFolderForm() { _dark: { color: 'base.300' }, }} > - Search Folder + Folder {!searchFolder ? ( { return ( - { + + {['all', 'diffusers'].includes(modelFormatFilter) && filteredDiffusersModels.length > 0 && ( From 7c3eb06a71d84fb32a8b11d64a1ca82f162635a3 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:44:16 +1200 Subject: [PATCH 34/34] fix: Scan again not refetching the model list --- .../AddModelsPanel/SearchFolderForm.tsx | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx index 238a4bf0e9..129e82738f 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx @@ -7,6 +7,7 @@ import IAIInput from 'common/components/IAIInput'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { FaSearch, FaSync, FaTrash } from 'react-icons/fa'; +import { useGetModelsInFolderQuery } from 'services/api/endpoints/models'; import { setAdvancedAddScanModel, setSearchFolder, @@ -24,6 +25,10 @@ function SearchFolderForm() { (state: RootState) => state.modelmanager.searchFolder ); + const { refetch: refetchFoundModels } = useGetModelsInFolderQuery({ + search_path: searchFolder ? searchFolder : '', + }); + const searchFolderForm = useForm({ initialValues: { folder: '', @@ -37,6 +42,10 @@ function SearchFolderForm() { [dispatch] ); + const scanAgainHandler = () => { + refetchFoundModels(); + }; + return ( @@ -89,14 +98,26 @@ function SearchFolderForm() { - : } - fontSize={18} - size="sm" - type="submit" - /> + {!searchFolder ? ( + } + fontSize={18} + size="sm" + type="submit" + /> + ) : ( + } + onClick={scanAgainHandler} + fontSize={18} + size="sm" + /> + )} +
- {t('modelManager.modelsFound')}: {foundModels.length} -
- {t('modelManager.selected')}: {modelsToAdd.length} -
+ {t('modelManager.modelsFound')}: {foundModels.length} +
+ {t('modelManager.selected')}: {modelsToAdd.length} +