diff --git a/invokeai/app/invocations/image.py b/invokeai/app/invocations/image.py index 1dbbff1c84..2612a58ba7 100644 --- a/invokeai/app/invocations/image.py +++ b/invokeai/app/invocations/image.py @@ -5,6 +5,7 @@ from typing import Literal, Optional import numpy from PIL import Image, ImageFilter, ImageOps, ImageChops from pydantic import BaseModel, Field +from typing import Union from ..models.image import ImageCategory, ImageField, ResourceOrigin from .baseinvocation import ( @@ -398,8 +399,8 @@ class ImageResizeInvocation(BaseInvocation, PILInvocationConfig): # Inputs image: Optional[ImageField] = Field(default=None, description="The image to resize") - width: int = Field(ge=64, multiple_of=8, description="The width to resize to (px)") - height: int = Field(ge=64, multiple_of=8, description="The height to resize to (px)") + width: Union[int, None] = Field(ge=64, multiple_of=8, description="The width to resize to (px)") + height: Union[int, None] = Field(ge=64, multiple_of=8, description="The height to resize to (px)") resample_mode: PIL_RESAMPLING_MODES = Field(default="bicubic", description="The resampling mode") # fmt: on diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index d28df6e900..f4485bf67a 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -614,6 +614,7 @@ class ModelManager(object): rmtree(str(model_path)) else: model_path.unlink() + self.commit() # LS: tested def add_model( diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 4329261049..a8f52742d4 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -343,6 +343,7 @@ "safetensorModels": "SafeTensors", "modelAdded": "Model Added", "modelUpdated": "Model Updated", + "modelUpdateFailed": "Model Update Failed", "modelEntryDeleted": "Model Entry Deleted", "cannotUseSpaces": "Cannot Use Spaces", "addNew": "Add New", @@ -397,8 +398,8 @@ "delete": "Delete", "deleteModel": "Delete Model", "deleteConfig": "Delete Config", - "deleteMsg1": "Are you sure you want to delete this model entry from InvokeAI?", - "deleteMsg2": "This will not delete the model checkpoint file from your disk. You can readd them if you wish to.", + "deleteMsg1": "Are you sure you want to delete this model from InvokeAI?", + "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.", "formMessageDiffusersVAELocation": "VAE Location", @@ -409,7 +410,7 @@ "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.", "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 4GB-7GB in size.", + "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", "v1": "v1", @@ -420,12 +421,14 @@ "pathToCustomConfig": "Path To Custom Config", "statusConverting": "Converting", "modelConverted": "Model Converted", + "modelConversionFailed": "Model Conversion Failed", "sameFolder": "Same folder", "invokeRoot": "InvokeAI folder", "custom": "Custom", "customSaveLocation": "Custom Save Location", "merge": "Merge", "modelsMerged": "Models Merged", + "modelsMergeFailed": "Model Merge Failed", "mergeModels": "Merge Models", "modelOne": "Model 1", "modelTwo": "Model 2", @@ -446,7 +449,8 @@ "weightedSum": "Weighted Sum", "none": "none", "addDifference": "Add Difference", - "pickModelType": "Pick Model Type" + "pickModelType": "Pick Model Type", + "selectModel": "Select Model" }, "parameters": { "general": "General", @@ -599,7 +603,6 @@ "nodesLoaded": "Nodes Loaded", "nodesLoadedFailed": "Failed To Load Nodes", "nodesCleared": "Nodes Cleared" - }, "tooltip": { "feature": { diff --git a/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx b/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx index 1e86e0ce1b..621b196ae0 100644 --- a/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx +++ b/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx @@ -9,9 +9,9 @@ import { theme as invokeAITheme } from 'theme/theme'; import '@fontsource-variable/inter'; import { MantineProvider } from '@mantine/core'; -import { mantineTheme } from 'mantine-theme/theme'; import 'overlayscrollbars/overlayscrollbars.css'; import 'theme/css/overlayscrollbars.css'; +import { useMantineTheme } from 'mantine-theme/theme'; type ThemeLocaleProviderProps = { children: ReactNode; @@ -35,8 +35,10 @@ function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) { document.body.dir = direction; }, [direction]); + const mantineTheme = useMantineTheme(); + return ( - + {children} diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index fe4bce682b..f01c3911da 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -1,6 +1,7 @@ import { log } from 'app/logging/useLogger'; -import { appSocketConnected, socketConnected } from 'services/events/actions'; +import { modelsApi } from 'services/api/endpoints/models'; import { receivedOpenAPISchema } from 'services/api/thunks/schema'; +import { appSocketConnected, socketConnected } from 'services/events/actions'; import { startAppListening } from '../..'; const moduleLog = log.child({ namespace: 'socketio' }); @@ -23,6 +24,13 @@ export const addSocketConnectedEventListener = () => { // pass along the socket event as an application action dispatch(appSocketConnected(action.payload)); + + // update all server state + dispatch(modelsApi.endpoints.getMainModels.initiate()); + dispatch(modelsApi.endpoints.getControlNetModels.initiate()); + dispatch(modelsApi.endpoints.getLoRAModels.initiate()); + dispatch(modelsApi.endpoints.getTextualInversionModels.initiate()); + dispatch(modelsApi.endpoints.getVaeModels.initiate()); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index da09b496d7..2bafd21a74 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -100,10 +100,11 @@ export const store = configureStore({ // manually type state, cannot type the arg // const typedState = state as ReturnType; - if (action.type.startsWith('api/')) { - // don't log api actions, with manual cache updates they are extremely noisy - return false; - } + // TODO: doing this breaks the rtk query devtools, commenting out for now + // if (action.type.startsWith('api/')) { + // // don't log api actions, with manual cache updates they are extremely noisy + // return false; + // } if (actionsDenylist.includes(action.type)) { // don't log other noisy actions diff --git a/invokeai/frontend/web/src/common/components/IAIForms/IAIFormItemWrapper.tsx b/invokeai/frontend/web/src/common/components/IAIForms/IAIFormItemWrapper.tsx index 1b1ca29d76..83e91366c2 100644 --- a/invokeai/frontend/web/src/common/components/IAIForms/IAIFormItemWrapper.tsx +++ b/invokeai/frontend/web/src/common/components/IAIForms/IAIFormItemWrapper.tsx @@ -1,11 +1,13 @@ -import { Flex } from '@chakra-ui/react'; +import { Flex, useColorMode } from '@chakra-ui/react'; import { ReactElement } from 'react'; +import { mode } from 'theme/util/mode'; export function IAIFormItemWrapper({ children, }: { children: ReactElement | ReactElement[]; }) { + const { colorMode } = useColorMode(); return ( {children} diff --git a/invokeai/frontend/web/src/common/components/IAIMantineInput.tsx b/invokeai/frontend/web/src/common/components/IAIMantineInput.tsx new file mode 100644 index 0000000000..d60f6614df --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAIMantineInput.tsx @@ -0,0 +1,44 @@ +import { useColorMode } from '@chakra-ui/react'; +import { TextInput, TextInputProps } from '@mantine/core'; +import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens'; +import { mode } from 'theme/util/mode'; + +type IAIMantineTextInputProps = TextInputProps; + +export default function IAIMantineTextInput(props: IAIMantineTextInputProps) { + const { ...rest } = props; + const { + base50, + base100, + base200, + base300, + base800, + base700, + base900, + accent500, + accent300, + } = useChakraThemeTokens(); + const { colorMode } = useColorMode(); + + return ( + ({ + input: { + color: mode(base900, base100)(colorMode), + backgroundColor: mode(base50, base900)(colorMode), + borderColor: mode(base200, base800)(colorMode), + borderWidth: 2, + outline: 'none', + ':focus': { + borderColor: mode(accent300, accent500)(colorMode), + }, + }, + label: { + color: mode(base700, base300)(colorMode), + fontWeight: 'normal', + }, + })} + {...rest} + /> + ); +} diff --git a/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx b/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx index dc6db707e7..dd5c602150 100644 --- a/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx +++ b/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx @@ -1,10 +1,9 @@ -import { Tooltip, useColorMode, useToken } from '@chakra-ui/react'; +import { Tooltip } from '@chakra-ui/react'; import { MultiSelect, MultiSelectProps } from '@mantine/core'; import { useAppDispatch } from 'app/store/storeHooks'; -import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens'; import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice'; +import { useMantineMultiSelectStyles } from 'mantine-theme/hooks/useMantineMultiSelectStyles'; import { KeyboardEvent, RefObject, memo, useCallback } from 'react'; -import { mode } from 'theme/util/mode'; type IAIMultiSelectProps = MultiSelectProps & { tooltip?: string; @@ -14,25 +13,6 @@ type IAIMultiSelectProps = MultiSelectProps & { const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => { const { searchable = true, tooltip, inputRef, ...rest } = props; const dispatch = useAppDispatch(); - const { - base50, - base100, - base200, - base300, - base400, - base500, - base600, - base700, - base800, - base900, - accent200, - accent300, - accent400, - accent500, - accent600, - } = useChakraThemeTokens(); - const [boxShadow] = useToken('shadows', ['dark-lg']); - const { colorMode } = useColorMode(); const handleKeyDown = useCallback( (e: KeyboardEvent) => { @@ -52,6 +32,8 @@ const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => { [dispatch] ); + const styles = useMantineMultiSelectStyles(); + return ( { onKeyUp={handleKeyUp} searchable={searchable} maxDropdownHeight={300} - styles={() => ({ - label: { - color: mode(base700, base300)(colorMode), - fontWeight: 'normal', - }, - searchInput: { - ':placeholder': { - color: mode(base300, base700)(colorMode), - }, - }, - input: { - backgroundColor: mode(base50, base900)(colorMode), - borderWidth: '2px', - borderColor: mode(base200, base800)(colorMode), - color: mode(base900, base100)(colorMode), - paddingRight: 24, - fontWeight: 600, - '&:hover': { borderColor: mode(base300, base600)(colorMode) }, - '&:focus': { - borderColor: mode(accent300, accent600)(colorMode), - }, - '&:is(:focus, :hover)': { - borderColor: mode(base400, base500)(colorMode), - }, - '&:focus-within': { - borderColor: mode(accent200, accent600)(colorMode), - }, - '&[data-disabled]': { - backgroundColor: mode(base300, base700)(colorMode), - color: mode(base600, base400)(colorMode), - cursor: 'not-allowed', - }, - }, - value: { - backgroundColor: mode(base200, base800)(colorMode), - color: mode(base900, base100)(colorMode), - button: { - color: mode(base900, base100)(colorMode), - }, - '&:hover': { - backgroundColor: mode(base300, base700)(colorMode), - cursor: 'pointer', - }, - }, - dropdown: { - backgroundColor: mode(base200, base800)(colorMode), - borderColor: mode(base200, base800)(colorMode), - boxShadow, - }, - item: { - backgroundColor: mode(base200, base800)(colorMode), - color: mode(base800, base200)(colorMode), - padding: 6, - '&[data-hovered]': { - color: mode(base900, base100)(colorMode), - backgroundColor: mode(base300, base700)(colorMode), - }, - '&[data-active]': { - backgroundColor: mode(base300, base700)(colorMode), - '&:hover': { - color: mode(base900, base100)(colorMode), - backgroundColor: mode(base300, base700)(colorMode), - }, - }, - '&[data-selected]': { - backgroundColor: mode(accent400, accent600)(colorMode), - color: mode(base50, base100)(colorMode), - fontWeight: 600, - '&:hover': { - backgroundColor: mode(accent500, accent500)(colorMode), - color: mode('white', base50)(colorMode), - }, - }, - '&[data-disabled]': { - color: mode(base500, base600)(colorMode), - cursor: 'not-allowed', - }, - }, - rightSection: { - width: 24, - padding: 20, - button: { - color: mode(base900, base100)(colorMode), - }, - }, - })} + styles={styles} {...rest} /> diff --git a/invokeai/frontend/web/src/common/components/IAIMantineSearchableSelect.tsx b/invokeai/frontend/web/src/common/components/IAIMantineSearchableSelect.tsx new file mode 100644 index 0000000000..edf1665bb4 --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAIMantineSearchableSelect.tsx @@ -0,0 +1,78 @@ +import { Tooltip } from '@chakra-ui/react'; +import { Select, SelectProps } from '@mantine/core'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice'; +import { useMantineSelectStyles } from 'mantine-theme/hooks/useMantineSelectStyles'; +import { KeyboardEvent, RefObject, memo, useCallback, useState } from 'react'; + +export type IAISelectDataType = { + value: string; + label: string; + tooltip?: string; +}; + +type IAISelectProps = SelectProps & { + tooltip?: string; + inputRef?: RefObject; +}; + +const IAIMantineSearchableSelect = (props: IAISelectProps) => { + const { searchable = true, tooltip, inputRef, onChange, ...rest } = props; + const dispatch = useAppDispatch(); + + const [searchValue, setSearchValue] = useState(''); + + // we want to capture shift keypressed even when an input is focused + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.shiftKey) { + dispatch(shiftKeyPressed(true)); + } + }, + [dispatch] + ); + + const handleKeyUp = useCallback( + (e: KeyboardEvent) => { + if (!e.shiftKey) { + dispatch(shiftKeyPressed(false)); + } + }, + [dispatch] + ); + + // wrap onChange to clear search value on select + const handleChange = useCallback( + (v: string | null) => { + setSearchValue(''); + + if (!onChange) { + return; + } + + onChange(v); + }, + [onChange] + ); + + const styles = useMantineSelectStyles(); + + return ( + + ({ - label: { - color: mode(base700, base300)(colorMode), - fontWeight: 'normal', - }, - input: { - backgroundColor: mode(base50, base900)(colorMode), - borderWidth: '2px', - borderColor: mode(base200, base800)(colorMode), - color: mode(base900, base100)(colorMode), - paddingRight: 24, - fontWeight: 600, - '&:hover': { borderColor: mode(base300, base600)(colorMode) }, - '&:focus': { - borderColor: mode(accent300, accent600)(colorMode), - }, - '&:is(:focus, :hover)': { - borderColor: mode(base400, base500)(colorMode), - }, - '&:focus-within': { - borderColor: mode(accent200, accent600)(colorMode), - }, - '&[data-disabled]': { - backgroundColor: mode(base300, base700)(colorMode), - color: mode(base600, base400)(colorMode), - cursor: 'not-allowed', - }, - }, - value: { - backgroundColor: mode(base100, base900)(colorMode), - color: mode(base900, base100)(colorMode), - button: { - color: mode(base900, base100)(colorMode), - }, - '&:hover': { - backgroundColor: mode(base300, base700)(colorMode), - cursor: 'pointer', - }, - }, - dropdown: { - backgroundColor: mode(base200, base800)(colorMode), - borderColor: mode(base200, base800)(colorMode), - boxShadow, - }, - item: { - backgroundColor: mode(base200, base800)(colorMode), - color: mode(base800, base200)(colorMode), - padding: 6, - '&[data-hovered]': { - color: mode(base900, base100)(colorMode), - backgroundColor: mode(base300, base700)(colorMode), - }, - '&[data-active]': { - backgroundColor: mode(base300, base700)(colorMode), - '&:hover': { - color: mode(base900, base100)(colorMode), - backgroundColor: mode(base300, base700)(colorMode), - }, - }, - '&[data-selected]': { - backgroundColor: mode(accent400, accent600)(colorMode), - color: mode(base50, base100)(colorMode), - fontWeight: 600, - '&:hover': { - backgroundColor: mode(accent500, accent500)(colorMode), - color: mode('white', base50)(colorMode), - }, - }, - '&[data-disabled]': { - color: mode(base500, base600)(colorMode), - cursor: 'not-allowed', - }, - }, - rightSection: { - width: 32, - button: { - color: mode(base900, base100)(colorMode), - }, - }, - })} - {...rest} - /> +