mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-12 16:54:55 -05:00
feat(ui): viewer integrates progress (wip)
This commit is contained in:
@@ -1,20 +1,28 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasAlertsInvocationProgress } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsInvocationProgress';
|
||||
import { DndImage } from 'features/dnd/DndImage';
|
||||
import ImageMetadataViewer from 'features/gallery/components/ImageMetadataViewer/ImageMetadataViewer';
|
||||
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
||||
import type { ProgressImage as ProgressImageType } from 'features/nodes/types/common';
|
||||
import { selectShouldShowImageDetails } from 'features/ui/store/uiSelectors';
|
||||
import type { AnimationProps } from 'framer-motion';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { memo, useCallback, useRef, useState } from 'react';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { ImageDTO, S } from 'services/api/types';
|
||||
import { $socket } from 'services/events/stores';
|
||||
|
||||
import { ImageMetadataMini } from './ImageMetadataMini';
|
||||
import { NoContentForViewer } from './NoContentForViewer';
|
||||
import { ProgressImage } from './ProgressImage2';
|
||||
import { ProgressIndicator } from './ProgressIndicator2';
|
||||
|
||||
export const CurrentImagePreview = memo(({ imageDTO }: { imageDTO?: ImageDTO }) => {
|
||||
const shouldShowImageDetails = useAppSelector(selectShouldShowImageDetails);
|
||||
const socket = useStore($socket);
|
||||
const [progressEvent, setProgressEvent] = useState<S['InvocationProgressEvent'] | null>(null);
|
||||
const [progressImage, setProgressImage] = useState<ProgressImageType | null>(null);
|
||||
|
||||
// Show and hide the next/prev buttons on mouse move
|
||||
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] = useState<boolean>(false);
|
||||
@@ -29,6 +37,34 @@ export const CurrentImagePreview = memo(({ imageDTO }: { imageDTO?: ImageDTO })
|
||||
}, 500);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
const onInvocationProgress = (data: S['InvocationProgressEvent']) => {
|
||||
setProgressEvent(data);
|
||||
if (data.image) {
|
||||
setProgressImage(data.image);
|
||||
}
|
||||
};
|
||||
|
||||
socket.on('invocation_progress', onInvocationProgress);
|
||||
|
||||
return () => {
|
||||
socket.off('invocation_progress', onInvocationProgress);
|
||||
};
|
||||
}, [socket]);
|
||||
|
||||
const onLoadImage = useCallback(() => {
|
||||
if (!progressEvent || !imageDTO) {
|
||||
return;
|
||||
}
|
||||
if (progressEvent.session_id === imageDTO.session_id) {
|
||||
setProgressImage(null);
|
||||
}
|
||||
}, [imageDTO, progressEvent]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
onMouseOver={onMouseOver}
|
||||
@@ -39,7 +75,19 @@ export const CurrentImagePreview = memo(({ imageDTO }: { imageDTO?: ImageDTO })
|
||||
justifyContent="center"
|
||||
position="relative"
|
||||
>
|
||||
<ImageContent imageDTO={imageDTO} />
|
||||
{imageDTO ? (
|
||||
<Flex w="full" h="full" position="absolute" alignItems="center" justifyContent="center">
|
||||
<DndImage imageDTO={imageDTO} onLoad={onLoadImage} />
|
||||
</Flex>
|
||||
) : (
|
||||
<NoContentForViewer />
|
||||
)}
|
||||
{progressEvent && progressImage && (
|
||||
<Flex w="full" h="full" position="absolute" alignItems="center" justifyContent="center" bg="base.900">
|
||||
<ProgressImage progressImage={progressImage} />
|
||||
<ProgressIndicator progressEvent={progressEvent} position="absolute" top={6} right={6} size={8} />
|
||||
</Flex>
|
||||
)}
|
||||
<Flex flexDir="column" gap={2} position="absolute" top={0} insetInlineStart={0} alignItems="flex-start">
|
||||
<CanvasAlertsInvocationProgress />
|
||||
{imageDTO && <ImageMetadataMini imageName={imageDTO.image_name} />}
|
||||
@@ -73,19 +121,6 @@ export const CurrentImagePreview = memo(({ imageDTO }: { imageDTO?: ImageDTO })
|
||||
});
|
||||
CurrentImagePreview.displayName = 'CurrentImagePreview';
|
||||
|
||||
const ImageContent = memo(({ imageDTO }: { imageDTO?: ImageDTO }) => {
|
||||
if (!imageDTO) {
|
||||
return <NoContentForViewer />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex w="full" h="full" position="absolute" alignItems="center" justifyContent="center">
|
||||
<DndImage imageDTO={imageDTO} />
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
ImageContent.displayName = 'ImageContent';
|
||||
|
||||
const initial: AnimationProps['initial'] = {
|
||||
opacity: 0,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { ProgressImage } from 'features/gallery/components/ImageViewer/ProgressImage2';
|
||||
import { ProgressIndicator } from 'features/gallery/components/ImageViewer/ProgressIndicator2';
|
||||
import type { ProgressImage as ProgressImageType } from 'features/nodes/types/common';
|
||||
import { memo } from 'react';
|
||||
import type { S } from 'services/api/types';
|
||||
|
||||
export const Progress = memo(
|
||||
({
|
||||
progressEvent,
|
||||
progressImage,
|
||||
}: {
|
||||
progressEvent: S['InvocationProgressEvent'];
|
||||
progressImage: ProgressImageType;
|
||||
}) => (
|
||||
<Flex position="relative" flexDir="column" w="full" h="full" overflow="hidden" p={2}>
|
||||
<ProgressImage progressImage={progressImage} />
|
||||
<ProgressIndicator progressEvent={progressEvent} position="absolute" top={6} right={6} size={8} />
|
||||
</Flex>
|
||||
)
|
||||
);
|
||||
Progress.displayName = 'Progress';
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Flex, Image } from '@invoke-ai/ui-library';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import type { ProgressImage as ProgressImageType } from 'features/nodes/types/common';
|
||||
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
const selectShouldAntialiasProgressImage = createSelector(
|
||||
selectSystemSlice,
|
||||
(system) => system.shouldAntialiasProgressImage
|
||||
);
|
||||
|
||||
export const ProgressImage = memo(({ progressImage }: { progressImage: ProgressImageType }) => {
|
||||
const shouldAntialiasProgressImage = useAppSelector(selectShouldAntialiasProgressImage);
|
||||
|
||||
const sx = useMemo<SystemStyleObject>(
|
||||
() => ({
|
||||
imageRendering: shouldAntialiasProgressImage ? 'auto' : 'pixelated',
|
||||
}),
|
||||
[shouldAntialiasProgressImage]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex width="full" height="full" alignItems="center" justifyContent="center" minW={0} minH={0}>
|
||||
<Image
|
||||
src={progressImage.dataURL}
|
||||
width={progressImage.width}
|
||||
height={progressImage.height}
|
||||
draggable={false}
|
||||
data-testid="progress-image"
|
||||
objectFit="contain"
|
||||
maxWidth="full"
|
||||
maxHeight="full"
|
||||
borderRadius="base"
|
||||
sx={sx}
|
||||
minH={0}
|
||||
minW={0}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
ProgressImage.displayName = 'ProgressImage';
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { CircularProgressProps, SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { CircularProgress, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { memo } from 'react';
|
||||
import type { S } from 'services/api/types';
|
||||
import { formatProgressMessage } from 'services/events/stores';
|
||||
|
||||
const circleStyles: SystemStyleObject = {
|
||||
circle: {
|
||||
transitionProperty: 'none',
|
||||
transitionDuration: '0s',
|
||||
},
|
||||
};
|
||||
|
||||
export const ProgressIndicator = memo(
|
||||
({ progressEvent, ...rest }: { progressEvent: S['InvocationProgressEvent'] } & CircularProgressProps) => {
|
||||
return (
|
||||
<Tooltip label={formatProgressMessage(progressEvent)}>
|
||||
<CircularProgress
|
||||
size="14px"
|
||||
color="invokeBlue.500"
|
||||
thickness={14}
|
||||
isIndeterminate={!progressEvent || progressEvent.percentage === null}
|
||||
value={progressEvent?.percentage ? progressEvent.percentage * 100 : undefined}
|
||||
sx={circleStyles}
|
||||
{...rest}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
);
|
||||
ProgressIndicator.displayName = 'ProgressMessage';
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Flex, Text } from '@invoke-ai/ui-library';
|
||||
import { useCallbackOnDragEnter } from 'common/hooks/useCallbackOnDragEnter';
|
||||
import type { IDockviewPanelHeaderProps } from 'dockview';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
|
||||
export const TabWithoutCloseButton = (props: IDockviewPanelHeaderProps) => {
|
||||
export const TabWithoutCloseButton = memo((props: IDockviewPanelHeaderProps) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const setActive = useCallback(() => {
|
||||
if (!props.api.isActive) {
|
||||
@@ -20,5 +20,5 @@ export const TabWithoutCloseButton = (props: IDockviewPanelHeaderProps) => {
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
});
|
||||
TabWithoutCloseButton.displayName = 'TabWithoutCloseButton';
|
||||
|
||||
@@ -2,10 +2,10 @@ import { Flex, Text } from '@invoke-ai/ui-library';
|
||||
import { useCallbackOnDragEnter } from 'common/hooks/useCallbackOnDragEnter';
|
||||
import type { IDockviewPanelHeaderProps } from 'dockview';
|
||||
import ProgressBar from 'features/system/components/ProgressBar';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { useIsGenerationInProgress } from 'services/api/endpoints/queue';
|
||||
|
||||
export const TabWithoutCloseButtonAndWithProgressIndicator = (props: IDockviewPanelHeaderProps) => {
|
||||
export const TabWithoutCloseButtonAndWithProgressIndicator = memo((props: IDockviewPanelHeaderProps) => {
|
||||
const isGenerationInProgress = useIsGenerationInProgress();
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
@@ -27,5 +27,5 @@ export const TabWithoutCloseButtonAndWithProgressIndicator = (props: IDockviewPa
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
});
|
||||
TabWithoutCloseButtonAndWithProgressIndicator.displayName = 'TabWithoutCloseButtonAndWithProgressIndicator';
|
||||
|
||||
@@ -69,7 +69,7 @@ const initializeCenterPanelLayout = (api: DockviewApi) => {
|
||||
id: VIEWER_PANEL_ID,
|
||||
component: VIEWER_PANEL_ID,
|
||||
title: 'Image Viewer',
|
||||
tabComponent: DEFAULT_TAB_ID,
|
||||
tabComponent: TAB_WITH_PROGRESS_INDICATOR_ID,
|
||||
position: {
|
||||
direction: 'within',
|
||||
referencePanel: LAUNCHPAD_PANEL_ID,
|
||||
|
||||
@@ -54,7 +54,7 @@ const initializeCenterPanelLayout = (api: DockviewApi) => {
|
||||
id: VIEWER_PANEL_ID,
|
||||
component: VIEWER_PANEL_ID,
|
||||
title: 'Image Viewer',
|
||||
tabComponent: DEFAULT_TAB_ID,
|
||||
tabComponent: TAB_WITH_PROGRESS_INDICATOR_ID,
|
||||
position: {
|
||||
direction: 'within',
|
||||
referencePanel: LAUNCHPAD_PANEL_ID,
|
||||
|
||||
@@ -54,7 +54,7 @@ const initializeCenterLayout = (api: DockviewApi) => {
|
||||
id: VIEWER_PANEL_ID,
|
||||
component: VIEWER_PANEL_ID,
|
||||
title: 'Image Viewer',
|
||||
tabComponent: DEFAULT_TAB_ID,
|
||||
tabComponent: TAB_WITH_PROGRESS_INDICATOR_ID,
|
||||
position: {
|
||||
direction: 'within',
|
||||
referencePanel: LAUNCHPAD_PANEL_ID,
|
||||
|
||||
@@ -67,7 +67,7 @@ const initializeCenterPanelLayout = (api: DockviewApi) => {
|
||||
id: VIEWER_PANEL_ID,
|
||||
component: VIEWER_PANEL_ID,
|
||||
title: 'Image Viewer',
|
||||
tabComponent: DEFAULT_TAB_ID,
|
||||
tabComponent: TAB_WITH_PROGRESS_INDICATOR_ID,
|
||||
position: {
|
||||
direction: 'within',
|
||||
referencePanel: LAUNCHPAD_PANEL_ID,
|
||||
|
||||
Reference in New Issue
Block a user