feat: move board logic to save_image node

- Remove the add-to-board node
- Create `BoardField` field type & add it to `save_image` node
- Add UI for `BoardField`
- Tighten up some loose types
- Make `save_image` node, in workflow editor, default to not intermediate
- Patch bump `save_image`
This commit is contained in:
psychedelicious
2023-09-22 12:09:24 +10:00
committed by Kent Keirsey
parent 627444e17c
commit 43fbac26df
15 changed files with 245 additions and 124 deletions

View File

@@ -16,6 +16,7 @@ import SchedulerInputField from './inputs/SchedulerInputField';
import StringInputField from './inputs/StringInputField';
import VaeModelInputField from './inputs/VaeModelInputField';
import IPAdapterModelInputField from './inputs/IPAdapterModelInputField';
import BoardInputField from './inputs/BoardInputField';
type InputFieldProps = {
nodeId: string;
@@ -99,6 +100,16 @@ const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => {
);
}
if (field?.type === 'BoardField' && fieldTemplate?.type === 'BoardField') {
return (
<BoardInputField
nodeId={nodeId}
field={field}
fieldTemplate={fieldTemplate}
/>
);
}
if (
field?.type === 'MainModelField' &&
fieldTemplate?.type === 'MainModelField'

View File

@@ -0,0 +1,64 @@
import { SelectItem } from '@mantine/core';
import { useAppDispatch } from 'app/store/storeHooks';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import { fieldBoardValueChanged } from 'features/nodes/store/nodesSlice';
import {
BoardInputFieldTemplate,
BoardInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { memo, useCallback } from 'react';
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
const BoardInputFieldComponent = (
props: FieldComponentProps<BoardInputFieldValue, BoardInputFieldTemplate>
) => {
const { nodeId, field } = props;
const dispatch = useAppDispatch();
const { data, hasBoards } = useListAllBoardsQuery(undefined, {
selectFromResult: ({ data }) => {
const boards: SelectItem[] = [
{
label: 'None',
value: 'none',
},
];
data?.forEach(({ board_id, board_name }) => {
boards.push({
label: board_name,
value: board_id,
});
});
return {
data: boards,
hasBoards: boards.length > 1,
};
},
});
const handleChange = useCallback(
(v: string | null) => {
dispatch(
fieldBoardValueChanged({
nodeId,
fieldName: field.name,
value: v && v !== 'none' ? { board_id: v } : undefined,
})
);
},
[dispatch, field.name, nodeId]
);
return (
<IAIMantineSearchableSelect
className="nowheel nodrag"
value={field.value?.board_id ?? 'none'}
data={data}
onChange={handleChange}
disabled={!hasBoards}
/>
);
};
export default memo(BoardInputFieldComponent);

View File

@@ -65,11 +65,6 @@ const SchedulerInputField = (
return (
<IAIMantineSearchableSelect
className="nowheel nodrag"
sx={{
'.mantine-Select-dropdown': {
width: '14rem !important',
},
}}
value={field.value}
data={data}
onChange={handleChange}

View File

@@ -143,7 +143,7 @@ export const useBuildNodeData = () => {
notes: '',
isOpen: true,
embedWorkflow: false,
isIntermediate: true,
isIntermediate: type === 'save_image' ? false : true,
inputs,
outputs,
useCache: template.useCache,

View File

@@ -30,6 +30,7 @@ import {
import { v4 as uuidv4 } from 'uuid';
import { DRAG_HANDLE_CLASSNAME } from '../types/constants';
import {
BoardInputFieldValue,
BooleanInputFieldValue,
ColorInputFieldValue,
ControlNetModelInputFieldValue,
@@ -494,6 +495,12 @@ const nodesSlice = createSlice({
) => {
fieldValueReducer(state, action);
},
fieldBoardValueChanged: (
state,
action: FieldValueAction<BoardInputFieldValue>
) => {
fieldValueReducer(state, action);
},
fieldImageValueChanged: (
state,
action: FieldValueAction<ImageInputFieldValue>
@@ -897,6 +904,7 @@ export const {
imageCollectionFieldValueChanged,
fieldStringValueChanged,
fieldNumberValueChanged,
fieldBoardValueChanged,
fieldBooleanValueChanged,
fieldImageValueChanged,
fieldColorValueChanged,

View File

@@ -1,4 +1,9 @@
import { FieldType, FieldUIConfig } from './types';
import {
FieldType,
FieldTypeMap,
FieldTypeMapWithNumber,
FieldUIConfig,
} from './types';
import { t } from 'i18next';
export const HANDLE_TOOLTIP_OPEN_DELAY = 500;
@@ -28,7 +33,7 @@ export const COLLECTION_TYPES: FieldType[] = [
'ColorCollection',
];
export const POLYMORPHIC_TYPES = [
export const POLYMORPHIC_TYPES: FieldType[] = [
'IntegerPolymorphic',
'BooleanPolymorphic',
'FloatPolymorphic',
@@ -40,7 +45,7 @@ export const POLYMORPHIC_TYPES = [
'ColorPolymorphic',
];
export const MODEL_TYPES = [
export const MODEL_TYPES: FieldType[] = [
'IPAdapterModelField',
'ControlNetModelField',
'LoRAModelField',
@@ -54,7 +59,7 @@ export const MODEL_TYPES = [
'ClipField',
];
export const COLLECTION_MAP = {
export const COLLECTION_MAP: FieldTypeMapWithNumber = {
integer: 'IntegerCollection',
boolean: 'BooleanCollection',
number: 'FloatCollection',
@@ -71,7 +76,7 @@ export const isCollectionItemType = (
): itemType is keyof typeof COLLECTION_MAP =>
Boolean(itemType && itemType in COLLECTION_MAP);
export const SINGLE_TO_POLYMORPHIC_MAP = {
export const SINGLE_TO_POLYMORPHIC_MAP: FieldTypeMapWithNumber = {
integer: 'IntegerPolymorphic',
boolean: 'BooleanPolymorphic',
number: 'FloatPolymorphic',
@@ -84,7 +89,7 @@ export const SINGLE_TO_POLYMORPHIC_MAP = {
ColorField: 'ColorPolymorphic',
};
export const POLYMORPHIC_TO_SINGLE_MAP = {
export const POLYMORPHIC_TO_SINGLE_MAP: FieldTypeMap = {
IntegerPolymorphic: 'integer',
BooleanPolymorphic: 'boolean',
FloatPolymorphic: 'float',
@@ -96,7 +101,7 @@ export const POLYMORPHIC_TO_SINGLE_MAP = {
ColorPolymorphic: 'ColorField',
};
export const TYPES_WITH_INPUT_COMPONENTS = [
export const TYPES_WITH_INPUT_COMPONENTS: FieldType[] = [
'string',
'StringPolymorphic',
'boolean',
@@ -117,6 +122,7 @@ export const TYPES_WITH_INPUT_COMPONENTS = [
'SDXLMainModelField',
'Scheduler',
'IPAdapterModelField',
'BoardField',
];
export const isPolymorphicItemType = (
@@ -240,6 +246,11 @@ export const FIELDS: Record<FieldType, FieldUIConfig> = {
description: t('nodes.imageFieldDescription'),
title: t('nodes.imageField'),
},
BoardField: {
color: 'purple.500',
description: t('nodes.imageFieldDescription'),
title: t('nodes.imageField'),
},
ImagePolymorphic: {
color: 'purple.500',
description: t('nodes.imagePolymorphicDescription'),

View File

@@ -72,6 +72,7 @@ export type FieldUIConfig = {
// TODO: Get this from the OpenAPI schema? may be tricky...
export const zFieldType = z.enum([
'BoardField',
'boolean',
'BooleanCollection',
'BooleanPolymorphic',
@@ -119,6 +120,10 @@ export const zFieldType = z.enum([
]);
export type FieldType = z.infer<typeof zFieldType>;
export type FieldTypeMap = { [key in FieldType]?: FieldType };
export type FieldTypeMapWithNumber = {
[key in FieldType | 'number']?: FieldType;
};
export const zReservedFieldType = z.enum([
'WorkflowField',
@@ -187,6 +192,11 @@ export const zImageField = z.object({
});
export type ImageField = z.infer<typeof zImageField>;
export const zBoardField = z.object({
board_id: z.string().trim().min(1),
});
export type BoardField = z.infer<typeof zBoardField>;
export const zLatentsField = z.object({
latents_name: z.string().trim().min(1),
seed: z.number().int().optional(),
@@ -494,6 +504,12 @@ export const zImageInputFieldValue = zInputFieldValueBase.extend({
});
export type ImageInputFieldValue = z.infer<typeof zImageInputFieldValue>;
export const zBoardInputFieldValue = zInputFieldValueBase.extend({
type: z.literal('BoardField'),
value: zBoardField.optional(),
});
export type BoardInputFieldValue = z.infer<typeof zBoardInputFieldValue>;
export const zImagePolymorphicInputFieldValue = zInputFieldValueBase.extend({
type: z.literal('ImagePolymorphic'),
value: zImageField.optional(),
@@ -630,6 +646,7 @@ export type SchedulerInputFieldValue = z.infer<
>;
export const zInputFieldValue = z.discriminatedUnion('type', [
zBoardInputFieldValue,
zBooleanCollectionInputFieldValue,
zBooleanInputFieldValue,
zBooleanPolymorphicInputFieldValue,
@@ -770,6 +787,11 @@ export type BooleanPolymorphicInputFieldTemplate = Omit<
type: 'BooleanPolymorphic';
};
export type BoardInputFieldTemplate = InputFieldTemplateBase & {
default: BoardField;
type: 'BoardField';
};
export type ImageInputFieldTemplate = InputFieldTemplateBase & {
default: ImageField;
type: 'ImageField';
@@ -952,6 +974,7 @@ export type WorkflowInputFieldTemplate = InputFieldTemplateBase & {
* maximum length, pattern to match, etc).
*/
export type InputFieldTemplate =
| BoardInputFieldTemplate
| BooleanCollectionInputFieldTemplate
| BooleanPolymorphicInputFieldTemplate
| BooleanInputFieldTemplate

View File

@@ -62,6 +62,8 @@ import {
ConditioningField,
IPAdapterInputFieldTemplate,
IPAdapterModelInputFieldTemplate,
BoardInputFieldTemplate,
InputFieldTemplate,
} from '../types/types';
import { ControlField } from 'services/api/types';
@@ -450,6 +452,19 @@ const buildIPAdapterModelInputFieldTemplate = ({
return template;
};
const buildBoardInputFieldTemplate = ({
schemaObject,
baseField,
}: BuildInputFieldArg): BoardInputFieldTemplate => {
const template: BoardInputFieldTemplate = {
...baseField,
type: 'BoardField',
default: schemaObject.default ?? undefined,
};
return template;
};
const buildImageInputFieldTemplate = ({
schemaObject,
baseField,
@@ -851,7 +866,10 @@ export const getFieldType = (
return;
};
const TEMPLATE_BUILDER_MAP = {
const TEMPLATE_BUILDER_MAP: {
[key in FieldType]?: (arg: BuildInputFieldArg) => InputFieldTemplate;
} = {
BoardField: buildBoardInputFieldTemplate,
boolean: buildBooleanInputFieldTemplate,
BooleanCollection: buildBooleanCollectionInputFieldTemplate,
BooleanPolymorphic: buildBooleanPolymorphicInputFieldTemplate,
@@ -937,7 +955,13 @@ export const buildInputFieldTemplate = (
return;
}
return TEMPLATE_BUILDER_MAP[fieldType]({
const builder = TEMPLATE_BUILDER_MAP[fieldType];
if (!builder) {
return;
}
return builder({
schemaObject: fieldSchema,
baseField,
});

View File

@@ -1,7 +1,10 @@
import { InputFieldTemplate, InputFieldValue } from '../types/types';
import { FieldType, InputFieldTemplate, InputFieldValue } from '../types/types';
const FIELD_VALUE_FALLBACK_MAP = {
const FIELD_VALUE_FALLBACK_MAP: {
[key in FieldType]: InputFieldValue['value'];
} = {
enum: '',
BoardField: undefined,
boolean: false,
BooleanCollection: [],
BooleanPolymorphic: false,