mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
chore(ui): Cleanup Invocation Component
This commit is contained in:
parent
5f498e10bd
commit
77fa7519c4
@ -49,7 +49,7 @@ export const AddNodeMenu = () => {
|
|||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton as={IconButton} aria-label="Add Node" icon={<FaPlus />} />
|
<MenuButton as={IconButton} aria-label="Add Node" icon={<FaPlus />} />
|
||||||
<MenuList>
|
<MenuList overflowY="scroll" height={400}>
|
||||||
{map(invocationTemplates, ({ title, description, type }, key) => {
|
{map(invocationTemplates, ({ title, description, type }, key) => {
|
||||||
return (
|
return (
|
||||||
<Tooltip key={key} label={description} placement="end" hasArrow>
|
<Tooltip key={key} label={description} placement="end" hasArrow>
|
||||||
|
@ -19,11 +19,11 @@ const handleBaseStyles: CSSProperties = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const inputHandleStyles: CSSProperties = {
|
const inputHandleStyles: CSSProperties = {
|
||||||
left: '-1.7rem',
|
left: '-1.6rem',
|
||||||
};
|
};
|
||||||
|
|
||||||
const outputHandleStyles: CSSProperties = {
|
const outputHandleStyles: CSSProperties = {
|
||||||
right: '-1.7rem',
|
right: '-0.6rem',
|
||||||
};
|
};
|
||||||
|
|
||||||
const requiredConnectionStyles: CSSProperties = {
|
const requiredConnectionStyles: CSSProperties = {
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import { Flex, Heading, Tooltip, Icon } from '@chakra-ui/react';
|
||||||
|
import { InvocationTemplate } from 'features/nodes/types/types';
|
||||||
|
import { MutableRefObject } from 'react';
|
||||||
|
import { FaInfoCircle } from 'react-icons/fa';
|
||||||
|
|
||||||
|
interface IAINodeHeaderProps {
|
||||||
|
nodeId: string;
|
||||||
|
template: MutableRefObject<InvocationTemplate | undefined>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function IAINodeHeader(props: IAINodeHeaderProps) {
|
||||||
|
const { nodeId, template } = props;
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
borderRadius="sm"
|
||||||
|
justifyContent="space-between"
|
||||||
|
background="base.700"
|
||||||
|
px={2}
|
||||||
|
py={1}
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Tooltip label={nodeId}>
|
||||||
|
<Heading size="sm" fontWeight={600} color="base.100">
|
||||||
|
{template.current?.title}
|
||||||
|
</Heading>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip
|
||||||
|
label={template.current?.description}
|
||||||
|
placement="top"
|
||||||
|
hasArrow
|
||||||
|
shouldWrapChildren
|
||||||
|
>
|
||||||
|
<Icon color="base.300" as={FaInfoCircle} />
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,147 @@
|
|||||||
|
import {
|
||||||
|
InputFieldTemplate,
|
||||||
|
InputFieldValue,
|
||||||
|
InvocationTemplate,
|
||||||
|
} from 'features/nodes/types/types';
|
||||||
|
import { MutableRefObject, ReactNode } from 'react';
|
||||||
|
import { map } from 'lodash';
|
||||||
|
import { useAppSelector } from 'app/storeHooks';
|
||||||
|
import { RootState } from 'app/store';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
HStack,
|
||||||
|
Tooltip,
|
||||||
|
Icon,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { FieldHandle } from '../FieldHandle';
|
||||||
|
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
|
||||||
|
import { InputFieldComponent } from '../InputFieldComponent';
|
||||||
|
import { FaInfoCircle } from 'react-icons/fa';
|
||||||
|
|
||||||
|
interface IAINodeInputProps {
|
||||||
|
nodeId: string;
|
||||||
|
|
||||||
|
input: InputFieldValue;
|
||||||
|
template?: InputFieldTemplate | undefined;
|
||||||
|
connected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function IAINodeInput(props: IAINodeInputProps) {
|
||||||
|
const { nodeId, input, template, connected } = props;
|
||||||
|
const isValidConnection = useIsValidConnection();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
p={2}
|
||||||
|
key={input.id}
|
||||||
|
position="relative"
|
||||||
|
borderWidth={1}
|
||||||
|
borderRadius="md"
|
||||||
|
borderLeft="none"
|
||||||
|
borderRight="none"
|
||||||
|
borderColor={
|
||||||
|
!template
|
||||||
|
? 'error.400'
|
||||||
|
: !connected &&
|
||||||
|
['always', 'connectionOnly'].includes(
|
||||||
|
String(template?.inputRequirement)
|
||||||
|
) &&
|
||||||
|
input.value === undefined
|
||||||
|
? 'warning.400'
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FormControl isDisabled={!template ? true : connected} pl={2}>
|
||||||
|
{!template ? (
|
||||||
|
<HStack justifyContent="space-between" alignItems="center">
|
||||||
|
<FormLabel>Unknown input: {input.name}</FormLabel>
|
||||||
|
</HStack>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<HStack justifyContent="space-between" alignItems="center">
|
||||||
|
<HStack>
|
||||||
|
<FormLabel>{template?.title}</FormLabel>
|
||||||
|
<Tooltip
|
||||||
|
label={template?.description}
|
||||||
|
placement="top"
|
||||||
|
hasArrow
|
||||||
|
shouldWrapChildren
|
||||||
|
>
|
||||||
|
<Icon color="base.400" as={FaInfoCircle} />
|
||||||
|
</Tooltip>
|
||||||
|
</HStack>
|
||||||
|
<InputFieldComponent
|
||||||
|
nodeId={nodeId}
|
||||||
|
field={input}
|
||||||
|
template={template}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{!['never', 'directOnly'].includes(
|
||||||
|
template?.inputRequirement ?? ''
|
||||||
|
) && (
|
||||||
|
<FieldHandle
|
||||||
|
nodeId={nodeId}
|
||||||
|
field={template}
|
||||||
|
isValidConnection={isValidConnection}
|
||||||
|
handleType="target"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAINodeInputsProps {
|
||||||
|
nodeId: string;
|
||||||
|
template: MutableRefObject<InvocationTemplate | undefined>;
|
||||||
|
inputs: Record<string, InputFieldValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function IAINodeInputs(props: IAINodeInputsProps) {
|
||||||
|
const { nodeId, template, inputs } = props;
|
||||||
|
|
||||||
|
const connectedInputs = useAppSelector(
|
||||||
|
(state: RootState) => state.nodes.edges
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderIAINodeInputs = () => {
|
||||||
|
const IAINodeInputsToRender: ReactNode[] = [];
|
||||||
|
const inputSockets = map(inputs);
|
||||||
|
|
||||||
|
inputSockets.forEach((inputSocket) => {
|
||||||
|
const inputTemplate = template.current?.inputs[inputSocket.name];
|
||||||
|
|
||||||
|
const isConnected = Boolean(
|
||||||
|
connectedInputs.filter((connectedInput) => {
|
||||||
|
return (
|
||||||
|
connectedInput.target === nodeId &&
|
||||||
|
connectedInput.targetHandle === inputSocket.name
|
||||||
|
);
|
||||||
|
}).length
|
||||||
|
);
|
||||||
|
|
||||||
|
IAINodeInputsToRender.push(
|
||||||
|
<IAINodeInput
|
||||||
|
nodeId={nodeId}
|
||||||
|
input={inputSocket}
|
||||||
|
template={inputTemplate}
|
||||||
|
connected={isConnected}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex flexDir="column" gap={2} p={2}>
|
||||||
|
{IAINodeInputsToRender}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return renderIAINodeInputs();
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
import {
|
||||||
|
InvocationTemplate,
|
||||||
|
OutputFieldTemplate,
|
||||||
|
OutputFieldValue,
|
||||||
|
} from 'features/nodes/types/types';
|
||||||
|
import { MutableRefObject, ReactNode } from 'react';
|
||||||
|
import { map } from 'lodash';
|
||||||
|
import { useAppSelector } from 'app/storeHooks';
|
||||||
|
import { RootState } from 'app/store';
|
||||||
|
import { Box, Flex, FormControl, FormLabel, HStack } from '@chakra-ui/react';
|
||||||
|
import { FieldHandle } from '../FieldHandle';
|
||||||
|
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
|
||||||
|
|
||||||
|
interface IAINodeOutputProps {
|
||||||
|
nodeId: string;
|
||||||
|
output: OutputFieldValue;
|
||||||
|
template?: OutputFieldTemplate | undefined;
|
||||||
|
connected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function IAINodeOutput(props: IAINodeOutputProps) {
|
||||||
|
const { nodeId, output, template, connected } = props;
|
||||||
|
const isValidConnection = useIsValidConnection();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box key={output.id} position="relative">
|
||||||
|
<FormControl isDisabled={!template ? true : connected} paddingRight={3}>
|
||||||
|
{!template ? (
|
||||||
|
<HStack justifyContent="space-between" alignItems="center">
|
||||||
|
<FormLabel color="error.400">
|
||||||
|
Unknown Output: {output.name}
|
||||||
|
</FormLabel>
|
||||||
|
</HStack>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FormLabel textAlign="end" padding={1}>
|
||||||
|
{template?.title}
|
||||||
|
</FormLabel>
|
||||||
|
<FieldHandle
|
||||||
|
key={output.id}
|
||||||
|
nodeId={nodeId}
|
||||||
|
field={template}
|
||||||
|
isValidConnection={isValidConnection}
|
||||||
|
handleType="source"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAINodeOutputsProps {
|
||||||
|
nodeId: string;
|
||||||
|
template: MutableRefObject<InvocationTemplate | undefined>;
|
||||||
|
outputs: Record<string, OutputFieldValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function IAINodeOutputs(props: IAINodeOutputsProps) {
|
||||||
|
const { nodeId, template, outputs } = props;
|
||||||
|
|
||||||
|
const connectedInputs = useAppSelector(
|
||||||
|
(state: RootState) => state.nodes.edges
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderIAINodeOutputs = () => {
|
||||||
|
const IAINodeOutputsToRender: ReactNode[] = [];
|
||||||
|
const outputSockets = map(outputs);
|
||||||
|
|
||||||
|
outputSockets.forEach((outputSocket) => {
|
||||||
|
const outputTemplate = template.current?.outputs[outputSocket.name];
|
||||||
|
|
||||||
|
const isConnected = Boolean(
|
||||||
|
connectedInputs.filter((connectedInput) => {
|
||||||
|
return (
|
||||||
|
connectedInput.source === nodeId &&
|
||||||
|
connectedInput.sourceHandle === outputSocket.name
|
||||||
|
);
|
||||||
|
}).length
|
||||||
|
);
|
||||||
|
|
||||||
|
IAINodeOutputsToRender.push(
|
||||||
|
<IAINodeOutput
|
||||||
|
nodeId={nodeId}
|
||||||
|
output={outputSocket}
|
||||||
|
template={outputTemplate}
|
||||||
|
connected={isConnected}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return <Flex flexDir="column">{IAINodeOutputsToRender}</Flex>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return renderIAINodeOutputs();
|
||||||
|
}
|
@ -1,48 +1,18 @@
|
|||||||
import { NodeProps, useReactFlow } from 'reactflow';
|
import { NodeProps } from 'reactflow';
|
||||||
import {
|
import { Box, Flex, Icon } from '@chakra-ui/react';
|
||||||
Box,
|
import { FaExclamationCircle } from 'react-icons/fa';
|
||||||
Flex,
|
|
||||||
FormControl,
|
|
||||||
FormLabel,
|
|
||||||
Heading,
|
|
||||||
HStack,
|
|
||||||
Tooltip,
|
|
||||||
Icon,
|
|
||||||
Code,
|
|
||||||
Text,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { FaExclamationCircle, FaInfoCircle } from 'react-icons/fa';
|
|
||||||
import { InvocationValue } from '../types/types';
|
import { InvocationValue } from '../types/types';
|
||||||
import { InputFieldComponent } from './InputFieldComponent';
|
|
||||||
import { FieldHandle } from './FieldHandle';
|
|
||||||
import { isEqual, map, size } from 'lodash';
|
|
||||||
import { memo, useMemo, useRef } from 'react';
|
|
||||||
import { useIsValidConnection } from '../hooks/useIsValidConnection';
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { RootState } from 'app/store';
|
|
||||||
import { useAppSelector } from 'app/storeHooks';
|
|
||||||
import { useGetInvocationTemplate } from '../hooks/useInvocationTemplate';
|
|
||||||
|
|
||||||
const connectedInputFieldsSelector = createSelector(
|
import { memo, useRef } from 'react';
|
||||||
[(state: RootState) => state.nodes.edges],
|
import { useGetInvocationTemplate } from '../hooks/useInvocationTemplate';
|
||||||
(edges) => {
|
import IAINodeOutputs from './IAINode/IAINodeOutputs';
|
||||||
// return edges.map((e) => e.targetHandle);
|
import IAINodeInputs from './IAINode/IAINodeInputs';
|
||||||
return edges;
|
import IAINodeHeader from './IAINode/IAINodeHeader';
|
||||||
},
|
|
||||||
{
|
|
||||||
memoizeOptions: {
|
|
||||||
resultEqualityCheck: isEqual,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
||||||
const { id: nodeId, data, selected } = props;
|
const { id: nodeId, data, selected } = props;
|
||||||
const { type, inputs, outputs } = data;
|
const { type, inputs, outputs } = data;
|
||||||
|
|
||||||
const isValidConnection = useIsValidConnection();
|
|
||||||
|
|
||||||
const connectedInputs = useAppSelector(connectedInputFieldsSelector);
|
|
||||||
const getInvocationTemplate = useGetInvocationTemplate();
|
const getInvocationTemplate = useGetInvocationTemplate();
|
||||||
// TODO: determine if a field/handle is connected and disable the input if so
|
// TODO: determine if a field/handle is connected and disable the input if so
|
||||||
|
|
||||||
@ -70,7 +40,6 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
padding: 4,
|
|
||||||
bg: 'base.800',
|
bg: 'base.800',
|
||||||
borderRadius: 'md',
|
borderRadius: 'md',
|
||||||
boxShadow: 'dark-lg',
|
boxShadow: 'dark-lg',
|
||||||
@ -79,163 +48,10 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex flexDirection="column" gap={2}>
|
<Flex flexDirection="column" gap={2}>
|
||||||
<>
|
<IAINodeHeader nodeId={nodeId} template={template} />
|
||||||
<Code>{nodeId}</Code>
|
<IAINodeOutputs nodeId={nodeId} outputs={outputs} template={template} />
|
||||||
<HStack justifyContent="space-between">
|
<IAINodeInputs nodeId={nodeId} inputs={inputs} template={template} />
|
||||||
<Heading size="sm" fontWeight={500} color="base.100">
|
|
||||||
{template.current.title}
|
|
||||||
</Heading>
|
|
||||||
<Tooltip
|
|
||||||
label={template.current.description}
|
|
||||||
placement="top"
|
|
||||||
hasArrow
|
|
||||||
shouldWrapChildren
|
|
||||||
>
|
|
||||||
<Icon color="base.300" as={FaInfoCircle} />
|
|
||||||
</Tooltip>
|
|
||||||
</HStack>
|
|
||||||
{map(inputs, (input, i) => {
|
|
||||||
const { id: fieldId } = input;
|
|
||||||
const inputTemplate = template.current?.inputs[input.name];
|
|
||||||
|
|
||||||
if (!inputTemplate) {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
key={fieldId}
|
|
||||||
position="relative"
|
|
||||||
p={2}
|
|
||||||
borderWidth={1}
|
|
||||||
borderRadius="md"
|
|
||||||
sx={{
|
|
||||||
borderColor: 'error.400',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormControl isDisabled={true}>
|
|
||||||
<HStack justifyContent="space-between" alignItems="center">
|
|
||||||
<FormLabel>Unknown input: {input.name}</FormLabel>
|
|
||||||
</HStack>
|
|
||||||
</FormControl>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isConnected = Boolean(
|
|
||||||
connectedInputs.filter((connectedInput) => {
|
|
||||||
return (
|
|
||||||
connectedInput.target === nodeId &&
|
|
||||||
connectedInput.targetHandle === input.name
|
|
||||||
);
|
|
||||||
}).length
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
key={fieldId}
|
|
||||||
position="relative"
|
|
||||||
p={2}
|
|
||||||
borderWidth={1}
|
|
||||||
borderRadius="md"
|
|
||||||
sx={{
|
|
||||||
borderColor:
|
|
||||||
!isConnected &&
|
|
||||||
['always', 'connectionOnly'].includes(
|
|
||||||
String(inputTemplate?.inputRequirement)
|
|
||||||
) &&
|
|
||||||
input.value === undefined
|
|
||||||
? 'warning.400'
|
|
||||||
: undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormControl isDisabled={isConnected}>
|
|
||||||
<HStack justifyContent="space-between" alignItems="center">
|
|
||||||
<FormLabel>{inputTemplate?.title}</FormLabel>
|
|
||||||
<Tooltip
|
|
||||||
label={inputTemplate?.description}
|
|
||||||
placement="top"
|
|
||||||
hasArrow
|
|
||||||
shouldWrapChildren
|
|
||||||
>
|
|
||||||
<Icon color="base.400" as={FaInfoCircle} />
|
|
||||||
</Tooltip>
|
|
||||||
</HStack>
|
|
||||||
<InputFieldComponent
|
|
||||||
nodeId={nodeId}
|
|
||||||
field={input}
|
|
||||||
template={inputTemplate}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
{!['never', 'directOnly'].includes(
|
|
||||||
inputTemplate?.inputRequirement ?? ''
|
|
||||||
) && (
|
|
||||||
<FieldHandle
|
|
||||||
nodeId={nodeId}
|
|
||||||
field={inputTemplate}
|
|
||||||
isValidConnection={isValidConnection}
|
|
||||||
handleType="target"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{map(outputs).map((output, i) => {
|
|
||||||
const outputTemplate = template.current?.outputs[output.name];
|
|
||||||
|
|
||||||
const isConnected = Boolean(
|
|
||||||
connectedInputs.filter((connectedInput) => {
|
|
||||||
return (
|
|
||||||
connectedInput.source === nodeId &&
|
|
||||||
connectedInput.sourceHandle === output.name
|
|
||||||
);
|
|
||||||
}).length
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!outputTemplate) {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
key={output.id}
|
|
||||||
position="relative"
|
|
||||||
p={2}
|
|
||||||
borderWidth={1}
|
|
||||||
borderRadius="md"
|
|
||||||
sx={{
|
|
||||||
borderColor: 'error.400',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormControl isDisabled={true}>
|
|
||||||
<HStack justifyContent="space-between" alignItems="center">
|
|
||||||
<FormLabel>Unknown output: {output.name}</FormLabel>
|
|
||||||
</HStack>
|
|
||||||
</FormControl>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
key={output.id}
|
|
||||||
position="relative"
|
|
||||||
p={2}
|
|
||||||
borderWidth={1}
|
|
||||||
borderRadius="md"
|
|
||||||
>
|
|
||||||
<FormControl isDisabled={isConnected}>
|
|
||||||
<FormLabel textAlign="end">
|
|
||||||
{outputTemplate?.title} Output
|
|
||||||
</FormLabel>
|
|
||||||
</FormControl>
|
|
||||||
<FieldHandle
|
|
||||||
key={output.id}
|
|
||||||
nodeId={nodeId}
|
|
||||||
field={outputTemplate}
|
|
||||||
isValidConnection={isValidConnection}
|
|
||||||
handleType="source"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex></Flex>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user