(wip) add informational popover base component and sample (#4522)

## What type of PR is this? (check all applicable)

- [ ] Refactor
- [x] Feature
- [ ] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [x] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [ ] Yes
- [ ] No


## Description
Adds a new common component `IAIInformationPopover` that composes JSX to
be rendered within a popover as a tooltip. We were not able to use the
`Tooltip` component provided by chakra because you cannot interact with
elements within those (at least not that I could get working).

This just a sample over positive prompt. We need content from
@hipsterusername and @Millu before we can roll this out.

## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
This commit is contained in:
Kent Keirsey 2023-09-20 13:37:12 -04:00 committed by GitHub
commit 8b8d589033
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 809 additions and 400 deletions

View File

@ -1154,6 +1154,136 @@
"variations": "Try a variation with a value between 0.1 and 1.0 to change the result for a given seed. Interesting variations of the seed are between 0.1 and 0.3." "variations": "Try a variation with a value between 0.1 and 1.0 to change the result for a given seed. Interesting variations of the seed are between 0.1 and 0.3."
} }
}, },
"popovers": {
"clipSkip": {
"heading": "CLIP Skip",
"paragraph": "Choose how many layers of the CLIP model to skip. Certain models are better suited to be used with CLIP Skip."
},
"compositingBlur": {
"heading": "Blur",
"paragraph": "The blur radius of the mask."
},
"compositingBlurMethod": {
"heading": "Blur Method",
"paragraph": "The method of blur applied to the masked area."
},
"compositingCoherencePass": {
"heading": "Coherence Pass",
"paragraph": "Composite the Inpainted/Outpainted images."
},
"compositingCoherenceMode": {
"heading": "Mode",
"paragraph": "The mode of the Coherence Pass."
},
"compositingCoherenceSteps": {
"heading": "Steps",
"paragraph": "Number of steps in the Coherence Pass. Similar to Denoising Steps."
},
"compositingStrength": {
"heading": "Strength",
"paragraph": "Amount of noise added for the Coherence Pass. Similar to Denoising Strength."
},
"compositingMaskAdjustments": {
"heading": "Mask Adjustments",
"paragraph": "Adjust the mask."
},
"controlNetBeginEnd": {
"heading": "Begin / End Step Percentage",
"paragraph": "Which parts of the denoising process will have the ControlNet applied. ControlNets applied at the start of the process guide composition, and ControlNets applied at the end guide details."
},
"controlNetControlMode": {
"heading": "Control Mode",
"paragraph": "Lends more weight to either the prompt or ControlNet."
},
"controlNetResizeMode": {
"heading": "Resize Mode",
"paragraph": "How the ControlNet image will be fit to the image generation Ratio"
},
"controlNetToggle": {
"heading": "Enable ControlNet",
"paragraph": "ControlNets provide guidance to the generation process, helping create images with controlled composition, structure, or style, depending on the model selected."
},
"controlNetWeight": {
"heading": "Weight",
"paragraph": "How strongly the ControlNet will impact the generated image."
},
"dynamicPromptsToggle": {
"heading": "Enable Dynamic Prompts",
"paragraph": "Dynamic prompts allow multiple options within a prompt. Dynamic prompts can be used by: {option1|option2|option3}. Combinations of prompts will be randomly generated until the “Images” number has been reached."
},
"dynamicPromptsCombinatorial": {
"heading": "Combinatorial Generation",
"paragraph": "Generate an image for every possible combination of Dynamic Prompt until the Max Prompts is reached."
},
"infillMethod": {
"heading": "Infill Method",
"paragraph": "Method to infill the selected area."
},
"lora": {
"heading": "LoRA",
"paragraph": "Weight of the LoRA. Higher weight will lead to larger impacts on the final image."
},
"noiseEnable": {
"heading": "Enable Noise Settings",
"paragraph": "Advanced control over noise generation."
},
"noiseUseCPU": {
"heading": "Use CPU Noise",
"paragraph": "Uses the CPU to generate random noise."
},
"paramCFGScale": {
"heading": "CFG Scale",
"paragraph": "Controls how much your prompt influences the generation process."
},
"paramDenoisingStrength": {
"heading": "Denoising Strength",
"paragraph": "How much noise is added to the input image. 0 will result in an identical image, while 1 will result in a completely new image."
},
"paramImages": {
"heading": "Images",
"paragraph": "Number of images that will be generated."
},
"paramModel": {
"heading": "Model",
"paragraph": "Model used for the denoising steps. Different models are trained to specialize in producing different aesthetic results and content."
},
"paramNegativeConditioning": {
"heading": "Negative Prompts",
"paragraph": "This is where you enter your negative prompts."
},
"paramPositiveConditioning": {
"heading": "Positive Prompts",
"paragraph": "This is where you enter your positive prompts."
},
"paramRatio": {
"heading": "Ratio",
"paragraph": "The ratio of the dimensions of the image generated. An image size (in number of pixels) equivalent to 512x512 is recommended for SD1.5 models and a size equivalent to 1024x1024 is recommended for SDXL models."
},
"paramScheduler": {
"heading": "Scheduler",
"paragraph": "Scheduler defines how to iteratively add noise to an image or how to update a sample based on a model's output."
},
"paramSeed": {
"heading": "Seed",
"paragraph": "Controls the starting noise used for generation. Disable “Random Seed” to produce identical results with the same generation settings."
},
"paramSteps": {
"heading": "Steps",
"paragraph": "Number of steps that will be performed in each generation. Higher step counts will typically create better images but will require more generation time."
},
"paramVAE": {
"heading": "VAE",
"paragraph": "Model used for translating AI output into the final image."
},
"paramVAEPrecision": {
"heading": "VAE Precision",
"paragraph": "The precision used during VAE encoding and decoding. Fp16/Half precision is more efficient, at the expense of minor image variations."
},
"scaleBeforeProcessing": {
"heading": "Scale Before Processing",
"paragraph": "Scales the selected area to the size best suited for the model before the image generation process."
}
},
"ui": { "ui": {
"hideProgressImages": "Hide Progress Images", "hideProgressImages": "Hide Progress Images",
"lockRatio": "Lock Ratio", "lockRatio": "Lock Ratio",

View File

@ -0,0 +1,112 @@
import {
Button,
Popover,
PopoverTrigger,
PopoverContent,
PopoverArrow,
PopoverCloseButton,
PopoverHeader,
PopoverBody,
PopoverProps,
Flex,
Text,
Image,
} from '@chakra-ui/react';
import { useAppSelector } from '../../app/store/storeHooks';
import { useTranslation } from 'react-i18next';
interface Props extends PopoverProps {
details: string;
children: JSX.Element;
image?: string;
buttonLabel?: string;
buttonHref?: string;
placement?: PopoverProps['placement'];
}
function IAIInformationalPopover({
details,
image,
buttonLabel,
buttonHref,
children,
placement,
}: Props): JSX.Element {
const shouldDisableInformationalPopovers = useAppSelector(
(state) => state.system.shouldDisableInformationalPopovers
);
const { t } = useTranslation();
const heading = t(`popovers.${details}.heading`);
const paragraph = t(`popovers.${details}.paragraph`);
if (shouldDisableInformationalPopovers) {
return children;
} else {
return (
<Popover
placement={placement || 'top'}
closeOnBlur={false}
trigger="hover"
variant="informational"
>
<PopoverTrigger>
<div>{children}</div>
</PopoverTrigger>
<PopoverContent>
<PopoverArrow />
<PopoverCloseButton />
<PopoverBody>
<Flex
sx={{
gap: 3,
flexDirection: 'column',
width: '100%',
alignItems: 'center',
}}
>
{image && (
<Image
sx={{
objectFit: 'contain',
maxW: '60%',
maxH: '60%',
backgroundColor: 'white',
}}
src={image}
alt="Optional Image"
/>
)}
<Flex
sx={{
gap: 3,
flexDirection: 'column',
width: '100%',
p: 3,
pt: heading ? 0 : 3,
}}
>
{heading && <PopoverHeader>{heading}</PopoverHeader>}
<Text sx={{ px: 3 }}>{paragraph}</Text>
{buttonLabel && (
<Flex sx={{ px: 3 }} justifyContent="flex-end">
<Button
onClick={() => window.open(buttonHref)}
size="sm"
variant="invokeAIOutline"
>
{buttonLabel}
</Button>
</Flex>
)}
</Flex>
</Flex>
</PopoverBody>
</PopoverContent>
</Popover>
);
}
}
export default IAIInformationalPopover;

View File

@ -10,6 +10,7 @@ import {
Tooltip, Tooltip,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import { import {
ControlNetConfig, ControlNetConfig,
controlNetBeginStepPctChanged, controlNetBeginStepPctChanged,
@ -49,58 +50,60 @@ const ParamControlNetBeginEnd = (props: Props) => {
); );
return ( return (
<FormControl isDisabled={!isEnabled}> <IAIInformationalPopover details="controlNetBeginEnd">
<FormLabel>{t('controlnet.beginEndStepPercent')}</FormLabel> <FormControl isDisabled={!isEnabled}>
<HStack w="100%" gap={2} alignItems="center"> <FormLabel>{t('controlnet.beginEndStepPercent')}</FormLabel>
<RangeSlider <HStack w="100%" gap={2} alignItems="center">
aria-label={['Begin Step %', 'End Step %!']} <RangeSlider
value={[beginStepPct, endStepPct]} aria-label={['Begin Step %', 'End Step %!']}
onChange={handleStepPctChanged} value={[beginStepPct, endStepPct]}
min={0} onChange={handleStepPctChanged}
max={1} min={0}
step={0.01} max={1}
minStepsBetweenThumbs={5} step={0.01}
isDisabled={!isEnabled} minStepsBetweenThumbs={5}
> isDisabled={!isEnabled}
<RangeSliderTrack>
<RangeSliderFilledTrack />
</RangeSliderTrack>
<Tooltip label={formatPct(beginStepPct)} placement="top" hasArrow>
<RangeSliderThumb index={0} />
</Tooltip>
<Tooltip label={formatPct(endStepPct)} placement="top" hasArrow>
<RangeSliderThumb index={1} />
</Tooltip>
<RangeSliderMark
value={0}
sx={{
insetInlineStart: '0 !important',
insetInlineEnd: 'unset !important',
}}
> >
0% <RangeSliderTrack>
</RangeSliderMark> <RangeSliderFilledTrack />
<RangeSliderMark </RangeSliderTrack>
value={0.5} <Tooltip label={formatPct(beginStepPct)} placement="top" hasArrow>
sx={{ <RangeSliderThumb index={0} />
insetInlineStart: '50% !important', </Tooltip>
transform: 'translateX(-50%)', <Tooltip label={formatPct(endStepPct)} placement="top" hasArrow>
}} <RangeSliderThumb index={1} />
> </Tooltip>
50% <RangeSliderMark
</RangeSliderMark> value={0}
<RangeSliderMark sx={{
value={1} insetInlineStart: '0 !important',
sx={{ insetInlineEnd: 'unset !important',
insetInlineStart: 'unset !important', }}
insetInlineEnd: '0 !important', >
}} 0%
> </RangeSliderMark>
100% <RangeSliderMark
</RangeSliderMark> value={0.5}
</RangeSlider> sx={{
</HStack> insetInlineStart: '50% !important',
</FormControl> transform: 'translateX(-50%)',
}}
>
50%
</RangeSliderMark>
<RangeSliderMark
value={1}
sx={{
insetInlineStart: 'unset !important',
insetInlineEnd: '0 !important',
}}
>
100%
</RangeSliderMark>
</RangeSlider>
</HStack>
</FormControl>
</IAIInformationalPopover>
); );
}; };

View File

@ -1,4 +1,5 @@
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIMantineSelect from 'common/components/IAIMantineSelect'; import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { import {
ControlModes, ControlModes,
@ -34,12 +35,14 @@ export default function ParamControlNetControlMode(
); );
return ( return (
<IAIMantineSelect <IAIInformationalPopover details="controlNetControlMode">
disabled={!isEnabled} <IAIMantineSelect
label={t('controlnet.controlMode')} disabled={!isEnabled}
data={CONTROL_MODE_DATA} label={t('controlnet.controlMode')}
value={String(controlMode)} data={CONTROL_MODE_DATA}
onChange={handleControlModeChange} value={String(controlMode)}
/> onChange={handleControlModeChange}
/>
</IAIInformationalPopover>
); );
} }

View File

@ -2,6 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { isControlNetEnabledToggled } from 'features/controlNet/store/controlNetSlice'; import { isControlNetEnabledToggled } from 'features/controlNet/store/controlNetSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
@ -25,14 +26,16 @@ const ParamControlNetFeatureToggle = () => {
}, [dispatch]); }, [dispatch]);
return ( return (
<IAISwitch <IAIInformationalPopover details="controlNetToggle">
label="Enable ControlNet" <IAISwitch
isChecked={isEnabled} label="Enable ControlNet"
onChange={handleChange} isChecked={isEnabled}
formControlProps={{ onChange={handleChange}
width: '100%', formControlProps={{
}} width: '100%',
/> }}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -1,4 +1,5 @@
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIMantineSelect from 'common/components/IAIMantineSelect'; import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { import {
ControlNetConfig, ControlNetConfig,
@ -33,12 +34,14 @@ export default function ParamControlNetResizeMode(
); );
return ( return (
<IAIMantineSelect <IAIInformationalPopover details="controlNetResizeMode">
disabled={!isEnabled} <IAIMantineSelect
label={t('controlnet.resizeMode')} disabled={!isEnabled}
data={RESIZE_MODE_DATA} label={t('controlnet.resizeMode')}
value={String(resizeMode)} data={RESIZE_MODE_DATA}
onChange={handleResizeModeChange} value={String(resizeMode)}
/> onChange={handleResizeModeChange}
/>
</IAIInformationalPopover>
); );
} }

View File

@ -1,4 +1,5 @@
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAISlider from 'common/components/IAISlider'; import IAISlider from 'common/components/IAISlider';
import { import {
ControlNetConfig, ControlNetConfig,
@ -23,17 +24,19 @@ const ParamControlNetWeight = (props: ParamControlNetWeightProps) => {
); );
return ( return (
<IAISlider <IAIInformationalPopover details="controlNetWeight">
isDisabled={!isEnabled} <IAISlider
label={t('controlnet.weight')} isDisabled={!isEnabled}
value={weight} label={t('controlnet.weight')}
onChange={handleWeightChanged} value={weight}
min={0} onChange={handleWeightChanged}
max={2} min={0}
step={0.01} max={2}
withSliderMarks step={0.01}
sliderMarks={[0, 1, 2]} withSliderMarks
/> sliderMarks={[0, 1, 2]}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -6,6 +6,7 @@ import IAISwitch from 'common/components/IAISwitch';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { combinatorialToggled } from '../store/dynamicPromptsSlice'; import { combinatorialToggled } from '../store/dynamicPromptsSlice';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -27,11 +28,13 @@ const ParamDynamicPromptsCombinatorial = () => {
}, [dispatch]); }, [dispatch]);
return ( return (
<IAISwitch <IAIInformationalPopover details="dynamicPromptsCombinatorial">
label={t('dynamicPrompts.combinatorial')} <IAISwitch
isChecked={combinatorial} label={t('dynamicPrompts.combinatorial')}
onChange={handleChange} isChecked={combinatorial}
/> onChange={handleChange}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -10,6 +10,7 @@ import {
loraWeightChanged, loraWeightChanged,
loraWeightReset, loraWeightReset,
} from '../store/loraSlice'; } from '../store/loraSlice';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
type Props = { type Props = {
lora: LoRA; lora: LoRA;
@ -35,30 +36,32 @@ const ParamLora = (props: Props) => {
}, [dispatch, lora.id]); }, [dispatch, lora.id]);
return ( return (
<Flex sx={{ gap: 2.5, alignItems: 'flex-end' }}> <IAIInformationalPopover details="lora">
<IAISlider <Flex sx={{ gap: 2.5, alignItems: 'flex-end' }}>
label={lora.model_name} <IAISlider
value={lora.weight} label={lora.model_name}
onChange={handleChange} value={lora.weight}
min={-1} onChange={handleChange}
max={2} min={-1}
step={0.01} max={2}
withInput step={0.01}
withReset withInput
handleReset={handleReset} withReset
withSliderMarks handleReset={handleReset}
sliderMarks={[-1, 0, 1, 2]} withSliderMarks
sliderNumberInputProps={{ min: -50, max: 50 }} sliderMarks={[-1, 0, 1, 2]}
/> sliderNumberInputProps={{ min: -50, max: 50 }}
<IAIIconButton />
size="sm" <IAIIconButton
onClick={handleRemoveLora} size="sm"
tooltip="Remove LoRA" onClick={handleRemoveLora}
aria-label="Remove LoRA" tooltip="Remove LoRA"
icon={<FaTrash />} aria-label="Remove LoRA"
colorScheme="error" icon={<FaTrash />}
/> colorScheme="error"
</Flex> />
</Flex>
</IAIInformationalPopover>
); );
}; };

View File

@ -1,5 +1,6 @@
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAISlider from 'common/components/IAISlider'; import IAISlider from 'common/components/IAISlider';
import { setClipSkip } from 'features/parameters/store/generationSlice'; import { setClipSkip } from 'features/parameters/store/generationSlice';
import { clipSkipMap } from 'features/parameters/types/constants'; import { clipSkipMap } from 'features/parameters/types/constants';
@ -42,19 +43,21 @@ export default function ParamClipSkip() {
}, [model]); }, [model]);
return ( return (
<IAISlider <IAIInformationalPopover details="clipSkip">
label={t('parameters.clipSkip')} <IAISlider
aria-label={t('parameters.clipSkip')} label={t('parameters.clipSkip')}
min={0} aria-label={t('parameters.clipSkip')}
max={max} min={0}
step={1} max={max}
value={clipSkip} step={1}
onChange={handleClipSkipChange} value={clipSkip}
withSliderMarks onChange={handleClipSkipChange}
sliderMarks={sliderMarks} withSliderMarks
withInput sliderMarks={sliderMarks}
withReset withInput
handleReset={handleClipSkipReset} withReset
/> handleReset={handleClipSkipReset}
/>
</IAIInformationalPopover>
); );
} }

View File

@ -18,6 +18,7 @@ import ParamAspectRatio, {
} from '../../Core/ParamAspectRatio'; } from '../../Core/ParamAspectRatio';
import ParamBoundingBoxHeight from './ParamBoundingBoxHeight'; import ParamBoundingBoxHeight from './ParamBoundingBoxHeight';
import ParamBoundingBoxWidth from './ParamBoundingBoxWidth'; import ParamBoundingBoxWidth from './ParamBoundingBoxWidth';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
const sizeOptsSelector = createSelector( const sizeOptsSelector = createSelector(
[generationSelector, canvasSelector], [generationSelector, canvasSelector],
@ -93,18 +94,20 @@ export default function ParamBoundingBoxSize() {
}} }}
> >
<Flex alignItems="center" gap={2}> <Flex alignItems="center" gap={2}>
<Text <IAIInformationalPopover details="paramRatio">
sx={{ <Text
fontSize: 'sm', sx={{
width: 'full', fontSize: 'sm',
color: 'base.700', width: 'full',
_dark: { color: 'base.700',
color: 'base.300', _dark: {
}, color: 'base.300',
}} },
> }}
{t('parameters.aspectRatio')} >
</Text> {t('parameters.aspectRatio')}
</Text>
</IAIInformationalPopover>
<Spacer /> <Spacer />
<ParamAspectRatio /> <ParamAspectRatio />
<IAIIconButton <IAIIconButton

View File

@ -1,5 +1,6 @@
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import { IAISelectDataType } from 'common/components/IAIMantineSearchableSelect'; import { IAISelectDataType } from 'common/components/IAIMantineSearchableSelect';
import IAIMantineSelect from 'common/components/IAIMantineSelect'; import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { setCanvasCoherenceMode } from 'features/parameters/store/generationSlice'; import { setCanvasCoherenceMode } from 'features/parameters/store/generationSlice';
@ -30,12 +31,14 @@ const ParamCanvasCoherenceMode = () => {
}; };
return ( return (
<IAIMantineSelect <IAIInformationalPopover details="compositingCoherenceMode">
label={t('parameters.coherenceMode')} <IAIMantineSelect
data={coherenceModeSelectData} label={t('parameters.coherenceMode')}
value={canvasCoherenceMode} data={coherenceModeSelectData}
onChange={handleCoherenceModeChange} value={canvasCoherenceMode}
/> onChange={handleCoherenceModeChange}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -1,5 +1,6 @@
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAISlider from 'common/components/IAISlider'; import IAISlider from 'common/components/IAISlider';
import { setCanvasCoherenceSteps } from 'features/parameters/store/generationSlice'; import { setCanvasCoherenceSteps } from 'features/parameters/store/generationSlice';
import { memo } from 'react'; import { memo } from 'react';
@ -13,23 +14,25 @@ const ParamCanvasCoherenceSteps = () => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<IAISlider <IAIInformationalPopover details="compositingCoherenceSteps">
label={t('parameters.coherenceSteps')} <IAISlider
min={1} label={t('parameters.coherenceSteps')}
max={100} min={1}
step={1} max={100}
sliderNumberInputProps={{ max: 999 }} step={1}
value={canvasCoherenceSteps} sliderNumberInputProps={{ max: 999 }}
onChange={(v) => { value={canvasCoherenceSteps}
dispatch(setCanvasCoherenceSteps(v)); onChange={(v) => {
}} dispatch(setCanvasCoherenceSteps(v));
withInput }}
withSliderMarks withInput
withReset withSliderMarks
handleReset={() => { withReset
dispatch(setCanvasCoherenceSteps(20)); handleReset={() => {
}} dispatch(setCanvasCoherenceSteps(20));
/> }}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -1,5 +1,6 @@
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAISlider from 'common/components/IAISlider'; import IAISlider from 'common/components/IAISlider';
import { setCanvasCoherenceStrength } from 'features/parameters/store/generationSlice'; import { setCanvasCoherenceStrength } from 'features/parameters/store/generationSlice';
import { memo } from 'react'; import { memo } from 'react';
@ -13,23 +14,25 @@ const ParamCanvasCoherenceStrength = () => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<IAISlider <IAIInformationalPopover details="compositingStrength">
label={t('parameters.coherenceStrength')} <IAISlider
min={0} label={t('parameters.coherenceStrength')}
max={1} min={0}
step={0.01} max={1}
sliderNumberInputProps={{ max: 999 }} step={0.01}
value={canvasCoherenceStrength} sliderNumberInputProps={{ max: 999 }}
onChange={(v) => { value={canvasCoherenceStrength}
dispatch(setCanvasCoherenceStrength(v)); onChange={(v) => {
}} dispatch(setCanvasCoherenceStrength(v));
withInput }}
withSliderMarks withInput
withReset withSliderMarks
handleReset={() => { withReset
dispatch(setCanvasCoherenceStrength(0.3)); handleReset={() => {
}} dispatch(setCanvasCoherenceStrength(0.3));
/> }}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -1,5 +1,6 @@
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAISlider from 'common/components/IAISlider'; import IAISlider from 'common/components/IAISlider';
import { setMaskBlur } from 'features/parameters/store/generationSlice'; import { setMaskBlur } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -12,21 +13,23 @@ export default function ParamMaskBlur() {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<IAISlider <IAIInformationalPopover details="compositingBlur">
label={t('parameters.maskBlur')} <IAISlider
min={0} label={t('parameters.maskBlur')}
max={64} min={0}
sliderNumberInputProps={{ max: 512 }} max={64}
value={maskBlur} sliderNumberInputProps={{ max: 512 }}
onChange={(v) => { value={maskBlur}
dispatch(setMaskBlur(v)); onChange={(v) => {
}} dispatch(setMaskBlur(v));
withInput }}
withSliderMarks withInput
withReset withSliderMarks
handleReset={() => { withReset
dispatch(setMaskBlur(16)); handleReset={() => {
}} dispatch(setMaskBlur(16));
/> }}
/>
</IAIInformationalPopover>
); );
} }

View File

@ -2,6 +2,7 @@ import { SelectItem } from '@mantine/core';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIMantineSelect from 'common/components/IAIMantineSelect'; import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { setMaskBlurMethod } from 'features/parameters/store/generationSlice'; import { setMaskBlurMethod } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -28,11 +29,13 @@ export default function ParamMaskBlurMethod() {
}; };
return ( return (
<IAIMantineSelect <IAIInformationalPopover details="compositingBlurMethod">
value={maskBlurMethod} <IAIMantineSelect
onChange={handleMaskBlurMethodChange} value={maskBlurMethod}
label={t('parameters.maskBlurMethod')} onChange={handleMaskBlurMethodChange}
data={maskBlurMethods} label={t('parameters.maskBlurMethod')}
/> data={maskBlurMethods}
/>
</IAIInformationalPopover>
); );
} }

View File

@ -15,13 +15,19 @@ const ParamCompositingSettingsCollapse = () => {
return ( return (
<IAICollapse label={t('parameters.compositingSettingsHeader')}> <IAICollapse label={t('parameters.compositingSettingsHeader')}>
<Flex sx={{ flexDirection: 'column', gap: 2 }}> <Flex sx={{ flexDirection: 'column', gap: 2 }}>
<SubParametersWrapper label={t('parameters.coherencePassHeader')}> <SubParametersWrapper
label={t('parameters.coherencePassHeader')}
headerInfoPopover="compositingCoherencePass"
>
<ParamCanvasCoherenceMode /> <ParamCanvasCoherenceMode />
<ParamCanvasCoherenceSteps /> <ParamCanvasCoherenceSteps />
<ParamCanvasCoherenceStrength /> <ParamCanvasCoherenceStrength />
</SubParametersWrapper> </SubParametersWrapper>
<Divider /> <Divider />
<SubParametersWrapper label={t('parameters.maskAdjustmentsHeader')}> <SubParametersWrapper
label={t('parameters.maskAdjustmentsHeader')}
headerInfoPopover="compositingMaskAdjustments"
>
<ParamMaskBlur /> <ParamMaskBlur />
<ParamMaskBlurMethod /> <ParamMaskBlurMethod />
</SubParametersWrapper> </SubParametersWrapper>

View File

@ -2,6 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIMantineSelect from 'common/components/IAIMantineSelect'; import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { setInfillMethod } from 'features/parameters/store/generationSlice'; import { setInfillMethod } from 'features/parameters/store/generationSlice';
@ -39,14 +40,16 @@ const ParamInfillMethod = () => {
); );
return ( return (
<IAIMantineSelect <IAIInformationalPopover details="infillMethod">
disabled={infill_methods?.length === 0} <IAIMantineSelect
placeholder={isLoading ? 'Loading...' : undefined} disabled={infill_methods?.length === 0}
label={t('parameters.infillMethod')} placeholder={isLoading ? 'Loading...' : undefined}
value={infillMethod} label={t('parameters.infillMethod')}
data={infill_methods ?? []} value={infillMethod}
onChange={handleChange} data={infill_methods ?? []}
/> onChange={handleChange}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -1,6 +1,7 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect'; import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { setBoundingBoxScaleMethod } from 'features/canvas/store/canvasSlice'; import { setBoundingBoxScaleMethod } from 'features/canvas/store/canvasSlice';
@ -35,12 +36,14 @@ const ParamScaleBeforeProcessing = () => {
}; };
return ( return (
<IAIMantineSearchableSelect <IAIInformationalPopover details="scaleBeforeProcessing">
label={t('parameters.scaleBeforeProcessing')} <IAIMantineSearchableSelect
data={BOUNDING_BOX_SCALES_DICT} label={t('parameters.scaleBeforeProcessing')}
value={boundingBoxScale} data={BOUNDING_BOX_SCALES_DICT}
onChange={handleChangeBoundingBoxScaleMethod} value={boundingBoxScale}
/> onChange={handleChangeBoundingBoxScaleMethod}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -2,6 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAINumberInput from 'common/components/IAINumberInput'; import IAINumberInput from 'common/components/IAINumberInput';
import IAISlider from 'common/components/IAISlider'; import IAISlider from 'common/components/IAISlider';
import { setCfgScale } from 'features/parameters/store/generationSlice'; import { setCfgScale } from 'features/parameters/store/generationSlice';
@ -53,31 +54,35 @@ const ParamCFGScale = () => {
); );
return shouldUseSliders ? ( return shouldUseSliders ? (
<IAISlider <IAIInformationalPopover details="paramCFGScale">
label={t('parameters.cfgScale')} <IAISlider
step={shift ? 0.1 : 0.5} label={t('parameters.cfgScale')}
min={min} step={shift ? 0.1 : 0.5}
max={sliderMax} min={min}
onChange={handleChange} max={sliderMax}
handleReset={handleReset} onChange={handleChange}
value={cfgScale} handleReset={handleReset}
sliderNumberInputProps={{ max: inputMax }} value={cfgScale}
withInput sliderNumberInputProps={{ max: inputMax }}
withReset withInput
withSliderMarks withReset
isInteger={false} withSliderMarks
/> isInteger={false}
/>
</IAIInformationalPopover>
) : ( ) : (
<IAINumberInput <IAIInformationalPopover details="paramCFGScale">
label={t('parameters.cfgScale')} <IAINumberInput
step={0.5} label={t('parameters.cfgScale')}
min={min} step={0.5}
max={inputMax} min={min}
onChange={handleChange} max={inputMax}
value={cfgScale} onChange={handleChange}
isInteger={false} value={cfgScale}
numberInputFieldProps={{ textAlign: 'center' }} isInteger={false}
/> numberInputFieldProps={{ textAlign: 'center' }}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -2,6 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAINumberInput from 'common/components/IAINumberInput'; import IAINumberInput from 'common/components/IAINumberInput';
import IAISlider from 'common/components/IAISlider'; import IAISlider from 'common/components/IAISlider';
import { setIterations } from 'features/parameters/store/generationSlice'; import { setIterations } from 'features/parameters/store/generationSlice';
@ -60,29 +61,33 @@ const ParamIterations = ({ asSlider }: Props) => {
}, [dispatch, initial]); }, [dispatch, initial]);
return asSlider || shouldUseSliders ? ( return asSlider || shouldUseSliders ? (
<IAISlider <IAIInformationalPopover details="paramImages">
label={t('parameters.iterations')} <IAISlider
step={step} label={t('parameters.iterations')}
min={min} step={step}
max={sliderMax} min={min}
onChange={handleChange} max={sliderMax}
handleReset={handleReset} onChange={handleChange}
value={iterations} handleReset={handleReset}
withInput value={iterations}
withReset withInput
withSliderMarks withReset
sliderNumberInputProps={{ max: inputMax }} withSliderMarks
/> sliderNumberInputProps={{ max: inputMax }}
/>
</IAIInformationalPopover>
) : ( ) : (
<IAINumberInput <IAIInformationalPopover details="paramImages">
label={t('parameters.iterations')} <IAINumberInput
step={step} label={t('parameters.iterations')}
min={min} step={step}
max={inputMax} min={min}
onChange={handleChange} max={inputMax}
value={iterations} onChange={handleChange}
numberInputFieldProps={{ textAlign: 'center' }} value={iterations}
/> numberInputFieldProps={{ textAlign: 'center' }}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -9,6 +9,7 @@ import { ChangeEvent, KeyboardEvent, memo, useCallback, useRef } from 'react';
import { flushSync } from 'react-dom'; import { flushSync } from 'react-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus'; import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
const ParamNegativeConditioning = () => { const ParamNegativeConditioning = () => {
const negativePrompt = useAppSelector( const negativePrompt = useAppSelector(
@ -81,18 +82,20 @@ const ParamNegativeConditioning = () => {
onClose={onClose} onClose={onClose}
onSelect={handleSelectEmbedding} onSelect={handleSelectEmbedding}
> >
<IAITextarea <IAIInformationalPopover details="paramNegativeConditioning">
id="negativePrompt" <IAITextarea
name="negativePrompt" id="negativePrompt"
ref={promptRef} name="negativePrompt"
value={negativePrompt} ref={promptRef}
placeholder={t('parameters.negativePromptPlaceholder')} value={negativePrompt}
onChange={handleChangePrompt} placeholder={t('parameters.negativePromptPlaceholder')}
resize="vertical" onChange={handleChangePrompt}
fontSize="sm" resize="vertical"
minH={16} fontSize="sm"
{...(isEmbeddingEnabled && { onKeyDown: handleKeyDown })} minH={16}
/> {...(isEmbeddingEnabled && { onKeyDown: handleKeyDown })}
/>
</IAIInformationalPopover>
</ParamEmbeddingPopover> </ParamEmbeddingPopover>
{!isOpen && isEmbeddingEnabled && ( {!isOpen && isEmbeddingEnabled && (
<Box <Box

View File

@ -12,6 +12,7 @@ import { flushSync } from 'react-dom';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus'; import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
import IAIInformationalPopover from '../../../../../common/components/IAIInformationalPopover';
const promptInputSelector = createSelector( const promptInputSelector = createSelector(
[stateSelector], [stateSelector],
@ -109,17 +110,19 @@ const ParamPositiveConditioning = () => {
onClose={onClose} onClose={onClose}
onSelect={handleSelectEmbedding} onSelect={handleSelectEmbedding}
> >
<IAITextarea <IAIInformationalPopover details="paramPositiveConditioning">
id="prompt" <IAITextarea
name="prompt" id="prompt"
ref={promptRef} name="prompt"
value={prompt} ref={promptRef}
placeholder={t('parameters.positivePromptPlaceholder')} value={prompt}
onChange={handleChangePrompt} placeholder={t('parameters.positivePromptPlaceholder')}
onKeyDown={handleKeyDown} onChange={handleChangePrompt}
resize="vertical" onKeyDown={handleKeyDown}
minH={32} resize="vertical"
/> minH={32}
/>
</IAIInformationalPopover>
</ParamEmbeddingPopover> </ParamEmbeddingPopover>
</FormControl> </FormControl>
{!isOpen && isEmbeddingEnabled && ( {!isOpen && isEmbeddingEnabled && (

View File

@ -1,6 +1,7 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect'; import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import { generationSelector } from 'features/parameters/store/generationSelectors'; import { generationSelector } from 'features/parameters/store/generationSelectors';
import { setScheduler } from 'features/parameters/store/generationSlice'; import { setScheduler } from 'features/parameters/store/generationSlice';
@ -51,12 +52,14 @@ const ParamScheduler = () => {
); );
return ( return (
<IAIMantineSearchableSelect <IAIInformationalPopover details="paramScheduler">
label={t('parameters.scheduler')} <IAIMantineSearchableSelect
value={scheduler} label={t('parameters.scheduler')}
data={data} value={scheduler}
onChange={handleChange} data={data}
/> onChange={handleChange}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -16,6 +16,7 @@ import { activeTabNameSelector } from '../../../../ui/store/uiSelectors';
import ParamAspectRatio, { mappedAspectRatios } from './ParamAspectRatio'; import ParamAspectRatio, { mappedAspectRatios } from './ParamAspectRatio';
import ParamHeight from './ParamHeight'; import ParamHeight from './ParamHeight';
import ParamWidth from './ParamWidth'; import ParamWidth from './ParamWidth';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
const sizeOptsSelector = createSelector( const sizeOptsSelector = createSelector(
@ -83,18 +84,20 @@ export default function ParamSize() {
}} }}
> >
<Flex alignItems="center" gap={2}> <Flex alignItems="center" gap={2}>
<Text <IAIInformationalPopover details="paramRatio">
sx={{ <Text
fontSize: 'sm', sx={{
width: 'full', fontSize: 'sm',
color: 'base.700', width: 'full',
_dark: { color: 'base.700',
color: 'base.300', _dark: {
}, color: 'base.300',
}} },
> }}
{t('parameters.aspectRatio')} >
</Text> {t('parameters.aspectRatio')}
</Text>
</IAIInformationalPopover>
<Spacer /> <Spacer />
<ParamAspectRatio /> <ParamAspectRatio />
<IAIIconButton <IAIIconButton

View File

@ -2,6 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAINumberInput from 'common/components/IAINumberInput'; import IAINumberInput from 'common/components/IAINumberInput';
import IAISlider from 'common/components/IAISlider'; import IAISlider from 'common/components/IAISlider';
@ -56,30 +57,34 @@ const ParamSteps = () => {
}, [dispatch]); }, [dispatch]);
return shouldUseSliders ? ( return shouldUseSliders ? (
<IAISlider <IAIInformationalPopover details="paramSteps">
label={t('parameters.steps')} <IAISlider
min={min} label={t('parameters.steps')}
max={sliderMax} min={min}
step={step} max={sliderMax}
onChange={handleChange} step={step}
handleReset={handleReset} onChange={handleChange}
value={steps} handleReset={handleReset}
withInput value={steps}
withReset withInput
withSliderMarks withReset
sliderNumberInputProps={{ max: inputMax }} withSliderMarks
/> sliderNumberInputProps={{ max: inputMax }}
/>
</IAIInformationalPopover>
) : ( ) : (
<IAINumberInput <IAIInformationalPopover details="paramSteps">
label={t('parameters.steps')} <IAINumberInput
min={min} label={t('parameters.steps')}
max={inputMax} min={min}
step={step} max={inputMax}
onChange={handleChange} step={step}
value={steps} onChange={handleChange}
numberInputFieldProps={{ textAlign: 'center' }} value={steps}
onBlur={handleBlur} numberInputFieldProps={{ textAlign: 'center' }}
/> onBlur={handleBlur}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -7,6 +7,7 @@ import { setImg2imgStrength } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import SubParametersWrapper from '../SubParametersWrapper'; import SubParametersWrapper from '../SubParametersWrapper';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
const selector = createSelector( const selector = createSelector(
[stateSelector], [stateSelector],
@ -46,20 +47,22 @@ const ImageToImageStrength = () => {
return ( return (
<SubParametersWrapper> <SubParametersWrapper>
<IAISlider <IAIInformationalPopover details="paramDenoisingStrength">
label={`${t('parameters.denoisingStrength')}`} <IAISlider
step={step} label={`${t('parameters.denoisingStrength')}`}
min={min} step={step}
max={sliderMax} min={min}
onChange={handleChange} max={sliderMax}
handleReset={handleReset} onChange={handleChange}
value={img2imgStrength} handleReset={handleReset}
isInteger={false} value={img2imgStrength}
withInput isInteger={false}
withSliderMarks withInput
withReset withSliderMarks
sliderNumberInputProps={{ max: inputMax }} withReset
/> sliderNumberInputProps={{ max: inputMax }}
/>
</IAIInformationalPopover>
</SubParametersWrapper> </SubParametersWrapper>
); );
}; };

View File

@ -21,6 +21,7 @@ import {
useGetOnnxModelsQuery, useGetOnnxModelsQuery,
} from 'services/api/endpoints/models'; } from 'services/api/endpoints/models';
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus'; import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -118,24 +119,28 @@ const ParamMainModelSelect = () => {
data={[]} data={[]}
/> />
) : ( ) : (
<Flex w="100%" alignItems="center" gap={3}> <IAIInformationalPopover details="paramModel" placement="bottom">
<IAIMantineSearchableSelect <Flex w="100%" alignItems="center" gap={3}>
tooltip={selectedModel?.description} <IAIMantineSearchableSelect
label={t('modelManager.model')} tooltip={selectedModel?.description}
value={selectedModel?.id} label={t('modelManager.model')}
placeholder={data.length > 0 ? 'Select a model' : 'No models available'} value={selectedModel?.id}
data={data} placeholder={
error={data.length === 0} data.length > 0 ? 'Select a model' : 'No models available'
disabled={data.length === 0} }
onChange={handleChangeModel} data={data}
w="100%" error={data.length === 0}
/> disabled={data.length === 0}
{isSyncModelEnabled && ( onChange={handleChangeModel}
<Box mt={7}> w="100%"
<SyncModelsButton iconMode /> />
</Box> {isSyncModelEnabled && (
)} <Box mt={7}>
</Flex> <SyncModelsButton iconMode />
</Box>
)}
</Flex>
</IAIInformationalPopover>
); );
}; };

View File

@ -1,4 +1,5 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { shouldUseCpuNoiseChanged } from 'features/parameters/store/generationSlice'; import { shouldUseCpuNoiseChanged } from 'features/parameters/store/generationSlice';
import { ChangeEvent, useCallback } from 'react'; import { ChangeEvent, useCallback } from 'react';
@ -19,10 +20,12 @@ export const ParamCpuNoiseToggle = () => {
); );
return ( return (
<IAISwitch <IAIInformationalPopover details="noiseUseCPU">
label={t('parameters.useCpuNoise')} <IAISwitch
isChecked={shouldUseCpuNoise} label={t('parameters.useCpuNoise')}
onChange={handleChange} isChecked={shouldUseCpuNoise}
/> onChange={handleChange}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -3,14 +3,17 @@ import { memo } from 'react';
import ParamSeed from './ParamSeed'; import ParamSeed from './ParamSeed';
import ParamSeedShuffle from './ParamSeedShuffle'; import ParamSeedShuffle from './ParamSeedShuffle';
import ParamSeedRandomize from './ParamSeedRandomize'; import ParamSeedRandomize from './ParamSeedRandomize';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
const ParamSeedFull = () => { const ParamSeedFull = () => {
return ( return (
<Flex sx={{ gap: 3, alignItems: 'flex-end' }}> <IAIInformationalPopover details="paramSeed">
<ParamSeed /> <Flex sx={{ gap: 3, alignItems: 'flex-end' }}>
<ParamSeedShuffle /> <ParamSeed />
<ParamSeedRandomize /> <ParamSeedShuffle />
</Flex> <ParamSeedRandomize />
</Flex>
</IAIInformationalPopover>
); );
}; };

View File

@ -1,9 +1,11 @@
import { Flex, Text } from '@chakra-ui/react'; import { Flex, Text } from '@chakra-ui/react';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import { ReactNode, memo } from 'react'; import { ReactNode, memo } from 'react';
type SubParameterWrapperProps = { type SubParameterWrapperProps = {
children: ReactNode | ReactNode[]; children: ReactNode | ReactNode[];
label?: string; label?: string;
headerInfoPopover?: string;
}; };
const SubParametersWrapper = (props: SubParameterWrapperProps) => ( const SubParametersWrapper = (props: SubParameterWrapperProps) => (
@ -21,7 +23,18 @@ const SubParametersWrapper = (props: SubParameterWrapperProps) => (
}, },
}} }}
> >
{props.label && ( {props.headerInfoPopover && props.label && (
<IAIInformationalPopover details={props.headerInfoPopover}>
<Text
fontSize="sm"
fontWeight="bold"
sx={{ color: 'base.600', _dark: { color: 'base.300' } }}
>
{props.label}
</Text>
</IAIInformationalPopover>
)}
{!props.headerInfoPopover && props.label && (
<Text <Text
fontSize="sm" fontSize="sm"
fontWeight="bold" fontWeight="bold"

View File

@ -15,6 +15,7 @@ import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectI
import { vaeSelected } from 'features/parameters/store/generationSlice'; import { vaeSelected } from 'features/parameters/store/generationSlice';
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants'; import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
import { modelIdToVAEModelParam } from 'features/parameters/util/modelIdToVAEModelParam'; import { modelIdToVAEModelParam } from 'features/parameters/util/modelIdToVAEModelParam';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -93,17 +94,19 @@ const ParamVAEModelSelect = () => {
); );
return ( return (
<IAIMantineSearchableSelect <IAIInformationalPopover details="paramVAE">
itemComponent={IAIMantineSelectItemWithTooltip} <IAIMantineSearchableSelect
tooltip={selectedVaeModel?.description} itemComponent={IAIMantineSelectItemWithTooltip}
label={t('modelManager.vae')} tooltip={selectedVaeModel?.description}
value={selectedVaeModel?.id ?? 'default'} label={t('modelManager.vae')}
placeholder="Default" value={selectedVaeModel?.id ?? 'default'}
data={data} placeholder="Default"
onChange={handleChangeModel} data={data}
disabled={data.length === 0} onChange={handleChangeModel}
clearable disabled={data.length === 0}
/> clearable
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -2,6 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIMantineSelect from 'common/components/IAIMantineSelect'; import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { vaePrecisionChanged } from 'features/parameters/store/generationSlice'; import { vaePrecisionChanged } from 'features/parameters/store/generationSlice';
import { PrecisionParam } from 'features/parameters/types/parameterSchemas'; import { PrecisionParam } from 'features/parameters/types/parameterSchemas';
@ -34,12 +35,14 @@ const ParamVAEModelSelect = () => {
); );
return ( return (
<IAIMantineSelect <IAIInformationalPopover details="paramVAEPrecision">
label="VAE Precision" <IAIMantineSelect
value={vaePrecision} label="VAE Precision"
data={DATA} value={vaePrecision}
onChange={handleChange} data={DATA}
/> onChange={handleChange}
/>
</IAIInformationalPopover>
); );
}; };

View File

@ -7,6 +7,7 @@ import SubParametersWrapper from 'features/parameters/components/Parameters/SubP
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { setSDXLImg2ImgDenoisingStrength } from '../store/sdxlSlice'; import { setSDXLImg2ImgDenoisingStrength } from '../store/sdxlSlice';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
const selector = createSelector( const selector = createSelector(
[stateSelector], [stateSelector],
@ -36,19 +37,21 @@ const ParamSDXLImg2ImgDenoisingStrength = () => {
return ( return (
<SubParametersWrapper> <SubParametersWrapper>
<IAISlider <IAIInformationalPopover details="paramDenoisingStrength">
label={t('sdxl.denoisingStrength')} <IAISlider
step={0.01} label={t('sdxl.denoisingStrength')}
min={0} step={0.01}
max={1} min={0}
onChange={handleChange} max={1}
handleReset={handleReset} onChange={handleChange}
value={sdxlImg2ImgDenoisingStrength} handleReset={handleReset}
isInteger={false} value={sdxlImg2ImgDenoisingStrength}
withInput isInteger={false}
withSliderMarks withInput
withReset withSliderMarks
/> withReset
/>
</IAIInformationalPopover>
</SubParametersWrapper> </SubParametersWrapper>
); );
}; };

View File

@ -23,6 +23,7 @@ import {
consoleLogLevelChanged, consoleLogLevelChanged,
setEnableImageDebugging, setEnableImageDebugging,
setShouldConfirmOnDelete, setShouldConfirmOnDelete,
setShouldDisableInformationalPopovers,
shouldAntialiasProgressImageChanged, shouldAntialiasProgressImageChanged,
shouldLogToConsoleChanged, shouldLogToConsoleChanged,
shouldUseNSFWCheckerChanged, shouldUseNSFWCheckerChanged,
@ -66,6 +67,7 @@ const selector = createSelector(
shouldAntialiasProgressImage, shouldAntialiasProgressImage,
shouldUseNSFWChecker, shouldUseNSFWChecker,
shouldUseWatermarker, shouldUseWatermarker,
shouldDisableInformationalPopovers,
} = system; } = system;
const { const {
@ -85,6 +87,7 @@ const selector = createSelector(
shouldUseNSFWChecker, shouldUseNSFWChecker,
shouldUseWatermarker, shouldUseWatermarker,
shouldAutoChangeDimensions, shouldAutoChangeDimensions,
shouldDisableInformationalPopovers,
}; };
}, },
{ {
@ -158,6 +161,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
shouldUseNSFWChecker, shouldUseNSFWChecker,
shouldUseWatermarker, shouldUseWatermarker,
shouldAutoChangeDimensions, shouldAutoChangeDimensions,
shouldDisableInformationalPopovers,
} = useAppSelector(selector); } = useAppSelector(selector);
const handleClickResetWebUI = useCallback(() => { const handleClickResetWebUI = useCallback(() => {
@ -307,6 +311,15 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
onChange={handleLanguageChanged} onChange={handleLanguageChanged}
/> />
)} )}
<SettingSwitch
label="Disable informational popovers"
isChecked={shouldDisableInformationalPopovers}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(
setShouldDisableInformationalPopovers(e.target.checked)
)
}
/>
</StyledFlex> </StyledFlex>
{shouldShowDeveloperSettings && ( {shouldShowDeveloperSettings && (

View File

@ -35,6 +35,7 @@ export const initialSystemState: SystemState = {
language: 'en', language: 'en',
shouldUseNSFWChecker: false, shouldUseNSFWChecker: false,
shouldUseWatermarker: false, shouldUseWatermarker: false,
shouldDisableInformationalPopovers: false,
status: 'DISCONNECTED', status: 'DISCONNECTED',
}; };
@ -75,6 +76,12 @@ export const systemSlice = createSlice({
shouldUseWatermarkerChanged(state, action: PayloadAction<boolean>) { shouldUseWatermarkerChanged(state, action: PayloadAction<boolean>) {
state.shouldUseWatermarker = action.payload; state.shouldUseWatermarker = action.payload;
}, },
setShouldDisableInformationalPopovers(
state,
action: PayloadAction<boolean>
) {
state.shouldDisableInformationalPopovers = action.payload;
},
}, },
extraReducers(builder) { extraReducers(builder) {
/** /**
@ -234,6 +241,7 @@ export const {
languageChanged, languageChanged,
shouldUseNSFWCheckerChanged, shouldUseNSFWCheckerChanged,
shouldUseWatermarkerChanged, shouldUseWatermarkerChanged,
setShouldDisableInformationalPopovers,
} = systemSlice.actions; } = systemSlice.actions;
export default systemSlice.reducer; export default systemSlice.reducer;

View File

@ -33,6 +33,7 @@ export interface SystemState {
shouldUseNSFWChecker: boolean; shouldUseNSFWChecker: boolean;
shouldUseWatermarker: boolean; shouldUseWatermarker: boolean;
status: SystemStatus; status: SystemStatus;
shouldDisableInformationalPopovers: boolean;
} }
export const LANGUAGES = { export const LANGUAGES = {

View File

@ -80,6 +80,13 @@ const invokeAIOutline = defineStyle((props) => {
return { return {
border: '1px solid', border: '1px solid',
borderColor: c === 'gray' ? borderColor : 'currentColor', borderColor: c === 'gray' ? borderColor : 'currentColor',
_hover: {
bg: mode(`${c}.500`, `${c}.500`)(props),
color: mode('white', `base.50`)(props),
svg: {
fill: mode('white', `base.50`)(props),
},
},
'.chakra-button__group[data-attached][data-orientation=horizontal] > &:not(:last-of-type)': '.chakra-button__group[data-attached][data-orientation=horizontal] > &:not(:last-of-type)':
{ {
marginEnd: '-1px', marginEnd: '-1px',

View File

@ -29,13 +29,34 @@ const invokeAIContent = defineStyle((props) => {
}; };
}); });
const informationalContent = defineStyle((props) => {
return {
[$arrowBg.variable]: mode('colors.base.100', 'colors.base.600')(props),
[$popperBg.variable]: mode('colors.base.100', 'colors.base.600')(props),
[$arrowShadowColor.variable]: mode(
'colors.base.400',
'colors.base.400'
)(props),
p: 0,
bg: mode('base.100', 'base.600')(props),
border: 'none',
shadow: 'dark-lg',
};
});
const invokeAI = definePartsStyle((props) => ({ const invokeAI = definePartsStyle((props) => ({
content: invokeAIContent(props), content: invokeAIContent(props),
})); }));
const informational = definePartsStyle((props) => ({
content: informationalContent(props),
body: { padding: 0 },
}));
export const popoverTheme = defineMultiStyleConfig({ export const popoverTheme = defineMultiStyleConfig({
variants: { variants: {
invokeAI, invokeAI,
informational,
}, },
defaultProps: { defaultProps: {
variant: 'invokeAI', variant: 'invokeAI',