Skip to content

Commit c3a7e35

Browse files
authored
Model Manager UI 3.0 (#3778)
This PR completely ports over the Model Manager to 3.0 -- all of the functionality has now been restored in addition to the following changes. - Model Manager now has been moved to its own tab on the left hand side. - Model Manager has three tabs - Model Manager, Import Models and Merge Models - The edit forms for the Models now allow the users to update the model name and the base model too along with other details. - Checkpoint Edit form now displays the available config files from InvokeAI and also allows users to supply their own custom config file. - Under Import Models you can directly add models or a scan a folder for your checkpoint files. - Adding models has two modes -- Simple and Advanced. - In Simple Mode, you just simply need to pass a path and InvokeAI will try to determine kind of model it is and fill up the rest of the details accordingly. This input lets you supply local paths to diffusers / local paths to checkpoints / huggingface repo ID's to download models / CivitAI links. - Simple Mode also allows you to download different models types like VAE's and Controlnet models and etc. Not just main models. - In cases where the auto detection system of InvokeAI fails to read a model correctly, you can take the manual approach and go to Advanced where you can configure your model while adding it exactly the way you want it. Both Diffusers and Checkpoint models now have their own custom forms. - Scan Models has been cleaned up. It will now only display the models that are not already installed to InvokeAI. And each item will have two options - Quick Add and Advanced .. replicating the Add Model behavior from above. - Scan Models now has a search bar for you to search through your scanned models. - Merge Models functionality has been restored. This is a wrap for this PR. **TODO: (Probably for 3.1)** - Add model management for model types such as VAE's and ControlNet Models - Replace the VAE slot on the edit forms with the installed VAE drop down + custom option
2 parents 0edb31f + ec3c15e commit c3a7e35

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1458
-862
lines changed

invokeai/app/api/routers/models.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,20 +63,35 @@ async def update_model(
6363
) -> UpdateModelResponse:
6464
""" Update model contents with a new config. If the model name or base fields are changed, then the model is renamed. """
6565
logger = ApiDependencies.invoker.services.logger
66+
67+
6668
try:
69+
previous_info = ApiDependencies.invoker.services.model_manager.list_model(
70+
model_name=model_name,
71+
base_model=base_model,
72+
model_type=model_type,
73+
)
74+
6775
# rename operation requested
6876
if info.model_name != model_name or info.base_model != base_model:
69-
result = ApiDependencies.invoker.services.model_manager.rename_model(
77+
ApiDependencies.invoker.services.model_manager.rename_model(
7078
base_model = base_model,
7179
model_type = model_type,
7280
model_name = model_name,
7381
new_name = info.model_name,
7482
new_base = info.base_model,
7583
)
76-
logger.debug(f'renaming result = {result}')
7784
logger.info(f'Successfully renamed {base_model}/{model_name}=>{info.base_model}/{info.model_name}')
85+
# update information to support an update of attributes
7886
model_name = info.model_name
7987
base_model = info.base_model
88+
new_info = ApiDependencies.invoker.services.model_manager.list_model(
89+
model_name=model_name,
90+
base_model=base_model,
91+
model_type=model_type,
92+
)
93+
if new_info.get('path') != previous_info.get('path'): # model manager moved model path during rename - don't overwrite it
94+
info.path = new_info.get('path')
8095

8196
ApiDependencies.invoker.services.model_manager.update_model(
8297
model_name=model_name,

invokeai/backend/model_management/model_manager.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,9 @@ def list_models(
568568
model_type=cur_model_type,
569569
)
570570

571+
# expose paths as absolute to help web UI
572+
if path := model_dict.get('path'):
573+
model_dict['path'] = str(self.app_config.root_path / path)
571574
models.append(model_dict)
572575

573576
return models
@@ -635,6 +638,10 @@ def add_model(
635638
The returned dict has the same format as the dict returned by
636639
model_info().
637640
"""
641+
# relativize paths as they go in - this makes it easier to move the root directory around
642+
if path := model_attributes.get('path'):
643+
if Path(path).is_relative_to(self.app_config.root_path):
644+
model_attributes['path'] = str(Path(path).relative_to(self.app_config.root_path))
638645

639646
model_class = MODEL_CLASSES[base_model][model_type]
640647
model_config = model_class.create_config(**model_attributes)
@@ -700,7 +707,7 @@ def rename_model(
700707

701708
# if this is a model file/directory that we manage ourselves, we need to move it
702709
if old_path.is_relative_to(self.app_config.models_path):
703-
new_path = self.app_config.root_path / 'models' / new_base.value / model_type.value / new_name
710+
new_path = self.app_config.root_path / 'models' / BaseModelType(new_base).value / ModelType(model_type).value / new_name
704711
move(old_path, new_path)
705712
model_cfg.path = str(new_path.relative_to(self.app_config.root_path))
706713

invokeai/backend/model_management/models/vae.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
calc_model_size_by_data,
1717
classproperty,
1818
InvalidModelException,
19+
ModelNotFoundException,
1920
)
2021
from invokeai.app.services.config import InvokeAIAppConfig
2122
from diffusers.utils import is_safetensors_available

invokeai/frontend/web/public/locales/en.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@
399399
"deleteModel": "Delete Model",
400400
"deleteConfig": "Delete Config",
401401
"deleteMsg1": "Are you sure you want to delete this model from InvokeAI?",
402+
"modelDeleted": "Model Deleted",
403+
"modelDeleteFailed": "Failed to delete model",
402404
"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.",
403405
"formMessageDiffusersModelLocation": "Diffusers Model Location",
404406
"formMessageDiffusersModelLocationDesc": "Please enter at least one.",
@@ -408,11 +410,13 @@
408410
"convertToDiffusers": "Convert To Diffusers",
409411
"convertToDiffusersHelpText1": "This model will be converted to the 🧨 Diffusers format.",
410412
"convertToDiffusersHelpText2": "This process will replace your Model Manager entry with the Diffusers version of the same model.",
411-
"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.",
413+
"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.",
412414
"convertToDiffusersHelpText4": "This is a one time process only. It might take around 30s-60s depending on the specifications of your computer.",
413415
"convertToDiffusersHelpText5": "Please make sure you have enough disk space. Models generally vary between 2GB-7GB in size.",
414416
"convertToDiffusersHelpText6": "Do you wish to convert this model?",
415417
"convertToDiffusersSaveLocation": "Save Location",
418+
"noCustomLocationProvided": "No Custom Location Provided",
419+
"convertingModelBegin": "Converting Model. Please wait.",
416420
"v1": "v1",
417421
"v2_base": "v2 (512px)",
418422
"v2_768": "v2 (768px)",
@@ -450,7 +454,8 @@
450454
"none": "none",
451455
"addDifference": "Add Difference",
452456
"pickModelType": "Pick Model Type",
453-
"selectModel": "Select Model"
457+
"selectModel": "Select Model",
458+
"importModels": "Import Models"
454459
},
455460
"parameters": {
456461
"general": "General",

invokeai/frontend/web/src/app/store/store.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import generationReducer from 'features/parameters/store/generationSlice';
2121
import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
2222
import configReducer from 'features/system/store/configSlice';
2323
import systemReducer from 'features/system/store/systemSlice';
24+
import modelmanagerReducer from 'features/ui/components/tabs/ModelManager/store/modelManagerSlice';
2425
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
2526
import uiReducer from 'features/ui/store/uiSlice';
2627

@@ -49,6 +50,7 @@ const allReducers = {
4950
dynamicPrompts: dynamicPromptsReducer,
5051
imageDeletion: imageDeletionReducer,
5152
lora: loraReducer,
53+
modelmanager: modelmanagerReducer,
5254
[api.reducerPath]: api.reducer,
5355
};
5456

@@ -67,6 +69,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [
6769
'controlNet',
6870
'dynamicPrompts',
6971
'lora',
72+
'modelmanager',
7073
];
7174

7275
export const store = configureStore({

invokeai/frontend/web/src/common/components/IAIInput.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,34 @@ import {
88
import { useAppDispatch } from 'app/store/storeHooks';
99
import { stopPastePropagation } from 'common/util/stopPastePropagation';
1010
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
11-
import { ChangeEvent, KeyboardEvent, memo, useCallback } from 'react';
11+
import {
12+
CSSProperties,
13+
ChangeEvent,
14+
KeyboardEvent,
15+
memo,
16+
useCallback,
17+
} from 'react';
1218

1319
interface IAIInputProps extends InputProps {
1420
label?: string;
21+
labelPos?: 'top' | 'side';
1522
value?: string;
1623
size?: string;
1724
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
1825
formControlProps?: Omit<FormControlProps, 'isInvalid' | 'isDisabled'>;
1926
}
2027

28+
const labelPosVerticalStyle: CSSProperties = {
29+
display: 'flex',
30+
flexDirection: 'row',
31+
alignItems: 'center',
32+
gap: 10,
33+
};
34+
2135
const IAIInput = (props: IAIInputProps) => {
2236
const {
2337
label = '',
38+
labelPos = 'top',
2439
isDisabled = false,
2540
isInvalid,
2641
formControlProps,
@@ -51,6 +66,7 @@ const IAIInput = (props: IAIInputProps) => {
5166
isInvalid={isInvalid}
5267
isDisabled={isDisabled}
5368
{...formControlProps}
69+
style={labelPos === 'side' ? labelPosVerticalStyle : undefined}
5470
>
5571
{label !== '' && <FormLabel>{label}</FormLabel>}
5672
<Input

invokeai/frontend/web/src/common/components/IAIMantineInput.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export default function IAIMantineTextInput(props: IAIMantineTextInputProps) {
3636
label: {
3737
color: mode(base700, base300)(colorMode),
3838
fontWeight: 'normal',
39+
marginBottom: 4,
3940
},
4041
})}
4142
{...rest}

invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ export type IAISelectDataType = {
99
tooltip?: string;
1010
};
1111

12-
type IAISelectProps = Omit<SelectProps, 'label'> & {
12+
export type IAISelectProps = Omit<SelectProps, 'label'> & {
1313
tooltip?: string;
1414
inputRef?: RefObject<HTMLInputElement>;
1515
label?: string;
1616
};
1717

1818
const IAIMantineSelect = (props: IAISelectProps) => {
19-
const { tooltip, inputRef, label, disabled, ...rest } = props;
19+
const { tooltip, inputRef, label, disabled, required, ...rest } = props;
2020

2121
const styles = useMantineSelectStyles();
2222

@@ -25,7 +25,7 @@ const IAIMantineSelect = (props: IAISelectProps) => {
2525
<Select
2626
label={
2727
label ? (
28-
<FormControl isDisabled={disabled}>
28+
<FormControl isRequired={required} isDisabled={disabled}>
2929
<FormLabel>{label}</FormLabel>
3030
</FormControl>
3131
) : undefined

invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@ import {
1616
ASSETS_CATEGORIES,
1717
IMAGE_CATEGORIES,
1818
IMAGE_LIMIT,
19-
selectImagesAll,
2019
} from 'features/gallery//store/gallerySlice';
2120
import { selectFilteredImages } from 'features/gallery/store/gallerySelectors';
2221
import { VirtuosoGrid } from 'react-virtuoso';
2322
import { receivedPageOfImages } from 'services/api/thunks/image';
23+
import { useListBoardImagesQuery } from '../../../../services/api/endpoints/boardImages';
2424
import ImageGridItemContainer from './ImageGridItemContainer';
2525
import ImageGridListContainer from './ImageGridListContainer';
26-
import { useListBoardImagesQuery } from '../../../../services/api/endpoints/boardImages';
2726

2827
const selector = createSelector(
2928
[stateSelector, selectFilteredImages],
@@ -180,7 +179,6 @@ const GalleryImageGrid = () => {
180179
</Box>
181180
);
182181
}
183-
console.log({ selectedBoardId });
184182

185183
if (status !== 'rejected') {
186184
return (

invokeai/frontend/web/src/features/system/store/systemSlice.ts

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { UseToastOptions } from '@chakra-ui/react';
22
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
3-
import * as InvokeAI from 'app/types/invokeai';
43

54
import { InvokeLogLevel } from 'app/logging/useLogger';
65
import { userInvoked } from 'app/store/actions';
76
import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice';
8-
import { TFuncKey, t } from 'i18next';
7+
import { t } from 'i18next';
98
import { LogLevelName } from 'roarr';
109
import { imageUploaded } from 'services/api/thunks/image';
1110
import {
@@ -44,8 +43,6 @@ export interface SystemState {
4443
isCancelable: boolean;
4544
enableImageDebugging: boolean;
4645
toastQueue: UseToastOptions[];
47-
searchFolder: string | null;
48-
foundModels: InvokeAI.FoundModel[] | null;
4946
/**
5047
* The current progress image
5148
*/
@@ -79,7 +76,7 @@ export interface SystemState {
7976
*/
8077
consoleLogLevel: InvokeLogLevel;
8178
shouldLogToConsole: boolean;
82-
statusTranslationKey: TFuncKey;
79+
statusTranslationKey: any;
8380
/**
8481
* When a session is canceled, its ID is stored here until a new session is created.
8582
*/
@@ -106,8 +103,6 @@ export const initialSystemState: SystemState = {
106103
isCancelable: true,
107104
enableImageDebugging: false,
108105
toastQueue: [],
109-
searchFolder: null,
110-
foundModels: null,
111106
progressImage: null,
112107
shouldAntialiasProgressImage: false,
113108
sessionId: null,
@@ -132,7 +127,7 @@ export const systemSlice = createSlice({
132127
setIsProcessing: (state, action: PayloadAction<boolean>) => {
133128
state.isProcessing = action.payload;
134129
},
135-
setCurrentStatus: (state, action: PayloadAction<TFuncKey>) => {
130+
setCurrentStatus: (state, action: any) => {
136131
state.statusTranslationKey = action.payload;
137132
},
138133
setShouldConfirmOnDelete: (state, action: PayloadAction<boolean>) => {
@@ -153,15 +148,6 @@ export const systemSlice = createSlice({
153148
clearToastQueue: (state) => {
154149
state.toastQueue = [];
155150
},
156-
setSearchFolder: (state, action: PayloadAction<string | null>) => {
157-
state.searchFolder = action.payload;
158-
},
159-
setFoundModels: (
160-
state,
161-
action: PayloadAction<InvokeAI.FoundModel[] | null>
162-
) => {
163-
state.foundModels = action.payload;
164-
},
165151
/**
166152
* A cancel was scheduled
167153
*/
@@ -426,8 +412,6 @@ export const {
426412
setEnableImageDebugging,
427413
addToast,
428414
clearToastQueue,
429-
setSearchFolder,
430-
setFoundModels,
431415
cancelScheduled,
432416
scheduledCancelAborted,
433417
cancelTypeChanged,

0 commit comments

Comments
 (0)