mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-01-15 06:18:03 -05:00
feat(ui): generate tab has separate w/h/aspect
This commit is contained in:
@@ -3,7 +3,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
||||
import { bboxSyncedToOptimalDimension } from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { loraDeleted } from 'features/controlLayers/store/lorasSlice';
|
||||
import { modelChanged, vaeSelected } from 'features/controlLayers/store/paramsSlice';
|
||||
import { modelChanged, syncedToOptimalDimension, vaeSelected } from 'features/controlLayers/store/paramsSlice';
|
||||
import { selectBboxModelBase } from 'features/controlLayers/store/selectors';
|
||||
import { modelSelected } from 'features/parameters/store/actions';
|
||||
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
||||
@@ -71,9 +71,16 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
|
||||
}
|
||||
|
||||
dispatch(modelChanged({ model: newModel, previousModel: state.params.model }));
|
||||
|
||||
const modelBase = selectBboxModelBase(state);
|
||||
if (!selectIsStaging(state) && modelBase !== state.params.model?.base) {
|
||||
dispatch(bboxSyncedToOptimalDimension());
|
||||
|
||||
if (modelBase !== state.params.model?.base) {
|
||||
// Sync generate tab settings whenever the model base changes
|
||||
dispatch(syncedToOptimalDimension());
|
||||
if (!selectIsStaging(state)) {
|
||||
// Canvas tab only syncs if not staging
|
||||
dispatch(bboxSyncedToOptimalDimension());
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -494,6 +494,12 @@ export const selectRefinerScheduler = createParamsSelector((params) => params.re
|
||||
export const selectRefinerStart = createParamsSelector((params) => params.refinerStart);
|
||||
export const selectRefinerSteps = createParamsSelector((params) => params.refinerSteps);
|
||||
|
||||
export const selectWidth = createParamsSelector((params) => params.dimensions.rect.width);
|
||||
export const selectHeight = createParamsSelector((params) => params.dimensions.rect.height);
|
||||
export const selectAspectRatioID = createParamsSelector((params) => params.dimensions.aspectRatio.id);
|
||||
export const selectAspectRatioValue = createParamsSelector((params) => params.dimensions.aspectRatio.value);
|
||||
export const selectAspectRatioIsLocked = createParamsSelector((params) => params.dimensions.aspectRatio.isLocked);
|
||||
|
||||
export const selectMainModelConfig = createSelector(
|
||||
selectModelConfigsQuery,
|
||||
selectParamsSlice,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { FormLabelProps } from '@invoke-ai/ui-library';
|
||||
import { Flex, FormControlGroup } from '@invoke-ai/ui-library';
|
||||
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 Dimensions = memo(() => {
|
||||
return (
|
||||
<Flex gap={4} alignItems="center">
|
||||
<Flex gap={4} flexDirection="column" width="full">
|
||||
<FormControlGroup formLabelProps={formLabelProps}>
|
||||
<Flex gap={4}>
|
||||
<DimensionsAspectRatioSelect />
|
||||
<DimensionsSwapButton />
|
||||
<DimensionsLockAspectRatioButton />
|
||||
<DimensionsSetOptimalSizeButton />
|
||||
</Flex>
|
||||
<DimensionsWidth />
|
||||
<DimensionsHeight />
|
||||
</FormControlGroup>
|
||||
</Flex>
|
||||
<Flex w="108px" h="108px" flexShrink={0} flexGrow={0} alignItems="center" justifyContent="center">
|
||||
<DimensionsPreview />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
Dimensions.displayName = 'Dimensions';
|
||||
|
||||
const formLabelProps: FormLabelProps = {
|
||||
minW: 10,
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import { FormControl, FormLabel, Select } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import {
|
||||
aspectRatioIdChanged,
|
||||
selectAspectRatioID,
|
||||
selectIsChatGPT4o,
|
||||
selectIsFluxKontext,
|
||||
selectIsImagen3,
|
||||
selectIsImagen4,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import {
|
||||
isAspectRatioID,
|
||||
zAspectRatioID,
|
||||
zChatGPT4oAspectRatioID,
|
||||
zFluxKontextAspectRatioID,
|
||||
zImagen3AspectRatioID,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCaretDownBold } from 'react-icons/pi';
|
||||
|
||||
export const DimensionsAspectRatioSelect = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const id = useAppSelector(selectAspectRatioID);
|
||||
const isImagen3 = useAppSelector(selectIsImagen3);
|
||||
const isChatGPT4o = useAppSelector(selectIsChatGPT4o);
|
||||
const isImagen4 = useAppSelector(selectIsImagen4);
|
||||
const isFluxKontext = useAppSelector(selectIsFluxKontext);
|
||||
const options = useMemo(() => {
|
||||
// Imagen3 and ChatGPT4o have different aspect ratio options, and do not support freeform sizes
|
||||
if (isImagen3 || isImagen4) {
|
||||
return zImagen3AspectRatioID.options;
|
||||
}
|
||||
if (isChatGPT4o) {
|
||||
return zChatGPT4oAspectRatioID.options;
|
||||
}
|
||||
if (isFluxKontext) {
|
||||
return zFluxKontextAspectRatioID.options;
|
||||
}
|
||||
// All other models
|
||||
return zAspectRatioID.options;
|
||||
}, [isImagen3, isChatGPT4o, isImagen4, isFluxKontext]);
|
||||
|
||||
const onChange = useCallback<ChangeEventHandler<HTMLSelectElement>>(
|
||||
(e) => {
|
||||
if (!isAspectRatioID(e.target.value)) {
|
||||
return;
|
||||
}
|
||||
dispatch(aspectRatioIdChanged({ id: e.target.value }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="paramAspect">
|
||||
<FormLabel>{t('parameters.aspect')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<Select size="sm" value={id} onChange={onChange} cursor="pointer" iconSize="0.75rem" icon={<PiCaretDownBold />}>
|
||||
{options.map((ratio) => (
|
||||
<option key={ratio} value={ratio}>
|
||||
{ratio}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
});
|
||||
|
||||
DimensionsAspectRatioSelect.displayName = 'DimensionsAspectRatioSelect';
|
||||
@@ -0,0 +1,58 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { heightChanged, selectHeight } from 'features/controlLayers/store/paramsSlice';
|
||||
import { selectGridSize, selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { selectHeightConfig } from 'features/system/store/configSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const DimensionsHeight = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const height = useAppSelector(selectHeight);
|
||||
const config = useAppSelector(selectHeightConfig);
|
||||
const gridSize = useAppSelector(selectGridSize);
|
||||
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(heightChanged({ height: v }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const marks = useMemo(
|
||||
() => [config.sliderMin, optimalDimension, config.sliderMax],
|
||||
[config.sliderMin, config.sliderMax, optimalDimension]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="paramHeight">
|
||||
<FormLabel>{t('parameters.height')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<CompositeSlider
|
||||
value={height}
|
||||
defaultValue={optimalDimension}
|
||||
onChange={onChange}
|
||||
min={config.sliderMin}
|
||||
max={config.sliderMax}
|
||||
step={config.coarseStep}
|
||||
fineStep={gridSize}
|
||||
marks={marks}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={height}
|
||||
defaultValue={optimalDimension}
|
||||
onChange={onChange}
|
||||
min={config.numberInputMin}
|
||||
max={config.numberInputMax}
|
||||
step={config.coarseStep}
|
||||
fineStep={gridSize}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
});
|
||||
|
||||
DimensionsHeight.displayName = 'DimensionsHeight';
|
||||
@@ -0,0 +1,32 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { aspectRatioLockToggled, selectAspectRatioIsLocked } from 'features/controlLayers/store/paramsSlice';
|
||||
import { useIsApiModel } from 'features/parameters/hooks/useIsApiModel';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi';
|
||||
|
||||
export const DimensionsLockAspectRatioButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isLocked = useAppSelector(selectAspectRatioIsLocked);
|
||||
const isApiModel = useIsApiModel();
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(aspectRatioLockToggled());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
tooltip={t('parameters.lockAspectRatio')}
|
||||
aria-label={t('parameters.lockAspectRatio')}
|
||||
onClick={onClick}
|
||||
variant={isLocked ? 'outline' : 'ghost'}
|
||||
size="sm"
|
||||
icon={isLocked ? <PiLockSimpleFill /> : <PiLockSimpleOpenBold />}
|
||||
isDisabled={isApiModel}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
DimensionsLockAspectRatioButton.displayName = 'DimensionsLockAspectRatioButton';
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Flex, Grid, GridItem } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectAspectRatioValue, selectHeight, selectWidth } from 'features/controlLayers/store/paramsSlice';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useMeasure } from 'react-use';
|
||||
|
||||
export const DimensionsPreview = memo(() => {
|
||||
const bboxWidth = useAppSelector(selectWidth);
|
||||
const bboxHeight = useAppSelector(selectHeight);
|
||||
const aspectRatioValue = useAppSelector(selectAspectRatioValue);
|
||||
const [ref, dims] = useMeasure<HTMLDivElement>();
|
||||
|
||||
const previewBoxSize = useMemo(() => {
|
||||
if (!dims) {
|
||||
return { width: 0, height: 0 };
|
||||
}
|
||||
|
||||
let width = bboxWidth;
|
||||
let height = bboxHeight;
|
||||
|
||||
if (bboxWidth > bboxHeight) {
|
||||
width = dims.width;
|
||||
height = width / aspectRatioValue;
|
||||
} else {
|
||||
height = dims.height;
|
||||
width = height * aspectRatioValue;
|
||||
}
|
||||
|
||||
return { width, height };
|
||||
}, [dims, bboxWidth, bboxHeight, aspectRatioValue]);
|
||||
|
||||
return (
|
||||
<Flex w="full" h="full" alignItems="center" justifyContent="center" ref={ref}>
|
||||
<Flex
|
||||
position="relative"
|
||||
borderRadius="base"
|
||||
borderColor="base.600"
|
||||
borderWidth="3px"
|
||||
width={`${previewBoxSize.width}px`}
|
||||
height={`${previewBoxSize.height}px`}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Grid
|
||||
borderRadius="base"
|
||||
position="absolute"
|
||||
top={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
gridTemplateColumns="1fr 1fr 1fr"
|
||||
gridTemplateRows="1fr 1fr 1fr"
|
||||
gap="1px"
|
||||
bg="base.700"
|
||||
>
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
</Grid>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
DimensionsPreview.displayName = 'DimensionsPreview';
|
||||
@@ -0,0 +1,53 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectHeight, selectWidth, sizeOptimized } from 'features/controlLayers/store/paramsSlice';
|
||||
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { useIsApiModel } from 'features/parameters/hooks/useIsApiModel';
|
||||
import { getIsSizeTooLarge, getIsSizeTooSmall } from 'features/parameters/util/optimalDimension';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiSparkleFill } from 'react-icons/pi';
|
||||
|
||||
export const DimensionsSetOptimalSizeButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isApiModel = useIsApiModel();
|
||||
const width = useAppSelector(selectWidth);
|
||||
const height = useAppSelector(selectHeight);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const isSizeTooSmall = useMemo(
|
||||
() => getIsSizeTooSmall(width, height, optimalDimension),
|
||||
[height, width, optimalDimension]
|
||||
);
|
||||
const isSizeTooLarge = useMemo(
|
||||
() => getIsSizeTooLarge(width, height, optimalDimension),
|
||||
[height, width, optimalDimension]
|
||||
);
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(sizeOptimized());
|
||||
}, [dispatch]);
|
||||
const tooltip = useMemo(() => {
|
||||
if (isSizeTooSmall) {
|
||||
return t('parameters.setToOptimalSizeTooSmall');
|
||||
}
|
||||
if (isSizeTooLarge) {
|
||||
return t('parameters.setToOptimalSizeTooLarge');
|
||||
}
|
||||
return t('parameters.setToOptimalSize');
|
||||
}, [isSizeTooLarge, isSizeTooSmall, t]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
tooltip={tooltip}
|
||||
aria-label={t('parameters.setToOptimalSize')}
|
||||
onClick={onClick}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
icon={<PiSparkleFill />}
|
||||
colorScheme={isSizeTooSmall || isSizeTooLarge ? 'warning' : 'base'}
|
||||
isDisabled={isApiModel}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
DimensionsSetOptimalSizeButton.displayName = 'DimensionsSetOptimalSizeButton';
|
||||
@@ -0,0 +1,26 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { dimensionsSwapped } from 'features/controlLayers/store/paramsSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowsDownUpBold } from 'react-icons/pi';
|
||||
|
||||
export const DimensionsSwapButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(dimensionsSwapped());
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<IconButton
|
||||
tooltip={t('parameters.swapDimensions')}
|
||||
aria-label={t('parameters.swapDimensions')}
|
||||
onClick={onClick}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
icon={<PiArrowsDownUpBold />}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
DimensionsSwapButton.displayName = 'DimensionsSwapButton';
|
||||
@@ -0,0 +1,60 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { selectWidth, widthChanged } from 'features/controlLayers/store/paramsSlice';
|
||||
import { selectGridSize, selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { useIsApiModel } from 'features/parameters/hooks/useIsApiModel';
|
||||
import { selectWidthConfig } from 'features/system/store/configSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const DimensionsWidth = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const width = useAppSelector(selectWidth);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const config = useAppSelector(selectWidthConfig);
|
||||
const isApiModel = useIsApiModel();
|
||||
const gridSize = useAppSelector(selectGridSize);
|
||||
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(widthChanged({ width: v }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const marks = useMemo(
|
||||
() => [config.sliderMin, optimalDimension, config.sliderMax],
|
||||
[config.sliderMax, config.sliderMin, optimalDimension]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl isDisabled={isApiModel}>
|
||||
<InformationalPopover feature="paramWidth">
|
||||
<FormLabel>{t('parameters.width')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<CompositeSlider
|
||||
value={width}
|
||||
onChange={onChange}
|
||||
defaultValue={optimalDimension}
|
||||
min={config.sliderMin}
|
||||
max={config.sliderMax}
|
||||
step={config.coarseStep}
|
||||
fineStep={gridSize}
|
||||
marks={marks}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={width}
|
||||
onChange={onChange}
|
||||
defaultValue={optimalDimension}
|
||||
min={config.numberInputMin}
|
||||
max={config.numberInputMax}
|
||||
step={config.coarseStep}
|
||||
fineStep={gridSize}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
});
|
||||
|
||||
DimensionsWidth.displayName = 'Dimensions';
|
||||
@@ -46,7 +46,7 @@ const scalingLabelProps: FormLabelProps = {
|
||||
minW: '4.5rem',
|
||||
};
|
||||
|
||||
export const ImageSettingsAccordion = memo(() => {
|
||||
export const CanvasTabImageSettingsAccordion = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const badges = useAppSelector(selectBadges);
|
||||
const scaleMethod = useAppSelector(selectScaleMethod);
|
||||
@@ -99,4 +99,4 @@ export const ImageSettingsAccordion = memo(() => {
|
||||
);
|
||||
});
|
||||
|
||||
ImageSettingsAccordion.displayName = 'ImageSettingsAccordion';
|
||||
CanvasTabImageSettingsAccordion.displayName = 'CanvasTabImageSettingsAccordion';
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Flex, StandaloneAccordion } from '@invoke-ai/ui-library';
|
||||
import { EMPTY_ARRAY } from 'app/store/constants';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
selectAspectRatioID,
|
||||
selectAspectRatioIsLocked,
|
||||
selectHeight,
|
||||
selectShouldRandomizeSeed,
|
||||
selectWidth,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import { Dimensions } from 'features/parameters/components/Dimensions/Dimensions';
|
||||
import { ParamSeed } from 'features/parameters/components/Seed/ParamSeed';
|
||||
import { useIsApiModel } from 'features/parameters/hooks/useIsApiModel';
|
||||
import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selectBadges = createMemoizedSelector(
|
||||
[selectWidth, selectHeight, selectAspectRatioID, selectAspectRatioIsLocked, selectShouldRandomizeSeed],
|
||||
(width, height, aspectRatioID, aspectRatioIsLocked, shouldRandomizeSeed) => {
|
||||
const badges: string[] = [];
|
||||
|
||||
badges.push(`${width}×${height}`);
|
||||
badges.push(aspectRatioID);
|
||||
|
||||
if (aspectRatioIsLocked) {
|
||||
badges.push('locked');
|
||||
}
|
||||
|
||||
if (!shouldRandomizeSeed) {
|
||||
badges.push('Manual Seed');
|
||||
}
|
||||
|
||||
if (badges.length === 0) {
|
||||
return EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
badges;
|
||||
}
|
||||
);
|
||||
|
||||
export const GenerateTabImageSettingsAccordion = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const badges = useAppSelector(selectBadges);
|
||||
const { isOpen: isOpenAccordion, onToggle: onToggleAccordion } = useStandaloneAccordionToggle({
|
||||
id: 'image-settings-generate-tab',
|
||||
defaultIsOpen: true,
|
||||
});
|
||||
const isApiModel = useIsApiModel();
|
||||
|
||||
return (
|
||||
<StandaloneAccordion
|
||||
label={t('accordions.image.title')}
|
||||
badges={badges}
|
||||
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>
|
||||
</StandaloneAccordion>
|
||||
);
|
||||
});
|
||||
|
||||
GenerateTabImageSettingsAccordion.displayName = 'GenerateTabImageSettingsAccordion';
|
||||
@@ -1,16 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ImageSettingsAccordion } from './ImageSettingsAccordion';
|
||||
|
||||
const meta: Meta<typeof ImageSettingsAccordion> = {
|
||||
title: 'Feature/ImageSettingsAccordion',
|
||||
tags: ['autodocs'],
|
||||
component: ImageSettingsAccordion,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ImageSettingsAccordion>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <ImageSettingsAccordion />,
|
||||
};
|
||||
@@ -8,7 +8,7 @@ import { useIsApiModel } from 'features/parameters/hooks/useIsApiModel';
|
||||
import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
|
||||
import { CompositingSettingsAccordion } from 'features/settingsAccordions/components/CompositingSettingsAccordion/CompositingSettingsAccordion';
|
||||
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
|
||||
import { ImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion';
|
||||
import { CanvasTabImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/CanvasTabImageSettingsAccordion';
|
||||
import { RefinerSettingsAccordion } from 'features/settingsAccordions/components/RefinerSettingsAccordion/RefinerSettingsAccordion';
|
||||
import { StylePresetMenu } from 'features/stylePresets/components/StylePresetMenu';
|
||||
import { StylePresetMenuTrigger } from 'features/stylePresets/components/StylePresetMenuTrigger';
|
||||
@@ -44,7 +44,7 @@ export const ParametersPanelCanvas = memo(() => {
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<Prompts />
|
||||
<ImageSettingsAccordion />
|
||||
<CanvasTabImageSettingsAccordion />
|
||||
<GenerationSettingsAccordion />
|
||||
{!isApiModel && <CompositingSettingsAccordion />}
|
||||
{isSDXL && <RefinerSettingsAccordion />}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Prompts } from 'features/parameters/components/Prompts/Prompts';
|
||||
import { useIsApiModel } from 'features/parameters/hooks/useIsApiModel';
|
||||
import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
|
||||
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
|
||||
import { ImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion';
|
||||
import { GenerateTabImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/GenerateTabImageSettingsAccordion';
|
||||
import { RefinerSettingsAccordion } from 'features/settingsAccordions/components/RefinerSettingsAccordion/RefinerSettingsAccordion';
|
||||
import { StylePresetMenu } from 'features/stylePresets/components/StylePresetMenu';
|
||||
import { StylePresetMenuTrigger } from 'features/stylePresets/components/StylePresetMenuTrigger';
|
||||
@@ -43,7 +43,7 @@ export const ParametersPanelGenerate = memo(() => {
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<Prompts />
|
||||
<ImageSettingsAccordion />
|
||||
<GenerateTabImageSettingsAccordion />
|
||||
<GenerationSettingsAccordion />
|
||||
{isSDXL && <RefinerSettingsAccordion />}
|
||||
{!isCogview4 && !isApiModel && <AdvancedSettingsAccordion />}
|
||||
|
||||
Reference in New Issue
Block a user