diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes.ts index 9bfb784439..4b30d14e74 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes.ts @@ -9,7 +9,6 @@ import { isIntegerFieldCollectionInputInstance, isStringFieldCollectionInputInstance, } from 'features/nodes/types/field'; -import { getNumberFieldCollectionValue } from 'features/nodes/types/fieldValidators'; import type { InvocationNodeEdge } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation'; import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph'; @@ -107,7 +106,7 @@ export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) = // Find outgoing edges from the batch node, we will remove these from the graph and create batch data collection items from them instead const edgesFromStringBatch = nodes.edges.filter((e) => e.source === node.id && e.sourceHandle === 'value'); - addBatchDataCollectionItem(edgesFromStringBatch, getNumberFieldCollectionValue(integers.value)); + addBatchDataCollectionItem(edgesFromStringBatch, integers.value); } // Grab float batch nodes for special handling @@ -126,7 +125,7 @@ export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) = // Find outgoing edges from the batch node, we will remove these from the graph and create batch data collection items from them instead const edgesFromStringBatch = nodes.edges.filter((e) => e.source === node.id && e.sourceHandle === 'value'); - addBatchDataCollectionItem(edgesFromStringBatch, getNumberFieldCollectionValue(floats.value)); + addBatchDataCollectionItem(edgesFromStringBatch, floats.value); } const batchConfig: BatchConfig = { diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberFieldCollectionInputComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberFieldCollectionInputComponent.tsx index 5d07ee97c3..bfe6685c9a 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberFieldCollectionInputComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberFieldCollectionInputComponent.tsx @@ -1,34 +1,21 @@ import type { SystemStyleObject } from '@invoke-ai/ui-library'; -import { - Box, - CompositeNumberInput, - Flex, - FormControl, - FormLabel, - Grid, - GridItem, - IconButton, - Text, -} from '@invoke-ai/ui-library'; +import { Box, CompositeNumberInput, Flex, Grid, GridItem, IconButton } from '@invoke-ai/ui-library'; import { NUMPY_RAND_MAX } from 'app/constants'; import { useAppStore } from 'app/store/nanostores/store'; -import { useAppDispatch } from 'app/store/storeHooks'; import { getOverlayScrollbarsParams, overlayScrollbarsStyles } from 'common/components/OverlayScrollbars/constants'; import { useFieldIsInvalid } from 'features/nodes/hooks/useFieldIsInvalid'; import { fieldNumberCollectionValueChanged } from 'features/nodes/store/nodesSlice'; import type { FloatFieldCollectionInputInstance, FloatFieldCollectionInputTemplate, - FloatStartStepCountGenerator, IntegerFieldCollectionInputInstance, IntegerFieldCollectionInputTemplate, - IntegerStartStepCountGenerator, } from 'features/nodes/types/field'; import { isNil } from 'lodash-es'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiLightbulbFill, PiPencilSimpleFill, PiPlusBold, PiXBold } from 'react-icons/pi'; +import { PiPlusBold, PiXBold } from 'react-icons/pi'; import type { FieldComponentProps } from './types'; @@ -50,40 +37,68 @@ export const NumberFieldCollectionInputComponent = memo( ) => { const { nodeId, field, fieldTemplate } = props; const store = useAppStore(); + const isInvalid = useFieldIsInvalid(nodeId, field.name); const isIntegerField = useMemo(() => fieldTemplate.type.name === 'IntegerField', [fieldTemplate.type]); - const entryMode = useMemo(() => { - if (!field.value) { - return 'manual'; - } - if (Array.isArray(field.value)) { - return 'manual'; - } - return 'step'; - }, [field.value]); - - const toggleEntryMode = useCallback(() => { - if (!field.value || Array.isArray(field.value)) { - const newValue: IntegerStartStepCountGenerator | FloatStartStepCountGenerator = isIntegerField - ? { type: 'integer-start-step-count-generator', start: 0, step: 1, count: 1 } - : { type: 'float-start-step-count-generator', start: 0, step: 1, count: 1 }; + const onRemoveNumber = useCallback( + (index: number) => { + const newValue = field.value ? [...field.value] : []; + newValue.splice(index, 1); store.dispatch(fieldNumberCollectionValueChanged({ nodeId, fieldName: field.name, value: newValue })); - } else { - store.dispatch( - fieldNumberCollectionValueChanged({ - nodeId, - fieldName: field.name, - value: [0], - }) - ); - } - }, [field.name, field.value, isIntegerField, nodeId, store]); + }, + [field.name, field.value, nodeId, store] + ); + + const onChangeNumber = useCallback( + (index: number, value: number) => { + const newValue = field.value ? [...field.value] : []; + newValue[index] = value; + store.dispatch(fieldNumberCollectionValueChanged({ nodeId, fieldName: field.name, value: newValue })); + }, + [field.name, field.value, nodeId, store] + ); const onAddNumber = useCallback(() => { - const newValue = field.value && Array.isArray(field.value) ? [...field.value, 0] : [0]; + const newValue = field.value ? [...field.value, 0] : [0]; store.dispatch(fieldNumberCollectionValueChanged({ nodeId, fieldName: field.name, value: newValue })); - }, [field.value, field.name, store, nodeId]); + }, [field.name, field.value, nodeId, store]); + + const min = useMemo(() => { + let min = -NUMPY_RAND_MAX; + if (!isNil(fieldTemplate.minimum)) { + min = fieldTemplate.minimum; + } + if (!isNil(fieldTemplate.exclusiveMinimum)) { + min = fieldTemplate.exclusiveMinimum + 0.01; + } + return min; + }, [fieldTemplate.exclusiveMinimum, fieldTemplate.minimum]); + + const max = useMemo(() => { + let max = NUMPY_RAND_MAX; + if (!isNil(fieldTemplate.maximum)) { + max = fieldTemplate.maximum; + } + if (!isNil(fieldTemplate.exclusiveMaximum)) { + max = fieldTemplate.exclusiveMaximum - 0.01; + } + return max; + }, [fieldTemplate.exclusiveMaximum, fieldTemplate.maximum]); + + const step = useMemo(() => { + if (isNil(fieldTemplate.multipleOf)) { + return isIntegerField ? 1 : 0.1; + } + return fieldTemplate.multipleOf; + }, [fieldTemplate.multipleOf, isIntegerField]); + + const fineStep = useMemo(() => { + if (isNil(fieldTemplate.multipleOf)) { + return isIntegerField ? 1 : 0.01; + } + return fieldTemplate.multipleOf; + }, [fieldTemplate.multipleOf, isIntegerField]); return ( - - {!field.value || - (Array.isArray(field.value) && ( - <> - Manual + {(!field.value || field.value.length === 0) && ( + + } + variant="ghost" + size="sm" + /> + + )} + {field.value && field.value.length > 0 && ( + + + } variant="ghost" size="sm" /> - - ))} - {field.value && !Array.isArray(field.value) && ( - <> - Generator - - )} - : } - variant="ghost" - size="sm" - /> - - {field.value && !Array.isArray(field.value) && ( - - )} - {field.value && Array.isArray(field.value) && field.value.length > 0 && ( - + {field.value.map((value, index) => ( + + + + ))} + + + )} ); @@ -144,163 +164,6 @@ export const NumberFieldCollectionInputComponent = memo( NumberFieldCollectionInputComponent.displayName = 'NumberFieldCollectionInputComponent'; -const GeneratorEntry = ({ - nodeId, - fieldName, - value, - fieldTemplate, -}: { - nodeId: string; - fieldName: string; - value: IntegerStartStepCountGenerator | FloatStartStepCountGenerator; - fieldTemplate: IntegerFieldCollectionInputTemplate | FloatFieldCollectionInputTemplate; -}) => { - const dispatch = useAppDispatch(); - const isIntegerField = useMemo(() => fieldTemplate.type.name === 'IntegerField', [fieldTemplate.type]); - const onChangeStart = useCallback( - (v: number) => { - const newValue = { ...value, start: v }; - dispatch(fieldNumberCollectionValueChanged({ nodeId, fieldName, value: newValue })); - }, - [dispatch, fieldName, nodeId, value] - ); - const onChangeCount = useCallback( - (v: number) => { - const newValue = { ...value, count: v }; - dispatch(fieldNumberCollectionValueChanged({ nodeId, fieldName, value: newValue })); - }, - [dispatch, fieldName, nodeId, value] - ); - const onChangeStep = useCallback( - (v: number) => { - const newValue = { ...value, step: v }; - dispatch(fieldNumberCollectionValueChanged({ nodeId, fieldName, value: newValue })); - }, - [dispatch, fieldName, nodeId, value] - ); - - return ( - - - Start - - - - Count - - - - Step - - - - ); -}; - -const ManualEntry = ({ - nodeId, - fieldName, - value, - fieldTemplate, -}: { - nodeId: string; - fieldName: string; - value: number[]; - fieldTemplate: IntegerFieldCollectionInputTemplate | FloatFieldCollectionInputTemplate; -}) => { - const dispatch = useAppDispatch(); - const isIntegerField = useMemo(() => fieldTemplate.type.name === 'IntegerField', [fieldTemplate.type]); - - const onRemoveNumber = useCallback( - (index: number) => { - const newValue = [...value]; - newValue.splice(index, 1); - dispatch(fieldNumberCollectionValueChanged({ nodeId, fieldName, value: newValue })); - }, - [value, dispatch, nodeId, fieldName] - ); - - const onChangeNumber = useCallback( - (index: number, num: number) => { - const newValue = [...value]; - newValue[index] = num; - dispatch(fieldNumberCollectionValueChanged({ nodeId, fieldName, value: newValue })); - }, - [value, dispatch, nodeId, fieldName] - ); - - const min = useMemo(() => { - let min = -NUMPY_RAND_MAX; - if (!isNil(fieldTemplate.minimum)) { - min = fieldTemplate.minimum; - } - if (!isNil(fieldTemplate.exclusiveMinimum)) { - min = fieldTemplate.exclusiveMinimum + 0.01; - } - return min; - }, [fieldTemplate.exclusiveMinimum, fieldTemplate.minimum]); - - const max = useMemo(() => { - let max = NUMPY_RAND_MAX; - if (!isNil(fieldTemplate.maximum)) { - max = fieldTemplate.maximum; - } - if (!isNil(fieldTemplate.exclusiveMaximum)) { - max = fieldTemplate.exclusiveMaximum - 0.01; - } - return max; - }, [fieldTemplate.exclusiveMaximum, fieldTemplate.maximum]); - - const step = useMemo(() => { - if (isNil(fieldTemplate.multipleOf)) { - return isIntegerField ? 1 : 0.1; - } - return fieldTemplate.multipleOf; - }, [fieldTemplate.multipleOf, isIntegerField]); - - const fineStep = useMemo(() => { - if (isNil(fieldTemplate.multipleOf)) { - return isIntegerField ? 1 : 0.01; - } - return fieldTemplate.multipleOf; - }, [fieldTemplate.multipleOf, isIntegerField]); - - return ( - - - - {value.map((value, index) => ( - - - - ))} - - - - ); -}; - type NumberListItemContentProps = { value: number; index: number; diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts index 9dde995d42..5b031dc072 100644 --- a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts +++ b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts @@ -15,7 +15,6 @@ import type { ControlNetModelFieldValue, EnumFieldValue, FieldValue, - FloatFieldCollectionValue, FloatFieldValue, FluxVAEModelFieldValue, ImageFieldCollectionValue, @@ -323,10 +322,7 @@ export const nodesSlice = createSlice({ fieldNumberValueChanged: (state, action: FieldValueAction) => { fieldValueReducer(state, action, zIntegerFieldValue.or(zFloatFieldValue)); }, - fieldNumberCollectionValueChanged: ( - state, - action: FieldValueAction - ) => { + fieldNumberCollectionValueChanged: (state, action: FieldValueAction) => { fieldValueReducer(state, action, zIntegerFieldCollectionValue.or(zFloatFieldCollectionValue)); }, fieldBooleanValueChanged: (state, action: FieldValueAction) => { diff --git a/invokeai/frontend/web/src/features/nodes/types/field.ts b/invokeai/frontend/web/src/features/nodes/types/field.ts index 4355efbf3b..1ccc6ec9bb 100644 --- a/invokeai/frontend/web/src/features/nodes/types/field.ts +++ b/invokeai/frontend/web/src/features/nodes/types/field.ts @@ -279,17 +279,7 @@ export const isIntegerFieldInputTemplate = buildTypeGuard(zIntegerFieldInputTemp // #endregion // #region IntegerField Collection -const zIntegerStartStepCountGenerator = z.object({ - type: z.literal('integer-start-step-count-generator'), - start: z.number().int(), - step: z.number().int(), - count: z.number().int().gte(1), -}); -export type IntegerStartStepCountGenerator = z.infer; -export const isIntegerStartStepCountGenerator = buildTypeGuard(zIntegerStartStepCountGenerator); -export const zIntegerFieldCollectionValue = z - .union([z.array(zIntegerFieldValue), zIntegerStartStepCountGenerator]) - .optional(); +export const zIntegerFieldCollectionValue = z.array(zIntegerFieldValue).optional(); const zIntegerFieldCollectionInputInstance = zFieldInputInstanceBase.extend({ value: zIntegerFieldCollectionValue, }); @@ -327,6 +317,7 @@ export const isIntegerFieldCollectionInputTemplate = buildTypeGuard(zIntegerFiel // #endregion // #region FloatField + export const zFloatFieldValue = z.number(); const zFloatFieldInputInstance = zFieldInputInstanceBase.extend({ value: zFloatFieldValue, @@ -352,17 +343,7 @@ export const isFloatFieldInputTemplate = buildTypeGuard(zFloatFieldInputTemplate // #endregion // #region FloatField Collection -const zFloatStartStepCountGenerator = z.object({ - type: z.literal('float-start-step-count-generator'), - start: z.number(), - step: z.number(), - count: z.number().gte(1), -}); -export type FloatStartStepCountGenerator = z.infer; -export const isFloatStartStepCountGenerator = buildTypeGuard(zFloatStartStepCountGenerator); -export const zFloatFieldCollectionValue = z - .union([z.array(zFloatFieldValue), zFloatStartStepCountGenerator]) - .optional(); +export const zFloatFieldCollectionValue = z.array(zFloatFieldValue).optional(); const zFloatFieldCollectionInputInstance = zFieldInputInstanceBase.extend({ value: zFloatFieldCollectionValue, }); diff --git a/invokeai/frontend/web/src/features/nodes/types/fieldValidators.ts b/invokeai/frontend/web/src/features/nodes/types/fieldValidators.ts index 9fc2fd1909..4dbfc588c0 100644 --- a/invokeai/frontend/web/src/features/nodes/types/fieldValidators.ts +++ b/invokeai/frontend/web/src/features/nodes/types/fieldValidators.ts @@ -8,7 +8,6 @@ import type { StringFieldCollectionInputTemplate, StringFieldCollectionValue, } from 'features/nodes/types/field'; -import { numberStartStepCountGenerator } from 'features/nodes/types/generators'; import { t } from 'i18next'; export const validateImageFieldCollectionValue = ( @@ -68,24 +67,12 @@ export const validateStringFieldCollectionValue = ( return reasons; }; -export const getNumberFieldCollectionValue = ( - fieldValue: NonNullable | NonNullable -): number[] => { - if (Array.isArray(fieldValue)) { - return fieldValue; - } - return numberStartStepCountGenerator(fieldValue); -}; - export const validateNumberFieldCollectionValue = ( - fieldValue: NonNullable | NonNullable, + value: NonNullable | NonNullable, template: IntegerFieldCollectionInputTemplate | FloatFieldCollectionInputTemplate ): string[] => { const reasons: string[] = []; const { minItems, maxItems, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf } = template; - - const value = getNumberFieldCollectionValue(fieldValue); - const count = value.length; // Image collections may have min or max items to validate diff --git a/invokeai/frontend/web/src/features/nodes/types/generators.ts b/invokeai/frontend/web/src/features/nodes/types/generators.ts deleted file mode 100644 index ddcf958632..0000000000 --- a/invokeai/frontend/web/src/features/nodes/types/generators.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { FloatStartStepCountGenerator, IntegerStartStepCountGenerator } from 'features/nodes/types/field'; - -export const numberStartStepCountGenerator = ({ - start, - step, - count, -}: FloatStartStepCountGenerator | IntegerStartStepCountGenerator): number[] => { - return Array.from({ length: count }, (_, i) => start + i * step); -};