Skip to content

Commit 9f94950

Browse files
feat(ui): abstract out workflow editor model combobox, ensure consistent ui for all model fields
1 parent 74cdd91 commit 9f94950

22 files changed

+248
-489
lines changed

invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/CLIPEmbedModelFieldInputComponent.tsx

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library';
2-
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
3-
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
1+
import { useAppDispatch } from 'app/store/storeHooks';
2+
import { ModelFieldCombobox } from 'features/nodes/components/flow/nodes/Invocation/fields/inputs/ModelFieldCombobox';
43
import { fieldCLIPEmbedValueChanged } from 'features/nodes/store/nodesSlice';
5-
import { NO_DRAG_CLASS, NO_WHEEL_CLASS } from 'features/nodes/types/constants';
64
import type { CLIPEmbedModelFieldInputInstance, CLIPEmbedModelFieldInputTemplate } from 'features/nodes/types/field';
75
import { memo, useCallback } from 'react';
8-
import { useTranslation } from 'react-i18next';
96
import { useCLIPEmbedModels } from 'services/api/hooks/modelsByType';
107
import type { CLIPEmbedModelConfig } from 'services/api/types';
118

@@ -15,11 +12,9 @@ type Props = FieldComponentProps<CLIPEmbedModelFieldInputInstance, CLIPEmbedMode
1512

1613
const CLIPEmbedModelFieldInputComponent = (props: Props) => {
1714
const { nodeId, field } = props;
18-
const { t } = useTranslation();
19-
const disabledTabs = useAppSelector((s) => s.config.disabledTabs);
2015
const dispatch = useAppDispatch();
2116
const [modelConfigs, { isLoading }] = useCLIPEmbedModels();
22-
const _onChange = useCallback(
17+
const onChange = useCallback(
2318
(value: CLIPEmbedModelConfig | null) => {
2419
if (!value) {
2520
return;
@@ -34,32 +29,15 @@ const CLIPEmbedModelFieldInputComponent = (props: Props) => {
3429
},
3530
[dispatch, field.name, nodeId]
3631
);
37-
const { options, value, onChange, placeholder, noOptionsMessage } = useGroupedModelCombobox({
38-
modelConfigs,
39-
onChange: _onChange,
40-
isLoading,
41-
selectedModel: field.value,
42-
});
43-
const required = props.fieldTemplate.required;
4432

4533
return (
46-
<Flex w="full" alignItems="center" gap={2}>
47-
<Tooltip label={!disabledTabs.includes('models') && t('modelManager.starterModelsInModelManager')}>
48-
<FormControl
49-
className={`${NO_WHEEL_CLASS} ${NO_DRAG_CLASS}`}
50-
isDisabled={!options.length}
51-
isInvalid={!value && required}
52-
>
53-
<Combobox
54-
value={value}
55-
placeholder={required ? placeholder : `(Optional) ${placeholder}`}
56-
options={options}
57-
onChange={onChange}
58-
noOptionsMessage={noOptionsMessage}
59-
/>
60-
</FormControl>
61-
</Tooltip>
62-
</Flex>
34+
<ModelFieldCombobox
35+
value={field.value}
36+
modelConfigs={modelConfigs}
37+
isLoadingConfigs={isLoading}
38+
onChange={onChange}
39+
required={props.fieldTemplate.required}
40+
/>
6341
);
6442
};
6543

invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/CLIPGEmbedModelFieldInputComponent.tsx

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library';
2-
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
3-
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
1+
import { useAppDispatch } from 'app/store/storeHooks';
2+
import { ModelFieldCombobox } from 'features/nodes/components/flow/nodes/Invocation/fields/inputs/ModelFieldCombobox';
43
import { fieldCLIPGEmbedValueChanged } from 'features/nodes/store/nodesSlice';
5-
import { NO_DRAG_CLASS, NO_WHEEL_CLASS } from 'features/nodes/types/constants';
64
import type { CLIPGEmbedModelFieldInputInstance, CLIPGEmbedModelFieldInputTemplate } from 'features/nodes/types/field';
75
import { memo, useCallback } from 'react';
8-
import { useTranslation } from 'react-i18next';
96
import { useCLIPEmbedModels } from 'services/api/hooks/modelsByType';
107
import { type CLIPGEmbedModelConfig, isCLIPGEmbedModelConfig } from 'services/api/types';
118

@@ -15,12 +12,10 @@ type Props = FieldComponentProps<CLIPGEmbedModelFieldInputInstance, CLIPGEmbedMo
1512

1613
const CLIPGEmbedModelFieldInputComponent = (props: Props) => {
1714
const { nodeId, field } = props;
18-
const { t } = useTranslation();
19-
const disabledTabs = useAppSelector((s) => s.config.disabledTabs);
2015
const dispatch = useAppDispatch();
2116
const [modelConfigs, { isLoading }] = useCLIPEmbedModels();
2217

23-
const _onChange = useCallback(
18+
const onChange = useCallback(
2419
(value: CLIPGEmbedModelConfig | null) => {
2520
if (!value) {
2621
return;
@@ -35,32 +30,15 @@ const CLIPGEmbedModelFieldInputComponent = (props: Props) => {
3530
},
3631
[dispatch, field.name, nodeId]
3732
);
38-
const { options, value, onChange, placeholder, noOptionsMessage } = useGroupedModelCombobox({
39-
modelConfigs: modelConfigs.filter((config) => isCLIPGEmbedModelConfig(config)),
40-
onChange: _onChange,
41-
isLoading,
42-
selectedModel: field.value,
43-
});
44-
const required = props.fieldTemplate.required;
4533

4634
return (
47-
<Flex w="full" alignItems="center" gap={2}>
48-
<Tooltip label={!disabledTabs.includes('models') && t('modelManager.starterModelsInModelManager')}>
49-
<FormControl
50-
className={`${NO_WHEEL_CLASS} ${NO_DRAG_CLASS}`}
51-
isDisabled={!options.length}
52-
isInvalid={!value && required}
53-
>
54-
<Combobox
55-
value={value}
56-
placeholder={required ? placeholder : `(Optional) ${placeholder}`}
57-
options={options}
58-
onChange={onChange}
59-
noOptionsMessage={noOptionsMessage}
60-
/>
61-
</FormControl>
62-
</Tooltip>
63-
</Flex>
35+
<ModelFieldCombobox
36+
value={field.value}
37+
modelConfigs={modelConfigs.filter((config) => isCLIPGEmbedModelConfig(config))}
38+
isLoadingConfigs={isLoading}
39+
onChange={onChange}
40+
required={props.fieldTemplate.required}
41+
/>
6442
);
6543
};
6644

invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/CLIPLEmbedModelFieldInputComponent.tsx

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library';
2-
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
3-
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
1+
import { useAppDispatch } from 'app/store/storeHooks';
2+
import { ModelFieldCombobox } from 'features/nodes/components/flow/nodes/Invocation/fields/inputs/ModelFieldCombobox';
43
import { fieldCLIPLEmbedValueChanged } from 'features/nodes/store/nodesSlice';
5-
import { NO_DRAG_CLASS, NO_WHEEL_CLASS } from 'features/nodes/types/constants';
64
import type { CLIPLEmbedModelFieldInputInstance, CLIPLEmbedModelFieldInputTemplate } from 'features/nodes/types/field';
75
import { memo, useCallback } from 'react';
8-
import { useTranslation } from 'react-i18next';
96
import { useCLIPEmbedModels } from 'services/api/hooks/modelsByType';
107
import { type CLIPLEmbedModelConfig, isCLIPLEmbedModelConfig } from 'services/api/types';
118

@@ -15,12 +12,10 @@ type Props = FieldComponentProps<CLIPLEmbedModelFieldInputInstance, CLIPLEmbedMo
1512

1613
const CLIPLEmbedModelFieldInputComponent = (props: Props) => {
1714
const { nodeId, field } = props;
18-
const { t } = useTranslation();
19-
const disabledTabs = useAppSelector((s) => s.config.disabledTabs);
2015
const dispatch = useAppDispatch();
2116
const [modelConfigs, { isLoading }] = useCLIPEmbedModels();
2217

23-
const _onChange = useCallback(
18+
const onChange = useCallback(
2419
(value: CLIPLEmbedModelConfig | null) => {
2520
if (!value) {
2621
return;
@@ -35,32 +30,15 @@ const CLIPLEmbedModelFieldInputComponent = (props: Props) => {
3530
},
3631
[dispatch, field.name, nodeId]
3732
);
38-
const { options, value, onChange, placeholder, noOptionsMessage } = useGroupedModelCombobox({
39-
modelConfigs: modelConfigs.filter((config) => isCLIPLEmbedModelConfig(config)),
40-
onChange: _onChange,
41-
isLoading,
42-
selectedModel: field.value,
43-
});
44-
const required = props.fieldTemplate.required;
4533

4634
return (
47-
<Flex w="full" alignItems="center" gap={2}>
48-
<Tooltip label={!disabledTabs.includes('models') && t('modelManager.starterModelsInModelManager')}>
49-
<FormControl
50-
className={`${NO_WHEEL_CLASS} ${NO_DRAG_CLASS}`}
51-
isDisabled={!options.length}
52-
isInvalid={!value && required}
53-
>
54-
<Combobox
55-
value={value}
56-
placeholder={required ? placeholder : `(Optional) ${placeholder}`}
57-
options={options}
58-
onChange={onChange}
59-
noOptionsMessage={noOptionsMessage}
60-
/>
61-
</FormControl>
62-
</Tooltip>
63-
</Flex>
35+
<ModelFieldCombobox
36+
value={field.value}
37+
modelConfigs={modelConfigs.filter((config) => isCLIPLEmbedModelConfig(config))}
38+
isLoadingConfigs={isLoading}
39+
onChange={onChange}
40+
required={props.fieldTemplate.required}
41+
/>
6442
);
6543
};
6644

invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ControlLoraModelFieldInputComponent.tsx

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,24 @@
1-
import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library';
2-
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
3-
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
1+
import { useAppDispatch } from 'app/store/storeHooks';
2+
import { ModelFieldCombobox } from 'features/nodes/components/flow/nodes/Invocation/fields/inputs/ModelFieldCombobox';
43
import { fieldControlLoRAModelValueChanged } from 'features/nodes/store/nodesSlice';
5-
import { NO_DRAG_CLASS, NO_WHEEL_CLASS } from 'features/nodes/types/constants';
64
import type {
75
ControlLoRAModelFieldInputInstance,
86
ControlLoRAModelFieldInputTemplate,
97
} from 'features/nodes/types/field';
108
import { memo, useCallback } from 'react';
11-
import { useTranslation } from 'react-i18next';
129
import { useControlLoRAModel } from 'services/api/hooks/modelsByType';
13-
import { type ControlLoRAModelConfig, isControlLoRAModelConfig } from 'services/api/types';
10+
import type { ControlLoRAModelConfig } from 'services/api/types';
1411

1512
import type { FieldComponentProps } from './types';
1613

1714
type Props = FieldComponentProps<ControlLoRAModelFieldInputInstance, ControlLoRAModelFieldInputTemplate>;
1815

1916
const ControlLoRAModelFieldInputComponent = (props: Props) => {
2017
const { nodeId, field } = props;
21-
const { t } = useTranslation();
22-
const disabledTabs = useAppSelector((s) => s.config.disabledTabs);
2318
const dispatch = useAppDispatch();
2419
const [modelConfigs, { isLoading }] = useControlLoRAModel();
2520

26-
const _onChange = useCallback(
21+
const onChange = useCallback(
2722
(value: ControlLoRAModelConfig | null) => {
2823
if (!value) {
2924
return;
@@ -38,32 +33,15 @@ const ControlLoRAModelFieldInputComponent = (props: Props) => {
3833
},
3934
[dispatch, field.name, nodeId]
4035
);
41-
const { options, value, onChange, placeholder, noOptionsMessage } = useGroupedModelCombobox({
42-
modelConfigs: modelConfigs.filter((config) => isControlLoRAModelConfig(config)),
43-
onChange: _onChange,
44-
isLoading,
45-
selectedModel: field.value,
46-
});
47-
const required = props.fieldTemplate.required;
4836

4937
return (
50-
<Flex w="full" alignItems="center" gap={2}>
51-
<Tooltip label={!disabledTabs.includes('models') && t('modelManager.starterModelsInModelManager')}>
52-
<FormControl
53-
className={`${NO_WHEEL_CLASS} ${NO_DRAG_CLASS}`}
54-
isDisabled={!options.length}
55-
isInvalid={!value && required}
56-
>
57-
<Combobox
58-
value={value}
59-
placeholder={required ? placeholder : `(Optional) ${placeholder}`}
60-
options={options}
61-
onChange={onChange}
62-
noOptionsMessage={noOptionsMessage}
63-
/>
64-
</FormControl>
65-
</Tooltip>
66-
</Flex>
38+
<ModelFieldCombobox
39+
value={field.value}
40+
modelConfigs={modelConfigs}
41+
isLoadingConfigs={isLoading}
42+
onChange={onChange}
43+
required={props.fieldTemplate.required}
44+
/>
6745
);
6846
};
6947

invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ControlNetModelFieldInputComponent.tsx

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { Combobox, FormControl, Tooltip } from '@invoke-ai/ui-library';
21
import { useAppDispatch } from 'app/store/storeHooks';
3-
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
2+
import { ModelFieldCombobox } from 'features/nodes/components/flow/nodes/Invocation/fields/inputs/ModelFieldCombobox';
43
import { fieldControlNetModelValueChanged } from 'features/nodes/store/nodesSlice';
5-
import { NO_DRAG_CLASS, NO_WHEEL_CLASS } from 'features/nodes/types/constants';
64
import type { ControlNetModelFieldInputInstance, ControlNetModelFieldInputTemplate } from 'features/nodes/types/field';
75
import { memo, useCallback } from 'react';
86
import { useControlNetModels } from 'services/api/hooks/modelsByType';
@@ -17,7 +15,7 @@ const ControlNetModelFieldInputComponent = (props: Props) => {
1715
const dispatch = useAppDispatch();
1816
const [modelConfigs, { isLoading }] = useControlNetModels();
1917

20-
const _onChange = useCallback(
18+
const onChange = useCallback(
2119
(value: ControlNetModelConfig | null) => {
2220
if (!value) {
2321
return;
@@ -33,25 +31,14 @@ const ControlNetModelFieldInputComponent = (props: Props) => {
3331
[dispatch, field.name, nodeId]
3432
);
3533

36-
const { options, value, onChange, placeholder, noOptionsMessage } = useGroupedModelCombobox({
37-
modelConfigs,
38-
onChange: _onChange,
39-
selectedModel: field.value,
40-
isLoading,
41-
});
42-
4334
return (
44-
<Tooltip label={value?.description}>
45-
<FormControl className={`${NO_WHEEL_CLASS} ${NO_DRAG_CLASS}`} isInvalid={!value}>
46-
<Combobox
47-
value={value}
48-
placeholder={placeholder}
49-
options={options}
50-
onChange={onChange}
51-
noOptionsMessage={noOptionsMessage}
52-
/>
53-
</FormControl>
54-
</Tooltip>
35+
<ModelFieldCombobox
36+
value={field.value}
37+
modelConfigs={modelConfigs}
38+
isLoadingConfigs={isLoading}
39+
onChange={onChange}
40+
required={props.fieldTemplate.required}
41+
/>
5542
);
5643
};
5744

invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/FluxMainModelFieldInputComponent.tsx

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { Combobox, Flex, FormControl } from '@invoke-ai/ui-library';
21
import { useAppDispatch } from 'app/store/storeHooks';
3-
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
2+
import { ModelFieldCombobox } from 'features/nodes/components/flow/nodes/Invocation/fields/inputs/ModelFieldCombobox';
43
import { fieldMainModelValueChanged } from 'features/nodes/store/nodesSlice';
5-
import { NO_DRAG_CLASS, NO_WHEEL_CLASS } from 'features/nodes/types/constants';
64
import type { FluxMainModelFieldInputInstance, FluxMainModelFieldInputTemplate } from 'features/nodes/types/field';
75
import { memo, useCallback } from 'react';
86
import { useFluxModels } from 'services/api/hooks/modelsByType';
@@ -16,7 +14,7 @@ const FluxMainModelFieldInputComponent = (props: Props) => {
1614
const { nodeId, field } = props;
1715
const dispatch = useAppDispatch();
1816
const [modelConfigs, { isLoading }] = useFluxModels();
19-
const _onChange = useCallback(
17+
const onChange = useCallback(
2018
(value: MainModelConfig | null) => {
2119
if (!value) {
2220
return;
@@ -31,25 +29,15 @@ const FluxMainModelFieldInputComponent = (props: Props) => {
3129
},
3230
[dispatch, field.name, nodeId]
3331
);
34-
const { options, value, onChange, placeholder, noOptionsMessage } = useGroupedModelCombobox({
35-
modelConfigs,
36-
onChange: _onChange,
37-
isLoading,
38-
selectedModel: field.value,
39-
});
4032

4133
return (
42-
<Flex w="full" alignItems="center" gap={2}>
43-
<FormControl className={`${NO_WHEEL_CLASS} ${NO_DRAG_CLASS}`} isDisabled={!options.length} isInvalid={!value}>
44-
<Combobox
45-
value={value}
46-
placeholder={placeholder}
47-
options={options}
48-
onChange={onChange}
49-
noOptionsMessage={noOptionsMessage}
50-
/>
51-
</FormControl>
52-
</Flex>
34+
<ModelFieldCombobox
35+
value={field.value}
36+
modelConfigs={modelConfigs}
37+
isLoadingConfigs={isLoading}
38+
onChange={onChange}
39+
required={props.fieldTemplate.required}
40+
/>
5341
);
5442
};
5543

0 commit comments

Comments
 (0)