Skip to content

Commit 04c0700

Browse files
Merge branch 'main' into psychedelicious-patch-1
2 parents 53db91e + c3a7e35 commit 04c0700

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

+17-2
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

+8-1
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

+1
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

+7-2
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

+3
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

+17-1
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

+1
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

+3-3
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

+1-3
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

+3-19
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,

invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
22
import i18n from 'i18n';
33
import { ReactNode, memo } from 'react';
4-
import AddModelsPanel from './subpanels/AddModelsPanel';
4+
import ImportModelsPanel from './subpanels/ImportModelsPanel';
55
import MergeModelsPanel from './subpanels/MergeModelsPanel';
66
import ModelManagerPanel from './subpanels/ModelManagerPanel';
77

8-
type ModelManagerTabName = 'modelManager' | 'addModels' | 'mergeModels';
8+
type ModelManagerTabName = 'modelManager' | 'importModels' | 'mergeModels';
99

1010
type ModelManagerTabInfo = {
1111
id: ModelManagerTabName;
@@ -20,9 +20,9 @@ const tabs: ModelManagerTabInfo[] = [
2020
content: <ModelManagerPanel />,
2121
},
2222
{
23-
id: 'addModels',
24-
label: i18n.t('modelManager.addModel'),
25-
content: <AddModelsPanel />,
23+
id: 'importModels',
24+
label: i18n.t('modelManager.importModels'),
25+
content: <ImportModelsPanel />,
2626
},
2727
{
2828
id: 'mergeModels',
@@ -46,7 +46,7 @@ const ModelManagerTab = () => {
4646
</Tab>
4747
))}
4848
</TabList>
49-
<TabPanels sx={{ w: 'full', h: 'full', p: 4 }}>
49+
<TabPanels sx={{ w: 'full', h: 'full' }}>
5050
{tabs.map((tab) => (
5151
<TabPanel sx={{ w: 'full', h: 'full' }} key={tab.id}>
5252
{tab.content}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
2+
3+
type ModelManagerState = {
4+
searchFolder: string | null;
5+
advancedAddScanModel: string | null;
6+
};
7+
8+
const initialModelManagerState: ModelManagerState = {
9+
searchFolder: null,
10+
advancedAddScanModel: null,
11+
};
12+
13+
export const modelManagerSlice = createSlice({
14+
name: 'modelmanager',
15+
initialState: initialModelManagerState,
16+
reducers: {
17+
setSearchFolder: (state, action: PayloadAction<string | null>) => {
18+
state.searchFolder = action.payload;
19+
},
20+
setAdvancedAddScanModel: (state, action: PayloadAction<string | null>) => {
21+
state.advancedAddScanModel = action.payload;
22+
},
23+
},
24+
});
25+
26+
export const { setSearchFolder, setAdvancedAddScanModel } =
27+
modelManagerSlice.actions;
28+
29+
export default modelManagerSlice.reducer;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { RootState } from 'app/store/store';
2+
3+
export const modelmanagerSelector = (state: RootState) => state.modelmanager;

0 commit comments

Comments
 (0)