hook MM default model settings up to API

This commit is contained in:
Mary Hipp 2024-02-29 14:50:31 -05:00
parent 6e0665e3d7
commit 058cc715d4
14 changed files with 1263 additions and 1636 deletions

View File

@ -741,6 +741,8 @@
"customConfig": "Custom Config",
"customConfigFileLocation": "Custom Config File Location",
"customSaveLocation": "Custom Save Location",
"defaultSettings": "Default Settings",
"defaultSettingsSaved": "Default Settings Saved",
"delete": "Delete",
"deleteConfig": "Delete Config",
"deleteModel": "Delete Model",

View File

@ -1,7 +1,7 @@
import type { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
import type { ParameterPrecision, ParameterScheduler } from 'features/parameters/types/parameterSchemas';
import type { InvokeTabName } from 'features/ui/store/tabMap';
import type { O } from 'ts-toolbelt';
import { ParameterPrecision, ParameterScheduler } from '../../features/parameters/types/parameterSchemas';
/**
* A disable-able application feature

View File

@ -1,33 +1,13 @@
import { Button, Flex, Heading } from '@invoke-ai/ui-library';
import { SubmitHandler, useForm } from 'react-hook-form';
import { SettingToggle } from './DefaultSettings/SettingToggle';
import { DefaultCfgScale } from './DefaultSettings/DefaultCfgScale';
import { DefaultSteps } from './DefaultSettings/DefaultSteps';
import { useAppSelector } from '../../../../app/store/storeHooks';
import { DefaultScheduler } from './DefaultSettings/DefaultScheduler';
import { selectGenerationSlice } from '../../../parameters/store/generationSlice';
import { createMemoizedSelector } from '../../../../app/store/createMemoizedSelector';
import { selectConfigSlice } from '../../../system/store/configSlice';
import { DefaultVaePrecision } from './DefaultSettings/DefaultVaePrecision';
import { DefaultCfgRescaleMultiplier } from './DefaultSettings/DefaultCfgRescaleMultiplier';
import { DefaultVae } from './DefaultSettings/DefaultVae';
import { t } from 'i18next';
import { IoPencil } from 'react-icons/io5';
import { each } from 'lodash-es';
import { skipToken } from '@reduxjs/toolkit/query';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import Loading from 'common/components/Loading/Loading';
import { selectConfigSlice } from 'features/system/store/configSlice';
import { isNil } from 'lodash-es';
import { useMemo } from 'react';
import { useGetModelMetadataQuery } from 'services/api/endpoints/models';
export interface FormField<T> {
value: T;
isEnabled: boolean;
}
export type DefaultSettingsFormData = {
vae: FormField<string | null>;
vaePrecision: FormField<string>;
scheduler: FormField<string>;
steps: FormField<number>;
cfgScale: FormField<number>;
cfgRescaleMultiplier: FormField<number>;
};
import { DefaultSettingsForm } from './DefaultSettings/DefaultSettingsForm';
const initialStatesSelector = createMemoizedSelector(selectConfigSlice, (config) => {
const { steps, guidance, scheduler, cfgRescaleMultiplier, vaePrecision } = config.sd;
@ -42,78 +22,45 @@ const initialStatesSelector = createMemoizedSelector(selectConfigSlice, (config)
});
export const DefaultSettings = () => {
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
const { data, isLoading } = useGetModelMetadataQuery(selectedModelKey ?? skipToken);
const { initialSteps, initialCfg, initialScheduler, initialCfgRescaleMultiplier, initialVaePrecision } =
useAppSelector(initialStatesSelector);
const { handleSubmit, control, formState } = useForm<DefaultSettingsFormData>({
defaultValues: {
vae: { isEnabled: false, value: null },
vaePrecision: { isEnabled: false, value: initialVaePrecision },
scheduler: { isEnabled: false, value: initialScheduler },
steps: { isEnabled: false, value: initialSteps },
cfgScale: { isEnabled: false, value: initialCfg },
cfgRescaleMultiplier: { isEnabled: false, value: initialCfgRescaleMultiplier },
},
});
const defaultSettingsDefaults = useMemo(() => {
return {
vae: { isEnabled: !isNil(data?.default_settings?.vae), value: data?.default_settings?.vae || 'default' },
vaePrecision: {
isEnabled: !isNil(data?.default_settings?.vae_precision),
value: data?.default_settings?.vae_precision || initialVaePrecision || 'fp32',
},
scheduler: {
isEnabled: !isNil(data?.default_settings?.scheduler),
value: data?.default_settings?.scheduler || initialScheduler || 'euler',
},
steps: { isEnabled: !isNil(data?.default_settings?.steps), value: data?.default_settings?.steps || initialSteps },
cfgScale: {
isEnabled: !isNil(data?.default_settings?.cfg_scale),
value: data?.default_settings?.cfg_scale || initialCfg,
},
cfgRescaleMultiplier: {
isEnabled: !isNil(data?.default_settings?.cfg_rescale_multiplier),
value: data?.default_settings?.cfg_rescale_multiplier || initialCfgRescaleMultiplier,
},
};
}, [
data?.default_settings,
initialSteps,
initialCfg,
initialScheduler,
initialCfgRescaleMultiplier,
initialVaePrecision,
]);
const onSubmit: SubmitHandler<DefaultSettingsFormData> = (data) => {
const body: { [key: string]: string | number | null } = {};
each(data, (value, key) => {
if (value.isEnabled) {
body[key] = value.value;
}
});
console.log(body);
};
if (isLoading) {
return <Loading />;
}
return (
<>
<Flex gap="2" justifyContent="space-between" w="full" mb={5}>
<Heading fontSize="md">Default Settings</Heading>
<Button
size="sm"
leftIcon={<IoPencil />}
colorScheme="invokeYellow"
isDisabled={!formState.isDirty}
onClick={handleSubmit(onSubmit)}
type="submit"
>
{t('common.save')}
</Button>
</Flex>
<Flex flexDir="column" gap={8}>
<Flex gap={8}>
<Flex gap={4} w="full">
<SettingToggle control={control} name="vae" />
<DefaultVae control={control} name="vae" />
</Flex>
<Flex gap={4} w="full">
<SettingToggle control={control} name="vaePrecision" />
<DefaultVaePrecision control={control} name="vaePrecision" />
</Flex>
</Flex>
<Flex gap={8}>
<Flex gap={4} w="full">
<SettingToggle control={control} name="scheduler" />
<DefaultScheduler control={control} name="scheduler" />
</Flex>
<Flex gap={4} w="full">
<SettingToggle control={control} name="steps" />
<DefaultSteps control={control} name="steps" />
</Flex>
</Flex>
<Flex gap={8}>
<Flex gap={4} w="full">
<SettingToggle control={control} name="cfgScale" />
<DefaultCfgScale control={control} name="cfgScale" />
</Flex>
<Flex gap={4} w="full">
<SettingToggle control={control} name="cfgRescaleMultiplier" />
<DefaultCfgRescaleMultiplier control={control} name="cfgRescaleMultiplier" />
</Flex>
</Flex>
</Flex>
</>
);
return <DefaultSettingsForm defaultSettingsDefaults={defaultSettingsDefaults} />;
};

View File

@ -1,11 +1,12 @@
import { FormControl, FormLabel, CompositeSlider, CompositeNumberInput, Flex } from '@invoke-ai/ui-library';
import { useMemo } from 'react';
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 { useCallback,useMemo } from 'react';
import type {UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useAppSelector } from '../../../../../app/store/storeHooks';
import { InformationalPopover } from '../../../../../common/components/InformationalPopover/InformationalPopover';
import { UseControllerProps, useController } from 'react-hook-form';
import { DefaultSettingsFormData } from '../DefaultSettings';
import { useCallback } from 'react';
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
type DefaultCfgRescaleMultiplierType = DefaultSettingsFormData['cfgRescaleMultiplier'];

View File

@ -1,11 +1,12 @@
import { FormControl, FormLabel, CompositeSlider, CompositeNumberInput, Flex } from '@invoke-ai/ui-library';
import { useMemo } from 'react';
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 { useCallback,useMemo } from 'react';
import type {UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useAppSelector } from '../../../../../app/store/storeHooks';
import { InformationalPopover } from '../../../../../common/components/InformationalPopover/InformationalPopover';
import { UseControllerProps, useController } from 'react-hook-form';
import { DefaultSettingsFormData } from '../DefaultSettings';
import { useCallback } from 'react';
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
type DefaultCfgType = DefaultSettingsFormData['cfgScale'];

View File

@ -4,9 +4,11 @@ import { InformationalPopover } from 'common/components/InformationalPopover/Inf
import { SCHEDULER_OPTIONS } from 'features/parameters/types/constants';
import { isParameterScheduler } from 'features/parameters/types/parameterSchemas';
import { useCallback, useMemo } from 'react';
import type {UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { DefaultSettingsFormData } from '../DefaultSettings';
import { UseControllerProps, useController } from 'react-hook-form';
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
type DefaultSchedulerType = DefaultSettingsFormData['scheduler'];

View File

@ -0,0 +1,147 @@
import { Button, Flex, Heading } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import type { ParameterScheduler } from 'features/parameters/types/parameterSchemas';
import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast';
import { useCallback } from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { IoPencil } from 'react-icons/io5';
import { useUpdateModelMetadataMutation } from 'services/api/endpoints/models';
import { DefaultCfgRescaleMultiplier } from './DefaultCfgRescaleMultiplier';
import { DefaultCfgScale } from './DefaultCfgScale';
import { DefaultScheduler } from './DefaultScheduler';
import { DefaultSteps } from './DefaultSteps';
import { DefaultVae } from './DefaultVae';
import { DefaultVaePrecision } from './DefaultVaePrecision';
import { SettingToggle } from './SettingToggle';
export interface FormField<T> {
value: T;
isEnabled: boolean;
}
export type DefaultSettingsFormData = {
vae: FormField<string>;
vaePrecision: FormField<string>;
scheduler: FormField<ParameterScheduler>;
steps: FormField<number>;
cfgScale: FormField<number>;
cfgRescaleMultiplier: FormField<number>;
};
export const DefaultSettingsForm = ({
defaultSettingsDefaults,
}: {
defaultSettingsDefaults: DefaultSettingsFormData;
}) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
const [editModelMetadata, { isLoading }] = useUpdateModelMetadataMutation();
const { handleSubmit, control, formState } = useForm<DefaultSettingsFormData>({
defaultValues: defaultSettingsDefaults,
});
const onSubmit = useCallback<SubmitHandler<DefaultSettingsFormData>>(
(data) => {
if (!selectedModelKey) {
return;
}
const body = {
vae: data.vae.isEnabled ? data.vae.value : null,
vae_precision: data.vaePrecision.isEnabled ? data.vaePrecision.value : null,
cfg_scale: data.cfgScale.isEnabled ? data.cfgScale.value : null,
cfg_rescale_multiplier: data.cfgRescaleMultiplier.isEnabled ? data.cfgRescaleMultiplier.value : null,
steps: data.steps.isEnabled ? data.steps.value : null,
scheduler: data.scheduler.isEnabled ? data.scheduler.value : null,
};
editModelMetadata({
key: selectedModelKey,
body: { default_settings: body },
})
.unwrap()
.then((_) => {
dispatch(
addToast(
makeToast({
title: t('modelManager.defaultSettingsSaved'),
status: 'success',
})
)
);
})
.catch((error) => {
if (error) {
dispatch(
addToast(
makeToast({
title: `${error.data.detail} `,
status: 'error',
})
)
);
}
});
},
[selectedModelKey, dispatch, editModelMetadata]
);
return (
<>
<Flex gap="2" justifyContent="space-between" w="full" mb={5}>
<Heading fontSize="md">{t('modelManager.defaultSettings')}</Heading>
<Button
size="sm"
leftIcon={<IoPencil />}
colorScheme="invokeYellow"
isDisabled={!formState.isDirty}
onClick={handleSubmit(onSubmit)}
type="submit"
isLoading={isLoading}
>
{t('common.save')}
</Button>
</Flex>
<Flex flexDir="column" gap={8}>
<Flex gap={8}>
<Flex gap={4} w="full">
<SettingToggle control={control} name="vae" />
<DefaultVae control={control} name="vae" />
</Flex>
<Flex gap={4} w="full">
<SettingToggle control={control} name="vaePrecision" />
<DefaultVaePrecision control={control} name="vaePrecision" />
</Flex>
</Flex>
<Flex gap={8}>
<Flex gap={4} w="full">
<SettingToggle control={control} name="scheduler" />
<DefaultScheduler control={control} name="scheduler" />
</Flex>
<Flex gap={4} w="full">
<SettingToggle control={control} name="steps" />
<DefaultSteps control={control} name="steps" />
</Flex>
</Flex>
<Flex gap={8}>
<Flex gap={4} w="full">
<SettingToggle control={control} name="cfgScale" />
<DefaultCfgScale control={control} name="cfgScale" />
</Flex>
<Flex gap={4} w="full">
<SettingToggle control={control} name="cfgRescaleMultiplier" />
<DefaultCfgRescaleMultiplier control={control} name="cfgRescaleMultiplier" />
</Flex>
</Flex>
</Flex>
</>
);
};

View File

@ -1,11 +1,12 @@
import { FormControl, FormLabel, CompositeSlider, CompositeNumberInput, Flex } from '@invoke-ai/ui-library';
import { useMemo } from 'react';
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 { useCallback,useMemo } from 'react';
import type {UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useAppSelector } from '../../../../../app/store/storeHooks';
import { InformationalPopover } from '../../../../../common/components/InformationalPopover/InformationalPopover';
import { UseControllerProps, useController } from 'react-hook-form';
import { DefaultSettingsFormData } from '../DefaultSettings';
import { useCallback } from 'react';
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
type DefaultSteps = DefaultSettingsFormData['steps'];

View File

@ -1,14 +1,16 @@
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { DefaultSettingsFormData } from '../DefaultSettings';
import { UseControllerProps, useController } from 'react-hook-form';
import { useGetModelConfigQuery, useGetVaeModelsQuery } from '../../../../../services/api/endpoints/models';
import { map } from 'lodash-es';
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppSelector } from '../../../../../app/store/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { map } from 'lodash-es';
import { useCallback, useMemo } from 'react';
import type {UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useGetModelConfigQuery, useGetVaeModelsQuery } from 'services/api/endpoints/models';
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
type DefaultVaeType = DefaultSettingsFormData['vae'];
@ -25,13 +27,15 @@ export function DefaultVae(props: UseControllerProps<DefaultSettingsFormData>) {
.filter((vae) => vae.base === modelData?.base)
.map((vae) => ({ label: vae.name, value: vae.key }));
return { compatibleOptions };
const defaultOption = { label: 'Default VAE', value: 'default' };
return { compatibleOptions: [defaultOption, ...compatibleOptions] };
},
});
const onChange = useCallback<ComboboxOnChange>(
(v) => {
const newValue = !v?.value ? null : v.value;
const newValue = !v?.value ? 'default' : v.value;
const updatedValue = {
...(field.value as DefaultVaeType),
@ -55,14 +59,7 @@ export function DefaultVae(props: UseControllerProps<DefaultSettingsFormData>) {
<InformationalPopover feature="paramVAE">
<FormLabel>{t('modelManager.vae')}</FormLabel>
</InformationalPopover>
<Combobox
isDisabled={isDisabled}
isClearable
value={value}
placeholder={value ? value.value : t('models.defaultVAE')}
options={compatibleOptions}
onChange={onChange}
/>
<Combobox isDisabled={isDisabled} value={value} options={compatibleOptions} onChange={onChange} />
</FormControl>
);
}

View File

@ -1,11 +1,13 @@
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { isParameterPrecision, isParameterScheduler } from 'features/parameters/types/parameterSchemas';
import { isParameterPrecision } from 'features/parameters/types/parameterSchemas';
import { useCallback, useMemo } from 'react';
import type {UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { DefaultSettingsFormData } from '../DefaultSettings';
import { UseControllerProps, useController } from 'react-hook-form';
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
const options = [
{ label: 'FP16', value: 'fp16' },

View File

@ -1,8 +1,10 @@
import { UseControllerProps, useController, useFormContext, useWatch } from 'react-hook-form';
import { DefaultSettingsFormData, FormField } from '../DefaultSettings';
import { useCallback } from 'react';
import { Switch } from '@invoke-ai/ui-library';
import { ChangeEvent } from 'react';
import type { ChangeEvent } from 'react';
import { useCallback , useMemo } from 'react';
import type { UseControllerProps} from 'react-hook-form';
import { useController } from 'react-hook-form';
import type { DefaultSettingsFormData, FormField } from './DefaultSettingsForm';
interface Props<T> extends UseControllerProps<DefaultSettingsFormData> {
name: keyof DefaultSettingsFormData;
@ -11,6 +13,10 @@ interface Props<T> extends UseControllerProps<DefaultSettingsFormData> {
export function SettingToggle<T>(props: Props<T>) {
const { field } = useController(props);
const value = useMemo(() => {
return !!(field.value as FormField<T>).isEnabled;
}, [field.value]);
const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const updatedValue: FormField<T> = {
@ -22,5 +28,5 @@ export function SettingToggle<T>(props: Props<T>) {
[field]
);
return <Switch onChange={onChange} />;
return <Switch isChecked={value} onChange={onChange} />;
}

View File

@ -1,4 +1,4 @@
import { Box, Button, Flex, Heading, Text } from '@invoke-ai/ui-library';
import { Box, Button, Flex, Text } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice';
@ -17,9 +17,9 @@ import type {
VAEModelConfig,
} from 'services/api/types';
import { DefaultSettings } from './DefaultSettings';
import { ModelAttrView } from './ModelAttrView';
import { ModelConvert } from './ModelConvert';
import { DefaultSettings } from './DefaultSettings';
export const ModelView = () => {
const { t } = useTranslation();

View File

@ -38,6 +38,7 @@ type GetModelConfigResponse = paths['/api/v2/models/i/{key}']['get']['responses'
type GetModelMetadataResponse =
paths['/api/v2/models/i/{key}/metadata']['get']['responses']['200']['content']['application/json'];
type ListModelsArg = NonNullable<paths['/api/v2/models/']['get']['parameters']['query']>;
type DeleteMainModelArg = {
@ -116,25 +117,25 @@ const anyModelConfigAdapterSelectors = anyModelConfigAdapter.getSelectors(undefi
const buildProvidesTags =
<TEntity extends AnyModelConfig>(tagType: (typeof tagTypes)[number]) =>
(result: EntityState<TEntity, string> | undefined) => {
const tags: ApiTagDescription[] = [{ type: tagType, id: LIST_TAG }, 'Model'];
if (result) {
tags.push(
...result.ids.map((id) => ({
type: tagType,
id,
}))
);
}
(result: EntityState<TEntity, string> | undefined) => {
const tags: ApiTagDescription[] = [{ type: tagType, id: LIST_TAG }, 'Model'];
if (result) {
tags.push(
...result.ids.map((id) => ({
type: tagType,
id,
}))
);
}
return tags;
};
return tags;
};
const buildTransformResponse =
<T extends AnyModelConfig>(adapter: EntityAdapter<T, string>) =>
(response: { models: T[] }) => {
return adapter.setAll(adapter.getInitialState(), response.models);
};
(response: { models: T[] }) => {
return adapter.setAll(adapter.getInitialState(), response.models);
};
/**
* Builds an endpoint URL for the models router

File diff suppressed because one or more lines are too long