Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
690331b
chore: Regen Schema
blessedcoolant Jul 15, 2023
79ca0d0
feat: Allow user to pick where to saved merged model
blessedcoolant Jul 15, 2023
8c8eddc
feat: Handle toasts for Model Delete
blessedcoolant Jul 15, 2023
9769b48
feat: Add Custom location support for model conversion
blessedcoolant Jul 15, 2023
558c26d
feat: Create Model Manager Store
blessedcoolant Jul 15, 2023
4a2f34f
wip: Model Search
blessedcoolant Jul 15, 2023
dcbb3dc
Merge branch 'main' into mm-ui
blessedcoolant Jul 15, 2023
e1c0ca1
feat: Add Auto Import Model
blessedcoolant Jul 15, 2023
b1e16aa
fix: placeholder text for Add model input
blessedcoolant Jul 15, 2023
cd033f4
fix: Refine some UI
blessedcoolant Jul 15, 2023
2e0370d
feat: Extract BaseModel and ModelVariant Select's
blessedcoolant Jul 16, 2023
421fcb7
feat: Manual Add Diffusers Model
blessedcoolant Jul 16, 2023
d93d42a
feat: Add Manual Checkpoint / Safetensor Models
blessedcoolant Jul 16, 2023
5b047ba
fix: Mantine Required icon being on new line
blessedcoolant Jul 16, 2023
5351171
cleanup: Scan Models component (to begin anew)
blessedcoolant Jul 16, 2023
92029e6
feat: Update Checkpoint Model Edit to use config picker
blessedcoolant Jul 16, 2023
dabd2bf
fix: Readd model name to edit forms
blessedcoolant Jul 16, 2023
2bc3e36
add missing exception name
Jul 16, 2023
aebd595
Merge branch 'main' into mm-ui
blessedcoolant Jul 17, 2023
641b90c
chore: regen types
blessedcoolant Jul 17, 2023
540f40c
fix: Better file and component naming for Add Models
blessedcoolant Jul 17, 2023
38e6e3b
feat: Add Quick Add To Scan Model
blessedcoolant Jul 17, 2023
cbd5be7
feat: Add Scan Models Advanced Add
blessedcoolant Jul 17, 2023
98e6a56
fix: Model Manager jank / bugs / refinement
blessedcoolant Jul 17, 2023
41e7b00
feat: Add search to Scanned Models
blessedcoolant Jul 17, 2023
f398fe4
fix: Merge models not respecting save directory
blessedcoolant Jul 17, 2023
cfdaa30
feat: Scan models add to differentiate between ckpt and diffusers
blessedcoolant Jul 17, 2023
107ca6b
expose model paths as absolute to web models API
Jul 17, 2023
3fba262
expose paths as absolute to web api
Jul 17, 2023
84a13ff
Merge branch 'mm-ui' of github.com:blessedcoolant/InvokeAI into mm-ui
Jul 17, 2023
0ea8d3c
prevent crash on rename operation on models in models directory
Jul 17, 2023
0712294
fix: Model Manager light mode color fixes
blessedcoolant Jul 17, 2023
08854b6
keep model path consistent with model manager key in model update api
Jul 17, 2023
e03e432
Merge branch 'mm-ui' of github.com:blessedcoolant/InvokeAI into mm-ui
Jul 17, 2023
fca6a5d
Merge branch 'main' into mm-ui
lstein Jul 17, 2023
337399f
fix: Add API tags for Scanned Models
blessedcoolant Jul 17, 2023
72c1a8d
fix: Diffusers Model edit form not closing on Scan Add
blessedcoolant Jul 17, 2023
715e321
feat: Improve Scanned / Model Lists layout
blessedcoolant Jul 18, 2023
7c3eb06
fix: Scan again not refetching the model list
blessedcoolant Jul 18, 2023
ec3c15e
Merge branch 'main' into mm-ui
blessedcoolant Jul 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions invokeai/app/api/routers/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,35 @@ async def update_model(
) -> UpdateModelResponse:
""" Update model contents with a new config. If the model name or base fields are changed, then the model is renamed. """
logger = ApiDependencies.invoker.services.logger


try:
previous_info = ApiDependencies.invoker.services.model_manager.list_model(
model_name=model_name,
base_model=base_model,
model_type=model_type,
)

# rename operation requested
if info.model_name != model_name or info.base_model != base_model:
result = ApiDependencies.invoker.services.model_manager.rename_model(
ApiDependencies.invoker.services.model_manager.rename_model(
base_model = base_model,
model_type = model_type,
model_name = model_name,
new_name = info.model_name,
new_base = info.base_model,
)
logger.debug(f'renaming result = {result}')
logger.info(f'Successfully renamed {base_model}/{model_name}=>{info.base_model}/{info.model_name}')
# update information to support an update of attributes
model_name = info.model_name
base_model = info.base_model
new_info = ApiDependencies.invoker.services.model_manager.list_model(
model_name=model_name,
base_model=base_model,
model_type=model_type,
)
if new_info.get('path') != previous_info.get('path'): # model manager moved model path during rename - don't overwrite it
info.path = new_info.get('path')

ApiDependencies.invoker.services.model_manager.update_model(
model_name=model_name,
Expand Down
9 changes: 8 additions & 1 deletion invokeai/backend/model_management/model_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,9 @@ def list_models(
model_type=cur_model_type,
)

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

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

model_class = MODEL_CLASSES[base_model][model_type]
model_config = model_class.create_config(**model_attributes)
Expand Down Expand Up @@ -700,7 +707,7 @@ def rename_model(

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

Expand Down
1 change: 1 addition & 0 deletions invokeai/backend/model_management/models/vae.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
calc_model_size_by_data,
classproperty,
InvalidModelException,
ModelNotFoundException,
)
from invokeai.app.services.config import InvokeAIAppConfig
from diffusers.utils import is_safetensors_available
Expand Down
9 changes: 7 additions & 2 deletions invokeai/frontend/web/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand All @@ -408,11 +410,13 @@
"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?",
"convertToDiffusersSaveLocation": "Save Location",
"noCustomLocationProvided": "No Custom Location Provided",
"convertingModelBegin": "Converting Model. Please wait.",
"v1": "v1",
"v2_base": "v2 (512px)",
"v2_768": "v2 (768px)",
Expand Down Expand Up @@ -450,7 +454,8 @@
"none": "none",
"addDifference": "Add Difference",
"pickModelType": "Pick Model Type",
"selectModel": "Select Model"
"selectModel": "Select Model",
"importModels": "Import Models"
},
"parameters": {
"general": "General",
Expand Down
3 changes: 3 additions & 0 deletions invokeai/frontend/web/src/app/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -49,6 +50,7 @@ const allReducers = {
dynamicPrompts: dynamicPromptsReducer,
imageDeletion: imageDeletionReducer,
lora: loraReducer,
modelmanager: modelmanagerReducer,
[api.reducerPath]: api.reducer,
};

Expand All @@ -67,6 +69,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [
'controlNet',
'dynamicPrompts',
'lora',
'modelmanager',
];

export const store = configureStore({
Expand Down
18 changes: 17 additions & 1 deletion invokeai/frontend/web/src/common/components/IAIInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,34 @@ import {
import { useAppDispatch } from 'app/store/storeHooks';
import { stopPastePropagation } from 'common/util/stopPastePropagation';
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
import { ChangeEvent, KeyboardEvent, memo, useCallback } from 'react';
import {
CSSProperties,
ChangeEvent,
KeyboardEvent,
memo,
useCallback,
} from 'react';

interface IAIInputProps extends InputProps {
label?: string;
labelPos?: 'top' | 'side';
value?: string;
size?: string;
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
formControlProps?: Omit<FormControlProps, 'isInvalid' | 'isDisabled'>;
}

const labelPosVerticalStyle: CSSProperties = {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: 10,
};

const IAIInput = (props: IAIInputProps) => {
const {
label = '',
labelPos = 'top',
isDisabled = false,
isInvalid,
formControlProps,
Expand Down Expand Up @@ -51,6 +66,7 @@ const IAIInput = (props: IAIInputProps) => {
isInvalid={isInvalid}
isDisabled={isDisabled}
{...formControlProps}
style={labelPos === 'side' ? labelPosVerticalStyle : undefined}
>
{label !== '' && <FormLabel>{label}</FormLabel>}
<Input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default function IAIMantineTextInput(props: IAIMantineTextInputProps) {
label: {
color: mode(base700, base300)(colorMode),
fontWeight: 'normal',
marginBottom: 4,
},
})}
{...rest}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ export type IAISelectDataType = {
tooltip?: string;
};

type IAISelectProps = Omit<SelectProps, 'label'> & {
export type IAISelectProps = Omit<SelectProps, 'label'> & {
tooltip?: string;
inputRef?: RefObject<HTMLInputElement>;
label?: string;
};

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

const styles = useMantineSelectStyles();

Expand All @@ -25,7 +25,7 @@ const IAIMantineSelect = (props: IAISelectProps) => {
<Select
label={
label ? (
<FormControl isDisabled={disabled}>
<FormControl isRequired={required} isDisabled={disabled}>
<FormLabel>{label}</FormLabel>
</FormControl>
) : undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ import {
ASSETS_CATEGORIES,
IMAGE_CATEGORIES,
IMAGE_LIMIT,
selectImagesAll,
} from 'features/gallery//store/gallerySlice';
import { selectFilteredImages } from 'features/gallery/store/gallerySelectors';
import { VirtuosoGrid } from 'react-virtuoso';
import { receivedPageOfImages } from 'services/api/thunks/image';
import { useListBoardImagesQuery } from '../../../../services/api/endpoints/boardImages';
import ImageGridItemContainer from './ImageGridItemContainer';
import ImageGridListContainer from './ImageGridListContainer';
import { useListBoardImagesQuery } from '../../../../services/api/endpoints/boardImages';

const selector = createSelector(
[stateSelector, selectFilteredImages],
Expand Down Expand Up @@ -180,7 +179,6 @@ const GalleryImageGrid = () => {
</Box>
);
}
console.log({ selectedBoardId });

if (status !== 'rejected') {
return (
Expand Down
22 changes: 3 additions & 19 deletions invokeai/frontend/web/src/features/system/store/systemSlice.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -44,8 +43,6 @@ export interface SystemState {
isCancelable: boolean;
enableImageDebugging: boolean;
toastQueue: UseToastOptions[];
searchFolder: string | null;
foundModels: InvokeAI.FoundModel[] | null;
/**
* The current progress image
*/
Expand Down Expand Up @@ -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.
*/
Expand All @@ -106,8 +103,6 @@ export const initialSystemState: SystemState = {
isCancelable: true,
enableImageDebugging: false,
toastQueue: [],
searchFolder: null,
foundModels: null,
progressImage: null,
shouldAntialiasProgressImage: false,
sessionId: null,
Expand All @@ -132,7 +127,7 @@ export const systemSlice = createSlice({
setIsProcessing: (state, action: PayloadAction<boolean>) => {
state.isProcessing = action.payload;
},
setCurrentStatus: (state, action: PayloadAction<TFuncKey>) => {
setCurrentStatus: (state, action: any) => {
state.statusTranslationKey = action.payload;
},
setShouldConfirmOnDelete: (state, action: PayloadAction<boolean>) => {
Expand All @@ -153,15 +148,6 @@ export const systemSlice = createSlice({
clearToastQueue: (state) => {
state.toastQueue = [];
},
setSearchFolder: (state, action: PayloadAction<string | null>) => {
state.searchFolder = action.payload;
},
setFoundModels: (
state,
action: PayloadAction<InvokeAI.FoundModel[] | null>
) => {
state.foundModels = action.payload;
},
/**
* A cancel was scheduled
*/
Expand Down Expand Up @@ -426,8 +412,6 @@ export const {
setEnableImageDebugging,
addToast,
clearToastQueue,
setSearchFolder,
setFoundModels,
cancelScheduled,
scheduledCancelAborted,
cancelTypeChanged,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
import i18n from 'i18n';
import { ReactNode, memo } from 'react';
import AddModelsPanel from './subpanels/AddModelsPanel';
import ImportModelsPanel from './subpanels/ImportModelsPanel';
import MergeModelsPanel from './subpanels/MergeModelsPanel';
import ModelManagerPanel from './subpanels/ModelManagerPanel';

type ModelManagerTabName = 'modelManager' | 'addModels' | 'mergeModels';
type ModelManagerTabName = 'modelManager' | 'importModels' | 'mergeModels';

type ModelManagerTabInfo = {
id: ModelManagerTabName;
Expand All @@ -20,9 +20,9 @@ const tabs: ModelManagerTabInfo[] = [
content: <ModelManagerPanel />,
},
{
id: 'addModels',
label: i18n.t('modelManager.addModel'),
content: <AddModelsPanel />,
id: 'importModels',
label: i18n.t('modelManager.importModels'),
content: <ImportModelsPanel />,
},
{
id: 'mergeModels',
Expand All @@ -46,7 +46,7 @@ const ModelManagerTab = () => {
</Tab>
))}
</TabList>
<TabPanels sx={{ w: 'full', h: 'full', p: 4 }}>
<TabPanels sx={{ w: 'full', h: 'full' }}>
{tabs.map((tab) => (
<TabPanel sx={{ w: 'full', h: 'full' }} key={tab.id}>
{tab.content}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { PayloadAction, createSlice } from '@reduxjs/toolkit';

type ModelManagerState = {
searchFolder: string | null;
advancedAddScanModel: string | null;
};

const initialModelManagerState: ModelManagerState = {
searchFolder: null,
advancedAddScanModel: null,
};

export const modelManagerSlice = createSlice({
name: 'modelmanager',
initialState: initialModelManagerState,
reducers: {
setSearchFolder: (state, action: PayloadAction<string | null>) => {
state.searchFolder = action.payload;
},
setAdvancedAddScanModel: (state, action: PayloadAction<string | null>) => {
state.advancedAddScanModel = action.payload;
},
},
});

export const { setSearchFolder, setAdvancedAddScanModel } =
modelManagerSlice.actions;

export default modelManagerSlice.reducer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { RootState } from 'app/store/store';

export const modelmanagerSelector = (state: RootState) => state.modelmanager;
Loading