From d23cdfd0addec4f345a85f41400440ae630088c6 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 23 Jun 2025 15:51:37 +1000 Subject: [PATCH] feat(ui): viewer integrates progress (wip) --- .../ImageViewer/CurrentImagePreview.tsx | 67 ++++++++++++++----- .../components/ImageViewer/Progress.tsx | 22 ++++++ .../components/ImageViewer/ProgressImage2.tsx | 44 ++++++++++++ .../ImageViewer/ProgressIndicator2.tsx | 31 +++++++++ .../ui/layouts/TabWithoutCloseButton.tsx | 6 +- ...outCloseButtonAndWithProgressIndicator.tsx | 6 +- .../ui/layouts/canvas-tab-auto-layout.tsx | 2 +- .../ui/layouts/generate-tab-auto-layout.tsx | 2 +- .../ui/layouts/upscaling-tab-auto-layout.tsx | 2 +- .../ui/layouts/workflows-tab-auto-layout.tsx | 2 +- 10 files changed, 158 insertions(+), 26 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/Progress.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/ProgressImage2.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/ProgressIndicator2.tsx diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx index 687cddd7e7..7d0ef2e1d4 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx @@ -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(null); + const [progressImage, setProgressImage] = useState(null); // Show and hide the next/prev buttons on mouse move const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] = useState(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 ( - + {imageDTO ? ( + + + + ) : ( + + )} + {progressEvent && progressImage && ( + + + + + )} {imageDTO && } @@ -73,19 +121,6 @@ export const CurrentImagePreview = memo(({ imageDTO }: { imageDTO?: ImageDTO }) }); CurrentImagePreview.displayName = 'CurrentImagePreview'; -const ImageContent = memo(({ imageDTO }: { imageDTO?: ImageDTO }) => { - if (!imageDTO) { - return ; - } - - return ( - - - - ); -}); -ImageContent.displayName = 'ImageContent'; - const initial: AnimationProps['initial'] = { opacity: 0, }; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/Progress.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/Progress.tsx new file mode 100644 index 0000000000..bc6aedff31 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/Progress.tsx @@ -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; + }) => ( + + + + + ) +); +Progress.displayName = 'Progress'; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ProgressImage2.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ProgressImage2.tsx new file mode 100644 index 0000000000..3e5a452a8c --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ProgressImage2.tsx @@ -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( + () => ({ + imageRendering: shouldAntialiasProgressImage ? 'auto' : 'pixelated', + }), + [shouldAntialiasProgressImage] + ); + + return ( + + + + ); +}); + +ProgressImage.displayName = 'ProgressImage'; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ProgressIndicator2.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ProgressIndicator2.tsx new file mode 100644 index 0000000000..b635c37d80 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ProgressIndicator2.tsx @@ -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 ( + + + + ); + } +); +ProgressIndicator.displayName = 'ProgressMessage'; diff --git a/invokeai/frontend/web/src/features/ui/layouts/TabWithoutCloseButton.tsx b/invokeai/frontend/web/src/features/ui/layouts/TabWithoutCloseButton.tsx index 71638d93e3..7bb0f240ad 100644 --- a/invokeai/frontend/web/src/features/ui/layouts/TabWithoutCloseButton.tsx +++ b/invokeai/frontend/web/src/features/ui/layouts/TabWithoutCloseButton.tsx @@ -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(null); const setActive = useCallback(() => { if (!props.api.isActive) { @@ -20,5 +20,5 @@ export const TabWithoutCloseButton = (props: IDockviewPanelHeaderProps) => { ); -}; +}); TabWithoutCloseButton.displayName = 'TabWithoutCloseButton'; diff --git a/invokeai/frontend/web/src/features/ui/layouts/TabWithoutCloseButtonAndWithProgressIndicator.tsx b/invokeai/frontend/web/src/features/ui/layouts/TabWithoutCloseButtonAndWithProgressIndicator.tsx index 3d1b960abf..7ac349116f 100644 --- a/invokeai/frontend/web/src/features/ui/layouts/TabWithoutCloseButtonAndWithProgressIndicator.tsx +++ b/invokeai/frontend/web/src/features/ui/layouts/TabWithoutCloseButtonAndWithProgressIndicator.tsx @@ -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(null); @@ -27,5 +27,5 @@ export const TabWithoutCloseButtonAndWithProgressIndicator = (props: IDockviewPa )} ); -}; +}); TabWithoutCloseButtonAndWithProgressIndicator.displayName = 'TabWithoutCloseButtonAndWithProgressIndicator'; diff --git a/invokeai/frontend/web/src/features/ui/layouts/canvas-tab-auto-layout.tsx b/invokeai/frontend/web/src/features/ui/layouts/canvas-tab-auto-layout.tsx index 727b5342b2..c82a7832f5 100644 --- a/invokeai/frontend/web/src/features/ui/layouts/canvas-tab-auto-layout.tsx +++ b/invokeai/frontend/web/src/features/ui/layouts/canvas-tab-auto-layout.tsx @@ -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, diff --git a/invokeai/frontend/web/src/features/ui/layouts/generate-tab-auto-layout.tsx b/invokeai/frontend/web/src/features/ui/layouts/generate-tab-auto-layout.tsx index d2254db0d3..b96059aa32 100644 --- a/invokeai/frontend/web/src/features/ui/layouts/generate-tab-auto-layout.tsx +++ b/invokeai/frontend/web/src/features/ui/layouts/generate-tab-auto-layout.tsx @@ -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, diff --git a/invokeai/frontend/web/src/features/ui/layouts/upscaling-tab-auto-layout.tsx b/invokeai/frontend/web/src/features/ui/layouts/upscaling-tab-auto-layout.tsx index d805e0feb8..22f2b26da2 100644 --- a/invokeai/frontend/web/src/features/ui/layouts/upscaling-tab-auto-layout.tsx +++ b/invokeai/frontend/web/src/features/ui/layouts/upscaling-tab-auto-layout.tsx @@ -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, diff --git a/invokeai/frontend/web/src/features/ui/layouts/workflows-tab-auto-layout.tsx b/invokeai/frontend/web/src/features/ui/layouts/workflows-tab-auto-layout.tsx index dfb539dcd7..07974fa3fb 100644 --- a/invokeai/frontend/web/src/features/ui/layouts/workflows-tab-auto-layout.tsx +++ b/invokeai/frontend/web/src/features/ui/layouts/workflows-tab-auto-layout.tsx @@ -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,