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) => { { + 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" + /> + )} +