mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): add progress image node
it is excluded from graph, so you can add it without affecting generation
This commit is contained in:
parent
2454b51d51
commit
d7218d44d7
@ -28,6 +28,12 @@ const selector = createSelector(
|
||||
};
|
||||
});
|
||||
|
||||
data.push({
|
||||
label: 'Progress Image',
|
||||
value: 'progress_image',
|
||||
description: 'Displays the progress image in the Node Editor',
|
||||
});
|
||||
|
||||
return { data };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useCallback } from 'react';
|
||||
import {
|
||||
Background,
|
||||
OnConnect,
|
||||
OnConnectEnd,
|
||||
OnConnectStart,
|
||||
OnEdgesChange,
|
||||
OnNodesChange,
|
||||
ReactFlow,
|
||||
OnConnectStart,
|
||||
OnConnectEnd,
|
||||
} from 'reactflow';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { RootState } from 'app/store/store';
|
||||
import {
|
||||
connectionEnded,
|
||||
connectionMade,
|
||||
@ -16,15 +17,18 @@ import {
|
||||
edgesChanged,
|
||||
nodesChanged,
|
||||
} from '../store/nodesSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { InvocationComponent } from './InvocationComponent';
|
||||
import TopLeftPanel from './panels/TopLeftPanel';
|
||||
import TopRightPanel from './panels/TopRightPanel';
|
||||
import TopCenterPanel from './panels/TopCenterPanel';
|
||||
import ProgressImageNode from './ProgressImageNode';
|
||||
import BottomLeftPanel from './panels/BottomLeftPanel.tsx';
|
||||
import MinimapPanel from './panels/MinimapPanel';
|
||||
import TopCenterPanel from './panels/TopCenterPanel';
|
||||
import TopLeftPanel from './panels/TopLeftPanel';
|
||||
import TopRightPanel from './panels/TopRightPanel';
|
||||
|
||||
const nodeTypes = { invocation: InvocationComponent };
|
||||
const nodeTypes = {
|
||||
invocation: InvocationComponent,
|
||||
progress_image: ProgressImageNode,
|
||||
};
|
||||
|
||||
export const Flow = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { Flex, Heading, Tooltip, Icon } from '@chakra-ui/react';
|
||||
import { InvocationTemplate } from 'features/nodes/types/types';
|
||||
import { Flex, Heading, Icon, Tooltip } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import { FaInfoCircle } from 'react-icons/fa';
|
||||
|
||||
interface IAINodeHeaderProps {
|
||||
nodeId: string;
|
||||
template: InvocationTemplate;
|
||||
nodeId?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const IAINodeHeader = (props: IAINodeHeaderProps) => {
|
||||
const { nodeId, template } = props;
|
||||
const { nodeId, title, description } = props;
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
@ -31,15 +31,10 @@ const IAINodeHeader = (props: IAINodeHeaderProps) => {
|
||||
_dark: { color: 'base.100' },
|
||||
}}
|
||||
>
|
||||
{template.title}
|
||||
{title}
|
||||
</Heading>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={template.description}
|
||||
placement="top"
|
||||
hasArrow
|
||||
shouldWrapChildren
|
||||
>
|
||||
<Tooltip label={description} placement="top" hasArrow shouldWrapChildren>
|
||||
<Icon
|
||||
sx={{
|
||||
h: 'min-content',
|
||||
|
@ -1,64 +1,16 @@
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { Box, Flex, Icon, useToken } from '@chakra-ui/react';
|
||||
import { Flex, Icon } from '@chakra-ui/react';
|
||||
import { FaExclamationCircle } from 'react-icons/fa';
|
||||
import { InvocationTemplate, InvocationValue } from '../types/types';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { InvocationValue } from '../types/types';
|
||||
|
||||
import { memo, PropsWithChildren, useMemo } from 'react';
|
||||
import IAINodeOutputs from './IAINode/IAINodeOutputs';
|
||||
import IAINodeInputs from './IAINode/IAINodeInputs';
|
||||
import IAINodeHeader from './IAINode/IAINodeHeader';
|
||||
import IAINodeResizer from './IAINode/IAINodeResizer';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { AnyInvocationType } from 'services/events/types';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { NODE_MIN_WIDTH } from 'app/constants';
|
||||
|
||||
type InvocationComponentWrapperProps = PropsWithChildren & {
|
||||
selected: boolean;
|
||||
};
|
||||
|
||||
const InvocationComponentWrapper = (props: InvocationComponentWrapperProps) => {
|
||||
const [nodeSelectedOutline, nodeShadow] = useToken('shadows', [
|
||||
'nodeSelectedOutline',
|
||||
'dark-lg',
|
||||
]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'relative',
|
||||
borderRadius: 'md',
|
||||
minWidth: NODE_MIN_WIDTH,
|
||||
shadow: props.selected
|
||||
? `${nodeSelectedOutline}, ${nodeShadow}`
|
||||
: `${nodeShadow}`,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const makeTemplateSelector = (type: AnyInvocationType) =>
|
||||
createSelector(
|
||||
[(state: RootState) => state.nodes],
|
||||
(nodes) => {
|
||||
const template = nodes.invocationTemplates[type];
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
return template;
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: (
|
||||
a: InvocationTemplate | undefined,
|
||||
b: InvocationTemplate | undefined
|
||||
) => a !== undefined && b !== undefined && a.type === b.type,
|
||||
},
|
||||
}
|
||||
);
|
||||
import { memo, useMemo } from 'react';
|
||||
import { makeTemplateSelector } from '../store/util/makeTemplateSelector';
|
||||
import IAINodeHeader from './IAINode/IAINodeHeader';
|
||||
import IAINodeInputs from './IAINode/IAINodeInputs';
|
||||
import IAINodeOutputs from './IAINode/IAINodeOutputs';
|
||||
import IAINodeResizer from './IAINode/IAINodeResizer';
|
||||
import NodeWrapper from './NodeWrapper';
|
||||
|
||||
export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
||||
const { id: nodeId, data, selected } = props;
|
||||
@ -70,7 +22,7 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
||||
|
||||
if (!template) {
|
||||
return (
|
||||
<InvocationComponentWrapper selected={selected}>
|
||||
<NodeWrapper selected={selected}>
|
||||
<Flex sx={{ alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Icon
|
||||
as={FaExclamationCircle}
|
||||
@ -82,13 +34,17 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
||||
></Icon>
|
||||
<IAINodeResizer />
|
||||
</Flex>
|
||||
</InvocationComponentWrapper>
|
||||
</NodeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<InvocationComponentWrapper selected={selected}>
|
||||
<IAINodeHeader nodeId={nodeId} template={template} />
|
||||
<NodeWrapper selected={selected}>
|
||||
<IAINodeHeader
|
||||
nodeId={nodeId}
|
||||
title={template.title}
|
||||
description={template.description}
|
||||
/>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
@ -102,7 +58,7 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
||||
<IAINodeInputs nodeId={nodeId} inputs={inputs} template={template} />
|
||||
</Flex>
|
||||
<IAINodeResizer />
|
||||
</InvocationComponentWrapper>
|
||||
</NodeWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,32 @@
|
||||
import { Box, useToken } from '@chakra-ui/react';
|
||||
import { NODE_MIN_WIDTH } from 'app/constants';
|
||||
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
type NodeWrapperProps = PropsWithChildren & {
|
||||
selected: boolean;
|
||||
};
|
||||
|
||||
const NodeWrapper = (props: NodeWrapperProps) => {
|
||||
const [nodeSelectedOutline, nodeShadow] = useToken('shadows', [
|
||||
'nodeSelectedOutline',
|
||||
'dark-lg',
|
||||
]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'relative',
|
||||
borderRadius: 'md',
|
||||
minWidth: NODE_MIN_WIDTH,
|
||||
shadow: props.selected
|
||||
? `${nodeSelectedOutline}, ${nodeShadow}`
|
||||
: `${nodeShadow}`,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodeWrapper;
|
@ -0,0 +1,64 @@
|
||||
import { Flex, Image } from '@chakra-ui/react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { InvocationValue } from '../types/types';
|
||||
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import { memo } from 'react';
|
||||
import IAINodeHeader from './IAINode/IAINodeHeader';
|
||||
import IAINodeResizer from './IAINode/IAINodeResizer';
|
||||
import NodeWrapper from './NodeWrapper';
|
||||
|
||||
const ProgressImageNode = (props: NodeProps<InvocationValue>) => {
|
||||
const progressImage = useAppSelector((state) => state.system.progressImage);
|
||||
const { selected } = props;
|
||||
|
||||
return (
|
||||
<NodeWrapper selected={selected}>
|
||||
<IAINodeHeader
|
||||
title="Progress Image"
|
||||
description="Displays the progress image in the Node Editor"
|
||||
/>
|
||||
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
borderBottomRadius: 'md',
|
||||
p: 2,
|
||||
bg: 'base.200',
|
||||
_dark: { bg: 'base.800' },
|
||||
}}
|
||||
>
|
||||
{progressImage ? (
|
||||
<Image
|
||||
src={progressImage.dataURL}
|
||||
sx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Flex
|
||||
sx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
minW: 32,
|
||||
minH: 32,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<IAINoContentFallback />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<IAINodeResizer
|
||||
maxHeight={progressImage?.height ?? 512}
|
||||
maxWidth={progressImage?.width ?? 512}
|
||||
/>
|
||||
</NodeWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ProgressImageNode);
|
@ -24,7 +24,23 @@ export const useBuildInvocation = () => {
|
||||
const flow = useReactFlow();
|
||||
|
||||
return useCallback(
|
||||
(type: AnyInvocationType) => {
|
||||
(type: AnyInvocationType | 'progress_image') => {
|
||||
if (type === 'progress_image') {
|
||||
const { x, y } = flow.project({
|
||||
x: window.innerWidth / 2.5,
|
||||
y: window.innerHeight / 8,
|
||||
});
|
||||
|
||||
const node: Node = {
|
||||
id: 'progress_image',
|
||||
type: 'progress_image',
|
||||
position: { x: x, y: y },
|
||||
data: {},
|
||||
};
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
const template = invocationTemplates[type];
|
||||
|
||||
if (template === undefined) {
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { InvocationTemplate } from 'features/nodes/types/types';
|
||||
import { AnyInvocationType } from 'services/events/types';
|
||||
|
||||
export const makeTemplateSelector = (type: AnyInvocationType) =>
|
||||
createSelector(
|
||||
[(state: RootState) => state.nodes],
|
||||
(nodes) => {
|
||||
const template = nodes.invocationTemplates[type];
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
return template;
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: (
|
||||
a: InvocationTemplate | undefined,
|
||||
b: InvocationTemplate | undefined
|
||||
) => a !== undefined && b !== undefined && a.type === b.type,
|
||||
},
|
||||
}
|
||||
);
|
@ -54,8 +54,10 @@ export const parseFieldValue = (field: InputFieldValue) => {
|
||||
export const buildNodesGraph = (state: RootState): Graph => {
|
||||
const { nodes, edges } = state.nodes;
|
||||
|
||||
const filteredNodes = nodes.filter((n) => n.type !== 'progress_image');
|
||||
|
||||
// Reduce the node editor nodes into invocation graph nodes
|
||||
const parsedNodes = nodes.reduce<NonNullable<Graph['nodes']>>(
|
||||
const parsedNodes = filteredNodes.reduce<NonNullable<Graph['nodes']>>(
|
||||
(nodesAccumulator, node, nodeIndex) => {
|
||||
const { id, data } = node;
|
||||
const { type, inputs } = data;
|
||||
|
Loading…
Reference in New Issue
Block a user