diff --git a/frontend/src/app/features.ts b/frontend/src/app/features.ts new file mode 100644 index 0000000000..cb8455f09d --- /dev/null +++ b/frontend/src/app/features.ts @@ -0,0 +1,59 @@ +type FeatureHelpInfo = { + text: string; + href: string; + guideImage: string; +}; + +export enum Feature { + PROMPT, + GALLERY, + OUTPUT, + SEED_AND_VARIATION, + ESRGAN, + FACE_CORRECTION, + IMAGE_TO_IMAGE, + SAMPLER, +} + +export const FEATURES: Record = { + [Feature.PROMPT]: { + text: 'This field will take all prompt text, including both content and stylistic terms. CLI Commands will not work in the prompt.', + href: 'link/to/docs/feature3.html', + guideImage: 'asset/path.gif', + }, + [Feature.GALLERY]: { + text: 'As new invocations are generated, files from the output directory will be displayed here. Generations have additional options to configure new generations.', + href: 'link/to/docs/feature3.html', + guideImage: 'asset/path.gif', + }, + [Feature.OUTPUT]: { + text: 'The Height and Width of generations can be controlled here. If you experience errors, you may be generating an image too large for your system. The seamless option will more often result in repeating patterns in outputs.', + href: 'link/to/docs/feature3.html', + guideImage: 'asset/path.gif', + }, + [Feature.SEED_AND_VARIATION]: { + text: 'Seed values provide an initial set of noise which guide the denoising process. Try a variation with an amount of between 0 and 1 to change the output image for that seed.', + href: 'link/to/docs/feature3.html', + guideImage: 'asset/path.gif', + }, + [Feature.ESRGAN]: { + text: 'The ESRGAN setting can be used to increase the output resolution without requiring a higher width/height in the initial generation.', + href: 'link/to/docs/feature1.html', + guideImage: 'asset/path.gif', + }, + [Feature.FACE_CORRECTION]: { + text: 'Using GFPGAN or CodeFormer, Face Correction will attempt to identify faces in outputs, and correct any defects/abnormalities. Higher values will apply a stronger corrective pressure on outputs.', + href: 'link/to/docs/feature2.html', + guideImage: 'asset/path.gif', + }, + [Feature.IMAGE_TO_IMAGE]: { + text: 'ImageToImage allows the upload of an initial image, which InvokeAI will use to guide the generation process, along with a prompt. A lower value for this setting will more closely resemble the original image. Values between 0-1 are accepted, and a range of .25-.75 is recommended ', + href: 'link/to/docs/feature3.html', + guideImage: 'asset/path.gif', + }, + [Feature.SAMPLER]: { + text: 'This setting allows for different denoising samplers to be used, as well as the number of denoising steps used, which will change the resulting output.', + href: 'link/to/docs/feature3.html', + guideImage: 'asset/path.gif', + }, +}; diff --git a/frontend/src/common/components/GuideIcon.tsx b/frontend/src/common/components/GuideIcon.tsx new file mode 100644 index 0000000000..2f4312ae76 --- /dev/null +++ b/frontend/src/common/components/GuideIcon.tsx @@ -0,0 +1,22 @@ +import { Box, forwardRef, Icon } from '@chakra-ui/react'; +import { IconType } from 'react-icons'; +import { MdHelp } from 'react-icons/md'; +import { Feature } from '../../app/features'; +import GuidePopover from './GuidePopover'; + +type GuideIconProps = { + feature: Feature; + icon?: IconType; +}; + +const GuideIcon = forwardRef( + ({ feature, icon = MdHelp }: GuideIconProps, ref) => ( + + + + + + ) +); + +export default GuideIcon; diff --git a/frontend/src/common/components/GuidePopover.tsx b/frontend/src/common/components/GuidePopover.tsx new file mode 100644 index 0000000000..48a2f8d48f --- /dev/null +++ b/frontend/src/common/components/GuidePopover.tsx @@ -0,0 +1,51 @@ +import { + Popover, + PopoverArrow, + PopoverContent, + PopoverTrigger, + PopoverHeader, + Flex, + Box, +} from '@chakra-ui/react'; +import { SystemState } from '../../features/system/systemSlice'; +import { useAppSelector } from '../../app/store'; +import { RootState } from '../../app/store'; +import { createSelector } from '@reduxjs/toolkit'; +import { ReactElement } from 'react'; +import { Feature, FEATURES } from '../../app/features'; + +type GuideProps = { + children: ReactElement; + feature: Feature; +}; + +const systemSelector = createSelector( + (state: RootState) => state.system, + (system: SystemState) => system.shouldDisplayGuides +); + +const GuidePopover = ({ children, feature }: GuideProps) => { + const shouldDisplayGuides = useAppSelector(systemSelector); + const { text } = FEATURES[feature]; + return shouldDisplayGuides ? ( + + + {children} + + e.preventDefault()} + cursor={'initial'} + > + + + {text} + + + + ) : ( + <> + ); +}; + +export default GuidePopover; diff --git a/frontend/src/features/options/OptionsAccordion.tsx b/frontend/src/features/options/OptionsAccordion.tsx index ab60e98228..2568717090 100644 --- a/frontend/src/features/options/OptionsAccordion.tsx +++ b/frontend/src/features/options/OptionsAccordion.tsx @@ -31,6 +31,9 @@ import OutputOptions from './OutputOptions'; import ImageToImageOptions from './ImageToImageOptions'; import { ChangeEvent } from 'react'; +import GuideIcon from '../../common/components/GuideIcon'; +import { Feature } from '../../app/features'; + const optionsSelector = createSelector( (state: RootState) => state.options, (options: OptionsState) => { @@ -108,6 +111,7 @@ const OptionsAccordion = () => { Seed & Variation + @@ -121,6 +125,7 @@ const OptionsAccordion = () => { Sampler + @@ -144,6 +149,7 @@ const OptionsAccordion = () => { onChange={handleChangeShouldRunESRGAN} /> + @@ -160,13 +166,14 @@ const OptionsAccordion = () => { width={'100%'} mr={2} > - Fix Faces (GFPGAN) + Face Correction + @@ -190,6 +197,7 @@ const OptionsAccordion = () => { onChange={handleChangeShouldUseInitImage} /> + @@ -203,6 +211,7 @@ const OptionsAccordion = () => { Output + diff --git a/frontend/src/features/system/SettingsModal.tsx b/frontend/src/features/system/SettingsModal.tsx index 2f636dc44e..11b10bdbae 100644 --- a/frontend/src/features/system/SettingsModal.tsx +++ b/frontend/src/features/system/SettingsModal.tsx @@ -20,6 +20,7 @@ import { useAppDispatch, useAppSelector } from '../../app/store'; import { setShouldConfirmOnDelete, setShouldDisplayInProgress, + setShouldDisplayGuides, SystemState, } from './systemSlice'; import { RootState } from '../../app/store'; @@ -31,8 +32,8 @@ import { cloneElement, ReactElement } from 'react'; const systemSelector = createSelector( (state: RootState) => state.system, (system: SystemState) => { - const { shouldDisplayInProgress, shouldConfirmOnDelete } = system; - return { shouldDisplayInProgress, shouldConfirmOnDelete }; + const { shouldDisplayInProgress, shouldConfirmOnDelete, shouldDisplayGuides } = system; + return { shouldDisplayInProgress, shouldConfirmOnDelete, shouldDisplayGuides }; }, { memoizeOptions: { resultEqualityCheck: isEqual }, @@ -63,7 +64,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => { onClose: onRefreshModalClose, } = useDisclosure(); - const { shouldDisplayInProgress, shouldConfirmOnDelete } = + const { shouldDisplayInProgress, shouldConfirmOnDelete, shouldDisplayGuides } = useAppSelector(systemSelector); const dispatch = useAppDispatch(); @@ -116,6 +117,19 @@ const SettingsModal = ({ children }: SettingsModalProps) => { /> + + + + Display help guides in configuration menus + + + dispatch(setShouldDisplayGuides(e.target.checked)) + } + /> + + Reset Web UI diff --git a/frontend/src/features/system/systemSlice.ts b/frontend/src/features/system/systemSlice.ts index f3d8295b05..fbb44bd9e1 100644 --- a/frontend/src/features/system/systemSlice.ts +++ b/frontend/src/features/system/systemSlice.ts @@ -31,6 +31,8 @@ export interface SystemState extends InvokeAI.SystemStatus, InvokeAI.SystemConfi totalIterations: number; currentStatus: string; currentStatusHasSteps: boolean; + + shouldDisplayGuides: boolean; } const initialSystemState = { @@ -39,6 +41,7 @@ const initialSystemState = { log: [], shouldShowLogViewer: false, shouldDisplayInProgress: false, + shouldDisplayGuides: true, isGFPGANAvailable: true, isESRGANAvailable: true, socketId: '', @@ -117,6 +120,9 @@ export const systemSlice = createSlice({ setSystemConfig: (state, action: PayloadAction) => { return { ...state, ...action.payload }; }, + setShouldDisplayGuides: (state, action: PayloadAction) => { + state.shouldDisplayGuides = action.payload; + }, }, }); @@ -132,6 +138,7 @@ export const { setSystemStatus, setCurrentStatus, setSystemConfig, + setShouldDisplayGuides, } = systemSlice.actions; export default systemSlice.reducer;