chore(ui): Cleanup Invocation Component

This commit is contained in:
blessedcoolant 2023-04-22 20:13:35 +12:00 committed by psychedelicious
parent 5f498e10bd
commit 77fa7519c4
6 changed files with 294 additions and 198 deletions

View File

@ -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>

View File

@ -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 = {

View File

@ -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>
);
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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>
); );
}); });