feat(ui): IAICustomSelect v2, implement for scheduler & model

This commit is contained in:
psychedelicious 2023-05-13 23:46:47 +10:00
parent 37da0fc075
commit 658b556544
8 changed files with 181 additions and 176 deletions

View File

@ -1,28 +1,25 @@
import { CheckIcon, ChevronUpIcon } from '@chakra-ui/icons';
import { CheckIcon } from '@chakra-ui/icons';
import {
Box,
Flex,
FlexProps,
FormControl,
FormControlProps,
FormLabel,
Grid,
GridItem,
Input,
List,
ListItem,
Select,
Spacer,
Text,
Tooltip,
TooltipProps,
} from '@chakra-ui/react';
import { useEnsureOnScreen } from 'common/hooks/useEnsureOnScreen';
import { autoUpdate, offset, shift, useFloating } from '@floating-ui/react-dom';
import { useSelect } from 'downshift';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import { memo, useRef } from 'react';
import { useIntersection } from 'react-use';
const BUTTON_BG = 'base.900';
const BORDER_HOVER = 'base.700';
const BORDER_FOCUS = 'accent.600';
import { memo } from 'react';
type IAICustomSelectProps = {
label?: string;
@ -31,6 +28,9 @@ type IAICustomSelectProps = {
setSelectedItem: (v: string | null | undefined) => void;
withCheckIcon?: boolean;
formControlProps?: FormControlProps;
buttonProps?: FlexProps;
tooltip?: string;
tooltipProps?: Omit<TooltipProps, 'children'>;
};
const IAICustomSelect = (props: IAICustomSelectProps) => {
@ -41,6 +41,9 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
selectedItem,
withCheckIcon,
formControlProps,
tooltip,
buttonProps,
tooltipProps,
} = props;
const {
@ -57,105 +60,111 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
setSelectedItem(newSelectedItem),
});
const toggleButtonRef = useRef<HTMLButtonElement>(null);
const menuRef = useRef<HTMLUListElement>(null);
const { refs, floatingStyles } = useFloating<HTMLButtonElement>({
whileElementsMounted: autoUpdate,
middleware: [offset(4), shift({ crossAxis: true, padding: 8 })],
});
return (
<FormControl {...formControlProps}>
<FormControl sx={{ w: 'full' }} {...formControlProps}>
{label && (
<FormLabel
{...getLabelProps()}
onClick={() => {
toggleButtonRef.current && toggleButtonRef.current.focus();
refs.floating.current && refs.floating.current.focus();
}}
>
{label}
</FormLabel>
)}
<Select
as={Flex}
{...getToggleButtonProps({
ref: toggleButtonRef,
})}
ref={toggleButtonRef}
sx={{
alignItems: 'center',
userSelect: 'none',
cursor: 'pointer',
}}
>
<Text sx={{ fontSize: 'sm', fontWeight: 500, color: 'base.100' }}>
{selectedItem}
</Text>
</Select>
<List
{...getMenuProps({ ref: menuRef })}
as={Flex}
sx={{
position: 'absolute',
visibility: isOpen ? 'visible' : 'hidden',
flexDirection: 'column',
zIndex: 1,
bg: 'base.800',
borderRadius: 'base',
border: '1px',
borderColor: 'base.700',
shadow: 'dark-lg',
py: 2,
px: 0,
h: 'fit-content',
maxH: 64,
mt: 1,
}}
>
<OverlayScrollbarsComponent defer>
{isOpen &&
items.map((item, index) => (
<ListItem
sx={{
bg: highlightedIndex === index ? 'base.700' : undefined,
py: 1,
paddingInlineStart: 3,
paddingInlineEnd: 6,
cursor: 'pointer',
transitionProperty: 'common',
transitionDuration: '0.15s',
}}
key={`${item}${index}`}
{...getItemProps({ item, index })}
>
{withCheckIcon ? (
<Grid gridTemplateColumns="1.25rem auto">
<GridItem>
{selectedItem === item && <CheckIcon boxSize={2} />}
</GridItem>
<GridItem>
<Text
sx={{
fontSize: 'sm',
color: 'base.100',
fontWeight: 500,
}}
>
{item}
</Text>
</GridItem>
</Grid>
) : (
<Text
sx={{
fontSize: 'sm',
color: 'base.100',
fontWeight: 500,
}}
>
{item}
</Text>
)}
</ListItem>
))}
</OverlayScrollbarsComponent>
</List>
<Tooltip label={tooltip} {...tooltipProps}>
<Select
{...getToggleButtonProps({ ref: refs.setReference })}
{...buttonProps}
as={Flex}
sx={{
alignItems: 'center',
userSelect: 'none',
cursor: 'pointer',
}}
>
<Text sx={{ fontSize: 'sm', fontWeight: 500, color: 'base.100' }}>
{selectedItem}
</Text>
</Select>
</Tooltip>
<Box {...getMenuProps()}>
{isOpen && (
<List
as={Flex}
ref={refs.setFloating}
sx={{
...floatingStyles,
width: 'max-content',
top: 0,
left: 0,
flexDirection: 'column',
zIndex: 1,
bg: 'base.800',
borderRadius: 'base',
border: '1px',
borderColor: 'base.700',
shadow: 'dark-lg',
py: 2,
px: 0,
h: 'fit-content',
maxH: 64,
}}
>
<OverlayScrollbarsComponent>
{items.map((item, index) => (
<ListItem
sx={{
bg: highlightedIndex === index ? 'base.700' : undefined,
py: 1,
paddingInlineStart: 3,
paddingInlineEnd: 6,
cursor: 'pointer',
transitionProperty: 'common',
transitionDuration: '0.15s',
}}
key={`${item}${index}`}
{...getItemProps({ item, index })}
>
{withCheckIcon ? (
<Grid gridTemplateColumns="1.25rem auto">
<GridItem>
{selectedItem === item && <CheckIcon boxSize={2} />}
</GridItem>
<GridItem>
<Text
sx={{
fontSize: 'sm',
color: 'base.100',
fontWeight: 500,
}}
>
{item}
</Text>
</GridItem>
</Grid>
) : (
<Text
sx={{
fontSize: 'sm',
color: 'base.100',
fontWeight: 500,
}}
>
{item}
</Text>
)}
</ListItem>
))}
</OverlayScrollbarsComponent>
</List>
)}
</Box>
</FormControl>
);
};

View File

@ -1,5 +1,6 @@
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAICustomSelect from 'common/components/IAICustomSelect';
import IAISelect from 'common/components/IAISelect';
import { setSampler } from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
@ -23,21 +24,26 @@ const ParamSampler = () => {
const { t } = useTranslation();
const handleChange = useCallback(
(e: ChangeEvent<HTMLSelectElement>) => dispatch(setSampler(e.target.value)),
(v: string | null | undefined) => {
if (!v) {
return;
}
dispatch(setSampler(v));
},
[dispatch]
);
return (
<IAISelect
<IAICustomSelect
label={t('parameters.sampler')}
value={sampler}
onChange={handleChange}
validValues={
selectedItem={sampler}
setSelectedItem={handleChange}
items={
['img2img', 'unifiedCanvas'].includes(activeTabName)
? img2imgSchedulers
: schedulers
}
minWidth={36}
withCheckIcon
/>
);
};

View File

@ -0,0 +1,19 @@
import { Box, Flex } from '@chakra-ui/react';
import { memo } from 'react';
import ParamSampler from './ParamSampler';
import ModelSelect from 'features/system/components/ModelSelect';
const ParamSchedulerAndModel = () => {
return (
<Flex gap={3} w="full">
<Box w="16rem">
<ParamSampler />
</Box>
<Box w="full">
<ModelSelect />
</Box>
</Flex>
);
};
export default memo(ParamSchedulerAndModel);

View File

@ -2,8 +2,9 @@ import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/types/invokeai';
import promptToString from 'common/util/promptToString';
import { clamp } from 'lodash-es';
import { clamp, sample } from 'lodash-es';
import { setAllParametersReducer } from './setAllParametersReducer';
import { receivedModels } from 'services/thunks/model';
export interface GenerationState {
cfgScale: number;
@ -236,6 +237,16 @@ export const generationSlice = createSlice({
state.model = action.payload;
},
},
extraReducers: (builder) => {
builder.addCase(receivedModels.fulfilled, (state, action) => {
if (!state.model) {
const randomModel = sample(action.payload);
if (randomModel) {
state.model = randomModel.name;
}
}
});
},
});
export const {

View File

@ -1,21 +1,20 @@
import { createSelector } from '@reduxjs/toolkit';
import { ChangeEvent, memo } from 'react';
import { memo, useCallback } from 'react';
import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISelect from 'common/components/IAISelect';
import { selectModelsById, selectModelsIds } from '../store/modelSlice';
import { RootState } from 'app/store/store';
import { modelSelected } from 'features/parameters/store/generationSlice';
import { generationSelector } from 'features/parameters/store/generationSelectors';
import IAICustomSelect from 'common/components/IAICustomSelect';
const selector = createSelector(
[(state: RootState) => state, generationSelector],
(state, generation) => {
// const selectedModel = selectedModelSelector(state);
const selectedModel = selectModelsById(state, generation.model);
const allModelNames = selectModelsIds(state);
const allModelNames = selectModelsIds(state).map((id) => String(id));
return {
allModelNames,
selectedModel,
@ -32,19 +31,25 @@ const ModelSelect = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { allModelNames, selectedModel } = useAppSelector(selector);
const handleChangeModel = (e: ChangeEvent<HTMLSelectElement>) => {
dispatch(modelSelected(e.target.value));
};
const handleChangeModel = useCallback(
(v: string | null | undefined) => {
if (!v) {
return;
}
dispatch(modelSelected(v));
},
[dispatch]
);
return (
<IAISelect
<IAICustomSelect
label={t('modelManager.model')}
style={{ fontSize: 'sm' }}
aria-label={t('accessibility.modelSelect')}
tooltip={selectedModel?.description || ''}
value={selectedModel?.name || undefined}
validValues={allModelNames}
onChange={handleChangeModel}
tooltip={selectedModel?.description}
items={allModelNames}
selectedItem={selectedModel?.name ?? ''}
setSelectedItem={handleChangeModel}
withCheckIcon={true}
tooltipProps={{ placement: 'top', hasArrow: true }}
/>
);
};

View File

@ -1,5 +1,5 @@
import { memo } from 'react';
import { Box, Flex } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { uiSelector } from 'features/ui/store/uiSelectors';
import { useAppSelector } from 'app/store/storeHooks';
@ -9,11 +9,10 @@ import ParamSteps from 'features/parameters/components/Parameters/Core/ParamStep
import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale';
import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth';
import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight';
import ParamSampler from 'features/parameters/components/Parameters/Core/ParamSampler';
import ModelSelect from 'features/system/components/ModelSelect';
import ImageToImageStrength from 'features/parameters/components/Parameters/ImageToImage/ImageToImageStrength';
import ImageToImageFit from 'features/parameters/components/Parameters/ImageToImage/ImageToImageFit';
import { generationSelector } from 'features/parameters/store/generationSelectors';
import ParamSchedulerAndModel from 'features/parameters/components/Parameters/Core/ParamSchedulerAndModel';
const selector = createSelector(
[uiSelector, generationSelector],
@ -48,14 +47,7 @@ const ImageToImageTabCoreParameters = () => {
<ParamHeight isDisabled={!shouldFitToWidthHeight} />
<ImageToImageStrength />
<ImageToImageFit />
<Flex gap={3} w="full">
<Box flexGrow={2}>
<ParamSampler />
</Box>
<Box flexGrow={3}>
<ModelSelect />
</Box>
</Flex>
<ParamSchedulerAndModel />
</Flex>
) : (
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
@ -64,14 +56,7 @@ const ImageToImageTabCoreParameters = () => {
<ParamSteps />
<ParamCFGScale />
</Flex>
<Flex gap={3} w="full">
<Box flexGrow={2}>
<ParamSampler />
</Box>
<Box flexGrow={3}>
<ModelSelect />
</Box>
</Flex>
<ParamSchedulerAndModel />
<ParamWidth isDisabled={!shouldFitToWidthHeight} />
<ParamHeight isDisabled={!shouldFitToWidthHeight} />
<ImageToImageStrength />

View File

@ -3,14 +3,13 @@ import ParamSteps from 'features/parameters/components/Parameters/Core/ParamStep
import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale';
import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth';
import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight';
import ParamSampler from 'features/parameters/components/Parameters/Core/ParamSampler';
import ModelSelect from 'features/system/components/ModelSelect';
import { Box, Flex } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react';
import { useAppSelector } from 'app/store/storeHooks';
import { createSelector } from '@reduxjs/toolkit';
import { uiSelector } from 'features/ui/store/uiSelectors';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { memo } from 'react';
import ParamSchedulerAndModel from 'features/parameters/components/Parameters/Core/ParamSchedulerAndModel';
const selector = createSelector(
uiSelector,
@ -42,14 +41,7 @@ const TextToImageTabCoreParameters = () => {
<ParamCFGScale />
<ParamWidth />
<ParamHeight />
<Flex gap={3} w="full">
<Box flexGrow={2}>
<ParamSampler />
</Box>
<Box flexGrow={3}>
<ModelSelect />
</Box>
</Flex>
<ParamSchedulerAndModel />
</Flex>
) : (
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
@ -58,14 +50,7 @@ const TextToImageTabCoreParameters = () => {
<ParamSteps />
<ParamCFGScale />
</Flex>
<Flex gap={3} w="full">
<Box flexGrow={2}>
<ParamSampler />
</Box>
<Box flexGrow={3}>
<ModelSelect />
</Box>
</Flex>
<ParamSchedulerAndModel />
<ParamWidth />
<ParamHeight />
</Flex>

View File

@ -1,10 +1,9 @@
import { memo } from 'react';
import { Box, Flex } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { uiSelector } from 'features/ui/store/uiSelectors';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import ModelSelect from 'features/system/components/ModelSelect';
import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations';
import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps';
import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale';
@ -12,7 +11,7 @@ import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidt
import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight';
import ImageToImageStrength from 'features/parameters/components/Parameters/ImageToImage/ImageToImageStrength';
import ImageToImageFit from 'features/parameters/components/Parameters/ImageToImage/ImageToImageFit';
import ParamSampler from 'features/parameters/components/Parameters/Core/ParamSampler';
import ParamSchedulerAndModel from 'features/parameters/components/Parameters/Core/ParamSchedulerAndModel';
const selector = createSelector(
uiSelector,
@ -46,14 +45,7 @@ const UnifiedCanvasCoreParameters = () => {
<ParamHeight />
<ImageToImageStrength />
<ImageToImageFit />
<Flex gap={3} w="full">
<Box flexGrow={2}>
<ParamSampler />
</Box>
<Box flexGrow={3}>
<ModelSelect />
</Box>
</Flex>
<ParamSchedulerAndModel />
</Flex>
) : (
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
@ -62,14 +54,7 @@ const UnifiedCanvasCoreParameters = () => {
<ParamSteps />
<ParamCFGScale />
</Flex>
<Flex gap={3} w="full">
<Box flexGrow={2}>
<ParamSampler />
</Box>
<Box flexGrow={3}>
<ModelSelect />
</Box>
</Flex>
<ParamSchedulerAndModel />
<ParamWidth />
<ParamHeight />
<ImageToImageStrength />