diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index d6fb5f5e5b..9df481be23 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -907,6 +907,7 @@ "downloadImage": "Download Image", "general": "General", "globalSettings": "Global Settings", + "guidance": "Guidance", "height": "Height", "imageFit": "Fit Initial Image To Output Size", "images": "Images", @@ -929,6 +930,8 @@ "noModelForControlAdapter": "Control Adapter #{{number}} has no model selected.", "incompatibleBaseModelForControlAdapter": "Control Adapter #{{number}} model is incompatible with main model.", "noModelSelected": "No model selected", + "noT5EncoderModelSelected": "No T5 Encoder model selected for FLUX generation", + "noCLIPEmbedModelSelected": "No CLIP Embed model selected for FLUX generation", "canvasManagerNotLoaded": "Canvas Manager not loaded", "canvasIsFiltering": "Canvas is filtering", "canvasIsTransforming": "Canvas is transforming", diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts index 373c12e523..c61c24e8f0 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts @@ -10,7 +10,7 @@ import { rgIPAdapterModelChanged, } from 'features/controlLayers/store/canvasSlice'; import { loraDeleted } from 'features/controlLayers/store/lorasSlice'; -import { modelChanged, refinerModelChanged, vaeSelected } from 'features/controlLayers/store/paramsSlice'; +import { clipEmbedModelSelected, modelChanged, refinerModelChanged, t5EncoderModelSelected, vaeSelected } from 'features/controlLayers/store/paramsSlice'; import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { getEntityIdentifier } from 'features/controlLayers/store/types'; import { calculateNewSize } from 'features/parameters/components/Bbox/calculateNewSize'; @@ -21,12 +21,14 @@ import type { Logger } from 'roarr'; import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models'; import type { AnyModelConfig } from 'services/api/types'; import { + isCLIPEmbedModelConfig, isControlNetOrT2IAdapterModelConfig, isIPAdapterModelConfig, isLoRAModelConfig, isNonRefinerMainModelConfig, isRefinerMainModelModelConfig, isSpandrelImageToImageModelConfig, + isT5EncoderModelConfig, isVAEModelConfig, } from 'services/api/types'; @@ -50,6 +52,8 @@ export const addModelsLoadedListener = (startAppListening: AppStartListening) => handleControlAdapterModels(models, state, dispatch, log); handleSpandrelImageToImageModels(models, state, dispatch, log); handleIPAdapterModels(models, state, dispatch, log); + handleT5EncoderModels(models, state, dispatch, log) + handleCLIPEmbedModels(models, state, dispatch, log) }, }); }; @@ -223,3 +227,31 @@ const handleSpandrelImageToImageModels: ModelHandler = (models, state, dispatch, dispatch(postProcessingModelChanged(firstModel)); } }; + +const handleT5EncoderModels: ModelHandler = (models, state, dispatch, _log) => { + const { t5EncoderModel: currentT5EncoderModel } = state.params; + const t5EncoderModels = models.filter(isT5EncoderModelConfig); + const firstModel = t5EncoderModels[0] || null; + + const isCurrentT5EncoderModelAvailable = currentT5EncoderModel + ? t5EncoderModels.some((m) => m.key === currentT5EncoderModel.key) + : false; + + if (!isCurrentT5EncoderModelAvailable) { + dispatch(t5EncoderModelSelected(firstModel)); + } +}; + +const handleCLIPEmbedModels: ModelHandler = (models, state, dispatch, _log) => { + const { clipEmbedModel: currentCLIPEmbedModel } = state.params; + const CLIPEmbedModels = models.filter(isCLIPEmbedModelConfig); + const firstModel = CLIPEmbedModels[0] || null; + + const isCurrentCLIPEmbedModelAvailable = currentCLIPEmbedModel + ? CLIPEmbedModels.some((m) => m.key === currentCLIPEmbedModel.key) + : false; + + if (!isCurrentCLIPEmbedModelAvailable) { + dispatch(clipEmbedModelSelected(firstModel)); + } +}; diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 436ed675c1..c23fdd3b7a 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -114,6 +114,9 @@ export type AppConfig = { weight: NumericalParameterConfig; }; }; + flux: { + guidance: NumericalParameterConfig + } }; export type PartialAppConfig = O.Partial; diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts index 0a354a9d58..b12610079f 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts @@ -147,6 +147,16 @@ const createSelector = ( reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') }); } + if (model?.base === 'flux') { + console.log({ params }) + if (!params.t5EncoderModel) { + reasons.push({ content: i18n.t('parameters.invoke.noT5EncoderModelSelected') }); + } + if (!params.clipEmbedModel) { + reasons.push({ content: i18n.t('parameters.invoke.noCLIPEmbedModelSelected') }); + } + } + canvas.controlLayers.entities .filter((controlLayer) => controlLayer.isEnabled) .forEach((controlLayer, i) => { diff --git a/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts index 9008c718d2..c11992060b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts @@ -20,6 +20,9 @@ import type { ParameterSteps, ParameterStrength, ParameterVAEModel, + ParameterGuidance, + ParameterT5EncoderModel, + ParameterCLIPEmbedModel } from 'features/parameters/types/parameterSchemas'; import { clamp } from 'lodash-es'; @@ -35,6 +38,7 @@ export type ParamsState = { infillColorValue: RgbaColor; cfgScale: ParameterCFGScale; cfgRescaleMultiplier: ParameterCFGRescaleMultiplier; + guidance: ParameterGuidance; img2imgStrength: ParameterStrength; iterations: number; scheduler: ParameterScheduler; @@ -60,6 +64,8 @@ export type ParamsState = { refinerPositiveAestheticScore: number; refinerNegativeAestheticScore: number; refinerStart: number; + t5EncoderModel: ParameterT5EncoderModel | null, + clipEmbedModel: ParameterCLIPEmbedModel | null }; const initialState: ParamsState = { @@ -74,6 +80,7 @@ const initialState: ParamsState = { infillColorValue: { r: 0, g: 0, b: 0, a: 1 }, cfgScale: 7.5, cfgRescaleMultiplier: 0, + guidance: 4, img2imgStrength: 0.75, iterations: 1, scheduler: 'euler', @@ -99,6 +106,8 @@ const initialState: ParamsState = { refinerPositiveAestheticScore: 6, refinerNegativeAestheticScore: 2.5, refinerStart: 0.8, + t5EncoderModel: null, + clipEmbedModel: null }; export const paramsSlice = createSlice({ @@ -114,6 +123,9 @@ export const paramsSlice = createSlice({ setCfgScale: (state, action: PayloadAction) => { state.cfgScale = action.payload; }, + setGuidance: (state, action: PayloadAction) => { + state.guidance = action.payload; + }, setCfgRescaleMultiplier: (state, action: PayloadAction) => { state.cfgRescaleMultiplier = action.payload; }, @@ -161,6 +173,12 @@ export const paramsSlice = createSlice({ // null is a valid VAE! state.vae = action.payload; }, + t5EncoderModelSelected: (state, action: PayloadAction) => { + state.t5EncoderModel = action.payload; + }, + clipEmbedModelSelected: (state, action: PayloadAction) => { + state.clipEmbedModel = action.payload; + }, vaePrecisionChanged: (state, action: PayloadAction) => { state.vaePrecision = action.payload; }, @@ -246,6 +264,7 @@ export const { setSteps, setCfgScale, setCfgRescaleMultiplier, + setGuidance, setScheduler, setSeed, setImg2imgStrength, @@ -254,6 +273,8 @@ export const { setShouldRandomizeSeed, vaeSelected, vaePrecisionChanged, + t5EncoderModelSelected, + clipEmbedModelSelected, setClipSkip, shouldUseCpuNoiseChanged, positivePromptChanged, @@ -289,11 +310,16 @@ export const createParamsSelector = (selector: Selector) => export const selectBase = createParamsSelector((params) => params.model?.base); export const selectIsSDXL = createParamsSelector((params) => params.model?.base === 'sdxl'); +export const selectIsFLUX = createParamsSelector((params) => params.model?.base === 'flux'); + export const selectModel = createParamsSelector((params) => params.model); export const selectModelKey = createParamsSelector((params) => params.model?.key); export const selectVAE = createParamsSelector((params) => params.vae); export const selectVAEKey = createParamsSelector((params) => params.vae?.key); +export const selectT5EncoderModel = createParamsSelector((params) => params.t5EncoderModel); +export const selectCLIPEmbedModel = createParamsSelector((params) => params.clipEmbedModel); export const selectCFGScale = createParamsSelector((params) => params.cfgScale); +export const selectGuidance = createParamsSelector((params) => params.guidance); export const selectSteps = createParamsSelector((params) => params.steps); export const selectCFGRescaleMultiplier = createParamsSelector((params) => params.cfgRescaleMultiplier); export const selectCLIPSKip = createParamsSelector((params) => params.clipSkip); diff --git a/invokeai/frontend/web/src/features/metadata/util/recallers.ts b/invokeai/frontend/web/src/features/metadata/util/recallers.ts index a88bc54ee8..3fa99677a9 100644 --- a/invokeai/frontend/web/src/features/metadata/util/recallers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/recallers.ts @@ -19,6 +19,7 @@ import { setScheduler, setSeed, setSteps, + t5EncoderModelSelected, vaeSelected, } from 'features/controlLayers/store/paramsSlice'; import type { LoRA } from 'features/controlLayers/store/types'; @@ -44,6 +45,7 @@ import type { ParameterSeed, ParameterSteps, ParameterStrength, + ParameterT5EncoderModel, ParameterVAEModel, ParameterWidth, } from 'features/parameters/types/parameterSchemas'; @@ -154,6 +156,10 @@ const recallVAE: MetadataRecallFunc = (vae getStore().dispatch(vaeSelected(vaeModel)); }; +const recallT5Encoder: MetadataRecallFunc = (t5EncoderModel) => { + getStore().dispatch(t5EncoderModelSelected(t5EncoderModel)); +}; + const recallLoRA: MetadataRecallFunc = (lora) => { getStore().dispatch(loraRecalled({ lora })); }; @@ -196,4 +202,5 @@ export const recallers = { vae: recallVAE, lora: recallLoRA, loras: recallAllLoRAs, + t5EncoderModel: recallT5Encoder } as const; diff --git a/invokeai/frontend/web/src/features/metadata/util/validators.ts b/invokeai/frontend/web/src/features/metadata/util/validators.ts index 9b9ebadcd4..23b1642247 100644 --- a/invokeai/frontend/web/src/features/metadata/util/validators.ts +++ b/invokeai/frontend/web/src/features/metadata/util/validators.ts @@ -7,7 +7,7 @@ import type { T2IAdapterConfigMetadata, } from 'features/metadata/types'; import { InvalidModelConfigError } from 'features/metadata/util/modelFetchingHelpers'; -import type { ParameterSDXLRefinerModel, ParameterVAEModel } from 'features/parameters/types/parameterSchemas'; +import type { ParameterSDXLRefinerModel, ParameterT5EncoderModel, ParameterVAEModel } from 'features/parameters/types/parameterSchemas'; import type { BaseModelType } from 'services/api/types'; /** @@ -40,6 +40,13 @@ const validateVAEModel: MetadataValidateFunc = (vaeModel) => }); }; +const validateT5EncoderModel: MetadataValidateFunc = (t5EncoderModel) => { + validateBaseCompatibility('flux', 'T5 Encoder incompatible with currently-selected model'); + return new Promise((resolve) => { + resolve(t5EncoderModel); + }); +}; + const validateLoRA: MetadataValidateFunc = (lora) => { validateBaseCompatibility(lora.model.base, 'LoRA incompatible with currently-selected model'); return new Promise((resolve) => { @@ -131,6 +138,7 @@ const validateIPAdapters: MetadataValidateFunc = (ipA export const validators = { refinerModel: validateRefinerModel, vaeModel: validateVAEModel, + t5EncoderModel: validateT5EncoderModel, lora: validateLoRA, loras: validateLoRAs, controlNet: validateControlNet, diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx index f474e55973..b408ebe711 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx @@ -9,7 +9,7 @@ import { import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { - useClipEmbedModels, + useCLIPEmbedModels, useControlNetModels, useEmbeddingModels, useIPAdapterModels, @@ -85,7 +85,7 @@ const ModelList = () => { [t5EncoderModels, searchTerm, filteredModelType] ); - const [clipEmbedModels, { isLoading: isLoadingClipEmbedModels }] = useClipEmbedModels(); + const [clipEmbedModels, { isLoading: isLoadingClipEmbedModels }] = useCLIPEmbedModels(); const filteredClipEmbedModels = useMemo( () => modelsFilter(clipEmbedModels, searchTerm, filteredModelType), [clipEmbedModels, searchTerm, filteredModelType] diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/CLIPEmbedModelFieldInputComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/CLIPEmbedModelFieldInputComponent.tsx index e3dc207420..fb55f693d5 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/CLIPEmbedModelFieldInputComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/CLIPEmbedModelFieldInputComponent.tsx @@ -5,8 +5,8 @@ import { fieldCLIPEmbedValueChanged } from 'features/nodes/store/nodesSlice'; import type { CLIPEmbedModelFieldInputInstance, CLIPEmbedModelFieldInputTemplate } from 'features/nodes/types/field'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { useClipEmbedModels } from 'services/api/hooks/modelsByType'; -import type { ClipEmbedModelConfig } from 'services/api/types'; +import { useCLIPEmbedModels } from 'services/api/hooks/modelsByType'; +import type { CLIPEmbedModelConfig } from 'services/api/types'; import type { FieldComponentProps } from './types'; @@ -17,9 +17,9 @@ const CLIPEmbedModelFieldInputComponent = (props: Props) => { const { t } = useTranslation(); const disabledTabs = useAppSelector((s) => s.config.disabledTabs); const dispatch = useAppDispatch(); - const [modelConfigs, { isLoading }] = useClipEmbedModels(); + const [modelConfigs, { isLoading }] = useCLIPEmbedModels(); const _onChange = useCallback( - (value: ClipEmbedModelConfig | null) => { + (value: CLIPEmbedModelConfig | null) => { if (!value) { return; } diff --git a/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamCLIPEmbedModelSelect.tsx b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamCLIPEmbedModelSelect.tsx new file mode 100644 index 0000000000..f2e46a39dc --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamCLIPEmbedModelSelect.tsx @@ -0,0 +1,41 @@ +import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { clipEmbedModelSelected, selectCLIPEmbedModel } from 'features/controlLayers/store/paramsSlice'; +import { zModelIdentifierField } from 'features/nodes/types/common'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useCLIPEmbedModels } from 'services/api/hooks/modelsByType'; +import type { CLIPEmbedModelConfig } from 'services/api/types'; +import { useModelCombobox } from '../../../../common/hooks/useModelCombobox'; + +const ParamCLIPEmbedModelSelect = () => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const clipEmbedModel = useAppSelector(selectCLIPEmbedModel); + const [modelConfigs, { isLoading }] = useCLIPEmbedModels(); + + const _onChange = useCallback( + (clipEmbedModel: CLIPEmbedModelConfig | null) => { + if (clipEmbedModel) { + dispatch(clipEmbedModelSelected(zModelIdentifierField.parse(clipEmbedModel))); + } + }, + [dispatch] + ); + + const { options, value, onChange, noOptionsMessage } = useModelCombobox({ + modelConfigs, + onChange: _onChange, + selectedModel: clipEmbedModel, + isLoading, + }); + + return ( + + {t('modelManager.clipEmbed')} + + + ); +}; + +export default memo(ParamCLIPEmbedModelSelect); diff --git a/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamT5EncoderModelSelect.tsx b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamT5EncoderModelSelect.tsx new file mode 100644 index 0000000000..062247347e --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamT5EncoderModelSelect.tsx @@ -0,0 +1,41 @@ +import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { selectT5EncoderModel, t5EncoderModelSelected } from 'features/controlLayers/store/paramsSlice'; +import { zModelIdentifierField } from 'features/nodes/types/common'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useT5EncoderModels } from 'services/api/hooks/modelsByType'; +import type { T5EncoderBnbQuantizedLlmInt8bModelConfig, T5EncoderModelConfig } from 'services/api/types'; +import { useModelCombobox } from '../../../../common/hooks/useModelCombobox'; + +const ParamT5EncoderModelSelect = () => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const t5EncoderModel = useAppSelector(selectT5EncoderModel); + const [modelConfigs, { isLoading }] = useT5EncoderModels(); + + const _onChange = useCallback( + (t5EncoderModel: T5EncoderBnbQuantizedLlmInt8bModelConfig | T5EncoderModelConfig | null) => { + if (t5EncoderModel) { + dispatch(t5EncoderModelSelected(zModelIdentifierField.parse(t5EncoderModel))); + } + }, + [dispatch] + ); + + const { options, value, onChange, noOptionsMessage } = useModelCombobox({ + modelConfigs, + onChange: _onChange, + selectedModel: t5EncoderModel, + isLoading, + }); + + return ( + + {t('modelManager.t5Encoder')} + + + ); +}; + +export default memo(ParamT5EncoderModelSelect); diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamGuidance.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamGuidance.tsx new file mode 100644 index 0000000000..bf7b62fb87 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamGuidance.tsx @@ -0,0 +1,49 @@ +import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { selectGuidance, setGuidance } from 'features/controlLayers/store/paramsSlice'; +import { selectGuidanceConfig } from 'features/system/store/configSlice'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +const ParamGuidance = () => { + const guidance = useAppSelector(selectGuidance); + const config = useAppSelector(selectGuidanceConfig); + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const marks = useMemo( + () => [ + config.sliderMin, + Math.floor(config.sliderMax - (config.sliderMax - config.sliderMin) / 2), + config.sliderMax, + ], + [config.sliderMax, config.sliderMin] + ); + const onChange = useCallback((v: number) => dispatch(setGuidance(v)), [dispatch]); + + return ( + + {t('parameters.guidance')} + + + + ); +}; + +export default memo(ParamGuidance); diff --git a/invokeai/frontend/web/src/features/parameters/components/MainModel/ParamMainModelSelect.tsx b/invokeai/frontend/web/src/features/parameters/components/MainModel/ParamMainModelSelect.tsx index 86445a9ca0..91dd0a94bf 100644 --- a/invokeai/frontend/web/src/features/parameters/components/MainModel/ParamMainModelSelect.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/MainModel/ParamMainModelSelect.tsx @@ -7,14 +7,14 @@ import { zModelIdentifierField } from 'features/nodes/types/common'; import { modelSelected } from 'features/parameters/store/actions'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { useSDMainModels } from 'services/api/hooks/modelsByType'; +import { useMainModels } from 'services/api/hooks/modelsByType'; import type { MainModelConfig } from 'services/api/types'; const ParamMainModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); const selectedModel = useAppSelector(selectModel); - const [modelConfigs, { isLoading }] = useSDMainModels(); + const [modelConfigs, { isLoading }] = useMainModels(); const tooltipLabel = useMemo(() => { if (!modelConfigs.length || !selectedModel) { return; diff --git a/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx b/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx index 04bace0f1c..ce22406ee9 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx @@ -1,6 +1,6 @@ import { Flex } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; -import { createParamsSelector } from 'features/controlLayers/store/paramsSlice'; +import { createParamsSelector, selectIsFLUX } from 'features/controlLayers/store/paramsSlice'; import { ParamNegativePrompt } from 'features/parameters/components/Core/ParamNegativePrompt'; import { ParamPositivePrompt } from 'features/parameters/components/Core/ParamPositivePrompt'; import { ParamSDXLNegativeStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt'; @@ -15,11 +15,12 @@ const selectWithStylePrompts = createParamsSelector((params) => { export const Prompts = memo(() => { const withStylePrompts = useAppSelector(selectWithStylePrompts); + const isFLUX = useAppSelector(selectIsFLUX); return ( {withStylePrompts && } - + {!isFLUX && } {withStylePrompts && } ); diff --git a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts index 3f767874ef..7b6eb81940 100644 --- a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts +++ b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts @@ -56,6 +56,13 @@ export const isParameterCFGScale = (val: unknown): val is ParameterCFGScale => zParameterCFGScale.safeParse(val).success; // #endregion +// #region Guidance parameter +const zParameterGuidance = z.number().min(1); +export type ParameterGuidance = z.infer; +export const isParameterGuidance = (val: unknown): val is ParameterGuidance => + zParameterGuidance.safeParse(val).success; +// #endregion + // #region CFG Rescale Multiplier const zParameterCFGRescaleMultiplier = z.number().gte(0).lt(1); export type ParameterCFGRescaleMultiplier = z.infer; @@ -106,6 +113,16 @@ export const zParameterVAEModel = zModelIdentifierField; export type ParameterVAEModel = z.infer; // #endregion +// #region T5Encoder Model +export const zParameterT5EncoderModel = zModelIdentifierField; +export type ParameterT5EncoderModel = z.infer; +// #endregion + +// #region CLIP embed Model +export const zParameterCLIPEmbedModel = zModelIdentifierField; +export type ParameterCLIPEmbedModel = z.infer; +// #endregion + // #region LoRA Model const zParameterLoRAModel = zModelIdentifierField; export type ParameterLoRAModel = z.infer; diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion.tsx index d618a54dae..c3c920cee7 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion.tsx @@ -3,7 +3,7 @@ import { Flex, FormControlGroup, StandaloneAccordion } from '@invoke-ai/ui-libra import { skipToken } from '@reduxjs/toolkit/query'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; -import { selectParamsSlice, selectVAEKey } from 'features/controlLayers/store/paramsSlice'; +import { selectIsFLUX, selectParamsSlice, selectVAEKey } from 'features/controlLayers/store/paramsSlice'; import ParamCFGRescaleMultiplier from 'features/parameters/components/Advanced/ParamCFGRescaleMultiplier'; import ParamClipSkip from 'features/parameters/components/Advanced/ParamClipSkip'; import ParamSeamlessXAxis from 'features/parameters/components/Seamless/ParamSeamlessXAxis'; @@ -18,6 +18,8 @@ import { selectActiveTab } from 'features/ui/store/uiSelectors'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useGetModelConfigQuery } from 'services/api/endpoints/models'; +import ParamT5EncoderModelSelect from '../../../parameters/components/Advanced/ParamT5EncoderModelSelect'; +import ParamCLIPEmbedModelSelect from '../../../parameters/components/Advanced/ParamCLIPEmbedModelSelect'; const formLabelProps: FormLabelProps = { minW: '9.2rem', @@ -31,32 +33,44 @@ export const AdvancedSettingsAccordion = memo(() => { const vaeKey = useAppSelector(selectVAEKey); const { currentData: vaeConfig } = useGetModelConfigQuery(vaeKey ?? skipToken); const activeTabName = useAppSelector(selectActiveTab); + const isFLUX = useAppSelector(selectIsFLUX); const selectBadges = useMemo( () => - createMemoizedSelector(selectParamsSlice, (params) => { + createMemoizedSelector([selectParamsSlice, selectIsFLUX], (params, isFLUX) => { const badges: (string | number)[] = []; - if (vaeConfig) { - let vaeBadge = vaeConfig.name; - if (params.vaePrecision === 'fp16') { - vaeBadge += ` ${params.vaePrecision}`; + if (isFLUX) { + if (vaeConfig) { + let vaeBadge = vaeConfig.name; + if (params.vaePrecision === 'fp16') { + vaeBadge += ` ${params.vaePrecision}`; + } + badges.push(vaeBadge); + } + } else { + if (vaeConfig) { + let vaeBadge = vaeConfig.name; + if (params.vaePrecision === 'fp16') { + vaeBadge += ` ${params.vaePrecision}`; + } + badges.push(vaeBadge); + } else if (params.vaePrecision === 'fp16') { + badges.push(`VAE ${params.vaePrecision}`); + } + if (params.clipSkip) { + badges.push(`Skip ${params.clipSkip}`); + } + if (params.cfgRescaleMultiplier) { + badges.push(`Rescale ${params.cfgRescaleMultiplier}`); + } + if (params.seamlessXAxis || params.seamlessYAxis) { + badges.push('seamless'); + } + if (activeTabName === 'upscaling' && !params.shouldRandomizeSeed) { + badges.push('Manual Seed'); } - badges.push(vaeBadge); - } else if (params.vaePrecision === 'fp16') { - badges.push(`VAE ${params.vaePrecision}`); - } - if (params.clipSkip) { - badges.push(`Skip ${params.clipSkip}`); - } - if (params.cfgRescaleMultiplier) { - badges.push(`Rescale ${params.cfgRescaleMultiplier}`); - } - if (params.seamlessXAxis || params.seamlessYAxis) { - badges.push('seamless'); - } - if (activeTabName === 'upscaling' && !params.shouldRandomizeSeed) { - badges.push('Manual Seed'); } + return badges; }), [vaeConfig, activeTabName] @@ -73,27 +87,36 @@ export const AdvancedSettingsAccordion = memo(() => { - + {!isFLUX && } - {activeTabName === 'upscaling' && ( + {activeTabName === 'upscaling' ? ( - )} - {activeTabName !== 'upscaling' && ( + ) : ( <> - - - - - - - - + {!isFLUX && ( + <> + + + + + + + + + + + + )} + {isFLUX && ( + + + - + )} )} diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion.tsx index 9edab01c37..c2ecd20e65 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion.tsx @@ -18,6 +18,8 @@ import { selectActiveTab } from 'features/ui/store/uiSelectors'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useSelectedModelConfig } from 'services/api/hooks/useSelectedModelConfig'; +import ParamGuidance from '../../../parameters/components/Core/ParamGuidance'; +import { selectIsFLUX } from '../../../controlLayers/store/paramsSlice'; const formLabelProps: FormLabelProps = { minW: '4rem', @@ -27,6 +29,7 @@ export const GenerationSettingsAccordion = memo(() => { const { t } = useTranslation(); const modelConfig = useSelectedModelConfig(); const activeTabName = useAppSelector(selectActiveTab); + const isFLUX = useAppSelector(selectIsFLUX); const selectBadges = useMemo( () => createMemoizedSelector(selectLoRAsSlice, (loras) => { @@ -71,9 +74,9 @@ export const GenerationSettingsAccordion = memo(() => { - + {!isFLUX && } - + {isFLUX ? : } diff --git a/invokeai/frontend/web/src/features/system/store/configSlice.ts b/invokeai/frontend/web/src/features/system/store/configSlice.ts index 5a1698556a..f55346960d 100644 --- a/invokeai/frontend/web/src/features/system/store/configSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/configSlice.ts @@ -167,6 +167,17 @@ const initialConfigState: AppConfig = { }, }, }, + flux: { + guidance: { + initial: 4, + sliderMin: 2, + sliderMax: 6, + numberInputMin: 2, + numberInputMax: 6, + fineStep: 0.1, + coarseStep: 1, + } + } }; export const configSlice = createSlice({ @@ -188,6 +199,7 @@ export const selectWidthConfig = createConfigSelector((config) => config.sd.widt export const selectHeightConfig = createConfigSelector((config) => config.sd.height); export const selectStepsConfig = createConfigSelector((config) => config.sd.steps); export const selectCFGScaleConfig = createConfigSelector((config) => config.sd.guidance); +export const selectGuidanceConfig = createConfigSelector((config) => config.flux.guidance); export const selectCLIPSkipConfig = createConfigSelector((config) => config.sd.clipSkip); export const selectCFGRescaleMultiplierConfig = createConfigSelector((config) => config.sd.cfgRescaleMultiplier); export const selectCanvasCoherenceEdgeSizeConfig = createConfigSelector((config) => config.sd.canvasCoherenceEdgeSize); diff --git a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts index e4c70a929a..57cb0a84f2 100644 --- a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts +++ b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts @@ -9,7 +9,7 @@ import { } from 'services/api/endpoints/models'; import type { AnyModelConfig } from 'services/api/types'; import { - isClipEmbedModelConfig, + isCLIPEmbedModelConfig, isControlNetModelConfig, isControlNetOrT2IAdapterModelConfig, isFluxMainModelModelConfig, @@ -30,18 +30,18 @@ import { const buildModelsHook = (typeGuard: (config: AnyModelConfig) => config is T) => - () => { - const result = useGetModelConfigsQuery(undefined); - const modelConfigs = useMemo(() => { - if (!result.data) { - return EMPTY_ARRAY; - } + () => { + const result = useGetModelConfigsQuery(undefined); + const modelConfigs = useMemo(() => { + if (!result.data) { + return EMPTY_ARRAY; + } - return modelConfigsAdapterSelectors.selectAll(result.data).filter(typeGuard); - }, [result]); + return modelConfigsAdapterSelectors.selectAll(result.data).filter(typeGuard); + }, [result]); - return [modelConfigs, result] as const; - }; + return [modelConfigs, result] as const; + }; export const useSDMainModels = buildModelsHook(isNonRefinerNonFluxMainModelConfig); export const useMainModels = buildModelsHook(isNonRefinerMainModelConfig); @@ -54,7 +54,7 @@ export const useControlNetAndT2IAdapterModels = buildModelsHook(isControlNetOrT2 export const useControlNetModels = buildModelsHook(isControlNetModelConfig); export const useT2IAdapterModels = buildModelsHook(isT2IAdapterModelConfig); export const useT5EncoderModels = buildModelsHook(isT5EncoderModelConfig); -export const useClipEmbedModels = buildModelsHook(isClipEmbedModelConfig); +export const useCLIPEmbedModels = buildModelsHook(isCLIPEmbedModelConfig); export const useSpandrelImageToImageModels = buildModelsHook(isSpandrelImageToImageModelConfig); export const useIPAdapterModels = buildModelsHook(isIPAdapterModelConfig); export const useEmbeddingModels = buildModelsHook(isTIModelConfig); diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index d0a78be11f..ef20618a1e 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -52,7 +52,7 @@ export type VAEModelConfig = S['VAECheckpointConfig'] | S['VAEDiffusersConfig']; export type ControlNetModelConfig = S['ControlNetDiffusersConfig'] | S['ControlNetCheckpointConfig']; export type IPAdapterModelConfig = S['IPAdapterInvokeAIConfig'] | S['IPAdapterCheckpointConfig']; export type T2IAdapterModelConfig = S['T2IAdapterConfig']; -export type ClipEmbedModelConfig = S['CLIPEmbedDiffusersConfig']; +export type CLIPEmbedModelConfig = S['CLIPEmbedDiffusersConfig']; export type T5EncoderModelConfig = S['T5EncoderConfig']; export type T5EncoderBnbQuantizedLlmInt8bModelConfig = S['T5EncoderBnbQuantizedLlmInt8bConfig']; export type SpandrelImageToImageModelConfig = S['SpandrelImageToImageConfig']; @@ -68,7 +68,7 @@ export type AnyModelConfig = | IPAdapterModelConfig | T5EncoderModelConfig | T5EncoderBnbQuantizedLlmInt8bModelConfig - | ClipEmbedModelConfig + | CLIPEmbedModelConfig | T2IAdapterModelConfig | SpandrelImageToImageModelConfig | TextualInversionModelConfig @@ -105,7 +105,7 @@ export const isT5EncoderModelConfig = ( return config.type === 't5_encoder'; }; -export const isClipEmbedModelConfig = (config: AnyModelConfig): config is ClipEmbedModelConfig => { +export const isCLIPEmbedModelConfig = (config: AnyModelConfig): config is CLIPEmbedModelConfig => { return config.type === 'clip_embed'; };