From 10df5ff60a2b8b94bdc69f04b09b3a76364cdf00 Mon Sep 17 00:00:00 2001 From: Attila Cseh Date: Tue, 26 Aug 2025 11:42:13 +0200 Subject: [PATCH 1/7] LoRA default weight --- .../model_records/model_records_base.py | 3 +- invokeai/backend/model_manager/config.py | 9 +- .../backend/model_manager/legacy_probe.py | 5 ++ invokeai/frontend/web/public/locales/en.json | 3 + .../frontend/web/src/app/types/invokeai.ts | 14 +++ .../controlLayers/store/lorasSlice.ts | 13 +-- .../src/features/lora/components/LoRACard.tsx | 26 +++--- .../hooks/useLoRAModelDefaultSettings.ts | 29 ++++++ .../DefaultWeight.tsx | 78 ++++++++++++++++ .../LoRAModelDefaultSettings.tsx | 88 +++++++++++++++++++ .../subpanels/ModelPanel/ModelView.tsx | 9 +- .../src/features/system/store/configSlice.ts | 1 + .../frontend/web/src/services/api/schema.ts | 20 ++++- 13 files changed, 272 insertions(+), 26 deletions(-) create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/hooks/useLoRAModelDefaultSettings.ts create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/LoRAModelDefaultSettings.tsx diff --git a/invokeai/app/services/model_records/model_records_base.py b/invokeai/app/services/model_records/model_records_base.py index 2cd29f5289d..c30afe6c951 100644 --- a/invokeai/app/services/model_records/model_records_base.py +++ b/invokeai/app/services/model_records/model_records_base.py @@ -15,6 +15,7 @@ from invokeai.backend.model_manager.config import ( AnyModelConfig, ControlAdapterDefaultSettings, + LoraModelDefaultSettings, MainModelDefaultSettings, ) from invokeai.backend.model_manager.taxonomy import ( @@ -83,7 +84,7 @@ class ModelRecordChanges(BaseModelExcludeNull): file_size: Optional[int] = Field(description="Size of model file", default=None) format: Optional[str] = Field(description="format of model file", default=None) trigger_phrases: Optional[set[str]] = Field(description="Set of trigger phrases for this model", default=None) - default_settings: Optional[MainModelDefaultSettings | ControlAdapterDefaultSettings] = Field( + default_settings: Optional[MainModelDefaultSettings | LoraModelDefaultSettings | ControlAdapterDefaultSettings] = Field( description="Default settings for this model", default=None ) diff --git a/invokeai/backend/model_manager/config.py b/invokeai/backend/model_manager/config.py index c9b5cd62336..9cf8ef54d63 100644 --- a/invokeai/backend/model_manager/config.py +++ b/invokeai/backend/model_manager/config.py @@ -90,6 +90,10 @@ class MainModelDefaultSettings(BaseModel): model_config = ConfigDict(extra="forbid") +class LoraModelDefaultSettings(BaseModel): + weight: float | None = Field(default=None, ge=-1, le=2, description="Default weight for this model") + model_config = ConfigDict(extra="forbid") + class ControlAdapterDefaultSettings(BaseModel): # This could be narrowed to controlnet processor nodes, but they change. Leaving this a string is safer. preprocessor: str | None @@ -287,6 +291,9 @@ class LoRAConfigBase(ABC, BaseModel): type: Literal[ModelType.LoRA] = ModelType.LoRA trigger_phrases: Optional[set[str]] = Field(description="Set of trigger phrases for this model", default=None) + default_settings: Optional[LoraModelDefaultSettings] = Field( + description="Default settings for this model", default=None + ) @classmethod def flux_lora_format(cls, mod: ModelOnDisk): @@ -748,7 +755,7 @@ def get_model_discriminator_value(v: Any) -> str: ] AnyModelConfigValidator = TypeAdapter(AnyModelConfig) -AnyDefaultSettings: TypeAlias = Union[MainModelDefaultSettings, ControlAdapterDefaultSettings] +AnyDefaultSettings: TypeAlias = Union[MainModelDefaultSettings, LoraModelDefaultSettings, ControlAdapterDefaultSettings] class ModelConfigFactory: diff --git a/invokeai/backend/model_manager/legacy_probe.py b/invokeai/backend/model_manager/legacy_probe.py index 9d04671f8a5..1b548605a60 100644 --- a/invokeai/backend/model_manager/legacy_probe.py +++ b/invokeai/backend/model_manager/legacy_probe.py @@ -23,6 +23,7 @@ AnyModelConfig, ControlAdapterDefaultSettings, InvalidModelConfigException, + LoraModelDefaultSettings, MainModelDefaultSettings, ModelConfigFactory, SubmodelDefinition, @@ -217,6 +218,8 @@ def probe( if not fields["default_settings"]: if fields["type"] in {ModelType.ControlNet, ModelType.T2IAdapter, ModelType.ControlLoRa}: fields["default_settings"] = get_default_settings_control_adapters(fields["name"]) + if fields["type"] in {ModelType.LoRA}: + fields["default_settings"] = get_default_settings_lora() elif fields["type"] is ModelType.Main: fields["default_settings"] = get_default_settings_main(fields["base"]) @@ -542,6 +545,8 @@ def get_default_settings_control_adapters(model_name: str) -> Optional[ControlAd return ControlAdapterDefaultSettings(preprocessor=v) return None +def get_default_settings_lora() -> LoraModelDefaultSettings: + return LoraModelDefaultSettings() def get_default_settings_main(model_base: BaseModelType) -> Optional[MainModelDefaultSettings]: if model_base is BaseModelType.StableDiffusion1 or model_base is BaseModelType.StableDiffusion2: diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 0528da9e43a..974c2125606 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -792,6 +792,9 @@ } } }, + "lora": { + "weight": "Weight" + }, "metadata": { "allPrompts": "All Prompts", "cfgScale": "CFG scale", diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index b24f83a1b15..0058c4d883b 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -127,6 +127,9 @@ export const zAppConfig = z.object({ flux: z.object({ guidance: zNumericalParameterConfig, }), + lora: z.object({ + weight: zNumericalParameterConfig, + }), }); export type AppConfig = z.infer; @@ -299,4 +302,15 @@ export const getDefaultAppConfig = (): AppConfig => ({ coarseStep: 0.5, }, }, + lora: { + weight: { + initial: 0.75, + sliderMin: -1, + sliderMax: 2, + numberInputMin: -1, + numberInputMax: 2, + fineStep: 0.01, + coarseStep: 0.05, + }, + }, }); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/lorasSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/lorasSlice.ts index c4fc5651463..63fb1c8ec0d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/lorasSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/lorasSlice.ts @@ -13,11 +13,6 @@ const zLoRAsState = z.object({ }); type LoRAsState = z.infer; -const defaultLoRAConfig: Pick = { - weight: 0.75, - isEnabled: true, -}; - const getInitialState = (): LoRAsState => ({ loras: [], }); @@ -32,6 +27,10 @@ const slice = createSlice({ reducer: (state, action: PayloadAction<{ model: LoRAModelConfig; id: string }>) => { const { model, id } = action.payload; const parsedModel = zModelIdentifierField.parse(model); + const defaultLoRAConfig: Pick = { + weight: model.default_settings?.weight ?? 0.75, + isEnabled: true, + }; state.loras.push({ ...defaultLoRAConfig, model: parsedModel, id }); }, prepare: (payload: { model: LoRAModelConfig }) => ({ payload: { ...payload, id: uuidv4() } }), @@ -87,3 +86,7 @@ export const lorasSliceConfig: SliceConfig = { export const selectLoRAsSlice = (state: RootState) => state.loras; export const selectAddedLoRAs = createSelector(selectLoRAsSlice, (loras) => loras.loras); +export const buildSelectLoRA = (id: string) => + createSelector([selectLoRAsSlice], (loras) => { + return selectLoRA(loras, id); + }); diff --git a/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx b/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx index 7d0652c4427..04221da61e5 100644 --- a/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx +++ b/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx @@ -9,16 +9,16 @@ import { Switch, Text, } from '@invoke-ai/ui-library'; -import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { + buildSelectLoRA, loraDeleted, loraIsEnabledChanged, loraWeightChanged, - selectLoRAsSlice, } from 'features/controlLayers/store/lorasSlice'; import type { LoRA } from 'features/controlLayers/store/types'; +import { selectLoRAWeightConfig } from 'features/system/store/configSlice'; import { memo, useCallback, useMemo } from 'react'; import { PiTrashSimpleBold } from 'react-icons/pi'; import { useGetModelConfigQuery } from 'services/api/endpoints/models'; @@ -26,10 +26,7 @@ import { useGetModelConfigQuery } from 'services/api/endpoints/models'; const marks = [-1, 0, 1, 2]; export const LoRACard = memo((props: { id: string }) => { - const selectLoRA = useMemo( - () => createSelector(selectLoRAsSlice, ({ loras }) => loras.find(({ id }) => id === props.id)), - [props.id] - ); + const selectLoRA = useMemo(() => buildSelectLoRA(props.id), [props.id]); const lora = useAppSelector(selectLoRA); if (!lora) { @@ -42,6 +39,7 @@ LoRACard.displayName = 'LoRACard'; const LoRAContent = memo(({ lora }: { lora: LoRA }) => { const dispatch = useAppDispatch(); + const config = useAppSelector(selectLoRAWeightConfig); const { data: loraConfig } = useGetModelConfigQuery(lora.model.key); const handleChange = useCallback( @@ -83,22 +81,22 @@ const LoRAContent = memo(({ lora }: { lora: LoRA }) => { diff --git a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useLoRAModelDefaultSettings.ts b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useLoRAModelDefaultSettings.ts new file mode 100644 index 00000000000..5ca6a985e0d --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useLoRAModelDefaultSettings.ts @@ -0,0 +1,29 @@ +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { useAppSelector } from 'app/store/storeHooks'; +import { isNil } from 'es-toolkit/compat'; +import { selectConfigSlice } from 'features/system/store/configSlice'; +import { useMemo } from 'react'; +import type { LoRAModelConfig } from 'services/api/types'; + +const initialStatesSelector = createMemoizedSelector(selectConfigSlice, (config) => { + const { weight } = config.lora; + + return { + initialWeight: weight.initial, + }; +}); + +export const useLoRAModelDefaultSettings = (modelConfig: LoRAModelConfig) => { + const { initialWeight } = useAppSelector(initialStatesSelector); + + const defaultSettingsDefaults = useMemo(() => { + return { + weight: { + isEnabled: !isNil(modelConfig?.default_settings?.weight), + value: modelConfig?.default_settings?.weight || initialWeight, + }, + }; + }, [modelConfig?.default_settings, initialWeight]); + + return defaultSettingsDefaults; +}; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx new file mode 100644 index 00000000000..7fc65b87da0 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx @@ -0,0 +1,78 @@ +import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { useAppSelector } from 'app/store/storeHooks'; +import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; +import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; +import { selectLoRAWeightConfig } from 'features/system/store/configSlice'; +import { memo, useCallback, useMemo } from 'react'; +import type { UseControllerProps } from 'react-hook-form'; +import { useController } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; + +import type { LoRAModelDefaultSettingsFormData } from './LoRAModelDefaultSettings'; + +type DefaultWeight = LoRAModelDefaultSettingsFormData['weight']; + +export const DefaultWeight = memo((props: UseControllerProps) => { + const { field } = useController(props); + + const config = useAppSelector(selectLoRAWeightConfig); + const { t } = useTranslation(); + const marks = useMemo( + () => [config.sliderMin, Math.floor(config.sliderMax / 2), config.sliderMax], + [config.sliderMax, config.sliderMin] + ); + + const onChange = useCallback( + (v: number) => { + const updatedValue = { + ...(field.value as DefaultWeight), + value: v, + }; + field.onChange(updatedValue); + }, + [field] + ); + + const value = useMemo(() => { + return (field.value as DefaultWeight).value; + }, [field.value]); + + const isDisabled = useMemo(() => { + return !(field.value as DefaultWeight).isEnabled; + }, [field.value]); + + return ( + + + + {t('lora.weight')} + + + + + + + + + + ); +}); + +DefaultWeight.displayName = 'DefaultWeight'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/LoRAModelDefaultSettings.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/LoRAModelDefaultSettings.tsx new file mode 100644 index 00000000000..8eaab6adae0 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/LoRAModelDefaultSettings.tsx @@ -0,0 +1,88 @@ +import { Button, Flex, Heading, SimpleGrid } from '@invoke-ai/ui-library'; +import { useLoRAModelDefaultSettings } from 'features/modelManagerV2/hooks/useLoraModelDefaultSettings'; +import { DefaultWeight } from 'features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight'; +import type { FormField } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings'; +import { toast } from 'features/toast/toast'; +import { memo, useCallback } from 'react'; +import type { SubmitHandler } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { PiCheckBold } from 'react-icons/pi'; +import { useUpdateModelMutation } from 'services/api/endpoints/models'; +import type { LoRAModelConfig } from 'services/api/types'; + +export type LoRAModelDefaultSettingsFormData = { + weight: FormField; +}; + +type Props = { + modelConfig: LoRAModelConfig; +}; + +export const LoRAModelDefaultSettings = memo(({ modelConfig }: Props) => { + const { t } = useTranslation(); + + const defaultSettingsDefaults = useLoRAModelDefaultSettings(modelConfig); + + const [updateModel, { isLoading: isLoadingUpdateModel }] = useUpdateModelMutation(); + + const { handleSubmit, control, formState, reset } = useForm({ + defaultValues: defaultSettingsDefaults, + }); + + const onSubmit = useCallback>( + (data) => { + const body = { + weight: data.weight.isEnabled ? data.weight.value : null, + }; + + updateModel({ + key: modelConfig.key, + body: { default_settings: body }, + }) + .unwrap() + .then((_) => { + toast({ + id: 'DEFAULT_SETTINGS_SAVED', + title: t('modelManager.defaultSettingsSaved'), + status: 'success', + }); + reset(data); + }) + .catch((error) => { + if (error) { + toast({ + id: 'DEFAULT_SETTINGS_SAVE_FAILED', + title: `${error.data.detail} `, + status: 'error', + }); + } + }); + }, + [updateModel, modelConfig.key, t, reset] + ); + + return ( + <> + + {t('modelManager.defaultSettings')} + + + + + + + + ); +}); + +LoRAModelDefaultSettings.displayName = 'LoRAModelDefaultSettings'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx index c30b4d1252c..391edae1c51 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx @@ -1,5 +1,6 @@ import { Box, Flex, SimpleGrid } from '@invoke-ai/ui-library'; import { ControlAdapterModelDefaultSettings } from 'features/modelManagerV2/subpanels/ModelPanel/ControlAdapterModelDefaultSettings/ControlAdapterModelDefaultSettings'; +import { LoRAModelDefaultSettings } from 'features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/LoRAModelDefaultSettings'; import { ModelConvertButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton'; import { ModelEditButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelEditButton'; import { ModelHeader } from 'features/modelManagerV2/subpanels/ModelPanel/ModelHeader'; @@ -79,9 +80,13 @@ export const ModelView = memo(({ modelConfig }: Props) => { {(modelConfig.type === 'controlnet' || modelConfig.type === 't2i_adapter' || modelConfig.type === 'control_lora') && } - {(modelConfig.type === 'main' || modelConfig.type === 'lora') && ( - + {modelConfig.type === 'lora' && ( + <> + + + )} + {modelConfig.type === 'main' && } )} diff --git a/invokeai/frontend/web/src/features/system/store/configSlice.ts b/invokeai/frontend/web/src/features/system/store/configSlice.ts index 7de5d228388..16fdad38074 100644 --- a/invokeai/frontend/web/src/features/system/store/configSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/configSlice.ts @@ -61,6 +61,7 @@ export const selectInfillTileSizeConfig = createConfigSelector((config) => confi export const selectImg2imgStrengthConfig = createConfigSelector((config) => config.sd.img2imgStrength); export const selectMaxPromptsConfig = createConfigSelector((config) => config.sd.dynamicPrompts.maxPrompts); export const selectIterationsConfig = createConfigSelector((config) => config.sd.iterations); +export const selectLoRAWeightConfig = createConfigSelector((config) => config.lora.weight); export const selectMaxUpscaleDimension = createConfigSelector((config) => config.maxUpscaleDimension); export const selectAllowPrivateStylePresets = createConfigSelector((config) => config.allowPrivateStylePresets); diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 4aea6d536cb..eda6e7771a1 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -12729,14 +12729,14 @@ export type components = { * Convert Cache Dir * Format: path * @description Path to the converted models cache directory (DEPRECATED, but do not delete because it is needed for migration from previous versions). - * @default models/.convert_cache + * @default models\.convert_cache */ convert_cache_dir?: string; /** * Download Cache Dir * Format: path * @description Path to the directory that contains dynamically downloaded models. - * @default models/.download_cache + * @default models\.download_cache */ download_cache_dir?: string; /** @@ -14211,6 +14211,8 @@ export type components = { * @description Set of trigger phrases for this model */ trigger_phrases?: string[] | null; + /** @description Default settings for this model */ + default_settings?: components["schemas"]["LoraModelDefaultSettings"] | null; }; /** LoRAField */ LoRAField: { @@ -14382,6 +14384,8 @@ export type components = { * @description Set of trigger phrases for this model */ trigger_phrases?: string[] | null; + /** @description Default settings for this model */ + default_settings?: components["schemas"]["LoraModelDefaultSettings"] | null; }; /** * LoRAMetadataField @@ -14476,6 +14480,8 @@ export type components = { * @description Set of trigger phrases for this model */ trigger_phrases?: string[] | null; + /** @description Default settings for this model */ + default_settings?: components["schemas"]["LoraModelDefaultSettings"] | null; }; /** * Select LoRA @@ -14558,6 +14564,14 @@ export type components = { * @enum {integer} */ LogLevel: 0 | 10 | 20 | 30 | 40 | 50; + /** LoraModelDefaultSettings */ + LoraModelDefaultSettings: { + /** + * Weight + * @description Default weight for this model + */ + weight?: number | null; + }; /** MDControlListOutput */ MDControlListOutput: { /** @@ -17351,7 +17365,7 @@ export type components = { * Default Settings * @description Default settings for this model */ - default_settings?: components["schemas"]["MainModelDefaultSettings"] | components["schemas"]["ControlAdapterDefaultSettings"] | null; + default_settings?: components["schemas"]["MainModelDefaultSettings"] | components["schemas"]["LoraModelDefaultSettings"] | components["schemas"]["ControlAdapterDefaultSettings"] | null; /** * Variant * @description The variant of the model. From fe78b76380b0bb1062b40b0423a3453b4de597b5 Mon Sep 17 00:00:00 2001 From: Attila Cseh Date: Tue, 26 Aug 2025 12:47:49 +0200 Subject: [PATCH 2/7] lint errors fixed --- .../LoRAModelDefaultSettings/LoRAModelDefaultSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/LoRAModelDefaultSettings.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/LoRAModelDefaultSettings.tsx index 8eaab6adae0..a012460161f 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/LoRAModelDefaultSettings.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/LoRAModelDefaultSettings.tsx @@ -1,5 +1,5 @@ import { Button, Flex, Heading, SimpleGrid } from '@invoke-ai/ui-library'; -import { useLoRAModelDefaultSettings } from 'features/modelManagerV2/hooks/useLoraModelDefaultSettings'; +import { useLoRAModelDefaultSettings } from 'features/modelManagerV2/hooks/useLoRAModelDefaultSettings'; import { DefaultWeight } from 'features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight'; import type { FormField } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings'; import { toast } from 'features/toast/toast'; From 41163abcfea834aaf1adc04bd903ca813b846563 Mon Sep 17 00:00:00 2001 From: Attila Cseh Date: Tue, 26 Aug 2025 12:51:23 +0200 Subject: [PATCH 3/7] python source code reformatted --- invokeai/app/services/model_records/model_records_base.py | 4 ++-- invokeai/backend/model_manager/config.py | 1 + invokeai/backend/model_manager/legacy_probe.py | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/invokeai/app/services/model_records/model_records_base.py b/invokeai/app/services/model_records/model_records_base.py index c30afe6c951..740d548a4a3 100644 --- a/invokeai/app/services/model_records/model_records_base.py +++ b/invokeai/app/services/model_records/model_records_base.py @@ -84,8 +84,8 @@ class ModelRecordChanges(BaseModelExcludeNull): file_size: Optional[int] = Field(description="Size of model file", default=None) format: Optional[str] = Field(description="format of model file", default=None) trigger_phrases: Optional[set[str]] = Field(description="Set of trigger phrases for this model", default=None) - default_settings: Optional[MainModelDefaultSettings | LoraModelDefaultSettings | ControlAdapterDefaultSettings] = Field( - description="Default settings for this model", default=None + default_settings: Optional[MainModelDefaultSettings | LoraModelDefaultSettings | ControlAdapterDefaultSettings] = ( + Field(description="Default settings for this model", default=None) ) # Checkpoint-specific changes diff --git a/invokeai/backend/model_manager/config.py b/invokeai/backend/model_manager/config.py index 9cf8ef54d63..1bfc15c046f 100644 --- a/invokeai/backend/model_manager/config.py +++ b/invokeai/backend/model_manager/config.py @@ -94,6 +94,7 @@ class LoraModelDefaultSettings(BaseModel): weight: float | None = Field(default=None, ge=-1, le=2, description="Default weight for this model") model_config = ConfigDict(extra="forbid") + class ControlAdapterDefaultSettings(BaseModel): # This could be narrowed to controlnet processor nodes, but they change. Leaving this a string is safer. preprocessor: str | None diff --git a/invokeai/backend/model_manager/legacy_probe.py b/invokeai/backend/model_manager/legacy_probe.py index 1b548605a60..36fd82667d7 100644 --- a/invokeai/backend/model_manager/legacy_probe.py +++ b/invokeai/backend/model_manager/legacy_probe.py @@ -545,9 +545,11 @@ def get_default_settings_control_adapters(model_name: str) -> Optional[ControlAd return ControlAdapterDefaultSettings(preprocessor=v) return None + def get_default_settings_lora() -> LoraModelDefaultSettings: return LoraModelDefaultSettings() + def get_default_settings_main(model_base: BaseModelType) -> Optional[MainModelDefaultSettings]: if model_base is BaseModelType.StableDiffusion1 or model_base is BaseModelType.StableDiffusion2: return MainModelDefaultSettings(width=512, height=512) From daedefc1f862e3fcc70b61be18e8dc62ae812bc6 Mon Sep 17 00:00:00 2001 From: Attila Cseh Date: Tue, 26 Aug 2025 13:04:04 +0200 Subject: [PATCH 4/7] type conversion fixed --- invokeai/frontend/web/src/services/api/schema.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index eda6e7771a1..2b4a35bb60c 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -12729,14 +12729,14 @@ export type components = { * Convert Cache Dir * Format: path * @description Path to the converted models cache directory (DEPRECATED, but do not delete because it is needed for migration from previous versions). - * @default models\.convert_cache + * @default models/.convert_cache */ convert_cache_dir?: string; /** * Download Cache Dir * Format: path * @description Path to the directory that contains dynamically downloaded models. - * @default models\.download_cache + * @default models/.download_cache */ download_cache_dir?: string; /** From 9c5e975513f19eaf35f0d65e045d04b7d4865da3 Mon Sep 17 00:00:00 2001 From: Attila Cseh Date: Mon, 1 Sep 2025 13:09:11 +0200 Subject: [PATCH 5/7] LoRA weight default values turned into constant --- .../frontend/web/src/app/types/invokeai.ts | 14 ---------- .../controlLayers/store/lorasSlice.ts | 3 ++- .../src/features/lora/components/LoRACard.tsx | 23 +++++++--------- .../hooks/useLoRAModelDefaultSettings.ts | 18 +++---------- .../DefaultWeight.tsx | 27 +++++++------------ .../src/features/system/store/configSlice.ts | 12 ++++++++- 6 files changed, 36 insertions(+), 61 deletions(-) diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 0058c4d883b..b24f83a1b15 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -127,9 +127,6 @@ export const zAppConfig = z.object({ flux: z.object({ guidance: zNumericalParameterConfig, }), - lora: z.object({ - weight: zNumericalParameterConfig, - }), }); export type AppConfig = z.infer; @@ -302,15 +299,4 @@ export const getDefaultAppConfig = (): AppConfig => ({ coarseStep: 0.5, }, }, - lora: { - weight: { - initial: 0.75, - sliderMin: -1, - sliderMax: 2, - numberInputMin: -1, - numberInputMax: 2, - fineStep: 0.01, - coarseStep: 0.05, - }, - }, }); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/lorasSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/lorasSlice.ts index 63fb1c8ec0d..f8c00ce2ea5 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/lorasSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/lorasSlice.ts @@ -4,6 +4,7 @@ import type { SliceConfig } from 'app/store/types'; import { paramsReset } from 'features/controlLayers/store/paramsSlice'; import { type LoRA, zLoRA } from 'features/controlLayers/store/types'; import { zModelIdentifierField } from 'features/nodes/types/common'; +import { DEFAULT_LORA_WEIGHT_CONFIG } from 'features/system/store/configSlice'; import type { LoRAModelConfig } from 'services/api/types'; import { v4 as uuidv4 } from 'uuid'; import z from 'zod'; @@ -28,7 +29,7 @@ const slice = createSlice({ const { model, id } = action.payload; const parsedModel = zModelIdentifierField.parse(model); const defaultLoRAConfig: Pick = { - weight: model.default_settings?.weight ?? 0.75, + weight: model.default_settings?.weight ?? DEFAULT_LORA_WEIGHT_CONFIG.initial, isEnabled: true, }; state.loras.push({ ...defaultLoRAConfig, model: parsedModel, id }); diff --git a/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx b/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx index 04221da61e5..9a1bac52807 100644 --- a/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx +++ b/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx @@ -18,13 +18,11 @@ import { loraWeightChanged, } from 'features/controlLayers/store/lorasSlice'; import type { LoRA } from 'features/controlLayers/store/types'; -import { selectLoRAWeightConfig } from 'features/system/store/configSlice'; +import { DEFAULT_LORA_WEIGHT_CONFIG } from 'features/system/store/configSlice'; import { memo, useCallback, useMemo } from 'react'; import { PiTrashSimpleBold } from 'react-icons/pi'; import { useGetModelConfigQuery } from 'services/api/endpoints/models'; -const marks = [-1, 0, 1, 2]; - export const LoRACard = memo((props: { id: string }) => { const selectLoRA = useMemo(() => buildSelectLoRA(props.id), [props.id]); const lora = useAppSelector(selectLoRA); @@ -39,7 +37,6 @@ LoRACard.displayName = 'LoRACard'; const LoRAContent = memo(({ lora }: { lora: LoRA }) => { const dispatch = useAppDispatch(); - const config = useAppSelector(selectLoRAWeightConfig); const { data: loraConfig } = useGetModelConfigQuery(lora.model.key); const handleChange = useCallback( @@ -81,22 +78,22 @@ const LoRAContent = memo(({ lora }: { lora: LoRA }) => { diff --git a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useLoRAModelDefaultSettings.ts b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useLoRAModelDefaultSettings.ts index 5ca6a985e0d..26f86149aa3 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useLoRAModelDefaultSettings.ts +++ b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useLoRAModelDefaultSettings.ts @@ -1,29 +1,17 @@ -import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; -import { useAppSelector } from 'app/store/storeHooks'; import { isNil } from 'es-toolkit/compat'; -import { selectConfigSlice } from 'features/system/store/configSlice'; +import { DEFAULT_LORA_WEIGHT_CONFIG } from 'features/system/store/configSlice'; import { useMemo } from 'react'; import type { LoRAModelConfig } from 'services/api/types'; -const initialStatesSelector = createMemoizedSelector(selectConfigSlice, (config) => { - const { weight } = config.lora; - - return { - initialWeight: weight.initial, - }; -}); - export const useLoRAModelDefaultSettings = (modelConfig: LoRAModelConfig) => { - const { initialWeight } = useAppSelector(initialStatesSelector); - const defaultSettingsDefaults = useMemo(() => { return { weight: { isEnabled: !isNil(modelConfig?.default_settings?.weight), - value: modelConfig?.default_settings?.weight || initialWeight, + value: modelConfig?.default_settings?.weight ?? DEFAULT_LORA_WEIGHT_CONFIG.initial, }, }; - }, [modelConfig?.default_settings, initialWeight]); + }, [modelConfig?.default_settings]); return defaultSettingsDefaults; }; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx index 7fc65b87da0..82266c18c06 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx @@ -1,8 +1,7 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library'; -import { useAppSelector } from 'app/store/storeHooks'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; -import { selectLoRAWeightConfig } from 'features/system/store/configSlice'; +import { DEFAULT_LORA_WEIGHT_CONFIG } from 'features/system/store/configSlice'; import { memo, useCallback, useMemo } from 'react'; import type { UseControllerProps } from 'react-hook-form'; import { useController } from 'react-hook-form'; @@ -14,13 +13,7 @@ type DefaultWeight = LoRAModelDefaultSettingsFormData['weight']; export const DefaultWeight = memo((props: UseControllerProps) => { const { field } = useController(props); - - const config = useAppSelector(selectLoRAWeightConfig); const { t } = useTranslation(); - const marks = useMemo( - () => [config.sliderMin, Math.floor(config.sliderMax / 2), config.sliderMax], - [config.sliderMax, config.sliderMin] - ); const onChange = useCallback( (v: number) => { @@ -53,20 +46,20 @@ export const DefaultWeight = memo((props: UseControllerProps diff --git a/invokeai/frontend/web/src/features/system/store/configSlice.ts b/invokeai/frontend/web/src/features/system/store/configSlice.ts index 16fdad38074..914ff3043d5 100644 --- a/invokeai/frontend/web/src/features/system/store/configSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/configSlice.ts @@ -6,6 +6,17 @@ import { getDefaultAppConfig, type PartialAppConfig, zAppConfig } from 'app/type import { merge } from 'es-toolkit/compat'; import z from 'zod'; +export const DEFAULT_LORA_WEIGHT_CONFIG = { + initial: 0.75, + sliderMin: -1, + sliderMax: 2, + marks: [-1, 0, 1, 2], + numberInputMin: -1, + numberInputMax: 2, + fineStep: 0.01, + coarseStep: 0.05, +} as const; + const zConfigState = z.object({ ...zAppConfig.shape, didLoad: z.boolean(), @@ -61,7 +72,6 @@ export const selectInfillTileSizeConfig = createConfigSelector((config) => confi export const selectImg2imgStrengthConfig = createConfigSelector((config) => config.sd.img2imgStrength); export const selectMaxPromptsConfig = createConfigSelector((config) => config.sd.dynamicPrompts.maxPrompts); export const selectIterationsConfig = createConfigSelector((config) => config.sd.iterations); -export const selectLoRAWeightConfig = createConfigSelector((config) => config.lora.weight); export const selectMaxUpscaleDimension = createConfigSelector((config) => config.maxUpscaleDimension); export const selectAllowPrivateStylePresets = createConfigSelector((config) => config.allowPrivateStylePresets); From 9c1bab80968b85680d7928af4eaa4d64249186f7 Mon Sep 17 00:00:00 2001 From: Attila Cseh Date: Mon, 1 Sep 2025 13:13:57 +0200 Subject: [PATCH 6/7] code review changes --- .../ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx index 82266c18c06..efc13717eb4 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx @@ -11,14 +11,14 @@ import type { LoRAModelDefaultSettingsFormData } from './LoRAModelDefaultSetting type DefaultWeight = LoRAModelDefaultSettingsFormData['weight']; -export const DefaultWeight = memo((props: UseControllerProps) => { +export const DefaultWeight = memo((props: UseControllerProps) => { const { field } = useController(props); const { t } = useTranslation(); const onChange = useCallback( (v: number) => { const updatedValue = { - ...(field.value as DefaultWeight), + ...(field.value), value: v, }; field.onChange(updatedValue); @@ -27,11 +27,11 @@ export const DefaultWeight = memo((props: UseControllerProps { - return (field.value as DefaultWeight).value; + return (field.value).value; }, [field.value]); const isDisabled = useMemo(() => { - return !(field.value as DefaultWeight).isEnabled; + return !(field.value).isEnabled; }, [field.value]); return ( From 63228a5c537fe5a3aa0c5b6e4f3be7bdfc54a80b Mon Sep 17 00:00:00 2001 From: Attila Cseh Date: Mon, 1 Sep 2025 13:22:04 +0200 Subject: [PATCH 7/7] prettier fix --- .../ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx index efc13717eb4..8f1ca4791ff 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/LoRAModelDefaultSettings/DefaultWeight.tsx @@ -18,7 +18,7 @@ export const DefaultWeight = memo((props: UseControllerProps { const updatedValue = { - ...(field.value), + ...field.value, value: v, }; field.onChange(updatedValue); @@ -27,11 +27,11 @@ export const DefaultWeight = memo((props: UseControllerProps { - return (field.value).value; + return field.value.value; }, [field.value]); const isDisabled = useMemo(() => { - return !(field.value).isEnabled; + return !field.value.isEnabled; }, [field.value]); return (