mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): IAICustomSelect v2, implement for scheduler & model
This commit is contained in:
parent
37da0fc075
commit
658b556544
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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);
|
@ -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 {
|
||||
|
@ -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 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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 />
|
||||
|
@ -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>
|
||||
|
@ -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 />
|
||||
|
Loading…
Reference in New Issue
Block a user