mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-16 23:01:28 -05:00
Reorganises internal state
`options` slice was huge and managed a mix of generation parameters and general app settings. It has been split up: - Generation parameters are now in `generationSlice`. - Postprocessing parameters are now in `postprocessingSlice` - UI related things are now in `uiSlice` There is probably more to be done, like `gallerySlice` perhaps should only manage internal gallery state, and not if the gallery is displayed. Full-slice selectors have been made for each slice. Other organisational tweaks.
This commit is contained in:
committed by
blessedcoolant
parent
ffe0e81ec9
commit
d74c4009cb
@@ -0,0 +1,53 @@
|
||||
.inpainting-bounding-box-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 0.4rem;
|
||||
border: 2px solid var(--tab-color);
|
||||
}
|
||||
|
||||
.inpainting-bounding-box-header {
|
||||
background-color: var(--tab-color);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.3rem 0.3rem 0 0;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
width: 0.5rem;
|
||||
height: 1.2rem;
|
||||
background: none;
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
// font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.inpainting-bounding-box-settings-items {
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 1rem;
|
||||
|
||||
.inpainting-bounding-box-reset-icon-btn {
|
||||
background-color: var(--btn-base-color);
|
||||
&:hover {
|
||||
background-color: var(--btn-base-color-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inpainting-bounding-box-dimensions-slider-numberinput {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, auto);
|
||||
column-gap: 1rem;
|
||||
}
|
||||
|
||||
.inpainting-bounding-box-darken {
|
||||
width: max-content;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||
import _ from 'lodash';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
canvasSelector,
|
||||
(canvas) => {
|
||||
const { boundingBoxDimensions, boundingBoxScaleMethod: boundingBoxScale } =
|
||||
canvas;
|
||||
return {
|
||||
boundingBoxDimensions,
|
||||
boundingBoxScale,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: _.isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const BoundingBoxSettings = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { boundingBoxDimensions } = useAppSelector(selector);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeWidth = (v: number) => {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
...boundingBoxDimensions,
|
||||
width: Math.floor(v),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleChangeHeight = (v: number) => {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
...boundingBoxDimensions,
|
||||
height: Math.floor(v),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleResetWidth = () => {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
...boundingBoxDimensions,
|
||||
width: Math.floor(512),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleResetHeight = () => {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
...boundingBoxDimensions,
|
||||
height: Math.floor(512),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="1rem">
|
||||
<IAISlider
|
||||
label={t('parameters:width')}
|
||||
min={64}
|
||||
max={1024}
|
||||
step={64}
|
||||
value={boundingBoxDimensions.width}
|
||||
onChange={handleChangeWidth}
|
||||
sliderNumberInputProps={{ max: 4096 }}
|
||||
withSliderMarks
|
||||
withInput
|
||||
inputReadOnly
|
||||
withReset
|
||||
handleReset={handleResetWidth}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('parameters:height')}
|
||||
min={64}
|
||||
max={1024}
|
||||
step={64}
|
||||
value={boundingBoxDimensions.height}
|
||||
onChange={handleChangeHeight}
|
||||
sliderNumberInputProps={{ max: 4096 }}
|
||||
withSliderMarks
|
||||
withInput
|
||||
inputReadOnly
|
||||
withReset
|
||||
handleReset={handleResetHeight}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoundingBoxSettings;
|
||||
|
||||
export const BoundingBoxSettingsHeader = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box flex="1" textAlign="left">
|
||||
{t('parameters:boundingBoxHeader')}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,180 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISelect from 'common/components/IAISelect';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
setBoundingBoxScaleMethod,
|
||||
setScaledBoundingBoxDimensions,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import {
|
||||
BoundingBoxScale,
|
||||
BOUNDING_BOX_SCALES_DICT,
|
||||
} from 'features/canvas/store/canvasTypes';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import {
|
||||
setInfillMethod,
|
||||
setTileSize,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import _ from 'lodash';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
[generationSelector, systemSelector, canvasSelector],
|
||||
(parameters, system, canvas) => {
|
||||
const { tileSize, infillMethod } = parameters;
|
||||
|
||||
const { infill_methods: availableInfillMethods } = system;
|
||||
|
||||
const {
|
||||
boundingBoxScaleMethod: boundingBoxScale,
|
||||
scaledBoundingBoxDimensions,
|
||||
} = canvas;
|
||||
|
||||
return {
|
||||
boundingBoxScale,
|
||||
scaledBoundingBoxDimensions,
|
||||
tileSize,
|
||||
infillMethod,
|
||||
availableInfillMethods,
|
||||
isManual: boundingBoxScale === 'manual',
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: _.isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const InfillAndScalingSettings = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const {
|
||||
tileSize,
|
||||
infillMethod,
|
||||
availableInfillMethods,
|
||||
boundingBoxScale,
|
||||
isManual,
|
||||
scaledBoundingBoxDimensions,
|
||||
} = useAppSelector(selector);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeScaledWidth = (v: number) => {
|
||||
dispatch(
|
||||
setScaledBoundingBoxDimensions({
|
||||
...scaledBoundingBoxDimensions,
|
||||
width: Math.floor(v),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleChangeScaledHeight = (v: number) => {
|
||||
dispatch(
|
||||
setScaledBoundingBoxDimensions({
|
||||
...scaledBoundingBoxDimensions,
|
||||
height: Math.floor(v),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleResetScaledWidth = () => {
|
||||
dispatch(
|
||||
setScaledBoundingBoxDimensions({
|
||||
...scaledBoundingBoxDimensions,
|
||||
width: Math.floor(512),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleResetScaledHeight = () => {
|
||||
dispatch(
|
||||
setScaledBoundingBoxDimensions({
|
||||
...scaledBoundingBoxDimensions,
|
||||
height: Math.floor(512),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleChangeBoundingBoxScaleMethod = (
|
||||
e: ChangeEvent<HTMLSelectElement>
|
||||
) => {
|
||||
dispatch(setBoundingBoxScaleMethod(e.target.value as BoundingBoxScale));
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="1rem">
|
||||
<IAISelect
|
||||
label={t('parameters:scaleBeforeProcessing')}
|
||||
validValues={BOUNDING_BOX_SCALES_DICT}
|
||||
value={boundingBoxScale}
|
||||
onChange={handleChangeBoundingBoxScaleMethod}
|
||||
/>
|
||||
<IAISlider
|
||||
isInputDisabled={!isManual}
|
||||
isResetDisabled={!isManual}
|
||||
isSliderDisabled={!isManual}
|
||||
label={t('parameters:scaledWidth')}
|
||||
min={64}
|
||||
max={1024}
|
||||
step={64}
|
||||
value={scaledBoundingBoxDimensions.width}
|
||||
onChange={handleChangeScaledWidth}
|
||||
sliderNumberInputProps={{ max: 4096 }}
|
||||
withSliderMarks
|
||||
withInput
|
||||
inputReadOnly
|
||||
withReset
|
||||
handleReset={handleResetScaledWidth}
|
||||
/>
|
||||
<IAISlider
|
||||
isInputDisabled={!isManual}
|
||||
isResetDisabled={!isManual}
|
||||
isSliderDisabled={!isManual}
|
||||
label={t('parameters:scaledHeight')}
|
||||
min={64}
|
||||
max={1024}
|
||||
step={64}
|
||||
value={scaledBoundingBoxDimensions.height}
|
||||
onChange={handleChangeScaledHeight}
|
||||
sliderNumberInputProps={{ max: 4096 }}
|
||||
withSliderMarks
|
||||
withInput
|
||||
inputReadOnly
|
||||
withReset
|
||||
handleReset={handleResetScaledHeight}
|
||||
/>
|
||||
<IAISelect
|
||||
label={t('parameters:infillMethod')}
|
||||
value={infillMethod}
|
||||
validValues={availableInfillMethods}
|
||||
onChange={(e) => dispatch(setInfillMethod(e.target.value))}
|
||||
/>
|
||||
<IAISlider
|
||||
isInputDisabled={infillMethod !== 'tile'}
|
||||
isResetDisabled={infillMethod !== 'tile'}
|
||||
isSliderDisabled={infillMethod !== 'tile'}
|
||||
sliderMarkRightOffset={-4}
|
||||
label={t('parameters:tileSize')}
|
||||
min={16}
|
||||
max={64}
|
||||
sliderNumberInputProps={{ max: 256 }}
|
||||
value={tileSize}
|
||||
onChange={(v) => {
|
||||
dispatch(setTileSize(v));
|
||||
}}
|
||||
withInput
|
||||
withSliderMarks
|
||||
withReset
|
||||
handleReset={() => {
|
||||
dispatch(setTileSize(32));
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfillAndScalingSettings;
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { setSeamBlur } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function SeamBlur() {
|
||||
const dispatch = useAppDispatch();
|
||||
const seamBlur = useAppSelector(
|
||||
(state: RootState) => state.generation.seamBlur
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
sliderMarkRightOffset={-4}
|
||||
label={t('parameters:seamBlur')}
|
||||
min={0}
|
||||
max={64}
|
||||
sliderNumberInputProps={{ max: 512 }}
|
||||
value={seamBlur}
|
||||
onChange={(v) => {
|
||||
dispatch(setSeamBlur(v));
|
||||
}}
|
||||
withInput
|
||||
withSliderMarks
|
||||
withReset
|
||||
handleReset={() => {
|
||||
dispatch(setSeamBlur(16));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import SeamBlur from './SeamBlur';
|
||||
import SeamSize from './SeamSize';
|
||||
import SeamSteps from './SeamSteps';
|
||||
import SeamStrength from './SeamStrength';
|
||||
|
||||
const SeamCorrectionSettings = () => {
|
||||
return (
|
||||
<Flex direction="column" gap="1rem">
|
||||
<SeamSize />
|
||||
<SeamBlur />
|
||||
<SeamStrength />
|
||||
<SeamSteps />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default SeamCorrectionSettings;
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { setSeamSize } from 'features/parameters/store/generationSlice';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function SeamSize() {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const seamSize = useAppSelector((state: RootState) => state.generation.seamSize);
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
sliderMarkRightOffset={-6}
|
||||
label={t('parameters:seamSize')}
|
||||
min={1}
|
||||
max={256}
|
||||
sliderNumberInputProps={{ max: 512 }}
|
||||
value={seamSize}
|
||||
onChange={(v) => {
|
||||
dispatch(setSeamSize(v));
|
||||
}}
|
||||
withInput
|
||||
withSliderMarks
|
||||
withReset
|
||||
handleReset={() => dispatch(setSeamSize(96))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { setSeamSteps } from 'features/parameters/store/generationSlice';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function SeamSteps() {
|
||||
const { t } = useTranslation();
|
||||
const seamSteps = useAppSelector(
|
||||
(state: RootState) => state.generation.seamSteps
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
sliderMarkRightOffset={-4}
|
||||
label={t('parameters:seamSteps')}
|
||||
min={1}
|
||||
max={100}
|
||||
sliderNumberInputProps={{ max: 999 }}
|
||||
value={seamSteps}
|
||||
onChange={(v) => {
|
||||
dispatch(setSeamSteps(v));
|
||||
}}
|
||||
withInput
|
||||
withSliderMarks
|
||||
withReset
|
||||
handleReset={() => {
|
||||
dispatch(setSeamSteps(30));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { setSeamStrength } from 'features/parameters/store/generationSlice';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function SeamStrength() {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const seamStrength = useAppSelector(
|
||||
(state: RootState) => state.generation.seamStrength
|
||||
);
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
sliderMarkRightOffset={-7}
|
||||
label={t('parameters:seamStrength')}
|
||||
min={0.01}
|
||||
max={0.99}
|
||||
step={0.01}
|
||||
value={seamStrength}
|
||||
onChange={(v) => {
|
||||
dispatch(setSeamStrength(v));
|
||||
}}
|
||||
withInput
|
||||
withSliderMarks
|
||||
withReset
|
||||
handleReset={() => {
|
||||
dispatch(setSeamStrength(0.7));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
|
||||
import { FacetoolType } from 'features/parameters/store/postprocessingSlice';
|
||||
|
||||
import {
|
||||
setCodeformerFidelity,
|
||||
setFacetoolStrength,
|
||||
setFacetoolType,
|
||||
} from 'features/parameters/store/postprocessingSlice';
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { isEqual } from 'lodash';
|
||||
import IAINumberInput from 'common/components/IAINumberInput';
|
||||
import IAISelect from 'common/components/IAISelect';
|
||||
import { FACETOOL_TYPES } from 'app/constants';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
|
||||
const optionsSelector = createSelector(
|
||||
[postprocessingSelector, systemSelector],
|
||||
(
|
||||
{ facetoolStrength, facetoolType, codeformerFidelity },
|
||||
{ isGFPGANAvailable }
|
||||
) => {
|
||||
return {
|
||||
facetoolStrength,
|
||||
facetoolType,
|
||||
codeformerFidelity,
|
||||
isGFPGANAvailable,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Displays face-fixing/GFPGAN options (strength).
|
||||
*/
|
||||
const FaceRestoreSettings = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const {
|
||||
facetoolStrength,
|
||||
facetoolType,
|
||||
codeformerFidelity,
|
||||
isGFPGANAvailable,
|
||||
} = useAppSelector(optionsSelector);
|
||||
|
||||
const handleChangeStrength = (v: number) => dispatch(setFacetoolStrength(v));
|
||||
|
||||
const handleChangeCodeformerFidelity = (v: number) =>
|
||||
dispatch(setCodeformerFidelity(v));
|
||||
|
||||
const handleChangeFacetoolType = (e: ChangeEvent<HTMLSelectElement>) =>
|
||||
dispatch(setFacetoolType(e.target.value as FacetoolType));
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Flex direction={'column'} gap={2}>
|
||||
<IAISelect
|
||||
label={t('parameters:type')}
|
||||
validValues={FACETOOL_TYPES.concat()}
|
||||
value={facetoolType}
|
||||
onChange={handleChangeFacetoolType}
|
||||
/>
|
||||
<IAINumberInput
|
||||
isDisabled={!isGFPGANAvailable}
|
||||
label={t('parameters:strength')}
|
||||
step={0.05}
|
||||
min={0}
|
||||
max={1}
|
||||
onChange={handleChangeStrength}
|
||||
value={facetoolStrength}
|
||||
width="90px"
|
||||
isInteger={false}
|
||||
/>
|
||||
{facetoolType === 'codeformer' && (
|
||||
<IAINumberInput
|
||||
isDisabled={!isGFPGANAvailable}
|
||||
label={t('parameters:codeformerFidelity')}
|
||||
step={0.05}
|
||||
min={0}
|
||||
max={1}
|
||||
onChange={handleChangeCodeformerFidelity}
|
||||
value={codeformerFidelity}
|
||||
width="90px"
|
||||
isInteger={false}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default FaceRestoreSettings;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { setShouldRunFacetool } from 'features/parameters/store/postprocessingSlice';
|
||||
|
||||
export default function FaceRestoreToggle() {
|
||||
const isGFPGANAvailable = useAppSelector(
|
||||
(state: RootState) => state.system.isGFPGANAvailable
|
||||
);
|
||||
|
||||
const shouldRunFacetool = useAppSelector(
|
||||
(state: RootState) => state.postprocessing.shouldRunFacetool
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleChangeShouldRunFacetool = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldRunFacetool(e.target.checked));
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
isDisabled={!isGFPGANAvailable}
|
||||
isChecked={shouldRunFacetool}
|
||||
onChange={handleChangeShouldRunFacetool}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { setShouldFitToWidthHeight } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function ImageFit() {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const shouldFitToWidthHeight = useAppSelector(
|
||||
(state: RootState) => state.generation.shouldFitToWidthHeight
|
||||
);
|
||||
|
||||
const handleChangeFit = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldFitToWidthHeight(e.target.checked));
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
label={t('parameters:imageFit')}
|
||||
isChecked={shouldFitToWidthHeight}
|
||||
onChange={handleChangeFit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { setImg2imgStrength } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface ImageToImageStrengthProps {
|
||||
label?: string;
|
||||
styleClass?: string;
|
||||
}
|
||||
|
||||
export default function ImageToImageStrength(props: ImageToImageStrengthProps) {
|
||||
const { t } = useTranslation();
|
||||
const { label = `${t('parameters:strength')}`, styleClass } = props;
|
||||
const img2imgStrength = useAppSelector(
|
||||
(state: RootState) => state.generation.img2imgStrength
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleChangeStrength = (v: number) => dispatch(setImg2imgStrength(v));
|
||||
|
||||
const handleImg2ImgStrengthReset = () => {
|
||||
dispatch(setImg2imgStrength(0.75));
|
||||
};
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
label={label}
|
||||
step={0.01}
|
||||
min={0.01}
|
||||
max={0.99}
|
||||
onChange={handleChangeStrength}
|
||||
value={img2imgStrength}
|
||||
isInteger={false}
|
||||
styleClass={styleClass}
|
||||
withInput
|
||||
withSliderMarks
|
||||
inputWidth={'5.5rem'}
|
||||
withReset
|
||||
handleReset={handleImg2ImgStrengthReset}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import type { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors';
|
||||
import {
|
||||
setHiresFix,
|
||||
setHiresStrength,
|
||||
} from 'features/parameters/store/postprocessingSlice';
|
||||
import { isEqual } from 'lodash';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const hiresStrengthSelector = createSelector(
|
||||
[postprocessingSelector],
|
||||
({ hiresFix, hiresStrength }) => ({ hiresFix, hiresStrength }),
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const HiresStrength = () => {
|
||||
const { hiresFix, hiresStrength } = useAppSelector(hiresStrengthSelector);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleHiresStrength = (v: number) => {
|
||||
dispatch(setHiresStrength(v));
|
||||
};
|
||||
|
||||
const handleHiResStrengthReset = () => {
|
||||
dispatch(setHiresStrength(0.75));
|
||||
};
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
label={t('parameters:hiresStrength')}
|
||||
step={0.01}
|
||||
min={0.01}
|
||||
max={0.99}
|
||||
onChange={handleHiresStrength}
|
||||
value={hiresStrength}
|
||||
isInteger={false}
|
||||
withInput
|
||||
withSliderMarks
|
||||
inputWidth={'5.5rem'}
|
||||
withReset
|
||||
handleReset={handleHiResStrengthReset}
|
||||
isSliderDisabled={!hiresFix}
|
||||
isInputDisabled={!hiresFix}
|
||||
isResetDisabled={!hiresFix}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hires Fix Toggle
|
||||
*/
|
||||
const HiresSettings = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const hiresFix = useAppSelector(
|
||||
(state: RootState) => state.postprocessing.hiresFix
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeHiresFix = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setHiresFix(e.target.checked));
|
||||
|
||||
return (
|
||||
<Flex gap={2} direction={'column'}>
|
||||
<IAISwitch
|
||||
label={t('parameters:hiresOptim')}
|
||||
fontSize={'md'}
|
||||
isChecked={hiresFix}
|
||||
onChange={handleChangeHiresFix}
|
||||
/>
|
||||
<HiresStrength />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default HiresSettings;
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import SeamlessSettings from './SeamlessSettings';
|
||||
|
||||
const ImageToImageOutputSettings = () => {
|
||||
return (
|
||||
<Flex gap={2} direction={'column'}>
|
||||
<SeamlessSettings />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageToImageOutputSettings;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import HiresSettings from './HiresSettings';
|
||||
import SeamlessSettings from './SeamlessSettings';
|
||||
|
||||
const OutputSettings = () => {
|
||||
return (
|
||||
<Flex gap={2} direction={'column'}>
|
||||
<SeamlessSettings />
|
||||
<HiresSettings />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default OutputSettings;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { setSeamless } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
/**
|
||||
* Seamless tiling toggle
|
||||
*/
|
||||
const SeamlessSettings = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const seamless = useAppSelector((state: RootState) => state.generation.seamless);
|
||||
|
||||
const handleChangeSeamless = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setSeamless(e.target.checked));
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Flex gap={2} direction={'column'}>
|
||||
<IAISwitch
|
||||
label={t('parameters:seamlessTiling')}
|
||||
fontSize={'md'}
|
||||
isChecked={seamless}
|
||||
onChange={handleChangeSeamless}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default SeamlessSettings;
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAINumberInput from 'common/components/IAINumberInput';
|
||||
import { setPerlin } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function Perlin() {
|
||||
const dispatch = useAppDispatch();
|
||||
const perlin = useAppSelector((state: RootState) => state.generation.perlin);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangePerlin = (v: number) => dispatch(setPerlin(v));
|
||||
|
||||
return (
|
||||
<IAINumberInput
|
||||
label={t('parameters:perlinNoise')}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
onChange={handleChangePerlin}
|
||||
value={perlin}
|
||||
isInteger={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { setShouldRandomizeSeed } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function RandomizeSeed() {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const shouldRandomizeSeed = useAppSelector(
|
||||
(state: RootState) => state.generation.shouldRandomizeSeed
|
||||
);
|
||||
|
||||
const handleChangeShouldRandomizeSeed = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldRandomizeSeed(e.target.checked));
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
label={t('parameters:randomizeSeed')}
|
||||
isChecked={shouldRandomizeSeed}
|
||||
onChange={handleChangeShouldRandomizeSeed}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAINumberInput from 'common/components/IAINumberInput';
|
||||
import { setSeed } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function Seed() {
|
||||
const seed = useAppSelector((state: RootState) => state.generation.seed);
|
||||
const shouldRandomizeSeed = useAppSelector(
|
||||
(state: RootState) => state.generation.shouldRandomizeSeed
|
||||
);
|
||||
const shouldGenerateVariations = useAppSelector(
|
||||
(state: RootState) => state.generation.shouldGenerateVariations
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleChangeSeed = (v: number) => dispatch(setSeed(v));
|
||||
|
||||
return (
|
||||
<IAINumberInput
|
||||
label={t('parameters:seed')}
|
||||
step={1}
|
||||
precision={0}
|
||||
flexGrow={1}
|
||||
min={NUMPY_RAND_MIN}
|
||||
max={NUMPY_RAND_MAX}
|
||||
isDisabled={shouldRandomizeSeed}
|
||||
isInvalid={seed < 0 && shouldGenerateVariations}
|
||||
onChange={handleChangeSeed}
|
||||
value={seed}
|
||||
width="auto"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import RandomizeSeed from './RandomizeSeed';
|
||||
import Seed from './Seed';
|
||||
import ShuffleSeed from './ShuffleSeed';
|
||||
import Threshold from './Threshold';
|
||||
import Perlin from './Perlin';
|
||||
|
||||
/**
|
||||
* Seed & variation options. Includes iteration, seed, seed randomization, variation options.
|
||||
*/
|
||||
const SeedSettings = () => {
|
||||
return (
|
||||
<Flex gap={2} direction={'column'}>
|
||||
<RandomizeSeed />
|
||||
<Flex gap={2}>
|
||||
<Seed />
|
||||
<ShuffleSeed />
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<Threshold />
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<Perlin />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default SeedSettings;
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import randomInt from 'common/util/randomInt';
|
||||
import { setSeed } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function ShuffleSeed() {
|
||||
const dispatch = useAppDispatch();
|
||||
const shouldRandomizeSeed = useAppSelector(
|
||||
(state: RootState) => state.generation.shouldRandomizeSeed
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleClickRandomizeSeed = () =>
|
||||
dispatch(setSeed(randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX)));
|
||||
|
||||
return (
|
||||
<Button
|
||||
size={'sm'}
|
||||
isDisabled={shouldRandomizeSeed}
|
||||
onClick={handleClickRandomizeSeed}
|
||||
padding="0 1.5rem"
|
||||
>
|
||||
<p>{t('parameters:shuffle')}</p>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAINumberInput from 'common/components/IAINumberInput';
|
||||
import { setThreshold } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function Threshold() {
|
||||
const dispatch = useAppDispatch();
|
||||
const threshold = useAppSelector(
|
||||
(state: RootState) => state.generation.threshold
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeThreshold = (v: number) => dispatch(setThreshold(v));
|
||||
|
||||
return (
|
||||
<IAINumberInput
|
||||
label={t('parameters:noiseThreshold')}
|
||||
min={0}
|
||||
max={1000}
|
||||
step={0.1}
|
||||
onChange={handleChangeThreshold}
|
||||
value={threshold}
|
||||
isInteger={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.upscale-settings {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
column-gap: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
|
||||
import { UpscalingLevel } from 'features/parameters/store/postprocessingSlice';
|
||||
import {
|
||||
setUpscalingLevel,
|
||||
setUpscalingStrength,
|
||||
} from 'features/parameters/store/postprocessingSlice';
|
||||
|
||||
import { UPSCALING_LEVELS } from 'app/constants';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { isEqual } from 'lodash';
|
||||
import { ChangeEvent } from 'react';
|
||||
import IAINumberInput from 'common/components/IAINumberInput';
|
||||
import IAISelect from 'common/components/IAISelect';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
|
||||
const parametersSelector = createSelector(
|
||||
[postprocessingSelector, systemSelector],
|
||||
|
||||
({ upscalingLevel, upscalingStrength }, { isESRGANAvailable }) => {
|
||||
return {
|
||||
upscalingLevel,
|
||||
upscalingStrength,
|
||||
isESRGANAvailable,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Displays upscaling/ESRGAN options (level and strength).
|
||||
*/
|
||||
const UpscaleSettings = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { upscalingLevel, upscalingStrength, isESRGANAvailable } =
|
||||
useAppSelector(parametersSelector);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeLevel = (e: ChangeEvent<HTMLSelectElement>) =>
|
||||
dispatch(setUpscalingLevel(Number(e.target.value) as UpscalingLevel));
|
||||
|
||||
const handleChangeStrength = (v: number) => dispatch(setUpscalingStrength(v));
|
||||
|
||||
return (
|
||||
<div className="upscale-settings">
|
||||
<IAISelect
|
||||
isDisabled={!isESRGANAvailable}
|
||||
label={t('parameters:scale')}
|
||||
value={upscalingLevel}
|
||||
onChange={handleChangeLevel}
|
||||
validValues={UPSCALING_LEVELS}
|
||||
/>
|
||||
<IAINumberInput
|
||||
isDisabled={!isESRGANAvailable}
|
||||
label={t('parameters:strength')}
|
||||
step={0.05}
|
||||
min={0}
|
||||
max={1}
|
||||
onChange={handleChangeStrength}
|
||||
value={upscalingStrength}
|
||||
isInteger={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpscaleSettings;
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { setShouldRunESRGAN } from 'features/parameters/store/postprocessingSlice';
|
||||
|
||||
export default function UpscaleToggle() {
|
||||
const isESRGANAvailable = useAppSelector(
|
||||
(state: RootState) => state.system.isESRGANAvailable
|
||||
);
|
||||
|
||||
const shouldRunESRGAN = useAppSelector(
|
||||
(state: RootState) => state.postprocessing.shouldRunESRGAN
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleChangeShouldRunESRGAN = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldRunESRGAN(e.target.checked));
|
||||
return (
|
||||
<IAISwitch
|
||||
isDisabled={!isESRGANAvailable}
|
||||
isChecked={shouldRunESRGAN}
|
||||
onChange={handleChangeShouldRunESRGAN}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { setShouldGenerateVariations } from 'features/parameters/store/generationSlice';
|
||||
|
||||
export default function GenerateVariationsToggle() {
|
||||
const shouldGenerateVariations = useAppSelector(
|
||||
(state: RootState) => state.generation.shouldGenerateVariations
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleChangeShouldGenerateVariations = (
|
||||
e: ChangeEvent<HTMLInputElement>
|
||||
) => dispatch(setShouldGenerateVariations(e.target.checked));
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
isChecked={shouldGenerateVariations}
|
||||
width={'auto'}
|
||||
onChange={handleChangeShouldGenerateVariations}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAIInput from 'common/components/IAIInput';
|
||||
import { validateSeedWeights } from 'common/util/seedWeightPairs';
|
||||
import { setSeedWeights } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function SeedWeights() {
|
||||
const seedWeights = useAppSelector(
|
||||
(state: RootState) => state.generation.seedWeights
|
||||
);
|
||||
|
||||
const shouldGenerateVariations = useAppSelector(
|
||||
(state: RootState) => state.generation.shouldGenerateVariations
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleChangeSeedWeights = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setSeedWeights(e.target.value));
|
||||
|
||||
return (
|
||||
<IAIInput
|
||||
label={t('parameters:seedWeights')}
|
||||
value={seedWeights}
|
||||
isInvalid={
|
||||
shouldGenerateVariations &&
|
||||
!(validateSeedWeights(seedWeights) || seedWeights === '')
|
||||
}
|
||||
isDisabled={!shouldGenerateVariations}
|
||||
onChange={handleChangeSeedWeights}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAINumberInput from 'common/components/IAINumberInput';
|
||||
import { setVariationAmount } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function VariationAmount() {
|
||||
const variationAmount = useAppSelector(
|
||||
(state: RootState) => state.generation.variationAmount
|
||||
);
|
||||
|
||||
const shouldGenerateVariations = useAppSelector(
|
||||
(state: RootState) => state.generation.shouldGenerateVariations
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleChangevariationAmount = (v: number) =>
|
||||
dispatch(setVariationAmount(v));
|
||||
|
||||
return (
|
||||
<IAINumberInput
|
||||
label={t('parameters:variationAmount')}
|
||||
value={variationAmount}
|
||||
step={0.01}
|
||||
min={0}
|
||||
max={1}
|
||||
isDisabled={!shouldGenerateVariations}
|
||||
onChange={handleChangevariationAmount}
|
||||
isInteger={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import SeedWeights from './SeedWeights';
|
||||
import VariationAmount from './VariationAmount';
|
||||
|
||||
/**
|
||||
* Seed & variation options. Includes iteration, seed, seed randomization, variation options.
|
||||
*/
|
||||
const VariationsSettings = () => {
|
||||
return (
|
||||
<Flex gap={2} direction={'column'}>
|
||||
<VariationAmount />
|
||||
<SeedWeights />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default VariationsSettings;
|
||||
Reference in New Issue
Block a user