feat(ui): show w/h, scaled bbox settings only when relevant

This commit is contained in:
psychedelicious
2025-08-27 19:42:50 +10:00
committed by Mary Hipp Rogers
parent 115053972c
commit 013e02d08b
9 changed files with 188 additions and 69 deletions

View File

@@ -4,7 +4,6 @@ import { bboxSyncedToOptimalDimension, rgRefImageModelChanged } from 'features/c
import { buildSelectIsStaging, selectCanvasSessionId } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { loraIsEnabledChanged } from 'features/controlLayers/store/lorasSlice';
import {
doesModelSupportRefImages,
modelChanged,
syncedToOptimalDimension,
vaeSelected,
@@ -17,6 +16,7 @@ import {
} from 'features/controlLayers/store/selectors';
import { getEntityIdentifier } from 'features/controlLayers/store/types';
import { modelSelected } from 'features/parameters/store/actions';
import { SUPPORTS_REF_IMAGES_BASE_MODELS } from 'features/parameters/types/constants';
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
import { toast } from 'features/toast/toast';
import { t } from 'i18next';
@@ -67,7 +67,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
modelsUpdatedDisabledOrCleared += 1;
}
if (doesModelSupportRefImages(newModel)) {
if (SUPPORTS_REF_IMAGES_BASE_MODELS.includes(newModel.base)) {
// Handle incompatible reference image models - switch to first compatible model, with some smart logic
// to choose the best available model based on the new main model.
const allRefImageModels = selectGlobalRefImageModels(state).filter(({ base }) => base === newBase);

View File

@@ -26,8 +26,10 @@ import {
CLIP_SKIP_MAP,
SUPPORTS_ASPECT_RATIO_BASE_MODELS,
SUPPORTS_NEGATIVE_PROMPT_BASE_MODELS,
SUPPORTS_OPTIMIZED_DENOISING_BASE_MODELS,
SUPPORTS_PIXEL_DIMENSIONS_BASE_MODELS,
SUPPORTS_REF_IMAGES_BASE_MODELS,
SUPPORTS_SEED_BASE_MODELS,
} from 'features/parameters/types/constants';
import type {
ParameterCanvasCoherenceMode,
@@ -550,6 +552,10 @@ export const selectModelSupportsNegativePrompt = createSelector(
selectModel,
(model) => !!model && SUPPORTS_NEGATIVE_PROMPT_BASE_MODELS.includes(model.base)
);
export const selectModelSupportsSeed = createSelector(
selectModel,
(model) => !!model && SUPPORTS_SEED_BASE_MODELS.includes(model.base)
);
export const selectModelSupportsRefImages = createSelector(
selectModel,
(model) => !!model && SUPPORTS_REF_IMAGES_BASE_MODELS.includes(model.base)
@@ -566,6 +572,10 @@ export const selectIsApiBaseModel = createSelector(
selectModel,
(model) => !!model && API_BASE_MODELS.includes(model.base)
);
export const selectModelSupportsOptimizedDenoising = createSelector(
selectModel,
(model) => !!model && SUPPORTS_OPTIMIZED_DENOISING_BASE_MODELS.includes(model.base)
);
export const selectScheduler = createParamsSelector((params) => params.scheduler);
export const selectSeamlessXAxis = createParamsSelector((params) => params.seamlessXAxis);
export const selectSeamlessYAxis = createParamsSelector((params) => params.seamlessYAxis);

View File

@@ -1,5 +1,10 @@
import type { FormLabelProps } from '@invoke-ai/ui-library';
import { Flex, FormControlGroup } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectModelSupportsAspectRatio,
selectModelSupportsPixelDimensions,
} from 'features/controlLayers/store/paramsSlice';
import { BboxAspectRatioSelect } from 'features/parameters/components/Bbox/BboxAspectRatioSelect';
import { BboxHeight } from 'features/parameters/components/Bbox/BboxHeight';
import { BboxLockAspectRatioButton } from 'features/parameters/components/Bbox/BboxLockAspectRatioButton';
@@ -8,8 +13,16 @@ import { BboxSetOptimalSizeButton } from 'features/parameters/components/Bbox/Bb
import { BboxSwapDimensionsButton } from 'features/parameters/components/Bbox/BboxSwapDimensionsButton';
import { BboxWidth } from 'features/parameters/components/Bbox/BboxWidth';
import { memo } from 'react';
import { PixelDimensionsUnsupportedAlert } from '../PixelDimensionsUnsupportedAlert';
export const BboxSettings = memo(() => {
const supportsAspectRatio = useAppSelector(selectModelSupportsAspectRatio);
const supportsPixelDimensions = useAppSelector(selectModelSupportsPixelDimensions);
if (!supportsAspectRatio) {
return null;
}
return (
<Flex gap={4} alignItems="center">
<Flex gap={4} flexDirection="column" width="full">
@@ -17,11 +30,20 @@ export const BboxSettings = memo(() => {
<Flex gap={4}>
<BboxAspectRatioSelect />
<BboxSwapDimensionsButton />
<BboxLockAspectRatioButton />
<BboxSetOptimalSizeButton />
{supportsPixelDimensions && (
<>
<BboxLockAspectRatioButton />
<BboxSetOptimalSizeButton />
</>
)}
</Flex>
<BboxWidth />
<BboxHeight />
{supportsPixelDimensions && (
<>
<BboxWidth />
<BboxHeight />
</>
)}
{!supportsPixelDimensions && <PixelDimensionsUnsupportedAlert />}
</FormControlGroup>
</Flex>
<Flex w="108px" h="108px" flexShrink={0} flexGrow={0} alignItems="center" justifyContent="center">

View File

@@ -1,7 +1,10 @@
import type { FormLabelProps } from '@invoke-ai/ui-library';
import { Alert, Flex, FormControlGroup, Text } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { selectIsApiBaseModel } from 'features/controlLayers/store/paramsSlice';
import {
selectModelSupportsAspectRatio,
selectModelSupportsPixelDimensions,
} from 'features/controlLayers/store/paramsSlice';
import { memo } from 'react';
import { DimensionsAspectRatioSelect } from './DimensionsAspectRatioSelect';
@@ -11,9 +14,15 @@ import { DimensionsPreview } from './DimensionsPreview';
import { DimensionsSetOptimalSizeButton } from './DimensionsSetOptimalSizeButton';
import { DimensionsSwapButton } from './DimensionsSwapButton';
import { DimensionsWidth } from './DimensionsWidth';
import { PixelDimensionsUnsupportedAlert } from '../PixelDimensionsUnsupportedAlert';
export const Dimensions = memo(() => {
const isApiModel = useAppSelector(selectIsApiBaseModel);
const supportsAspectRatio = useAppSelector(selectModelSupportsAspectRatio);
const supportsPixelDimensions = useAppSelector(selectModelSupportsPixelDimensions);
if (!supportsAspectRatio) {
return null;
}
return (
<Flex gap={4} alignItems="center">
@@ -22,26 +31,20 @@ export const Dimensions = memo(() => {
<Flex gap={4}>
<DimensionsAspectRatioSelect />
<DimensionsSwapButton />
{!isApiModel && (
{supportsPixelDimensions && (
<>
<DimensionsLockAspectRatioButton />
<DimensionsSetOptimalSizeButton />
</>
)}
</Flex>
{!isApiModel && (
{supportsPixelDimensions && (
<>
<DimensionsWidth />
<DimensionsHeight />
</>
)}
{isApiModel && (
<Alert status="info" borderRadius="base" flexDir="column" gap={2} overflow="unset">
<Text fontSize="md" fontWeight="semibold">
This model does not support pixel dimensions.
</Text>
</Alert>
)}
{!supportsPixelDimensions && <PixelDimensionsUnsupportedAlert />}
</FormControlGroup>
</Flex>
<Flex w="108px" h="108px" flexShrink={0} flexGrow={0} alignItems="center" justifyContent="center">

View File

@@ -0,0 +1,26 @@
import type { FormLabelProps } from '@invoke-ai/ui-library';
import { Alert, Flex, FormControlGroup, Text } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectModelSupportsAspectRatio,
selectModelSupportsPixelDimensions,
} from 'features/controlLayers/store/paramsSlice';
import { memo } from 'react';
import { DimensionsAspectRatioSelect } from './DimensionsAspectRatioSelect';
import { DimensionsHeight } from './DimensionsHeight';
import { DimensionsLockAspectRatioButton } from './DimensionsLockAspectRatioButton';
import { DimensionsPreview } from './DimensionsPreview';
import { DimensionsSetOptimalSizeButton } from './DimensionsSetOptimalSizeButton';
import { DimensionsSwapButton } from './DimensionsSwapButton';
import { DimensionsWidth } from './DimensionsWidth';
export const PixelDimensionsUnsupportedAlert = memo(() => {
return (
<Alert status="info" borderRadius="base" flexDir="column" gap={2} overflow="unset">
<Text fontSize="md">This model does not support user-defined width and height.</Text>
</Alert>
);
});
PixelDimensionsUnsupportedAlert.displayName = 'PixelDimensionsUnsupportedAlert';

View File

@@ -138,6 +138,10 @@ export const SCHEDULER_OPTIONS: ComboboxOption[] = [
*/
export const API_BASE_MODELS: BaseModelType[] = ['imagen3', 'imagen4', 'chatgpt-4o', 'flux-kontext', 'gemini-2.5'];
export const SUPPORTS_SEED_BASE_MODELS: BaseModelType[] = ['sd-1', 'sd-2', 'sd-3', 'sdxl', 'flux', 'cogview4'];
export const SUPPORTS_OPTIMIZED_DENOISING_BASE_MODELS: BaseModelType[] = ['flux', 'sd-3'];
export const SUPPORTS_REF_IMAGES_BASE_MODELS: BaseModelType[] = [
'sd-1',
'sdxl',

View File

@@ -9,7 +9,6 @@ import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { debounce, groupBy, upperFirst } from 'es-toolkit/compat';
import { useCanvasManagerSafe } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import {
doesModelSupportRefImages,
selectMainModelConfig,
selectParamsSlice,
} from 'features/controlLayers/store/paramsSlice';
@@ -38,6 +37,7 @@ import { resolveBatchValue } from 'features/nodes/util/node/resolveBatchValue';
import { useIsModelDisabled } from 'features/parameters/hooks/useIsModelDisabled';
import type { UpscaleState } from 'features/parameters/store/upscaleSlice';
import { selectUpscaleSlice } from 'features/parameters/store/upscaleSlice';
import { SUPPORTS_REF_IMAGES_BASE_MODELS } from 'features/parameters/types/constants';
import type { ParameterModel } from 'features/parameters/types/parameterSchemas';
import { getGridSize } from 'features/parameters/util/optimalDimension';
import { promptExpansionApi, type PromptExpansionRequestState } from 'features/prompt/PromptExpansion/state';
@@ -281,7 +281,7 @@ const getReasonsWhyCannotEnqueueGenerateTab = (arg: {
reasons.push({ content: i18n.t('parameters.invoke.promptExpansionResultPending') });
}
if (model && doesModelSupportRefImages(model)) {
if (model && SUPPORTS_REF_IMAGES_BASE_MODELS.includes(model.base)) {
const enabledRefImages = refImages.entities.filter(({ isEnabled }) => isEnabled);
enabledRefImages.forEach((entity, i) => {
@@ -632,7 +632,7 @@ const getReasonsWhyCannotEnqueueCanvasTab = (arg: {
}
});
if (model && doesModelSupportRefImages(model)) {
if (model && SUPPORTS_REF_IMAGES_BASE_MODELS.includes(model.base)) {
const enabledRefImages = refImages.entities.filter(({ isEnabled }) => isEnabled);
enabledRefImages.forEach((entity, i) => {

View File

@@ -7,9 +7,14 @@ import {
selectIsApiBaseModel,
selectIsFLUX,
selectIsSD3,
selectModelSupportsAspectRatio,
selectModelSupportsOptimizedDenoising,
selectModelSupportsPixelDimensions,
selectModelSupportsSeed,
selectParamsSlice,
selectShouldRandomizeSeed,
} from 'features/controlLayers/store/paramsSlice';
import { selectCanvasSlice, selectScaleMethod } from 'features/controlLayers/store/selectors';
import { selectBbox, selectCanvasSlice, selectScaleMethod } from 'features/controlLayers/store/selectors';
import { ParamOptimizedDenoisingToggle } from 'features/parameters/components/Advanced/ParamOptimizedDenoisingToggle';
import BboxScaledHeight from 'features/parameters/components/Bbox/BboxScaledHeight';
import BboxScaledWidth from 'features/parameters/components/Bbox/BboxScaledWidth';
@@ -21,30 +26,46 @@ import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
const selectBadges = createMemoizedSelector([selectCanvasSlice, selectParamsSlice], (canvas, params) => {
const { shouldRandomizeSeed } = params;
const badges: string[] = [];
const selectBadges = createMemoizedSelector(
[
selectBbox,
selectShouldRandomizeSeed,
selectModelSupportsSeed,
selectModelSupportsAspectRatio,
selectModelSupportsPixelDimensions,
],
(bbox, shouldRandomizeSeed, modelSupportsSeed, modelSupportsAspectRatio, modelSupportsPixelDimensions) => {
const badges: string[] = [];
const { aspectRatio } = canvas.bbox;
const { width, height } = canvas.bbox.rect;
const { aspectRatio, rect } = bbox;
const { width, height } = rect;
badges.push(`${width}×${height}`);
badges.push(aspectRatio.id);
if (modelSupportsPixelDimensions) {
badges.push(`${width}×${height}`);
}
if (aspectRatio.isLocked) {
badges.push('locked');
if (modelSupportsAspectRatio) {
badges.push(aspectRatio.id);
// If a model does not support pixel dimensions, the ratio is essentially always locked.
if (modelSupportsPixelDimensions && aspectRatio.isLocked) {
badges.push('locked');
}
}
if (modelSupportsSeed) {
if (!shouldRandomizeSeed) {
badges.push('Manual Seed');
}
}
if (badges.length === 0) {
return EMPTY_ARRAY;
}
return badges;
}
if (!shouldRandomizeSeed) {
badges.push('Manual Seed');
}
if (badges.length === 0) {
return EMPTY_ARRAY;
}
return badges;
});
);
const scalingLabelProps: FormLabelProps = {
minW: '4.5rem',
@@ -62,9 +83,16 @@ export const CanvasTabImageSettingsAccordion = memo(() => {
id: 'image-settings-advanced',
defaultIsOpen: false,
});
const isFLUX = useAppSelector(selectIsFLUX);
const isSD3 = useAppSelector(selectIsSD3);
const isApiModel = useAppSelector(selectIsApiBaseModel);
const modelSupportsOptimizedDenoising = useAppSelector(selectModelSupportsOptimizedDenoising);
const modelSupportsSeed = useAppSelector(selectModelSupportsSeed);
const modelSupportsAspectRatio = useAppSelector(selectModelSupportsAspectRatio);
const modelSupportsPixelDimensions = useAppSelector(selectModelSupportsPixelDimensions);
if (!modelSupportsAspectRatio && !modelSupportsSeed) {
return null;
}
const withAdvancedSettingsExpander = modelSupportsPixelDimensions;
return (
<StandaloneAccordion
@@ -76,18 +104,18 @@ export const CanvasTabImageSettingsAccordion = memo(() => {
<Flex
px={4}
pt={4}
pb={isApiModel ? 4 : 0}
pb={withAdvancedSettingsExpander ? 0 : 4}
w="full"
h="full"
flexDir="column"
data-testid="image-settings-accordion"
>
<BboxSettings />
{!isApiModel && <ParamSeed py={3} />}
{!isApiModel && (
{modelSupportsSeed && <ParamSeed pt={3} pb={withAdvancedSettingsExpander ? 0 : 3} />}
{withAdvancedSettingsExpander && (
<Expander label={t('accordions.advanced.options')} isOpen={isOpenExpander} onToggle={onToggleExpander}>
<Flex gap={4} pb={4} flexDir="column">
{(isFLUX || isSD3) && <ParamOptimizedDenoisingToggle />}
{modelSupportsOptimizedDenoising && <ParamOptimizedDenoisingToggle />}
<BboxScaleMethod />
{scaleMethod !== 'none' && (
<FormControlGroup formLabelProps={scalingLabelProps}>

View File

@@ -7,6 +7,9 @@ import {
selectAspectRatioIsLocked,
selectHeight,
selectIsApiBaseModel,
selectModelSupportsAspectRatio,
selectModelSupportsPixelDimensions,
selectModelSupportsSeed,
selectShouldRandomizeSeed,
selectWidth,
} from 'features/controlLayers/store/paramsSlice';
@@ -17,19 +20,45 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
const selectBadges = createMemoizedSelector(
[selectWidth, selectHeight, selectAspectRatioID, selectAspectRatioIsLocked, selectShouldRandomizeSeed],
(width, height, aspectRatioID, aspectRatioIsLocked, shouldRandomizeSeed) => {
[
selectWidth,
selectHeight,
selectAspectRatioID,
selectAspectRatioIsLocked,
selectShouldRandomizeSeed,
selectModelSupportsSeed,
selectModelSupportsAspectRatio,
selectModelSupportsPixelDimensions,
],
(
width,
height,
aspectRatioID,
aspectRatioIsLocked,
shouldRandomizeSeed,
modelSupportsSeed,
modelSupportsAspectRatio,
modelSupportsPixelDimensions
) => {
const badges: string[] = [];
badges.push(`${width}×${height}`);
badges.push(aspectRatioID);
if (aspectRatioIsLocked) {
badges.push('locked');
if (modelSupportsPixelDimensions) {
badges.push(`${width}×${height}`);
}
if (!shouldRandomizeSeed) {
badges.push('Manual Seed');
if (modelSupportsAspectRatio) {
badges.push(aspectRatioID);
// If a model does not support pixel dimensions, the ratio is essentially always locked.
if (modelSupportsPixelDimensions && aspectRatioIsLocked) {
badges.push('locked');
}
}
if (modelSupportsSeed) {
if (!shouldRandomizeSeed) {
badges.push('Manual Seed');
}
}
if (badges.length === 0) {
@@ -47,7 +76,12 @@ export const GenerateTabImageSettingsAccordion = memo(() => {
id: 'image-settings-generate-tab',
defaultIsOpen: true,
});
const isApiModel = useAppSelector(selectIsApiBaseModel);
const supportsSeed = useAppSelector(selectModelSupportsSeed);
const supportsAspectRatio = useAppSelector(selectModelSupportsAspectRatio);
if (!supportsAspectRatio && !supportsSeed) {
return;
}
return (
<StandaloneAccordion
@@ -56,17 +90,9 @@ export const GenerateTabImageSettingsAccordion = memo(() => {
isOpen={isOpenAccordion}
onToggle={onToggleAccordion}
>
<Flex
px={4}
pt={4}
pb={isApiModel ? 4 : 0}
w="full"
h="full"
flexDir="column"
data-testid="image-settings-accordion"
>
<Dimensions />
{!isApiModel && <ParamSeed py={3} />}
<Flex px={4} pt={4} pb={4} w="full" h="full" flexDir="column" data-testid="image-settings-accordion">
{supportsAspectRatio && <Dimensions />}
{supportsSeed && <ParamSeed py={3} />}
</Flex>
</StandaloneAccordion>
);