feat(ui): add all-sliders option

This commit is contained in:
blessedcoolant 2023-02-10 19:57:02 +13:00 committed by psychedelicious
parent 6551527fe2
commit 5f0848bf7d
48 changed files with 1239 additions and 937 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>InvokeAI - A Stable Diffusion Toolkit</title>
<link rel="shortcut icon" type="icon" href="./assets/favicon-0d253ced.ico" />
<script type="module" crossorigin src="./assets/index-12bd70ca.js"></script>
<link rel="stylesheet" href="./assets/index-c1af841f.css">
<script type="module" crossorigin src="./assets/index-3d708af9.js"></script>
<link rel="stylesheet" href="./assets/index-0b464cc9.css">
</head>
<body>

View File

@ -1,10 +1,12 @@
{
"general": "General",
"images": "Images",
"steps": "Steps",
"cfgScale": "CFG Scale",
"width": "Width",
"height": "Height",
"sampler": "Sampler",
"imageToImage": "Image To Image",
"seed": "Seed",
"randomizeSeed": "Randomize Seed",
"shuffle": "Shuffle",

View File

@ -1,10 +1,12 @@
{
"general": "General",
"images": "Images",
"steps": "Steps",
"cfgScale": "CFG Scale",
"width": "Width",
"height": "Height",
"sampler": "Sampler",
"imageToImage": "Image To Image",
"seed": "Seed",
"randomizeSeed": "Randomize Seed",
"shuffle": "Shuffle",

View File

@ -5,6 +5,7 @@
"confirmOnDelete": "Confirm On Delete",
"displayHelpIcons": "Display Help Icons",
"useCanvasBeta": "Use Canvas Beta Layout",
"useSlidersForAll": "Use Sliders For All Options",
"enableImageDebugging": "Enable Image Debugging",
"resetWebUI": "Reset Web UI",
"resetWebUIDesc1": "Resetting the web UI only resets the browser's local cache of your images and remembered settings. It does not delete any images from disk.",

View File

@ -1,10 +1,12 @@
{
"general": "General",
"images": "Images",
"steps": "Steps",
"cfgScale": "CFG Scale",
"width": "Width",
"height": "Height",
"sampler": "Sampler",
"imageToImage": "Image To Image",
"seed": "Seed",
"randomizeSeed": "Randomize Seed",
"shuffle": "Shuffle",

View File

@ -1,10 +1,12 @@
{
"general": "General",
"images": "Images",
"steps": "Steps",
"cfgScale": "CFG Scale",
"width": "Width",
"height": "Height",
"sampler": "Sampler",
"imageToImage": "Image To Image",
"seed": "Seed",
"randomizeSeed": "Randomize Seed",
"shuffle": "Shuffle",

View File

@ -5,6 +5,7 @@
"confirmOnDelete": "Confirm On Delete",
"displayHelpIcons": "Display Help Icons",
"useCanvasBeta": "Use Canvas Beta Layout",
"useSlidersForAll": "Use Sliders For All Options",
"enableImageDebugging": "Enable Image Debugging",
"resetWebUI": "Reset Web UI",
"resetWebUIDesc1": "Resetting the web UI only resets the browser's local cache of your images and remembered settings. It does not delete any images from disk.",

View File

@ -6,7 +6,6 @@
min-width: max-content;
margin: 0;
font-weight: bold;
font-size: 0.9rem;
color: var(--text-color-secondary);
}

View File

@ -78,7 +78,7 @@ export default function IAISlider(props: IAIFullSliderProps) {
tooltipSuffix = '',
withSliderMarks = false,
sliderMarkLeftOffset = 0,
sliderMarkRightOffset = -7,
sliderMarkRightOffset = -1,
withInput = false,
isInteger = false,
inputWidth = '5.5rem',
@ -164,6 +164,7 @@ export default function IAISlider(props: IAIFullSliderProps) {
>
<FormLabel
className="invokeai__slider-component-label"
fontSize="sm"
{...sliderFormLabelProps}
>
{label}

View File

@ -0,0 +1,55 @@
import { Box } from '@chakra-ui/react';
interface SubItemHookProps {
active?: boolean;
width?: string | number;
height?: string | number;
side?: 'left' | 'right';
}
export default function SubItemHook(props: SubItemHookProps) {
const {
active = true,
width = '1rem',
height = '1.3rem',
side = 'right',
} = props;
return (
<>
{side === 'right' && (
<Box
width={width}
height={height}
margin="-0.5rem 0.5rem 0 0.5rem"
borderLeft={
active
? '3px solid var(--subhook-color)'
: '3px solid var(--tab-hover-color)'
}
borderBottom={
active
? '3px solid var(--subhook-color)'
: '3px solid var(--tab-hover-color)'
}
/>
)}
{side === 'left' && (
<Box
width={width}
height={height}
margin="-0.5rem 0.5rem 0 0.5rem"
borderRight={
active
? '3px solid var(--subhook-color)'
: '3px solid var(--tab-hover-color)'
}
borderBottom={
active
? '3px solid var(--subhook-color)'
: '3px solid var(--tab-hover-color)'
}
/>
)}
</>
);
}

View File

@ -170,6 +170,9 @@ export const frontendToBackendParameters = (
let esrganParameters: false | BackendEsrGanParameters = false;
let facetoolParameters: false | BackendFacetoolParameters = false;
// Multiplying it by 10000 so the Slider can have values between 0 and 1 which makes more sense
generationParameters.threshold = threshold * 1000;
if (negativePrompt !== '') {
generationParameters.prompt = `${prompt} [${negativePrompt}]`;
}

View File

@ -68,7 +68,7 @@ const BoundingBoxSettings = () => {
};
return (
<Flex direction="column" gap="1rem">
<Flex direction="column" gap={2}>
<IAISlider
label={t('parameters:width')}
min={64}
@ -82,6 +82,7 @@ const BoundingBoxSettings = () => {
inputReadOnly
withReset
handleReset={handleResetWidth}
sliderMarkRightOffset={-7}
/>
<IAISlider
label={t('parameters:height')}
@ -96,6 +97,7 @@ const BoundingBoxSettings = () => {
inputReadOnly
withReset
handleReset={handleResetHeight}
sliderMarkRightOffset={-7}
/>
</Flex>
);

View File

@ -107,7 +107,7 @@ const InfillAndScalingSettings = () => {
};
return (
<Flex direction="column" gap="1rem">
<Flex direction="column" gap={4}>
<IAISelect
label={t('parameters:scaleBeforeProcessing')}
validValues={BOUNDING_BOX_SCALES_DICT}
@ -130,6 +130,7 @@ const InfillAndScalingSettings = () => {
inputReadOnly
withReset
handleReset={handleResetScaledWidth}
sliderMarkRightOffset={-7}
/>
<IAISlider
isInputDisabled={!isManual}
@ -147,6 +148,7 @@ const InfillAndScalingSettings = () => {
inputReadOnly
withReset
handleReset={handleResetScaledHeight}
sliderMarkRightOffset={-7}
/>
<IAISelect
label={t('parameters:infillMethod')}

View File

@ -6,7 +6,7 @@ import SeamStrength from './SeamStrength';
const SeamCorrectionSettings = () => {
return (
<Flex direction="column" gap="1rem">
<Flex direction="column" gap={2}>
<SeamSize />
<SeamBlur />
<SeamStrength />

View File

@ -0,0 +1,36 @@
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setCodeformerFidelity } from 'features/parameters/store/postprocessingSlice';
import { useTranslation } from 'react-i18next';
export default function CodeformerFidelity() {
const isGFPGANAvailable = useAppSelector(
(state: RootState) => state.system.isGFPGANAvailable
);
const codeformerFidelity = useAppSelector(
(state: RootState) => state.postprocessing.codeformerFidelity
);
const { t } = useTranslation();
const dispatch = useAppDispatch();
return (
<IAISlider
isSliderDisabled={!isGFPGANAvailable}
isInputDisabled={!isGFPGANAvailable}
isResetDisabled={!isGFPGANAvailable}
label={t('parameters:codeformerFidelity')}
step={0.05}
min={0}
max={1}
onChange={(v) => dispatch(setCodeformerFidelity(v))}
handleReset={() => dispatch(setCodeformerFidelity(1))}
value={codeformerFidelity}
withReset
withSliderMarks
withInput
/>
);
}

View File

@ -1,99 +1,23 @@
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 { FACETOOL_TYPES } from 'app/constants';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISelect from 'common/components/IAISelect';
import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
import { isEqual } from 'lodash';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
const optionsSelector = createSelector(
[postprocessingSelector, systemSelector],
(
{ facetoolStrength, facetoolType, codeformerFidelity },
{ isGFPGANAvailable }
) => {
return {
facetoolStrength,
facetoolType,
codeformerFidelity,
isGFPGANAvailable,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
import { useAppSelector } from 'app/storeHooks';
import type { RootState } from 'app/store';
import FaceRestoreType from './FaceRestoreType';
import FaceRestoreStrength from './FaceRestoreStrength';
import CodeformerFidelity from './CodeformerFidelity';
/**
* 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();
const facetoolType = useAppSelector(
(state: RootState) => state.postprocessing.facetoolType
);
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 direction="column" gap={2} minWidth="20rem">
<FaceRestoreType />
<FaceRestoreStrength />
{facetoolType === 'codeformer' && <CodeformerFidelity />}
</Flex>
);
};

View File

@ -0,0 +1,36 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setFacetoolStrength } from 'features/parameters/store/postprocessingSlice';
import { useTranslation } from 'react-i18next';
export default function FaceRestoreStrength() {
const isGFPGANAvailable = useAppSelector(
(state: RootState) => state.system.isGFPGANAvailable
);
const facetoolStrength = useAppSelector(
(state: RootState) => state.postprocessing.facetoolStrength
);
const { t } = useTranslation();
const dispatch = useAppDispatch();
return (
<IAISlider
isSliderDisabled={!isGFPGANAvailable}
isInputDisabled={!isGFPGANAvailable}
isResetDisabled={!isGFPGANAvailable}
label={t('parameters:strength')}
step={0.05}
min={0}
max={1}
onChange={(v) => dispatch(setFacetoolStrength(v))}
handleReset={() => dispatch(setFacetoolStrength(0.75))}
value={facetoolStrength}
withReset
withSliderMarks
withInput
/>
);
}

View File

@ -0,0 +1,31 @@
import { FACETOOL_TYPES } from 'app/constants';
import { type RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISelect from 'common/components/IAISelect';
import {
type FacetoolType,
setFacetoolType,
} from 'features/parameters/store/postprocessingSlice';
import { type ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
export default function FaceRestoreType() {
const facetoolType = useAppSelector(
(state: RootState) => state.postprocessing.facetoolType
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleChangeFacetoolType = (e: ChangeEvent<HTMLSelectElement>) =>
dispatch(setFacetoolType(e.target.value as FacetoolType));
return (
<IAISelect
label={t('parameters:type')}
validValues={FACETOOL_TYPES.concat()}
value={facetoolType}
onChange={handleChangeFacetoolType}
/>
);
}

View File

@ -4,6 +4,7 @@ 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 SubItemHook from 'common/components/SubItemHook';
import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors';
import {
setHiresFix,
@ -39,23 +40,27 @@ const HiresStrength = () => {
};
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}
/>
<Flex>
<SubItemHook active={hiresFix} />
<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}
sliderMarkRightOffset={-7}
/>
</Flex>
);
};
@ -75,7 +80,7 @@ const HiresSettings = () => {
dispatch(setHiresFix(e.target.checked));
return (
<Flex gap={2} direction="column">
<Flex rowGap="0.8rem" direction={'column'}>
<IAISwitch
label={t('parameters:hiresOptim')}
fontSize="md"

View File

@ -1,6 +1,6 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISlider from 'common/components/IAISlider';
import { setPerlin } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
@ -9,17 +9,18 @@ export default function Perlin() {
const perlin = useAppSelector((state: RootState) => state.generation.perlin);
const { t } = useTranslation();
const handleChangePerlin = (v: number) => dispatch(setPerlin(v));
return (
<IAINumberInput
<IAISlider
label={t('parameters:perlinNoise')}
min={0}
max={1}
step={0.05}
onChange={handleChangePerlin}
onChange={(v) => dispatch(setPerlin(v))}
handleReset={() => dispatch(setPerlin(0))}
value={perlin}
isInteger={false}
withInput
withReset
withSliderMarks
/>
);
}

View File

@ -1,6 +1,6 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISlider from 'common/components/IAISlider';
import { setThreshold } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
@ -11,17 +11,19 @@ export default function Threshold() {
);
const { t } = useTranslation();
const handleChangeThreshold = (v: number) => dispatch(setThreshold(v));
return (
<IAINumberInput
<IAISlider
label={t('parameters:noiseThreshold')}
min={0}
max={1000}
step={0.1}
onChange={handleChangeThreshold}
max={1}
step={0.005}
onChange={(v) => dispatch(setThreshold(v))}
handleReset={() => dispatch(setThreshold(0))}
value={threshold}
isInteger={false}
withInput
withReset
withSliderMarks
inputWidth="6rem"
/>
);
}

View File

@ -0,0 +1,38 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setUpscalingDenoising } from 'features/parameters/store/postprocessingSlice';
import { useTranslation } from 'react-i18next';
export default function UpscaleDenoisingStrength() {
const isESRGANAvailable = useAppSelector(
(state: RootState) => state.system.isESRGANAvailable
);
const upscalingDenoising = useAppSelector(
(state: RootState) => state.postprocessing.upscalingDenoising
);
const { t } = useTranslation();
const dispatch = useAppDispatch();
return (
<IAISlider
label={t('parameters:denoisingStrength')}
value={upscalingDenoising}
min={0}
max={1}
step={0.01}
onChange={(v) => {
dispatch(setUpscalingDenoising(v));
}}
handleReset={() => dispatch(setUpscalingDenoising(0.75))}
withSliderMarks
withInput
withReset
isSliderDisabled={!isESRGANAvailable}
isInputDisabled={!isESRGANAvailable}
isResetDisabled={!isESRGANAvailable}
/>
);
}

View File

@ -0,0 +1,36 @@
import { UPSCALING_LEVELS } from 'app/constants';
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISelect from 'common/components/IAISelect';
import {
setUpscalingLevel,
type UpscalingLevel,
} from 'features/parameters/store/postprocessingSlice';
import type { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
export default function UpscaleScale() {
const isESRGANAvailable = useAppSelector(
(state: RootState) => state.system.isESRGANAvailable
);
const upscalingLevel = useAppSelector(
(state: RootState) => state.postprocessing.upscalingLevel
);
const { t } = useTranslation();
const dispatch = useAppDispatch();
const handleChangeLevel = (e: ChangeEvent<HTMLSelectElement>) =>
dispatch(setUpscalingLevel(Number(e.target.value) as UpscalingLevel));
return (
<IAISelect
isDisabled={!isESRGANAvailable}
label={t('parameters:scale')}
value={upscalingLevel}
onChange={handleChangeLevel}
validValues={UPSCALING_LEVELS}
/>
);
}

View File

@ -1,104 +1,17 @@
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import {
setUpscalingDenoising,
setUpscalingLevel,
setUpscalingStrength,
UpscalingLevel,
} from 'features/parameters/store/postprocessingSlice';
import { createSelector } from '@reduxjs/toolkit';
import { UPSCALING_LEVELS } from 'app/constants';
import IAISelect from 'common/components/IAISelect';
import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
import { isEqual } from 'lodash';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
import IAISlider from 'common/components/IAISlider';
import { Flex } from '@chakra-ui/react';
const parametersSelector = createSelector(
[postprocessingSelector, systemSelector],
(
{ upscalingLevel, upscalingStrength, upscalingDenoising },
{ isESRGANAvailable }
) => {
return {
upscalingLevel,
upscalingDenoising,
upscalingStrength,
isESRGANAvailable,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
import UpscaleDenoisingStrength from './UpscaleDenoisingStrength';
import UpscaleStrength from './UpscaleStrength';
import UpscaleScale from './UpscaleScale';
/**
* Displays upscaling/ESRGAN options (level and strength).
*/
const UpscaleSettings = () => {
const dispatch = useAppDispatch();
const {
upscalingLevel,
upscalingStrength,
upscalingDenoising,
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 (
<Flex flexDir="column" rowGap="1rem" minWidth="20rem">
<IAISelect
isDisabled={!isESRGANAvailable}
label={t('parameters:scale')}
value={upscalingLevel}
onChange={handleChangeLevel}
validValues={UPSCALING_LEVELS}
/>
<IAISlider
label={t('parameters:denoisingStrength')}
value={upscalingDenoising}
min={0}
max={1}
step={0.01}
onChange={(v) => {
dispatch(setUpscalingDenoising(v));
}}
handleReset={() => dispatch(setUpscalingDenoising(0.75))}
withSliderMarks
withInput
withReset
isSliderDisabled={!isESRGANAvailable}
isInputDisabled={!isESRGANAvailable}
isResetDisabled={!isESRGANAvailable}
/>
<IAISlider
label={`${t('parameters:upscale')} ${t('parameters:strength')}`}
value={upscalingStrength}
min={0}
max={1}
step={0.05}
onChange={handleChangeStrength}
handleReset={() => dispatch(setUpscalingStrength(0.75))}
withSliderMarks
withInput
withReset
isSliderDisabled={!isESRGANAvailable}
isInputDisabled={!isESRGANAvailable}
isResetDisabled={!isESRGANAvailable}
/>
<Flex flexDir="column" rowGap={2} minWidth="20rem">
<UpscaleScale />
<UpscaleDenoisingStrength />
<UpscaleStrength />
</Flex>
);
};

View File

@ -0,0 +1,35 @@
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setUpscalingStrength } from 'features/parameters/store/postprocessingSlice';
import { useTranslation } from 'react-i18next';
export default function UpscaleStrength() {
const isESRGANAvailable = useAppSelector(
(state: RootState) => state.system.isESRGANAvailable
);
const upscalingStrength = useAppSelector(
(state: RootState) => state.postprocessing.upscalingStrength
);
const { t } = useTranslation();
const dispatch = useAppDispatch();
return (
<IAISlider
label={`${t('parameters:upscale')} ${t('parameters:strength')}`}
value={upscalingStrength}
min={0}
max={1}
step={0.05}
onChange={(v) => dispatch(setUpscalingStrength(v))}
handleReset={() => dispatch(setUpscalingStrength(0.75))}
withSliderMarks
withInput
withReset
isSliderDisabled={!isESRGANAvailable}
isInputDisabled={!isESRGANAvailable}
isResetDisabled={!isESRGANAvailable}
/>
);
}

View File

@ -1,6 +1,6 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISlider from 'common/components/IAISlider';
import { setVariationAmount } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
@ -16,19 +16,22 @@ export default function VariationAmount() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const handleChangevariationAmount = (v: number) =>
dispatch(setVariationAmount(v));
return (
<IAINumberInput
<IAISlider
label={t('parameters:variationAmount')}
value={variationAmount}
step={0.01}
min={0}
max={1}
isDisabled={!shouldGenerateVariations}
onChange={handleChangevariationAmount}
isInteger={false}
isSliderDisabled={!shouldGenerateVariations}
isInputDisabled={!shouldGenerateVariations}
isResetDisabled={!shouldGenerateVariations}
onChange={(v) => dispatch(setVariationAmount(v))}
handleReset={() => dispatch(setVariationAmount(0.1))}
withInput
withReset
withSliderMarks
/>
);
}

View File

@ -1,6 +1,7 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISlider from 'common/components/IAISlider';
import { setCfgScale } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
@ -9,11 +10,29 @@ export default function MainCFGScale() {
const cfgScale = useAppSelector(
(state: RootState) => state.generation.cfgScale
);
const shouldUseSliders = useAppSelector(
(state: RootState) => state.ui.shouldUseSliders
);
const { t } = useTranslation();
const handleChangeCfgScale = (v: number) => dispatch(setCfgScale(v));
return (
return shouldUseSliders ? (
<IAISlider
label={t('parameters:cfgScale')}
step={0.5}
min={1.01}
max={30}
onChange={handleChangeCfgScale}
handleReset={() => dispatch(setCfgScale(7.5))}
value={cfgScale}
sliderMarkRightOffset={-5}
sliderNumberInputProps={{ max: 200 }}
withInput
withReset
withSliderMarks
/>
) : (
<IAINumberInput
label={t('parameters:cfgScale')}
step={0.5}

View File

@ -2,29 +2,50 @@ import { HEIGHTS } from 'app/constants';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISelect from 'common/components/IAISelect';
import IAISlider from 'common/components/IAISlider';
import { setHeight } from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
export default function MainHeight() {
const height = useAppSelector((state: RootState) => state.generation.height);
const shouldUseSliders = useAppSelector(
(state: RootState) => state.ui.shouldUseSliders
);
const activeTabName = useAppSelector(activeTabNameSelector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleChangeHeight = (e: ChangeEvent<HTMLSelectElement>) =>
dispatch(setHeight(Number(e.target.value)));
return (
return shouldUseSliders ? (
<IAISlider
isSliderDisabled={activeTabName === 'unifiedCanvas'}
isInputDisabled={activeTabName === 'unifiedCanvas'}
isResetDisabled={activeTabName === 'unifiedCanvas'}
label={t('parameters:height')}
value={height}
min={64}
step={64}
max={2048}
onChange={(v) => dispatch(setHeight(v))}
handleReset={() => dispatch(setHeight(512))}
withInput
withReset
withSliderMarks
sliderMarkRightOffset={-8}
inputWidth="6.2rem"
sliderNumberInputProps={{ max: 15360 }}
/>
) : (
<IAISelect
isDisabled={activeTabName === 'unifiedCanvas'}
label={t('parameters:height')}
value={height}
flexGrow={1}
onChange={handleChangeHeight}
onChange={(e) => dispatch(setHeight(Number(e.target.value)))}
validValues={HEIGHTS}
styleClass="main-settings-block"
width="5.5rem"
/>
);
}

View File

@ -1,39 +1,41 @@
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import {
GenerationState,
setIterations,
} from 'features/parameters/store/generationSlice';
import { isEqual } from 'lodash';
import IAISlider from 'common/components/IAISlider';
import { setIterations } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
const mainIterationsSelector = createSelector(
[(state: RootState) => state.generation],
(parameters: GenerationState) => {
const { iterations } = parameters;
return {
iterations,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
export default function MainIterations() {
const iterations = useAppSelector(
(state: RootState) => state.generation.iterations
);
const shouldUseSliders = useAppSelector(
(state: RootState) => state.ui.shouldUseSliders
);
const dispatch = useAppDispatch();
const { iterations } = useAppSelector(mainIterationsSelector);
const { t } = useTranslation();
const handleChangeIterations = (v: number) => dispatch(setIterations(v));
return (
return shouldUseSliders ? (
<IAISlider
label={t('parameters:images')}
step={1}
min={1}
max={16}
onChange={handleChangeIterations}
handleReset={() => dispatch(setIterations(1))}
value={iterations}
withInput
withReset
withSliderMarks
sliderMarkRightOffset={-5}
sliderNumberInputProps={{ max: 9999 }}
/>
) : (
<IAINumberInput
label={t('parameters:images')}
step={1}

View File

@ -1,3 +1,8 @@
import { Flex } from '@chakra-ui/react';
import { type RootState } from 'app/store';
import { useAppSelector } from 'app/storeHooks';
import { useTranslation } from 'react-i18next';
import ParametersAccordion from '../ParametersAccordion';
import MainCFGScale from './MainCFGScale';
import MainHeight from './MainHeight';
import MainIterations from './MainIterations';
@ -8,20 +13,40 @@ import MainWidth from './MainWidth';
export const inputWidth = 'auto';
export default function MainSettings() {
return (
<div className="main-settings">
<div className="main-settings-list">
<div className="main-settings-row">
const { t } = useTranslation();
const shouldUseSliders = useAppSelector(
(state: RootState) => state.ui.shouldUseSliders
);
const accordionItems = {
main: {
header: `${t('parameters:general')}`,
feature: undefined,
content: shouldUseSliders ? (
<Flex flexDir="column" rowGap={2}>
<MainIterations />
<MainSteps />
<MainCFGScale />
</div>
<div className="main-settings-row">
<MainWidth />
<MainHeight />
<MainSampler />
</div>
</div>
</div>
);
</Flex>
) : (
<Flex flexDirection="column" rowGap={2}>
<Flex gap={2}>
<MainIterations />
<MainSteps />
<MainCFGScale />
</Flex>
<Flex>
<MainWidth />
<MainHeight />
<MainSampler />
</Flex>
</Flex>
),
},
};
return <ParametersAccordion accordionInfo={accordionItems} />;
}

View File

@ -27,6 +27,7 @@ export default function MainSampler() {
activeModel.format === 'diffusers' ? DIFFUSERS_SAMPLERS : SAMPLERS
}
styleClass="main-settings-block"
minWidth="9rem"
/>
);
}

View File

@ -1,17 +1,36 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISlider from 'common/components/IAISlider';
import { setSteps } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function MainSteps() {
const dispatch = useAppDispatch();
const steps = useAppSelector((state: RootState) => state.generation.steps);
const shouldUseSliders = useAppSelector(
(state: RootState) => state.ui.shouldUseSliders
);
const { t } = useTranslation();
const handleChangeSteps = (v: number) => dispatch(setSteps(v));
return (
return shouldUseSliders ? (
<IAISlider
label={t('parameters:steps')}
min={1}
step={1}
onChange={handleChangeSteps}
handleReset={() => dispatch(setSteps(20))}
value={steps}
withInput
withReset
withSliderMarks
sliderMarkRightOffset={-6}
sliderNumberInputProps={{ max: 9999 }}
/>
) : (
<IAINumberInput
label={t('parameters:steps')}
min={1}

View File

@ -2,30 +2,51 @@ import { WIDTHS } from 'app/constants';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISelect from 'common/components/IAISelect';
import IAISlider from 'common/components/IAISlider';
import { setWidth } from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
export default function MainWidth() {
const width = useAppSelector((state: RootState) => state.generation.width);
const shouldUseSliders = useAppSelector(
(state: RootState) => state.ui.shouldUseSliders
);
const activeTabName = useAppSelector(activeTabNameSelector);
const { t } = useTranslation();
const dispatch = useAppDispatch();
const handleChangeWidth = (e: ChangeEvent<HTMLSelectElement>) =>
dispatch(setWidth(Number(e.target.value)));
return (
return shouldUseSliders ? (
<IAISlider
isSliderDisabled={activeTabName === 'unifiedCanvas'}
isInputDisabled={activeTabName === 'unifiedCanvas'}
isResetDisabled={activeTabName === 'unifiedCanvas'}
label={t('parameters:width')}
value={width}
min={64}
step={64}
max={2048}
onChange={(v) => dispatch(setWidth(v))}
handleReset={() => dispatch(setWidth(512))}
withInput
withReset
withSliderMarks
sliderMarkRightOffset={-8}
inputWidth="6.2rem"
inputReadOnly
sliderNumberInputProps={{ max: 15360 }}
/>
) : (
<IAISelect
isDisabled={activeTabName === 'unifiedCanvas'}
label={t('parameters:width')}
value={width}
flexGrow={1}
onChange={handleChangeWidth}
onChange={(e) => dispatch(setWidth(Number(e.target.value)))}
validValues={WIDTHS}
styleClass="main-settings-block"
width="5.5rem"
/>
);
}

View File

@ -22,7 +22,7 @@ export interface PostprocessingState {
const initialPostprocessingState: PostprocessingState = {
codeformerFidelity: 0.75,
facetoolStrength: 0.8,
facetoolStrength: 0.75,
facetoolType: 'gfpgan',
hiresFix: false,
hiresStrength: 0.75,

View File

@ -14,7 +14,7 @@ import {
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { IN_PROGRESS_IMAGE_TYPES } from 'app/constants';
import { RootState } from 'app/store';
import { type RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISelect from 'common/components/IAISelect';
@ -27,9 +27,14 @@ import {
setShouldConfirmOnDelete,
setShouldDisplayGuides,
setShouldDisplayInProgressType,
type SystemState,
} from 'features/system/store/systemSlice';
import { uiSelector } from 'features/ui/store/uiSelectors';
import { setShouldUseCanvasBetaLayout } from 'features/ui/store/uiSlice';
import {
setShouldUseCanvasBetaLayout,
setShouldUseSliders,
} from 'features/ui/store/uiSlice';
import { type UIState } from 'features/ui/store/uiTypes';
import { isEqual, map } from 'lodash';
import { persistor } from 'persistor';
import { ChangeEvent, cloneElement, ReactElement } from 'react';
@ -37,7 +42,7 @@ import { useTranslation } from 'react-i18next';
const selector = createSelector(
[systemSelector, uiSelector],
(system, ui) => {
(system: SystemState, ui: UIState) => {
const {
shouldDisplayInProgressType,
shouldConfirmOnDelete,
@ -47,7 +52,7 @@ const selector = createSelector(
enableImageDebugging,
} = system;
const { shouldUseCanvasBetaLayout } = ui;
const { shouldUseCanvasBetaLayout, shouldUseSliders } = ui;
return {
shouldDisplayInProgressType,
@ -57,6 +62,7 @@ const selector = createSelector(
saveIntermediatesInterval,
enableImageDebugging,
shouldUseCanvasBetaLayout,
shouldUseSliders,
};
},
{
@ -100,6 +106,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
saveIntermediatesInterval,
enableImageDebugging,
shouldUseCanvasBetaLayout,
shouldUseSliders,
} = useAppSelector(selector);
/**
@ -191,6 +198,14 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
dispatch(setShouldUseCanvasBetaLayout(e.target.checked))
}
/>
<IAISwitch
styleClass="settings-modal-item"
label={t('settings:useSlidersForAll')}
isChecked={shouldUseSliders}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldUseSliders(e.target.checked))
}
/>
</div>
<div className="settings-modal-items">

View File

@ -0,0 +1,26 @@
import { Flex } from '@chakra-ui/react';
import ImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageFit';
import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength';
import ParametersAccordion from 'features/parameters/components/ParametersAccordion';
import { useTranslation } from 'react-i18next';
export default function ImageToImageOptions() {
const { t } = useTranslation();
const imageToImageAccordionItems = {
imageToImage: {
header: `${t('parameters:imageToImage')}`,
feature: undefined,
content: (
<Flex gap={2} flexDir="column">
<ImageToImageStrength
label={t('parameters:img2imgStrength')}
styleClass="main-settings-block image-to-image-strength-main-option"
/>
<ImageFit />
</Flex>
),
},
};
return <ParametersAccordion accordionInfo={imageToImageAccordionItems} />;
}

View File

@ -2,8 +2,6 @@ import { Flex } from '@chakra-ui/react';
import { Feature } from 'app/features';
import FaceRestoreSettings from 'features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreSettings';
import FaceRestoreToggle from 'features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreToggle';
import ImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageFit';
import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength';
import ImageToImageOutputSettings from 'features/parameters/components/AdvancedParameters/Output/ImageToImageOutputSettings';
import SeedSettings from 'features/parameters/components/AdvancedParameters/Seed/SeedSettings';
import UpscaleSettings from 'features/parameters/components/AdvancedParameters/Upscale/UpscaleSettings';
@ -17,6 +15,7 @@ import NegativePromptInput from 'features/parameters/components/PromptInput/Nega
import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
import InvokeOptionsPanel from 'features/ui/components/InvokeParametersPanel';
import { useTranslation } from 'react-i18next';
import ImageToImageOptions from './ImageToImageOptions';
export default function ImageToImagePanel() {
const { t } = useTranslation();
@ -60,11 +59,7 @@ export default function ImageToImagePanel() {
</Flex>
<ProcessButtons />
<MainSettings />
<ImageToImageStrength
label={t('parameters:img2imgStrength')}
styleClass="main-settings-block image-to-image-strength-main-option"
/>
<ImageFit />
<ImageToImageOptions />
<ParametersAccordion accordionInfo={imageToImageAccordions} />
</InvokeOptionsPanel>
);

View File

@ -32,7 +32,7 @@
.parameters-panel {
display: flex;
flex-direction: column;
row-gap: 1rem;
row-gap: 0.5rem;
height: 100%;
@include HideScrollbar;
background-color: var(--background-color);

View File

@ -20,6 +20,11 @@ export default function UnifiedCanvasPanel() {
const { t } = useTranslation();
const unifiedCanvasAccordions = {
seed: {
header: `${t('parameters:seed')}`,
feature: Feature.SEED,
content: <SeedSettings />,
},
boundingBox: {
header: `${t('parameters:boundingBoxHeader')}`,
feature: Feature.BOUNDING_BOX,
@ -35,11 +40,6 @@ export default function UnifiedCanvasPanel() {
feature: Feature.INFILL_AND_SCALING,
content: <InfillAndScalingSettings />,
},
seed: {
header: `${t('parameters:seed')}`,
feature: Feature.SEED,
content: <SeedSettings />,
},
variations: {
header: `${t('parameters:variations')}`,
feature: Feature.VARIATIONS,
@ -48,6 +48,19 @@ export default function UnifiedCanvasPanel() {
},
};
const unifiedCanvasImg2ImgAccordion = {
unifiedCanvasImg2Img: {
header: `${t('parameters:imageToImage')}`,
feature: undefined,
content: (
<ImageToImageStrength
label={t('parameters:img2imgStrength')}
styleClass="main-settings-block image-to-image-strength-main-option"
/>
),
},
};
return (
<InvokeOptionsPanel>
<Flex flexDir="column" rowGap="0.5rem">
@ -56,10 +69,7 @@ export default function UnifiedCanvasPanel() {
</Flex>
<ProcessButtons />
<MainSettings />
<ImageToImageStrength
label={t('parameters:img2imgStrength')}
styleClass="main-settings-block image-to-image-strength-main-option"
/>
<ParametersAccordion accordionInfo={unifiedCanvasImg2ImgAccordion} />
<ParametersAccordion accordionInfo={unifiedCanvasAccordions} />
</InvokeOptionsPanel>
);

View File

@ -14,6 +14,7 @@ const initialtabsState: UIState = {
shouldShowImageDetails: false,
shouldUseCanvasBetaLayout: false,
shouldShowExistingModelsInSearch: false,
shouldUseSliders: false,
addNewModelUIOption: null,
};
@ -66,6 +67,9 @@ export const uiSlice = createSlice({
) => {
state.shouldShowExistingModelsInSearch = action.payload;
},
setShouldUseSliders: (state, action: PayloadAction<boolean>) => {
state.shouldUseSliders = action.payload;
},
setAddNewModelUIOption: (state, action: PayloadAction<AddNewModelType>) => {
state.addNewModelUIOption = action.payload;
},
@ -83,6 +87,7 @@ export const {
setShouldShowImageDetails,
setShouldUseCanvasBetaLayout,
setShouldShowExistingModelsInSearch,
setShouldUseSliders,
setAddNewModelUIOption,
} = uiSlice.actions;

View File

@ -11,5 +11,6 @@ export interface UIState {
shouldShowImageDetails: boolean;
shouldUseCanvasBetaLayout: boolean;
shouldShowExistingModelsInSearch: boolean;
shouldUseSliders: boolean;
addNewModelUIOption: AddNewModelType;
}

View File

@ -137,4 +137,7 @@
// Scrollbar
--scrollbar-color: var(--accent-color);
--scrollbar-color-hover: var(--accent-color-bright);
// SubHook
--subhook-color: var(--accent-color);
}

View File

@ -135,4 +135,7 @@
// Scrollbar
--scrollbar-color: var(--accent-color);
--scrollbar-color-hover: var(--accent-color-bright);
// SubHook
--subhook-color: var(--accent-color);
}

View File

@ -132,4 +132,7 @@
// Scrollbar
--scrollbar-color: rgb(180, 180, 184);
--scrollbar-color-hover: rgb(150, 150, 154);
// SubHook
--subhook-color: rgb(0, 0, 0);
}

File diff suppressed because one or more lines are too long