diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNode.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNode.tsx index 624578003e..8f6a2531a0 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNode.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNode.tsx @@ -1,12 +1,14 @@ -import { Flex } from '@chakra-ui/react'; +import { Flex, Grid, GridItem } from '@chakra-ui/react'; import { memo } from 'react'; import InvocationNodeFooter from './InvocationNodeFooter'; import InvocationNodeHeader from './InvocationNodeHeader'; import NodeWrapper from '../common/NodeWrapper'; import OutputField from './fields/OutputField'; import InputField from './fields/InputField'; -import { useFieldNames } from 'features/nodes/hooks/useFieldNames'; +import { useOutputFieldNames } from 'features/nodes/hooks/useOutputFieldNames'; import { useWithFooter } from 'features/nodes/hooks/useWithFooter'; +import { useConnectionInputFieldNames } from 'features/nodes/hooks/useConnectionInputFieldNames'; +import { useAnyOrDirectInputFieldNames } from 'features/nodes/hooks/useAnyOrDirectInputFieldNames'; type Props = { nodeId: string; @@ -17,8 +19,9 @@ type Props = { }; const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => { - const inputFieldNames = useFieldNames(nodeId, 'input'); - const outputFieldNames = useFieldNames(nodeId, 'output'); + const inputConnectionFieldNames = useConnectionInputFieldNames(nodeId); + const inputAnyOrDirectFieldNames = useAnyOrDirectInputFieldNames(nodeId); + const outputFieldNames = useOutputFieldNames(nodeId); const withFooter = useWithFooter(nodeId); return ( @@ -44,14 +47,27 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => { }} > - {outputFieldNames.map((fieldName) => ( - - ))} - {inputFieldNames.map((fieldName) => ( + + {inputConnectionFieldNames.map((fieldName, i) => ( + + + + ))} + {outputFieldNames.map((fieldName, i) => ( + + + + ))} + + {inputAnyOrDirectFieldNames.map((fieldName) => ( { const handleSubmit = useCallback( async (newTitle: string) => { - if (newTitle === label || newTitle === fieldTemplateTitle) { + if (newTitle && (newTitle === label || newTitle === fieldTemplateTitle)) { return; } setLocalTitle(newTitle || fieldTemplateTitle || 'Unknown Field'); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputField.tsx index 7c88318fa1..e717423f65 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputField.tsx @@ -1,10 +1,4 @@ -import { - Flex, - FormControl, - FormLabel, - Spacer, - Tooltip, -} from '@chakra-ui/react'; +import { Flex, FormControl, FormLabel, Tooltip } from '@chakra-ui/react'; import { useConnectionState } from 'features/nodes/hooks/useConnectionState'; import { useFieldTemplate } from 'features/nodes/hooks/useFieldTemplate'; import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants'; @@ -42,7 +36,6 @@ const OutputField = ({ nodeId, fieldName }: Props) => { return ( - {children} diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useAnyOrDirectInputFieldNames.ts b/invokeai/frontend/web/src/features/nodes/hooks/useAnyOrDirectInputFieldNames.ts new file mode 100644 index 0000000000..21f19295d0 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/hooks/useAnyOrDirectInputFieldNames.ts @@ -0,0 +1,36 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; +import { useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { map } from 'lodash-es'; +import { useMemo } from 'react'; +import { isInvocationNode } from '../types/types'; + +export const useAnyOrDirectInputFieldNames = (nodeId: string) => { + const selector = useMemo( + () => + createSelector( + stateSelector, + ({ nodes }) => { + const node = nodes.nodes.find((node) => node.id === nodeId); + if (!isInvocationNode(node)) { + return []; + } + const nodeTemplate = nodes.nodeTemplates[node.data.type]; + if (!nodeTemplate) { + return []; + } + return map(nodeTemplate.inputs) + .filter((field) => ['any', 'direct'].includes(field.input)) + .sort((a, b) => (a.ui_order ?? 0) - (b.ui_order ?? 0)) + .map((field) => field.name) + .filter((fieldName) => fieldName !== 'is_intermediate'); + }, + defaultSelectorOptions + ), + [nodeId] + ); + + const fieldNames = useAppSelector(selector); + return fieldNames; +}; diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useConnectionInputFieldNames.ts b/invokeai/frontend/web/src/features/nodes/hooks/useConnectionInputFieldNames.ts new file mode 100644 index 0000000000..b7eef02e9d --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/hooks/useConnectionInputFieldNames.ts @@ -0,0 +1,36 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; +import { useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { map } from 'lodash-es'; +import { useMemo } from 'react'; +import { isInvocationNode } from '../types/types'; + +export const useConnectionInputFieldNames = (nodeId: string) => { + const selector = useMemo( + () => + createSelector( + stateSelector, + ({ nodes }) => { + const node = nodes.nodes.find((node) => node.id === nodeId); + if (!isInvocationNode(node)) { + return []; + } + const nodeTemplate = nodes.nodeTemplates[node.data.type]; + if (!nodeTemplate) { + return []; + } + return map(nodeTemplate.inputs) + .filter((field) => field.input === 'connection') + .sort((a, b) => (a.ui_order ?? 0) - (b.ui_order ?? 0)) + .map((field) => field.name) + .filter((fieldName) => fieldName !== 'is_intermediate'); + }, + defaultSelectorOptions + ), + [nodeId] + ); + + const fieldNames = useAppSelector(selector); + return fieldNames; +}; diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldNames.ts b/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldNames.ts similarity index 63% rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldNames.ts rename to invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldNames.ts index e2c7126012..41d3a1ac91 100644 --- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldNames.ts +++ b/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldNames.ts @@ -4,10 +4,9 @@ import { useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { map } from 'lodash-es'; import { useMemo } from 'react'; -import { KIND_MAP } from '../types/constants'; import { isInvocationNode } from '../types/types'; -export const useFieldNames = (nodeId: string, kind: 'input' | 'output') => { +export const useOutputFieldNames = (nodeId: string) => { const selector = useMemo( () => createSelector( @@ -17,13 +16,18 @@ export const useFieldNames = (nodeId: string, kind: 'input' | 'output') => { if (!isInvocationNode(node)) { return []; } - return map(node.data[KIND_MAP[kind]], (field) => field.name).filter( - (fieldName) => fieldName !== 'is_intermediate' - ); + const nodeTemplate = nodes.nodeTemplates[node.data.type]; + if (!nodeTemplate) { + return []; + } + return map(nodeTemplate.outputs) + .sort((a, b) => (a.ui_order ?? 0) - (b.ui_order ?? 0)) + .map((field) => field.name) + .filter((fieldName) => fieldName !== 'is_intermediate'); }, defaultSelectorOptions ), - [kind, nodeId] + [nodeId] ); const fieldNames = useAppSelector(selector); diff --git a/invokeai/frontend/web/src/features/nodes/types/types.ts b/invokeai/frontend/web/src/features/nodes/types/types.ts index af6b80c5d5..35be598176 100644 --- a/invokeai/frontend/web/src/features/nodes/types/types.ts +++ b/invokeai/frontend/web/src/features/nodes/types/types.ts @@ -203,7 +203,7 @@ export type OutputFieldTemplate = { type: FieldType; title: string; description: string; -}; +} & _OutputField; /** * Indicates the kind of input(s) this field may have. diff --git a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts index ef96b8b485..ca7674a021 100644 --- a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts +++ b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts @@ -467,7 +467,7 @@ export const buildInputFieldTemplate = ( const fieldType = getFieldType(fieldSchema); // console.log('input fieldType', fieldType); - const { input, ui_hidden, ui_component, ui_type } = fieldSchema; + const { input, ui_hidden, ui_component, ui_type, ui_order } = fieldSchema; const extra = { input, @@ -475,6 +475,7 @@ export const buildInputFieldTemplate = ( ui_component, ui_type, required: nodeSchema.required?.includes(name) ?? false, + ui_order, }; const baseField = { diff --git a/invokeai/frontend/web/src/features/nodes/util/parseSchema.ts b/invokeai/frontend/web/src/features/nodes/util/parseSchema.ts index c2f49d205d..4bbb754d58 100644 --- a/invokeai/frontend/web/src/features/nodes/util/parseSchema.ts +++ b/invokeai/frontend/web/src/features/nodes/util/parseSchema.ts @@ -142,6 +142,9 @@ export const parseSchema = ( title: property.title ?? '', description: property.description ?? '', type: fieldType, + ui_hidden: property.ui_hidden ?? false, + ui_type: property.ui_type, + ui_order: property.ui_order, }; return outputsAccumulator;