feat(ui): move socket event handling out of redux

Download events and invocation status events (including progress images) are very frequent. There's no real need for these to pass through redux. Handling them outside redux is a significant performance win - far fewer store subscription calls, far fewer trips through middleware.

All event handling is moved outside middleware. Cleanup of unused actions and listeners to follow.
This commit is contained in:
psychedelicious
2024-08-17 15:30:55 +10:00
parent 5dda364b2c
commit 82b1d8dab8
31 changed files with 809 additions and 301 deletions

View File

@@ -1,5 +1,7 @@
import { Flex, useShiftModifier } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { skipToken } from '@reduxjs/toolkit/query';
import { $isConnected } from 'app/hooks/useSocketIO';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
@@ -22,79 +24,85 @@ type Props = {
postUploadAction: PostUploadAction;
};
export const IPAdapterImagePreview = memo(({ image, onChangeImage, ipAdapterId, droppableData, postUploadAction }: Props) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isConnected = useAppSelector((s) => s.system.isConnected);
const optimalDimension = useAppSelector(selectOptimalDimension);
const shift = useShiftModifier();
export const IPAdapterImagePreview = memo(
({ image, onChangeImage, ipAdapterId, droppableData, postUploadAction }: Props) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isConnected = useStore($isConnected);
const optimalDimension = useAppSelector(selectOptimalDimension);
const shift = useShiftModifier();
const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery(image?.image_name ?? skipToken);
const handleResetControlImage = useCallback(() => {
onChangeImage(null);
}, [onChangeImage]);
const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery(
image?.image_name ?? skipToken
);
const handleResetControlImage = useCallback(() => {
onChangeImage(null);
}, [onChangeImage]);
const handleSetControlImageToDimensions = useCallback(() => {
if (!controlImage) {
return;
}
const handleSetControlImageToDimensions = useCallback(() => {
if (!controlImage) {
return;
}
const options = { updateAspectRatio: true, clamp: true };
if (shift) {
const { width, height } = controlImage;
dispatch(bboxWidthChanged({ width, ...options }));
dispatch(bboxHeightChanged({ height, ...options }));
} else {
const { width, height } = calculateNewSize(
controlImage.width / controlImage.height,
optimalDimension * optimalDimension
);
dispatch(bboxWidthChanged({ width, ...options }));
dispatch(bboxHeightChanged({ height, ...options }));
}
}, [controlImage, dispatch, optimalDimension, shift]);
const options = { updateAspectRatio: true, clamp: true };
if (shift) {
const { width, height } = controlImage;
dispatch(bboxWidthChanged({ width, ...options }));
dispatch(bboxHeightChanged({ height, ...options }));
} else {
const { width, height } = calculateNewSize(
controlImage.width / controlImage.height,
optimalDimension * optimalDimension
);
dispatch(bboxWidthChanged({ width, ...options }));
dispatch(bboxHeightChanged({ height, ...options }));
}
}, [controlImage, dispatch, optimalDimension, shift]);
const draggableData = useMemo<ImageDraggableData | undefined>(() => {
if (controlImage) {
return {
id: ipAdapterId,
payloadType: 'IMAGE_DTO',
payload: { imageDTO: controlImage },
};
}
}, [controlImage, ipAdapterId]);
const draggableData = useMemo<ImageDraggableData | undefined>(() => {
if (controlImage) {
return {
id: ipAdapterId,
payloadType: 'IMAGE_DTO',
payload: { imageDTO: controlImage },
};
}
}, [controlImage, ipAdapterId]);
useEffect(() => {
if (isConnected && isErrorControlImage) {
handleResetControlImage();
}
}, [handleResetControlImage, isConnected, isErrorControlImage]);
useEffect(() => {
if (isConnected && isErrorControlImage) {
handleResetControlImage();
}
}, [handleResetControlImage, isConnected, isErrorControlImage]);
return (
<Flex position="relative" w={36} h={36} alignItems="center">
<IAIDndImage
draggableData={draggableData}
droppableData={droppableData}
imageDTO={controlImage}
postUploadAction={postUploadAction}
/>
return (
<Flex position="relative" w={36} h={36} alignItems="center">
<IAIDndImage
draggableData={draggableData}
droppableData={droppableData}
imageDTO={controlImage}
postUploadAction={postUploadAction}
/>
{controlImage && (
<Flex position="absolute" flexDir="column" top={1} insetInlineEnd={1} gap={1}>
<IAIDndImageIcon
onClick={handleResetControlImage}
icon={<PiArrowCounterClockwiseBold size={16} />}
tooltip={t('controlnet.resetControlImage')}
/>
<IAIDndImageIcon
onClick={handleSetControlImageToDimensions}
icon={<PiRulerBold size={16} />}
tooltip={shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')}
/>
</Flex>
)}
</Flex>
);
});
{controlImage && (
<Flex position="absolute" flexDir="column" top={1} insetInlineEnd={1} gap={1}>
<IAIDndImageIcon
onClick={handleResetControlImage}
icon={<PiArrowCounterClockwiseBold size={16} />}
tooltip={t('controlnet.resetControlImage')}
/>
<IAIDndImageIcon
onClick={handleSetControlImageToDimensions}
icon={<PiRulerBold size={16} />}
tooltip={
shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')
}
/>
</Flex>
)}
</Flex>
);
}
);
IPAdapterImagePreview.displayName = 'IPAdapterImagePreview';

View File

@@ -48,7 +48,7 @@ export class CanvasProgressImage {
image: null,
};
this.manager.stateApi.$lastProgressEvent.listen((event) => {
this.manager.stateApi.$lastCanvasProgressEvent.listen((event) => {
this.lastProgressEvent = event;
this.render();
});

View File

@@ -76,7 +76,7 @@ export class CanvasStagingArea {
if (!this.image.isLoading && !this.image.isError) {
await this.image.updateImageSource(imageDTO.image_name);
this.manager.stateApi.$lastProgressEvent.set(null);
this.manager.stateApi.$lastCanvasProgressEvent.set(null);
}
this.image.konva.group.visible(shouldShowStagedImage);
} else {

View File

@@ -11,7 +11,6 @@ import {
$lastAddedPoint,
$lastCursorPos,
$lastMouseDownPos,
$lastProgressEvent,
$shouldShowStagedImage,
$spaceKey,
$stageAttrs,
@@ -51,6 +50,7 @@ import type {
import { RGBA_RED } from 'features/controlLayers/store/types';
import type { WritableAtom } from 'nanostores';
import { atom } from 'nanostores';
import { $lastCanvasProgressEvent } from 'services/events/setEventListeners';
type EntityStateAndAdapter =
| {
@@ -263,7 +263,7 @@ export class CanvasStateApi {
$lastAddedPoint = $lastAddedPoint;
$lastMouseDownPos = $lastMouseDownPos;
$lastCursorPos = $lastCursorPos;
$lastProgressEvent = $lastProgressEvent;
$lastCanvasProgressEvent = $lastCanvasProgressEvent;
$spaceKey = $spaceKey;
$altKey = $alt;
$ctrlKey = $ctrl;

View File

@@ -20,7 +20,6 @@ import { initialAspectRatioState } from 'features/parameters/components/Document
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
import { isEqual, pick } from 'lodash-es';
import { atom } from 'nanostores';
import type { InvocationDenoiseProgressEvent } from 'services/events/types';
import { assert } from 'tsafe';
import type {
@@ -622,7 +621,6 @@ export const $stageAttrs = atom<StageAttrs>({
scale: 0,
});
export const $shouldShowStagedImage = atom(true);
export const $lastProgressEvent = atom<InvocationDenoiseProgressEvent | null>(null);
export const $isDrawing = atom<boolean>(false);
export const $isMouseDown = atom<boolean>(false);
export const $lastAddedPoint = atom<Coordinate | null>(null);

View File

@@ -1,5 +1,7 @@
import type { IconButtonProps } from '@invoke-ai/ui-library';
import { IconButton } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { useAppSelector } from 'app/store/storeHooks';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -12,7 +14,7 @@ type DeleteImageButtonProps = Omit<IconButtonProps, 'aria-label'> & {
export const DeleteImageButton = memo((props: DeleteImageButtonProps) => {
const { onClick, isDisabled } = props;
const { t } = useTranslation();
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const imageSelectionLength: number = useAppSelector((s) => s.gallery.selection.length);
const labelMessage: string = `${t('gallery.deleteImage', { count: imageSelectionLength })} (Del)`;

View File

@@ -1,7 +1,7 @@
import { ButtonGroup, IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
import { skipToken } from '@reduxjs/toolkit/query';
import { $isConnected } from 'app/hooks/useSocketIO';
import { adHocPostProcessingRequested } from 'app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { DeleteImageButton } from 'features/deleteImageModal/components/DeleteImageButton';
@@ -10,17 +10,15 @@ import SingleSelectionMenuItems from 'features/gallery/components/ImageContextMe
import { useImageActions } from 'features/gallery/hooks/useImageActions';
import { sentImageToImg2Img } from 'features/gallery/store/actions';
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
import { parseAndRecallImageDimensions } from 'features/metadata/util/handlers';
import { $templates } from 'features/nodes/store/nodesSlice';
import { PostProcessingPopover } from 'features/parameters/components/PostProcessing/PostProcessingPopover';
import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { selectSystemSlice } from 'features/system/store/systemSlice';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow';
import { size } from 'lodash-es';
import { memo, useCallback } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import {
@@ -33,23 +31,17 @@ import {
PiRulerBold,
} from 'react-icons/pi';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
const selectShouldDisableToolbarButtons = createSelector(
selectSystemSlice,
selectGallerySlice,
selectLastSelectedImage,
(system, gallery, lastSelectedImage) => {
const hasProgressImage = Boolean(system.denoiseProgress?.progress_image);
return hasProgressImage || !lastSelectedImage;
}
);
import { $progressImage } from 'services/events/setEventListeners';
const CurrentImageButtons = () => {
const dispatch = useAppDispatch();
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const lastSelectedImage = useAppSelector(selectLastSelectedImage);
const progressImage = useStore($progressImage);
const selection = useAppSelector((s) => s.gallery.selection);
const shouldDisableToolbarButtons = useAppSelector(selectShouldDisableToolbarButtons);
const shouldDisableToolbarButtons = useMemo(() => {
return Boolean(progressImage) || !lastSelectedImage;
}, [lastSelectedImage, progressImage]);
const templates = useStore($templates);
const isUpscalingEnabled = useFeatureStatus('upscaling');
const isQueueMutationInProgress = useIsQueueMutationInProgress();

View File

@@ -1,4 +1,5 @@
import { Box, Flex } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppSelector } from 'app/store/storeHooks';
@@ -14,6 +15,7 @@ import { memo, useCallback, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PiImageBold } from 'react-icons/pi';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { $hasProgress } from 'services/events/setEventListeners';
import ProgressImage from './ProgressImage';
@@ -26,7 +28,7 @@ const CurrentImagePreview = () => {
const { t } = useTranslation();
const shouldShowImageDetails = useAppSelector((s) => s.ui.shouldShowImageDetails);
const imageName = useAppSelector(selectLastSelectedImageName);
const hasDenoiseProgress = useAppSelector((s) => Boolean(s.system.denoiseProgress));
const hasDenoiseProgress = useStore($hasProgress);
const shouldShowProgressInViewer = useAppSelector((s) => s.ui.shouldShowProgressInViewer);
const { currentData: imageDTO } = useGetImageDTOQuery(imageName ?? skipToken);

View File

@@ -1,10 +1,12 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { Image } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks';
import { memo, useMemo } from 'react';
import { $progressImage } from 'services/events/setEventListeners';
const CurrentImagePreview = () => {
const progress_image = useAppSelector((s) => s.system.denoiseProgress?.progress_image);
const progressImage = useStore($progressImage);
const shouldAntialiasProgressImage = useAppSelector((s) => s.system.shouldAntialiasProgressImage);
const sx = useMemo<SystemStyleObject>(
@@ -14,15 +16,15 @@ const CurrentImagePreview = () => {
[shouldAntialiasProgressImage]
);
if (!progress_image) {
if (!progressImage) {
return null;
}
return (
<Image
src={progress_image.dataURL}
width={progress_image.width}
height={progress_image.height}
src={progressImage.dataURL}
width={progressImage.width}
height={progressImage.height}
draggable={false}
data-testid="progress-image"
objectFit="contain"

View File

@@ -1,36 +1,33 @@
import { Flex, Image, Text } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
import { selectSystemSlice } from 'features/system/store/systemSlice';
import type { AnimationProps } from 'framer-motion';
import { motion } from 'framer-motion';
import type { CSSProperties, PropsWithChildren } from 'react';
import { memo, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { NodeProps } from 'reactflow';
const selector = createMemoizedSelector(selectSystemSlice, selectGallerySlice, (system, gallery) => {
const imageDTO = gallery.selection[gallery.selection.length - 1];
return {
imageDTO,
progressImage: system.denoiseProgress?.progress_image,
};
});
import { $lastProgressEvent } from 'services/events/setEventListeners';
const CurrentImageNode = (props: NodeProps) => {
const { progressImage, imageDTO } = useAppSelector(selector);
const imageDTO = useAppSelector((s) => s.gallery.selection[s.gallery.selection.length - 1]);
const lastProgressEvent = useStore($lastProgressEvent);
if (progressImage) {
if (lastProgressEvent?.progress_image) {
return (
<Wrapper nodeProps={props}>
<Image src={progressImage.dataURL} w="full" h="full" objectFit="contain" borderRadius="base" />
<Image
src={lastProgressEvent?.progress_image.dataURL}
w="full"
h="full"
objectFit="contain"
borderRadius="base"
/>
</Wrapper>
);
}

View File

@@ -1,6 +1,8 @@
import { Flex, Text } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { $isConnected } from 'app/hooks/useSocketIO';
import { useAppDispatch } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
@@ -17,7 +19,7 @@ import type { FieldComponentProps } from './types';
const ImageFieldInputComponent = (props: FieldComponentProps<ImageFieldInputInstance, ImageFieldInputTemplate>) => {
const { nodeId, field } = props;
const dispatch = useAppDispatch();
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const { currentData: imageDTO, isError } = useGetImageDTOQuery(field.value?.image_name ?? skipToken);
const handleReset = useCallback(() => {

View File

@@ -1,11 +1,12 @@
import { useAppSelector } from 'app/store/storeHooks';
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { toast } from 'features/toast/toast';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useCancelByBatchIdsMutation, useGetBatchStatusQuery } from 'services/api/endpoints/queue';
export const useCancelBatch = (batch_id: string) => {
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const { isCanceled } = useGetBatchStatusQuery(
{ batch_id },
{

View File

@@ -1,4 +1,5 @@
import { useAppSelector } from 'app/store/storeHooks';
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { toast } from 'features/toast/toast';
import { isNil } from 'lodash-es';
import { useCallback, useMemo } from 'react';
@@ -6,7 +7,7 @@ import { useTranslation } from 'react-i18next';
import { useCancelQueueItemMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue';
export const useCancelCurrentQueueItem = () => {
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const { data: queueStatus } = useGetQueueStatusQuery();
const [trigger, { isLoading }] = useCancelQueueItemMutation();
const { t } = useTranslation();

View File

@@ -1,11 +1,12 @@
import { useAppSelector } from 'app/store/storeHooks';
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { toast } from 'features/toast/toast';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useCancelQueueItemMutation } from 'services/api/endpoints/queue';
export const useCancelQueueItem = (item_id: number) => {
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const [trigger, { isLoading }] = useCancelQueueItemMutation();
const { t } = useTranslation();
const cancelQueueItem = useCallback(async () => {

View File

@@ -1,4 +1,5 @@
import { useAppSelector } from 'app/store/storeHooks';
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -7,7 +8,7 @@ import { useClearInvocationCacheMutation, useGetInvocationCacheStatusQuery } fro
export const useClearInvocationCache = () => {
const { t } = useTranslation();
const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const [trigger, { isLoading }] = useClearInvocationCacheMutation({
fixedCacheKey: 'clearInvocationCache',
});

View File

@@ -1,4 +1,6 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { useAppDispatch } from 'app/store/storeHooks';
import { listCursorChanged, listPriorityChanged } from 'features/queue/store/queueSlice';
import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react';
@@ -9,7 +11,7 @@ export const useClearQueue = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { data: queueStatus } = useGetQueueStatusQuery();
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const [trigger, { isLoading }] = useClearQueueMutation({
fixedCacheKey: 'clearQueue',
});

View File

@@ -1,4 +1,5 @@
import { useAppSelector } from 'app/store/storeHooks';
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -7,7 +8,7 @@ import { useDisableInvocationCacheMutation, useGetInvocationCacheStatusQuery } f
export const useDisableInvocationCache = () => {
const { t } = useTranslation();
const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const [trigger, { isLoading }] = useDisableInvocationCacheMutation({
fixedCacheKey: 'disableInvocationCache',
});

View File

@@ -1,4 +1,5 @@
import { useAppSelector } from 'app/store/storeHooks';
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -7,7 +8,7 @@ import { useEnableInvocationCacheMutation, useGetInvocationCacheStatusQuery } fr
export const useEnableInvocationCache = () => {
const { t } = useTranslation();
const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const [trigger, { isLoading }] = useEnableInvocationCacheMutation({
fixedCacheKey: 'enableInvocationCache',
});

View File

@@ -1,4 +1,5 @@
import { useAppSelector } from 'app/store/storeHooks';
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -6,7 +7,7 @@ import { useGetQueueStatusQuery, usePauseProcessorMutation } from 'services/api/
export const usePauseProcessor = () => {
const { t } = useTranslation();
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const { data: queueStatus } = useGetQueueStatusQuery();
const [trigger, { isLoading }] = usePauseProcessorMutation({
fixedCacheKey: 'pauseProcessor',

View File

@@ -1,4 +1,6 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { useAppDispatch } from 'app/store/storeHooks';
import { listCursorChanged, listPriorityChanged } from 'features/queue/store/queueSlice';
import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react';
@@ -8,7 +10,7 @@ import { useGetQueueStatusQuery, usePruneQueueMutation } from 'services/api/endp
export const usePruneQueue = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const [trigger, { isLoading }] = usePruneQueueMutation({
fixedCacheKey: 'pruneQueue',
});

View File

@@ -1,11 +1,12 @@
import { useAppSelector } from 'app/store/storeHooks';
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetQueueStatusQuery, useResumeProcessorMutation } from 'services/api/endpoints/queue';
export const useResumeProcessor = () => {
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const { data: queueStatus } = useGetQueueStatusQuery();
const { t } = useTranslation();
const [trigger, { isLoading }] = useResumeProcessorMutation({

View File

@@ -1,28 +1,28 @@
import { Progress } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectSystemSlice } from 'features/system/store/systemSlice';
import { memo } from 'react';
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
const selectProgressValue = createSelector(
selectSystemSlice,
(system) => (system.denoiseProgress?.percentage ?? 0) * 100
);
import { $lastProgressEvent } from 'services/events/setEventListeners';
const ProgressBar = () => {
const { t } = useTranslation();
const { data: queueStatus } = useGetQueueStatusQuery();
const isConnected = useAppSelector((s) => s.system.isConnected);
const hasSteps = useAppSelector((s) => Boolean(s.system.denoiseProgress));
const value = useAppSelector(selectProgressValue);
const isConnected = useStore($isConnected);
const lastProgressEvent = useStore($lastProgressEvent);
const value = useMemo(() => {
if (!lastProgressEvent) {
return 0;
}
return (lastProgressEvent.percentage ?? 0) * 100;
}, [lastProgressEvent]);
return (
<Progress
value={value}
aria-label={t('accessibility.invokeProgressBar')}
isIndeterminate={isConnected && Boolean(queueStatus?.queue.in_progress) && !hasSteps}
isIndeterminate={isConnected && Boolean(queueStatus?.queue.in_progress) && !lastProgressEvent}
h={2}
w="full"
colorScheme="invokeBlue"

View File

@@ -1,11 +1,12 @@
import { Icon, Tooltip } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiWarningBold } from 'react-icons/pi';
const StatusIndicator = () => {
const isConnected = useAppSelector((s) => s.system.isConnected);
const isConnected = useStore($isConnected);
const { t } = useTranslation();
if (!isConnected) {