From 6e0665e3d7a1e1a108d5b889128b5cb5668c813e Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 29 Feb 2024 13:04:53 -0500 Subject: [PATCH] UI for configuring default settings for models' --- .../subpanels/ModelPanel/DefaultSettings.tsx | 119 ++++++++++++++++++ .../DefaultCfgRescaleMultiplier.tsx | 71 +++++++++++ .../DefaultSettings/DefaultCfgScale.tsx | 71 +++++++++++ .../DefaultSettings/DefaultScheduler.tsx | 48 +++++++ .../DefaultSettings/DefaultSteps.tsx | 71 +++++++++++ .../ModelPanel/DefaultSettings/DefaultVae.tsx | 68 ++++++++++ .../DefaultSettings/DefaultVaePrecision.tsx | 49 ++++++++ .../DefaultSettings/SettingToggle.tsx | 26 ++++ .../subpanels/ModelPanel/ModelView.tsx | 8 +- 9 files changed, 529 insertions(+), 2 deletions(-) create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings.tsx create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultCfgRescaleMultiplier.tsx create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultCfgScale.tsx create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultScheduler.tsx create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultSteps.tsx create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultVae.tsx create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultVaePrecision.tsx create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/SettingToggle.tsx diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings.tsx new file mode 100644 index 0000000000..50771525ba --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings.tsx @@ -0,0 +1,119 @@ +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'; + +export interface FormField { + value: T; + isEnabled: boolean; +} + +export type DefaultSettingsFormData = { + vae: FormField; + vaePrecision: FormField; + scheduler: FormField; + steps: FormField; + cfgScale: FormField; + cfgRescaleMultiplier: FormField; +}; + +const initialStatesSelector = createMemoizedSelector(selectConfigSlice, (config) => { + const { steps, guidance, scheduler, cfgRescaleMultiplier, vaePrecision } = config.sd; + + return { + initialSteps: steps.initial, + initialCfg: guidance.initial, + initialScheduler: scheduler, + initialCfgRescaleMultiplier: cfgRescaleMultiplier.initial, + initialVaePrecision: vaePrecision, + }; +}); + +export const DefaultSettings = () => { + const { initialSteps, initialCfg, initialScheduler, initialCfgRescaleMultiplier, initialVaePrecision } = + useAppSelector(initialStatesSelector); + + const { handleSubmit, control, formState } = useForm({ + 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 onSubmit: SubmitHandler = (data) => { + const body: { [key: string]: string | number | null } = {}; + each(data, (value, key) => { + if (value.isEnabled) { + body[key] = value.value; + } + }); + console.log(body); + }; + + return ( + <> + + Default Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultCfgRescaleMultiplier.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultCfgRescaleMultiplier.tsx new file mode 100644 index 0000000000..7e7fe93fa1 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultCfgRescaleMultiplier.tsx @@ -0,0 +1,71 @@ +import { FormControl, FormLabel, CompositeSlider, CompositeNumberInput, Flex } from '@invoke-ai/ui-library'; +import { useMemo } from 'react'; +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'; + +type DefaultCfgRescaleMultiplierType = DefaultSettingsFormData['cfgRescaleMultiplier']; + +export function DefaultCfgRescaleMultiplier(props: UseControllerProps) { + const { field } = useController(props); + + const sliderMin = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.sliderMin); + const sliderMax = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.sliderMax); + const numberInputMin = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.numberInputMin); + const numberInputMax = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.numberInputMax); + const coarseStep = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.coarseStep); + const fineStep = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.fineStep); + const { t } = useTranslation(); + const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]); + + const onChange = useCallback( + (v: number) => { + const updatedValue = { + ...(field.value as DefaultCfgRescaleMultiplierType), + value: v, + }; + field.onChange(updatedValue); + }, + [field] + ); + + const value = useMemo(() => { + return (field.value as DefaultCfgRescaleMultiplierType).value; + }, [field.value]); + + const isDisabled = useMemo(() => { + return !(field.value as DefaultCfgRescaleMultiplierType).isEnabled; + }, [field.value]); + + return ( + + + {t('parameters.cfgRescaleMultiplier')} + + + + + + + ); +} diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultCfgScale.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultCfgScale.tsx new file mode 100644 index 0000000000..ea4f9a8468 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultCfgScale.tsx @@ -0,0 +1,71 @@ +import { FormControl, FormLabel, CompositeSlider, CompositeNumberInput, Flex } from '@invoke-ai/ui-library'; +import { useMemo } from 'react'; +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'; + +type DefaultCfgType = DefaultSettingsFormData['cfgScale']; + +export function DefaultCfgScale(props: UseControllerProps) { + const { field } = useController(props); + + const sliderMin = useAppSelector((s) => s.config.sd.guidance.sliderMin); + const sliderMax = useAppSelector((s) => s.config.sd.guidance.sliderMax); + const numberInputMin = useAppSelector((s) => s.config.sd.guidance.numberInputMin); + const numberInputMax = useAppSelector((s) => s.config.sd.guidance.numberInputMax); + const coarseStep = useAppSelector((s) => s.config.sd.guidance.coarseStep); + const fineStep = useAppSelector((s) => s.config.sd.guidance.fineStep); + const { t } = useTranslation(); + const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]); + + const onChange = useCallback( + (v: number) => { + const updatedValue = { + ...(field.value as DefaultCfgType), + value: v, + }; + field.onChange(updatedValue); + }, + [field] + ); + + const value = useMemo(() => { + return (field.value as DefaultCfgType).value; + }, [field.value]); + + const isDisabled = useMemo(() => { + return !(field.value as DefaultCfgType).isEnabled; + }, [field.value]); + + return ( + + + {t('parameters.cfgScale')} + + + + + + + ); +} diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultScheduler.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultScheduler.tsx new file mode 100644 index 0000000000..145748cd28 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultScheduler.tsx @@ -0,0 +1,48 @@ +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 { SCHEDULER_OPTIONS } from 'features/parameters/types/constants'; +import { isParameterScheduler } from 'features/parameters/types/parameterSchemas'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { DefaultSettingsFormData } from '../DefaultSettings'; +import { UseControllerProps, useController } from 'react-hook-form'; + +type DefaultSchedulerType = DefaultSettingsFormData['scheduler']; + +export function DefaultScheduler(props: UseControllerProps) { + const { t } = useTranslation(); + const { field } = useController(props); + + const onChange = useCallback( + (v) => { + if (!isParameterScheduler(v?.value)) { + return; + } + const updatedValue = { + ...(field.value as DefaultSchedulerType), + value: v.value, + }; + field.onChange(updatedValue); + }, + [field] + ); + + const value = useMemo( + () => SCHEDULER_OPTIONS.find((o) => o.value === (field.value as DefaultSchedulerType).value), + [field] + ); + + const isDisabled = useMemo(() => { + return !(field.value as DefaultSchedulerType).isEnabled; + }, [field.value]); + + return ( + + + {t('parameters.scheduler')} + + + + ); +} diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultSteps.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultSteps.tsx new file mode 100644 index 0000000000..5ff4d20f22 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultSteps.tsx @@ -0,0 +1,71 @@ +import { FormControl, FormLabel, CompositeSlider, CompositeNumberInput, Flex } from '@invoke-ai/ui-library'; +import { useMemo } from 'react'; +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'; + +type DefaultSteps = DefaultSettingsFormData['steps']; + +export function DefaultSteps(props: UseControllerProps) { + const { field } = useController(props); + + const sliderMin = useAppSelector((s) => s.config.sd.steps.sliderMin); + const sliderMax = useAppSelector((s) => s.config.sd.steps.sliderMax); + const numberInputMin = useAppSelector((s) => s.config.sd.steps.numberInputMin); + const numberInputMax = useAppSelector((s) => s.config.sd.steps.numberInputMax); + const coarseStep = useAppSelector((s) => s.config.sd.steps.coarseStep); + const fineStep = useAppSelector((s) => s.config.sd.steps.fineStep); + const { t } = useTranslation(); + const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]); + + const onChange = useCallback( + (v: number) => { + const updatedValue = { + ...(field.value as DefaultSteps), + value: v, + }; + field.onChange(updatedValue); + }, + [field] + ); + + const value = useMemo(() => { + return (field.value as DefaultSteps).value; + }, [field.value]); + + const isDisabled = useMemo(() => { + return !(field.value as DefaultSteps).isEnabled; + }, [field.value]); + + return ( + + + {t('parameters.steps')} + + + + + + + ); +} diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultVae.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultVae.tsx new file mode 100644 index 0000000000..9b139dca5a --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultVae.tsx @@ -0,0 +1,68 @@ +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'; + +type DefaultVaeType = DefaultSettingsFormData['vae']; + +export function DefaultVae(props: UseControllerProps) { + const { t } = useTranslation(); + const { field } = useController(props); + const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); + const { data: modelData } = useGetModelConfigQuery(selectedModelKey ?? skipToken); + + const { compatibleOptions } = useGetVaeModelsQuery(undefined, { + selectFromResult: ({ data }) => { + const modelArray = map(data?.entities); + const compatibleOptions = modelArray + .filter((vae) => vae.base === modelData?.base) + .map((vae) => ({ label: vae.name, value: vae.key })); + + return { compatibleOptions }; + }, + }); + + const onChange = useCallback( + (v) => { + const newValue = !v?.value ? null : v.value; + + const updatedValue = { + ...(field.value as DefaultVaeType), + value: newValue, + }; + field.onChange(updatedValue); + }, + [field] + ); + + const value = useMemo(() => { + return compatibleOptions.find((vae) => vae.value === (field.value as DefaultVaeType).value); + }, [compatibleOptions, field.value]); + + const isDisabled = useMemo(() => { + return !(field.value as DefaultVaeType).isEnabled; + }, [field.value]); + + return ( + + + {t('modelManager.vae')} + + + + ); +} diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultVaePrecision.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultVaePrecision.tsx new file mode 100644 index 0000000000..1a64ef93ed --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/DefaultVaePrecision.tsx @@ -0,0 +1,49 @@ +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 { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { DefaultSettingsFormData } from '../DefaultSettings'; +import { UseControllerProps, useController } from 'react-hook-form'; + +const options = [ + { label: 'FP16', value: 'fp16' }, + { label: 'FP32', value: 'fp32' }, +]; + +type DefaultVaePrecisionType = DefaultSettingsFormData['vaePrecision']; + +export function DefaultVaePrecision(props: UseControllerProps) { + const { t } = useTranslation(); + const { field } = useController(props); + + const onChange = useCallback( + (v) => { + if (!isParameterPrecision(v?.value)) { + return; + } + const updatedValue = { + ...(field.value as DefaultVaePrecisionType), + value: v.value, + }; + field.onChange(updatedValue); + }, + [field] + ); + + const value = useMemo(() => options.find((o) => o.value === (field.value as DefaultVaePrecisionType).value), [field]); + + const isDisabled = useMemo(() => { + return !(field.value as DefaultVaePrecisionType).isEnabled; + }, [field.value]); + + return ( + + + {t('modelManager.vaePrecision')} + + + + ); +} diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/SettingToggle.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/SettingToggle.tsx new file mode 100644 index 0000000000..4f4f401075 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/DefaultSettings/SettingToggle.tsx @@ -0,0 +1,26 @@ +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'; + +interface Props extends UseControllerProps { + name: keyof DefaultSettingsFormData; +} + +export function SettingToggle(props: Props) { + const { field } = useController(props); + + const onChange = useCallback( + (e: ChangeEvent) => { + const updatedValue: FormField = { + ...(field.value as FormField), + isEnabled: e.target.checked, + }; + field.onChange(updatedValue); + }, + [field] + ); + + return ; +} 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 ef3f6e7f05..362d820ea8 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx @@ -1,4 +1,4 @@ -import { Box, Button, Flex, Text } from '@invoke-ai/ui-library'; +import { Box, Button, Flex, Heading, 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'; @@ -19,6 +19,7 @@ import type { import { ModelAttrView } from './ModelAttrView'; import { ModelConvert } from './ModelConvert'; +import { DefaultSettings } from './DefaultSettings'; export const ModelView = () => { const { t } = useTranslation(); @@ -71,7 +72,7 @@ export const ModelView = () => { return {t('common.somethingWentWrong')}; } return ( - +