mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into doc_updates_23
This commit is contained in:
commit
2ad95f961c
@ -22,6 +22,7 @@ app_config.parse_args()
|
|||||||
logger = InvokeAILogger.getLogger(config=app_config)
|
logger = InvokeAILogger.getLogger(config=app_config)
|
||||||
|
|
||||||
import invokeai.frontend.web as web_dir
|
import invokeai.frontend.web as web_dir
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
from .api.dependencies import ApiDependencies
|
from .api.dependencies import ApiDependencies
|
||||||
from .api.routers import sessions, models, images, boards, board_images, app_info
|
from .api.routers import sessions, models, images, boards, board_images, app_info
|
||||||
@ -31,7 +32,12 @@ from .invocations.baseinvocation import BaseInvocation
|
|||||||
import torch
|
import torch
|
||||||
if torch.backends.mps.is_available():
|
if torch.backends.mps.is_available():
|
||||||
import invokeai.backend.util.mps_fixes
|
import invokeai.backend.util.mps_fixes
|
||||||
|
|
||||||
|
# fix for windows mimetypes registry entries being borked
|
||||||
|
# see https://github.com/invoke-ai/InvokeAI/discussions/3684#discussioncomment-6391352
|
||||||
|
mimetypes.add_type('application/javascript', '.js')
|
||||||
|
mimetypes.add_type('text/css', '.css')
|
||||||
|
|
||||||
# Create the app
|
# Create the app
|
||||||
# TODO: create this all in a method so configuration/etc. can be passed in?
|
# TODO: create this all in a method so configuration/etc. can be passed in?
|
||||||
app = FastAPI(title="Invoke AI", docs_url=None, redoc_url=None)
|
app = FastAPI(title="Invoke AI", docs_url=None, redoc_url=None)
|
||||||
|
@ -115,6 +115,7 @@ const ParamEmbeddingPopover = (props: Props) => {
|
|||||||
nothingFound="No matching Embeddings"
|
nothingFound="No matching Embeddings"
|
||||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||||
disabled={data.length === 0}
|
disabled={data.length === 0}
|
||||||
|
onDropdownClose={onClose}
|
||||||
filter={(value, item: SelectItem) =>
|
filter={(value, item: SelectItem) =>
|
||||||
item.label
|
item.label
|
||||||
?.toLowerCase()
|
?.toLowerCase()
|
||||||
|
@ -57,6 +57,7 @@ const selector = createSelector(
|
|||||||
images,
|
images,
|
||||||
allImagesTotal,
|
allImagesTotal,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
isFetching,
|
||||||
categories,
|
categories,
|
||||||
selectedBoardId,
|
selectedBoardId,
|
||||||
};
|
};
|
||||||
@ -82,8 +83,14 @@ const ImageGalleryGrid = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { images, isLoading, allImagesTotal, categories, selectedBoardId } =
|
const {
|
||||||
useAppSelector(selector);
|
images,
|
||||||
|
isLoading,
|
||||||
|
isFetching,
|
||||||
|
allImagesTotal,
|
||||||
|
categories,
|
||||||
|
selectedBoardId,
|
||||||
|
} = useAppSelector(selector);
|
||||||
|
|
||||||
const { selectedBoard } = useListAllBoardsQuery(undefined, {
|
const { selectedBoard } = useListAllBoardsQuery(undefined, {
|
||||||
selectFromResult: ({ data }) => ({
|
selectFromResult: ({ data }) => ({
|
||||||
@ -176,7 +183,7 @@ const ImageGalleryGrid = () => {
|
|||||||
<IAIButton
|
<IAIButton
|
||||||
onClick={handleLoadMoreImages}
|
onClick={handleLoadMoreImages}
|
||||||
isDisabled={!areMoreAvailable}
|
isDisabled={!areMoreAvailable}
|
||||||
isLoading={isLoading}
|
isLoading={isFetching}
|
||||||
loadingText="Loading"
|
loadingText="Loading"
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
>
|
>
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import { ChakraProps, Flex, Grid, IconButton } from '@chakra-ui/react';
|
import { ChakraProps, Flex, Grid, IconButton, Spinner } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { clamp, isEqual } from 'lodash-es';
|
|
||||||
import { useCallback, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { FaAngleLeft, FaAngleRight } from 'react-icons/fa';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import {
|
||||||
imageSelected,
|
imageSelected,
|
||||||
|
selectFilteredImages,
|
||||||
selectImagesById,
|
selectImagesById,
|
||||||
} from 'features/gallery/store/gallerySlice';
|
} from 'features/gallery/store/gallerySlice';
|
||||||
|
import { clamp, isEqual } from 'lodash-es';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { selectFilteredImages } from 'features/gallery/store/gallerySlice';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaAngleDoubleRight, FaAngleLeft, FaAngleRight } from 'react-icons/fa';
|
||||||
|
import { receivedPageOfImages } from 'services/api/thunks/image';
|
||||||
|
|
||||||
const nextPrevButtonTriggerAreaStyles: ChakraProps['sx'] = {
|
const nextPrevButtonTriggerAreaStyles: ChakraProps['sx'] = {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@ -26,6 +27,7 @@ const nextPrevButtonStyles: ChakraProps['sx'] = {
|
|||||||
export const nextPrevImageButtonsSelector = createSelector(
|
export const nextPrevImageButtonsSelector = createSelector(
|
||||||
[stateSelector, selectFilteredImages],
|
[stateSelector, selectFilteredImages],
|
||||||
(state, filteredImages) => {
|
(state, filteredImages) => {
|
||||||
|
const { total, isFetching } = state.gallery;
|
||||||
const lastSelectedImage =
|
const lastSelectedImage =
|
||||||
state.gallery.selection[state.gallery.selection.length - 1];
|
state.gallery.selection[state.gallery.selection.length - 1];
|
||||||
|
|
||||||
@ -63,6 +65,8 @@ export const nextPrevImageButtonsSelector = createSelector(
|
|||||||
isOnFirstImage: currentImageIndex === 0,
|
isOnFirstImage: currentImageIndex === 0,
|
||||||
isOnLastImage:
|
isOnLastImage:
|
||||||
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
|
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
|
||||||
|
areMoreImagesAvailable: total > imagesLength,
|
||||||
|
isFetching,
|
||||||
nextImage,
|
nextImage,
|
||||||
prevImage,
|
prevImage,
|
||||||
nextImageId,
|
nextImageId,
|
||||||
@ -80,8 +84,14 @@ const NextPrevImageButtons = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { isOnFirstImage, isOnLastImage, nextImageId, prevImageId } =
|
const {
|
||||||
useAppSelector(nextPrevImageButtonsSelector);
|
isOnFirstImage,
|
||||||
|
isOnLastImage,
|
||||||
|
nextImageId,
|
||||||
|
prevImageId,
|
||||||
|
areMoreImagesAvailable,
|
||||||
|
isFetching,
|
||||||
|
} = useAppSelector(nextPrevImageButtonsSelector);
|
||||||
|
|
||||||
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
|
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
@ -102,6 +112,14 @@ const NextPrevImageButtons = () => {
|
|||||||
nextImageId && dispatch(imageSelected(nextImageId));
|
nextImageId && dispatch(imageSelected(nextImageId));
|
||||||
}, [dispatch, nextImageId]);
|
}, [dispatch, nextImageId]);
|
||||||
|
|
||||||
|
const handleLoadMoreImages = useCallback(() => {
|
||||||
|
dispatch(
|
||||||
|
receivedPageOfImages({
|
||||||
|
is_intermediate: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'left',
|
'left',
|
||||||
() => {
|
() => {
|
||||||
@ -113,9 +131,21 @@ const NextPrevImageButtons = () => {
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
'right',
|
'right',
|
||||||
() => {
|
() => {
|
||||||
handleNextImage();
|
if (isOnLastImage && areMoreImagesAvailable && !isFetching) {
|
||||||
|
handleLoadMoreImages();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isOnLastImage) {
|
||||||
|
handleNextImage();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[nextImageId]
|
[
|
||||||
|
nextImageId,
|
||||||
|
isOnLastImage,
|
||||||
|
areMoreImagesAvailable,
|
||||||
|
handleLoadMoreImages,
|
||||||
|
isFetching,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -164,6 +194,34 @@ const NextPrevImageButtons = () => {
|
|||||||
sx={nextPrevButtonStyles}
|
sx={nextPrevButtonStyles}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{shouldShowNextPrevButtons &&
|
||||||
|
isOnLastImage &&
|
||||||
|
areMoreImagesAvailable &&
|
||||||
|
!isFetching && (
|
||||||
|
<IconButton
|
||||||
|
aria-label={t('accessibility.loadMore')}
|
||||||
|
icon={<FaAngleDoubleRight size={64} />}
|
||||||
|
variant="unstyled"
|
||||||
|
onClick={handleLoadMoreImages}
|
||||||
|
boxSize={16}
|
||||||
|
sx={nextPrevButtonStyles}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{shouldShowNextPrevButtons &&
|
||||||
|
isOnLastImage &&
|
||||||
|
areMoreImagesAvailable &&
|
||||||
|
isFetching && (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
w: 16,
|
||||||
|
h: 16,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Spinner opacity={0.5} size="xl" />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -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 };
|
return { data };
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
|
import { RootState } from 'app/store/store';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
Background,
|
Background,
|
||||||
OnConnect,
|
OnConnect,
|
||||||
|
OnConnectEnd,
|
||||||
|
OnConnectStart,
|
||||||
OnEdgesChange,
|
OnEdgesChange,
|
||||||
OnNodesChange,
|
OnNodesChange,
|
||||||
ReactFlow,
|
ReactFlow,
|
||||||
OnConnectStart,
|
|
||||||
OnConnectEnd,
|
|
||||||
} from 'reactflow';
|
} from 'reactflow';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { RootState } from 'app/store/store';
|
|
||||||
import {
|
import {
|
||||||
connectionEnded,
|
connectionEnded,
|
||||||
connectionMade,
|
connectionMade,
|
||||||
@ -16,15 +17,18 @@ import {
|
|||||||
edgesChanged,
|
edgesChanged,
|
||||||
nodesChanged,
|
nodesChanged,
|
||||||
} from '../store/nodesSlice';
|
} from '../store/nodesSlice';
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { InvocationComponent } from './InvocationComponent';
|
import { InvocationComponent } from './InvocationComponent';
|
||||||
import TopLeftPanel from './panels/TopLeftPanel';
|
import ProgressImageNode from './ProgressImageNode';
|
||||||
import TopRightPanel from './panels/TopRightPanel';
|
|
||||||
import TopCenterPanel from './panels/TopCenterPanel';
|
|
||||||
import BottomLeftPanel from './panels/BottomLeftPanel.tsx';
|
import BottomLeftPanel from './panels/BottomLeftPanel.tsx';
|
||||||
import MinimapPanel from './panels/MinimapPanel';
|
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 = () => {
|
export const Flow = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { Flex, Heading, Tooltip, Icon } from '@chakra-ui/react';
|
import { Flex, Heading, Icon, Tooltip } from '@chakra-ui/react';
|
||||||
import { InvocationTemplate } from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { FaInfoCircle } from 'react-icons/fa';
|
import { FaInfoCircle } from 'react-icons/fa';
|
||||||
|
|
||||||
interface IAINodeHeaderProps {
|
interface IAINodeHeaderProps {
|
||||||
nodeId: string;
|
nodeId?: string;
|
||||||
template: InvocationTemplate;
|
title?: string;
|
||||||
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const IAINodeHeader = (props: IAINodeHeaderProps) => {
|
const IAINodeHeader = (props: IAINodeHeaderProps) => {
|
||||||
const { nodeId, template } = props;
|
const { nodeId, title, description } = props;
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
@ -31,15 +31,10 @@ const IAINodeHeader = (props: IAINodeHeaderProps) => {
|
|||||||
_dark: { color: 'base.100' },
|
_dark: { color: 'base.100' },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{template.title}
|
{title}
|
||||||
</Heading>
|
</Heading>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip
|
<Tooltip label={description} placement="top" hasArrow shouldWrapChildren>
|
||||||
label={template.description}
|
|
||||||
placement="top"
|
|
||||||
hasArrow
|
|
||||||
shouldWrapChildren
|
|
||||||
>
|
|
||||||
<Icon
|
<Icon
|
||||||
sx={{
|
sx={{
|
||||||
h: 'min-content',
|
h: 'min-content',
|
||||||
|
@ -1,64 +1,16 @@
|
|||||||
import { NodeProps } from 'reactflow';
|
import { Flex, Icon } from '@chakra-ui/react';
|
||||||
import { Box, Flex, Icon, useToken } from '@chakra-ui/react';
|
|
||||||
import { FaExclamationCircle } from 'react-icons/fa';
|
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 { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { NODE_MIN_WIDTH } from 'app/constants';
|
import { memo, useMemo } from 'react';
|
||||||
|
import { makeTemplateSelector } from '../store/util/makeTemplateSelector';
|
||||||
type InvocationComponentWrapperProps = PropsWithChildren & {
|
import IAINodeHeader from './IAINode/IAINodeHeader';
|
||||||
selected: boolean;
|
import IAINodeInputs from './IAINode/IAINodeInputs';
|
||||||
};
|
import IAINodeOutputs from './IAINode/IAINodeOutputs';
|
||||||
|
import IAINodeResizer from './IAINode/IAINodeResizer';
|
||||||
const InvocationComponentWrapper = (props: InvocationComponentWrapperProps) => {
|
import NodeWrapper from './NodeWrapper';
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
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;
|
||||||
@ -70,7 +22,7 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
|||||||
|
|
||||||
if (!template) {
|
if (!template) {
|
||||||
return (
|
return (
|
||||||
<InvocationComponentWrapper selected={selected}>
|
<NodeWrapper selected={selected}>
|
||||||
<Flex sx={{ alignItems: 'center', justifyContent: 'center' }}>
|
<Flex sx={{ alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<Icon
|
<Icon
|
||||||
as={FaExclamationCircle}
|
as={FaExclamationCircle}
|
||||||
@ -82,13 +34,17 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
|||||||
></Icon>
|
></Icon>
|
||||||
<IAINodeResizer />
|
<IAINodeResizer />
|
||||||
</Flex>
|
</Flex>
|
||||||
</InvocationComponentWrapper>
|
</NodeWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InvocationComponentWrapper selected={selected}>
|
<NodeWrapper selected={selected}>
|
||||||
<IAINodeHeader nodeId={nodeId} template={template} />
|
<IAINodeHeader
|
||||||
|
nodeId={nodeId}
|
||||||
|
title={template.title}
|
||||||
|
description={template.description}
|
||||||
|
/>
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
@ -102,7 +58,7 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
|||||||
<IAINodeInputs nodeId={nodeId} inputs={inputs} template={template} />
|
<IAINodeInputs nodeId={nodeId} inputs={inputs} template={template} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<IAINodeResizer />
|
<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);
|
@ -5,6 +5,7 @@ import { memo, useCallback } from 'react';
|
|||||||
import { Panel } from 'reactflow';
|
import { Panel } from 'reactflow';
|
||||||
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
||||||
import NodeInvokeButton from '../ui/NodeInvokeButton';
|
import NodeInvokeButton from '../ui/NodeInvokeButton';
|
||||||
|
import CancelButton from 'features/parameters/components/ProcessButtons/CancelButton';
|
||||||
|
|
||||||
const TopCenterPanel = () => {
|
const TopCenterPanel = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -17,6 +18,7 @@ const TopCenterPanel = () => {
|
|||||||
<Panel position="top-center">
|
<Panel position="top-center">
|
||||||
<HStack>
|
<HStack>
|
||||||
<NodeInvokeButton />
|
<NodeInvokeButton />
|
||||||
|
<CancelButton />
|
||||||
<IAIButton onClick={handleReloadSchema}>Reload Schema</IAIButton>
|
<IAIButton onClick={handleReloadSchema}>Reload Schema</IAIButton>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
@ -24,7 +24,23 @@ export const useBuildInvocation = () => {
|
|||||||
const flow = useReactFlow();
|
const flow = useReactFlow();
|
||||||
|
|
||||||
return useCallback(
|
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];
|
const template = invocationTemplates[type];
|
||||||
|
|
||||||
if (template === undefined) {
|
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 => {
|
export const buildNodesGraph = (state: RootState): Graph => {
|
||||||
const { nodes, edges } = state.nodes;
|
const { nodes, edges } = state.nodes;
|
||||||
|
|
||||||
|
const filteredNodes = nodes.filter((n) => n.type !== 'progress_image');
|
||||||
|
|
||||||
// Reduce the node editor nodes into invocation graph nodes
|
// 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) => {
|
(nodesAccumulator, node, nodeIndex) => {
|
||||||
const { id, data } = node;
|
const { id, data } = node;
|
||||||
const { type, inputs } = data;
|
const { type, inputs } = data;
|
||||||
|
@ -10,20 +10,19 @@ const ParamModelandVAEandScheduler = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap={3} w="full" flexWrap={isVaeEnabled ? 'wrap' : 'nowrap'}>
|
<Flex gap={3} w="full" flexWrap={isVaeEnabled ? 'wrap' : 'nowrap'}>
|
||||||
|
<Box w="full">
|
||||||
|
<ModelSelect />
|
||||||
|
</Box>
|
||||||
<Flex gap={3} w="full">
|
<Flex gap={3} w="full">
|
||||||
<Box w="full">
|
|
||||||
<ModelSelect />
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{isVaeEnabled && (
|
{isVaeEnabled && (
|
||||||
<Box w="full">
|
<Box w="full">
|
||||||
<VAESelect />
|
<VAESelect />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
<Box w="full">
|
||||||
|
<ParamScheduler />
|
||||||
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box w="full">
|
|
||||||
<ParamScheduler />
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user