feat(ui): enhance IAICustomSelect

Now accepts an array of strings or array of `IAICustomSelectOption`s. This supports custom labels and tooltips within the select component.
This commit is contained in:
psychedelicious 2023-06-09 15:56:43 +10:00
parent 6ad7cc4f2a
commit a33327c651
7 changed files with 215 additions and 116 deletions

View File

@ -2,7 +2,6 @@ import { CheckIcon, ChevronUpIcon } from '@chakra-ui/icons';
import { import {
Box, Box,
Flex, Flex,
FlexProps,
FormControl, FormControl,
FormControlProps, FormControlProps,
FormLabel, FormLabel,
@ -16,6 +15,7 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { autoUpdate, offset, shift, useFloating } from '@floating-ui/react-dom'; import { autoUpdate, offset, shift, useFloating } from '@floating-ui/react-dom';
import { useSelect } from 'downshift'; import { useSelect } from 'downshift';
import { isString } from 'lodash-es';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
@ -23,15 +23,19 @@ import { getInputOutlineStyles } from 'theme/util/getInputOutlineStyles';
export type ItemTooltips = { [key: string]: string }; export type ItemTooltips = { [key: string]: string };
export type IAICustomSelectOption = {
value: string;
label: string;
tooltip?: string;
};
type IAICustomSelectProps = { type IAICustomSelectProps = {
label?: string; label?: string;
items: string[]; value: string;
itemTooltips?: ItemTooltips; data: IAICustomSelectOption[] | string[];
selectedItem: string; onChange: (v: string) => void;
setSelectedItem: (v: string | null | undefined) => void;
withCheckIcon?: boolean; withCheckIcon?: boolean;
formControlProps?: FormControlProps; formControlProps?: FormControlProps;
buttonProps?: FlexProps;
tooltip?: string; tooltip?: string;
tooltipProps?: Omit<TooltipProps, 'children'>; tooltipProps?: Omit<TooltipProps, 'children'>;
ellipsisPosition?: 'start' | 'end'; ellipsisPosition?: 'start' | 'end';
@ -40,18 +44,33 @@ type IAICustomSelectProps = {
const IAICustomSelect = (props: IAICustomSelectProps) => { const IAICustomSelect = (props: IAICustomSelectProps) => {
const { const {
label, label,
items,
itemTooltips,
setSelectedItem,
selectedItem,
withCheckIcon, withCheckIcon,
formControlProps, formControlProps,
tooltip, tooltip,
buttonProps,
tooltipProps, tooltipProps,
ellipsisPosition = 'end', ellipsisPosition = 'end',
data,
value,
onChange,
} = props; } = props;
const values = useMemo(() => {
return data.map<IAICustomSelectOption>((v) => {
if (isString(v)) {
return { value: v, label: v };
}
return v;
});
}, [data]);
const stringValues = useMemo(() => {
return values.map((v) => v.value);
}, [values]);
const valueData = useMemo(() => {
return values.find((v) => v.value === value);
}, [values, value]);
const { const {
isOpen, isOpen,
getToggleButtonProps, getToggleButtonProps,
@ -60,10 +79,11 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
highlightedIndex, highlightedIndex,
getItemProps, getItemProps,
} = useSelect({ } = useSelect({
items, items: stringValues,
selectedItem, selectedItem: value,
onSelectedItemChange: ({ selectedItem: newSelectedItem }) => onSelectedItemChange: ({ selectedItem: newSelectedItem }) => {
setSelectedItem(newSelectedItem), newSelectedItem && onChange(newSelectedItem);
},
}); });
const { refs, floatingStyles } = useFloating<HTMLButtonElement>({ const { refs, floatingStyles } = useFloating<HTMLButtonElement>({
@ -94,7 +114,6 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
<Tooltip label={tooltip} {...tooltipProps}> <Tooltip label={tooltip} {...tooltipProps}>
<Flex <Flex
{...getToggleButtonProps({ ref: refs.setReference })} {...getToggleButtonProps({ ref: refs.setReference })}
{...buttonProps}
sx={{ sx={{
alignItems: 'center', alignItems: 'center',
userSelect: 'none', userSelect: 'none',
@ -119,7 +138,7 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
direction: labelTextDirection, direction: labelTextDirection,
}} }}
> >
{selectedItem} {valueData?.label}
</Text> </Text>
<ChevronUpIcon <ChevronUpIcon
sx={{ sx={{
@ -155,8 +174,8 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
}} }}
> >
<OverlayScrollbarsComponent> <OverlayScrollbarsComponent>
{items.map((item, index) => { {values.map((v, index) => {
const isSelected = selectedItem === item; const isSelected = value === v.value;
const isHighlighted = highlightedIndex === index; const isHighlighted = highlightedIndex === index;
const fontWeight = isSelected ? 700 : 500; const fontWeight = isSelected ? 700 : 500;
const bg = isHighlighted const bg = isHighlighted
@ -166,9 +185,9 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
: undefined; : undefined;
return ( return (
<Tooltip <Tooltip
isDisabled={!itemTooltips} isDisabled={!v.tooltip}
key={`${item}${index}`} key={`${v.value}${index}`}
label={itemTooltips?.[item]} label={v.tooltip}
hasArrow hasArrow
placement="right" placement="right"
> >
@ -182,8 +201,7 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
transitionProperty: 'common', transitionProperty: 'common',
transitionDuration: '0.15s', transitionDuration: '0.15s',
}} }}
key={`${item}${index}`} {...getItemProps({ item: v.value, index })}
{...getItemProps({ item, index })}
> >
{withCheckIcon ? ( {withCheckIcon ? (
<Grid gridTemplateColumns="1.25rem auto"> <Grid gridTemplateColumns="1.25rem auto">
@ -198,7 +216,7 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
fontWeight, fontWeight,
}} }}
> >
{item} {v.label}
</Text> </Text>
</GridItem> </GridItem>
</Grid> </Grid>
@ -210,7 +228,7 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
fontWeight, fontWeight,
}} }}
> >
{item} {v.label}
</Text> </Text>
)} )}
</ListItem> </ListItem>

View File

@ -1,17 +1,26 @@
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import IAICustomSelect from 'common/components/IAICustomSelect'; import IAICustomSelect, {
IAICustomSelectOption,
} from 'common/components/IAICustomSelect';
import { import {
CONTROLNET_MODELS, CONTROLNET_MODELS,
ControlNetModel, ControlNetModelName,
} from 'features/controlNet/store/constants'; } from 'features/controlNet/store/constants';
import { controlNetModelChanged } from 'features/controlNet/store/controlNetSlice'; import { controlNetModelChanged } from 'features/controlNet/store/controlNetSlice';
import { map } from 'lodash-es';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
type ParamControlNetModelProps = { type ParamControlNetModelProps = {
controlNetId: string; controlNetId: string;
model: ControlNetModel; model: ControlNetModelName;
}; };
const DATA: IAICustomSelectOption[] = map(CONTROLNET_MODELS, (m) => ({
value: m.type,
label: m.label,
tooltip: m.type,
}));
const ParamControlNetModel = (props: ParamControlNetModelProps) => { const ParamControlNetModel = (props: ParamControlNetModelProps) => {
const { controlNetId, model } = props; const { controlNetId, model } = props;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -19,7 +28,7 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => {
const handleModelChanged = useCallback( const handleModelChanged = useCallback(
(val: string | null | undefined) => { (val: string | null | undefined) => {
// TODO: do not cast // TODO: do not cast
const model = val as ControlNetModel; const model = val as ControlNetModelName;
dispatch(controlNetModelChanged({ controlNetId, model })); dispatch(controlNetModelChanged({ controlNetId, model }));
}, },
[controlNetId, dispatch] [controlNetId, dispatch]
@ -29,9 +38,9 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => {
<IAICustomSelect <IAICustomSelect
tooltip={model} tooltip={model}
tooltipProps={{ placement: 'top', hasArrow: true }} tooltipProps={{ placement: 'top', hasArrow: true }}
items={CONTROLNET_MODELS} data={DATA}
selectedItem={model} value={model}
setSelectedItem={handleModelChanged} onChange={handleModelChanged}
ellipsisPosition="start" ellipsisPosition="start"
withCheckIcon withCheckIcon
/> />

View File

@ -1,4 +1,6 @@
import IAICustomSelect from 'common/components/IAICustomSelect'; import IAICustomSelect, {
IAICustomSelectOption,
} from 'common/components/IAICustomSelect';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { import {
ControlNetProcessorNode, ControlNetProcessorNode,
@ -7,15 +9,28 @@ import {
import { controlNetProcessorTypeChanged } from '../../store/controlNetSlice'; import { controlNetProcessorTypeChanged } from '../../store/controlNetSlice';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { CONTROLNET_PROCESSORS } from '../../store/constants'; import { CONTROLNET_PROCESSORS } from '../../store/constants';
import { map } from 'lodash-es';
type ParamControlNetProcessorSelectProps = { type ParamControlNetProcessorSelectProps = {
controlNetId: string; controlNetId: string;
processorNode: ControlNetProcessorNode; processorNode: ControlNetProcessorNode;
}; };
const CONTROLNET_PROCESSOR_TYPES = Object.keys( const CONTROLNET_PROCESSOR_TYPES: IAICustomSelectOption[] = map(
CONTROLNET_PROCESSORS CONTROLNET_PROCESSORS,
) as ControlNetProcessorType[]; (p) => ({
value: p.type,
label: p.label,
tooltip: p.description,
})
).sort((a, b) =>
// sort 'none' to the top
a.value === 'none'
? -1
: b.value === 'none'
? 1
: a.label.localeCompare(b.label)
);
const ParamControlNetProcessorSelect = ( const ParamControlNetProcessorSelect = (
props: ParamControlNetProcessorSelectProps props: ParamControlNetProcessorSelectProps
@ -36,9 +51,9 @@ const ParamControlNetProcessorSelect = (
return ( return (
<IAICustomSelect <IAICustomSelect
label="Processor" label="Processor"
items={CONTROLNET_PROCESSOR_TYPES} value={processorNode.type ?? 'canny_image_processor'}
selectedItem={processorNode.type ?? 'canny_image_processor'} data={CONTROLNET_PROCESSOR_TYPES}
setSelectedItem={handleProcessorTypeChanged} onChange={handleProcessorTypeChanged}
withCheckIcon withCheckIcon
/> />
); );

View File

@ -5,12 +5,12 @@ import {
} from './types'; } from './types';
type ControlNetProcessorsDict = Record< type ControlNetProcessorsDict = Record<
ControlNetProcessorType, string,
{ {
type: ControlNetProcessorType; type: ControlNetProcessorType | 'none';
label: string; label: string;
description: string; description: string;
default: RequiredControlNetProcessorNode; default: RequiredControlNetProcessorNode | { type: 'none' };
} }
>; >;
@ -23,10 +23,10 @@ type ControlNetProcessorsDict = Record<
* *
* TODO: Generate from the OpenAPI schema * TODO: Generate from the OpenAPI schema
*/ */
export const CONTROLNET_PROCESSORS = { export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
none: { none: {
type: 'none', type: 'none',
label: 'None', label: 'none',
description: '', description: '',
default: { default: {
type: 'none', type: 'none',
@ -116,7 +116,7 @@ export const CONTROLNET_PROCESSORS = {
}, },
mlsd_image_processor: { mlsd_image_processor: {
type: 'mlsd_image_processor', type: 'mlsd_image_processor',
label: 'MLSD', label: 'M-LSD',
description: '', description: '',
default: { default: {
id: 'mlsd_image_processor', id: 'mlsd_image_processor',
@ -174,39 +174,98 @@ export const CONTROLNET_PROCESSORS = {
}, },
}; };
export const CONTROLNET_MODELS = [ type ControlNetModel = {
'lllyasviel/control_v11p_sd15_canny', type: string;
'lllyasviel/control_v11p_sd15_inpaint', label: string;
'lllyasviel/control_v11p_sd15_mlsd', description?: string;
'lllyasviel/control_v11f1p_sd15_depth', defaultProcessor?: ControlNetProcessorType;
'lllyasviel/control_v11p_sd15_normalbae',
'lllyasviel/control_v11p_sd15_seg',
'lllyasviel/control_v11p_sd15_lineart',
'lllyasviel/control_v11p_sd15s2_lineart_anime',
'lllyasviel/control_v11p_sd15_scribble',
'lllyasviel/control_v11p_sd15_softedge',
'lllyasviel/control_v11e_sd15_shuffle',
'lllyasviel/control_v11p_sd15_openpose',
'lllyasviel/control_v11f1e_sd15_tile',
'lllyasviel/control_v11e_sd15_ip2p',
'CrucibleAI/ControlNetMediaPipeFace',
];
export type ControlNetModel = (typeof CONTROLNET_MODELS)[number];
export const CONTROLNET_MODEL_MAP: Record<
ControlNetModel,
ControlNetProcessorType
> = {
'lllyasviel/control_v11p_sd15_canny': 'canny_image_processor',
'lllyasviel/control_v11p_sd15_mlsd': 'mlsd_image_processor',
'lllyasviel/control_v11f1p_sd15_depth': 'midas_depth_image_processor',
'lllyasviel/control_v11p_sd15_normalbae': 'normalbae_image_processor',
'lllyasviel/control_v11p_sd15_lineart': 'lineart_image_processor',
'lllyasviel/control_v11p_sd15s2_lineart_anime':
'lineart_anime_image_processor',
'lllyasviel/control_v11p_sd15_softedge': 'hed_image_processor',
'lllyasviel/control_v11e_sd15_shuffle': 'content_shuffle_image_processor',
'lllyasviel/control_v11p_sd15_openpose': 'openpose_image_processor',
'CrucibleAI/ControlNetMediaPipeFace': 'mediapipe_face_processor',
}; };
export const CONTROLNET_MODELS: Record<string, ControlNetModel> = {
'lllyasviel/control_v11p_sd15_canny': {
type: 'lllyasviel/control_v11p_sd15_canny',
label: 'Canny',
description: '',
defaultProcessor: 'canny_image_processor',
},
'lllyasviel/control_v11p_sd15_inpaint': {
type: 'lllyasviel/control_v11p_sd15_inpaint',
label: 'Inpaint',
description: 'Requires preprocessed control image',
},
'lllyasviel/control_v11p_sd15_mlsd': {
type: 'lllyasviel/control_v11p_sd15_mlsd',
label: 'M-LSD',
description: '',
defaultProcessor: 'mlsd_image_processor',
},
'lllyasviel/control_v11f1p_sd15_depth': {
type: 'lllyasviel/control_v11f1p_sd15_depth',
label: 'Depth',
description: '',
defaultProcessor: 'midas_depth_image_processor',
},
'lllyasviel/control_v11p_sd15_normalbae': {
type: 'lllyasviel/control_v11p_sd15_normalbae',
label: 'Normal Map (BAE)',
description: '',
defaultProcessor: 'normalbae_image_processor',
},
'lllyasviel/control_v11p_sd15_seg': {
type: 'lllyasviel/control_v11p_sd15_seg',
label: 'Segment Anything',
description: 'Requires preprocessed control image',
},
'lllyasviel/control_v11p_sd15_lineart': {
type: 'lllyasviel/control_v11p_sd15_lineart',
label: 'Lineart',
description: '',
defaultProcessor: 'lineart_image_processor',
},
'lllyasviel/control_v11p_sd15s2_lineart_anime': {
type: 'lllyasviel/control_v11p_sd15s2_lineart_anime',
label: 'Lineart Anime',
description: '',
defaultProcessor: 'lineart_anime_image_processor',
},
'lllyasviel/control_v11p_sd15_scribble': {
type: 'lllyasviel/control_v11p_sd15_scribble',
label: 'Scribble',
description: 'Requires preprocessed control image',
},
'lllyasviel/control_v11p_sd15_softedge': {
type: 'lllyasviel/control_v11p_sd15_softedge',
label: 'Soft Edge',
description: '',
defaultProcessor: 'hed_image_processor',
},
'lllyasviel/control_v11e_sd15_shuffle': {
type: 'lllyasviel/control_v11e_sd15_shuffle',
label: 'Content Shuffle',
description: '',
defaultProcessor: 'content_shuffle_image_processor',
},
'lllyasviel/control_v11p_sd15_openpose': {
type: 'lllyasviel/control_v11p_sd15_openpose',
label: 'Openpose',
description: '',
defaultProcessor: 'openpose_image_processor',
},
'lllyasviel/control_v11f1e_sd15_tile': {
type: 'lllyasviel/control_v11f1e_sd15_tile',
label: 'Tile (experimental)',
},
'lllyasviel/control_v11e_sd15_ip2p': {
type: 'lllyasviel/control_v11e_sd15_ip2p',
label: 'Pix2Pix (experimental)',
description: 'Requires preprocessed control image',
},
'CrucibleAI/ControlNetMediaPipeFace': {
type: 'CrucibleAI/ControlNetMediaPipeFace',
label: 'Mediapipe Face',
description: '',
defaultProcessor: 'mediapipe_face_processor',
},
};
export type ControlNetModelName = keyof typeof CONTROLNET_MODELS;

View File

@ -9,9 +9,8 @@ import {
} from './types'; } from './types';
import { import {
CONTROLNET_MODELS, CONTROLNET_MODELS,
CONTROLNET_MODEL_MAP,
CONTROLNET_PROCESSORS, CONTROLNET_PROCESSORS,
ControlNetModel, ControlNetModelName,
} from './constants'; } from './constants';
import { controlNetImageProcessed } from './actions'; import { controlNetImageProcessed } from './actions';
import { imageDeleted, imageUrlsReceived } from 'services/thunks/image'; import { imageDeleted, imageUrlsReceived } from 'services/thunks/image';
@ -21,7 +20,7 @@ import { appSocketInvocationError } from 'services/events/actions';
export const initialControlNet: Omit<ControlNetConfig, 'controlNetId'> = { export const initialControlNet: Omit<ControlNetConfig, 'controlNetId'> = {
isEnabled: true, isEnabled: true,
model: CONTROLNET_MODELS[0], model: CONTROLNET_MODELS['lllyasviel/control_v11p_sd15_canny'].type,
weight: 1, weight: 1,
beginStepPct: 0, beginStepPct: 0,
endStepPct: 1, endStepPct: 1,
@ -36,7 +35,7 @@ export const initialControlNet: Omit<ControlNetConfig, 'controlNetId'> = {
export type ControlNetConfig = { export type ControlNetConfig = {
controlNetId: string; controlNetId: string;
isEnabled: boolean; isEnabled: boolean;
model: ControlNetModel; model: ControlNetModelName;
weight: number; weight: number;
beginStepPct: number; beginStepPct: number;
endStepPct: number; endStepPct: number;
@ -138,14 +137,17 @@ export const controlNetSlice = createSlice({
}, },
controlNetModelChanged: ( controlNetModelChanged: (
state, state,
action: PayloadAction<{ controlNetId: string; model: ControlNetModel }> action: PayloadAction<{
controlNetId: string;
model: ControlNetModelName;
}>
) => { ) => {
const { controlNetId, model } = action.payload; const { controlNetId, model } = action.payload;
state.controlNets[controlNetId].model = model; state.controlNets[controlNetId].model = model;
state.controlNets[controlNetId].processedControlImage = null; state.controlNets[controlNetId].processedControlImage = null;
if (state.controlNets[controlNetId].shouldAutoConfig) { if (state.controlNets[controlNetId].shouldAutoConfig) {
const processorType = CONTROLNET_MODEL_MAP[model]; const processorType = CONTROLNET_MODELS[model].defaultProcessor;
if (processorType) { if (processorType) {
state.controlNets[controlNetId].processorType = processorType; state.controlNets[controlNetId].processorType = processorType;
state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS[ state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS[
@ -225,7 +227,8 @@ export const controlNetSlice = createSlice({
if (newShouldAutoConfig) { if (newShouldAutoConfig) {
// manage the processor for the user // manage the processor for the user
const processorType = const processorType =
CONTROLNET_MODEL_MAP[state.controlNets[controlNetId].model]; CONTROLNET_MODELS[state.controlNets[controlNetId].model]
.defaultProcessor;
if (processorType) { if (processorType) {
state.controlNets[controlNetId].processorType = processorType; state.controlNets[controlNetId].processorType = processorType;
state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS[ state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS[

View File

@ -14,9 +14,11 @@ const selector = createSelector(
(ui, generation) => { (ui, generation) => {
// TODO: DPMSolverSinglestepScheduler is fixed in https://github.com/huggingface/diffusers/pull/3413 // TODO: DPMSolverSinglestepScheduler is fixed in https://github.com/huggingface/diffusers/pull/3413
// but we need to wait for the next release before removing this special handling. // but we need to wait for the next release before removing this special handling.
const allSchedulers = ui.schedulers.filter((scheduler) => { const allSchedulers = ui.schedulers
return !['dpmpp_2s'].includes(scheduler); .filter((scheduler) => {
}); return !['dpmpp_2s'].includes(scheduler);
})
.sort((a, b) => a.localeCompare(b));
return { return {
scheduler: generation.scheduler, scheduler: generation.scheduler,
@ -45,9 +47,9 @@ const ParamScheduler = () => {
return ( return (
<IAICustomSelect <IAICustomSelect
label={t('parameters.scheduler')} label={t('parameters.scheduler')}
selectedItem={scheduler} value={scheduler}
setSelectedItem={handleChange} data={allSchedulers}
items={allSchedulers} onChange={handleChange}
withCheckIcon withCheckIcon
/> />
); );

View File

@ -4,34 +4,29 @@ import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import { selectModelsAll, selectModelsById } from '../store/modelSlice';
selectModelsAll,
selectModelsById,
selectModelsIds,
} from '../store/modelSlice';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { modelSelected } from 'features/parameters/store/generationSlice'; import { modelSelected } from 'features/parameters/store/generationSlice';
import { generationSelector } from 'features/parameters/store/generationSelectors'; import { generationSelector } from 'features/parameters/store/generationSelectors';
import IAICustomSelect, { import IAICustomSelect, {
ItemTooltips, IAICustomSelectOption,
} from 'common/components/IAICustomSelect'; } from 'common/components/IAICustomSelect';
const selector = createSelector( const selector = createSelector(
[(state: RootState) => state, generationSelector], [(state: RootState) => state, generationSelector],
(state, generation) => { (state, generation) => {
const selectedModel = selectModelsById(state, generation.model); const selectedModel = selectModelsById(state, generation.model);
const allModelNames = selectModelsIds(state).map((id) => String(id));
const allModelTooltips = selectModelsAll(state).reduce( const modelData = selectModelsAll(state)
(allModelTooltips, model) => { .map<IAICustomSelectOption>((m) => ({
allModelTooltips[model.name] = model.description ?? ''; value: m.name,
return allModelTooltips; label: m.name,
}, tooltip: m.description,
{} as ItemTooltips }))
); .sort((a, b) => a.label.localeCompare(b.label));
return { return {
allModelNames,
allModelTooltips,
selectedModel, selectedModel,
modelData,
}; };
}, },
{ {
@ -44,8 +39,7 @@ const selector = createSelector(
const ModelSelect = () => { const ModelSelect = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const { allModelNames, allModelTooltips, selectedModel } = const { selectedModel, modelData } = useAppSelector(selector);
useAppSelector(selector);
const handleChangeModel = useCallback( const handleChangeModel = useCallback(
(v: string | null | undefined) => { (v: string | null | undefined) => {
if (!v) { if (!v) {
@ -60,10 +54,9 @@ const ModelSelect = () => {
<IAICustomSelect <IAICustomSelect
label={t('modelManager.model')} label={t('modelManager.model')}
tooltip={selectedModel?.description} tooltip={selectedModel?.description}
items={allModelNames} data={modelData}
itemTooltips={allModelTooltips} value={selectedModel?.name ?? ''}
selectedItem={selectedModel?.name ?? ''} onChange={handleChangeModel}
setSelectedItem={handleChangeModel}
withCheckIcon={true} withCheckIcon={true}
tooltipProps={{ placement: 'top', hasArrow: true }} tooltipProps={{ placement: 'top', hasArrow: true }}
/> />