mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): rework progress event handling
- Canvas manages its own progress socket event listeners and progress event data. - Remove cancellations listener jank. - Dip into low-level redux subscription API to watch for queue status changes, clearing the last "global" progress event when the queue has nothing in progress. Could also do this in a useEffect I guess. - Had to shuffle some things around to prevent circular imports, so there are a lot of tiny changes here.
This commit is contained in:
committed by
Kent Keirsey
parent
b08a66ecaf
commit
7db4d26837
@@ -1,7 +1,6 @@
|
||||
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';
|
||||
@@ -16,6 +15,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiRulerBold } from 'react-icons/pi';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
type Props = {
|
||||
image: ImageWithDims | null;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { $socket } from 'app/hooks/useSocketIO';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { useAppStore } from 'app/store/nanostores/store';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
@@ -7,6 +6,7 @@ import { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { $canvasManager } from 'features/controlLayers/store/canvasSlice';
|
||||
import Konva from 'konva';
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { $socket } from 'services/events/stores';
|
||||
import { useDevicePixelRatio } from 'use-device-pixel-ratio';
|
||||
|
||||
const log = logger('canvas');
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { AppSocket } from 'app/hooks/useSocketIO';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStore } from 'app/store/store';
|
||||
import type { SerializableObject } from 'common/types';
|
||||
@@ -31,6 +30,7 @@ import Konva from 'konva';
|
||||
import type { Atom } from 'nanostores';
|
||||
import { computed } from 'nanostores';
|
||||
import type { Logger } from 'roarr';
|
||||
import type { AppSocket } from 'services/events/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import { CanvasBackgroundModule } from './CanvasBackgroundModule';
|
||||
|
||||
@@ -4,7 +4,10 @@ import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase'
|
||||
import { getPrefixedId, loadImage } from 'features/controlLayers/konva/util';
|
||||
import { selectShowProgressOnCanvas } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import Konva from 'konva';
|
||||
import { atom } from 'nanostores';
|
||||
import type { Logger } from 'roarr';
|
||||
import { selectCanvasQueueCounts } from 'services/api/endpoints/queue';
|
||||
import type { S } from 'services/api/types';
|
||||
|
||||
export class CanvasProgressImageModule extends CanvasModuleBase {
|
||||
readonly type = 'progress_image';
|
||||
@@ -23,7 +26,8 @@ export class CanvasProgressImageModule extends CanvasModuleBase {
|
||||
imageElement: HTMLImageElement | null = null;
|
||||
|
||||
subscriptions = new Set<() => void>();
|
||||
|
||||
$lastProgressEvent = atom<S['InvocationDenoiseProgressEvent'] | null>(null);
|
||||
hasActiveGeneration: boolean = false;
|
||||
mutex: Mutex = new Mutex();
|
||||
|
||||
constructor(manager: CanvasManager) {
|
||||
@@ -41,11 +45,50 @@ export class CanvasProgressImageModule extends CanvasModuleBase {
|
||||
image: null,
|
||||
};
|
||||
|
||||
this.subscriptions.add(this.manager.stateApi.$lastCanvasProgressEvent.listen(this.render));
|
||||
this.subscriptions.add(this.manager.stagingArea.$shouldShowStagedImage.listen(this.render));
|
||||
this.subscriptions.add(this.manager.stateApi.createStoreSubscription(selectShowProgressOnCanvas, this.render));
|
||||
this.subscriptions.add(this.setSocketEventListeners());
|
||||
this.subscriptions.add(
|
||||
this.manager.stateApi.createStoreSubscription(selectCanvasQueueCounts, ({ data }) => {
|
||||
if (data && (data.in_progress > 0 || data.pending > 0)) {
|
||||
this.hasActiveGeneration = true;
|
||||
} else {
|
||||
this.hasActiveGeneration = false;
|
||||
this.$lastProgressEvent.set(null);
|
||||
}
|
||||
})
|
||||
);
|
||||
this.subscriptions.add(this.$lastProgressEvent.listen(this.render));
|
||||
}
|
||||
|
||||
setSocketEventListeners = (): (() => void) => {
|
||||
const progressListener = (data: S['InvocationDenoiseProgressEvent']) => {
|
||||
if (data.destination !== 'canvas') {
|
||||
return;
|
||||
}
|
||||
if (!this.hasActiveGeneration) {
|
||||
return;
|
||||
}
|
||||
this.$lastProgressEvent.set(data);
|
||||
};
|
||||
|
||||
const clearProgress = () => {
|
||||
this.$lastProgressEvent.set(null);
|
||||
};
|
||||
|
||||
this.manager.socket.on('invocation_denoise_progress', progressListener);
|
||||
this.manager.socket.on('connect', clearProgress);
|
||||
this.manager.socket.on('connect_error', clearProgress);
|
||||
this.manager.socket.on('disconnect', clearProgress);
|
||||
|
||||
return () => {
|
||||
this.manager.socket.off('invocation_denoise_progress', progressListener);
|
||||
this.manager.socket.off('connect', clearProgress);
|
||||
this.manager.socket.off('connect_error', clearProgress);
|
||||
this.manager.socket.off('disconnect', clearProgress);
|
||||
};
|
||||
};
|
||||
|
||||
getNodes = () => {
|
||||
return [this.konva.group];
|
||||
};
|
||||
@@ -53,7 +96,7 @@ export class CanvasProgressImageModule extends CanvasModuleBase {
|
||||
render = async () => {
|
||||
const release = await this.mutex.acquire();
|
||||
|
||||
const event = this.manager.stateApi.$lastCanvasProgressEvent.get();
|
||||
const event = this.$lastProgressEvent.get();
|
||||
const showProgressOnCanvas = this.manager.stateApi.runSelector(selectShowProgressOnCanvas);
|
||||
|
||||
if (!event || !showProgressOnCanvas) {
|
||||
|
||||
@@ -96,7 +96,7 @@ export class CanvasStagingAreaModule extends CanvasModuleBase {
|
||||
|
||||
if (!this.image.isLoading && !this.image.isError) {
|
||||
await this.image.update({ ...this.image.state, image: imageDTOToImageWithDims(imageDTO) }, true);
|
||||
this.manager.stateApi.$lastCanvasProgressEvent.set(null);
|
||||
this.manager.progressImage.$lastProgressEvent.set(null);
|
||||
}
|
||||
this.image.konva.group.visible(shouldShowStagedImage);
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
settingsEraserWidthChanged,
|
||||
} from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import {
|
||||
$lastCanvasProgressEvent,
|
||||
bboxChangedFromCanvas,
|
||||
entityBrushLineAdded,
|
||||
entityEraserLineAdded,
|
||||
@@ -382,12 +381,6 @@ export class CanvasStateApiModule extends CanvasModuleBase {
|
||||
*/
|
||||
$isRasterizing = computed(this.$rasterizingAdapter, (rasterizingAdapter) => Boolean(rasterizingAdapter));
|
||||
|
||||
/**
|
||||
* The last canvas progress event. This is set in a global event listener. The staging area may set it to null when it
|
||||
* consumes the event.
|
||||
*/
|
||||
$lastCanvasProgressEvent = $lastCanvasProgressEvent;
|
||||
|
||||
/**
|
||||
* Whether the space key is currently pressed.
|
||||
*/
|
||||
|
||||
@@ -30,13 +30,7 @@ import type { IRect } from 'konva/lib/types';
|
||||
import { merge, omit } from 'lodash-es';
|
||||
import { atom } from 'nanostores';
|
||||
import type { UndoableOptions } from 'redux-undo';
|
||||
import type {
|
||||
ControlNetModelConfig,
|
||||
ImageDTO,
|
||||
IPAdapterModelConfig,
|
||||
S,
|
||||
T2IAdapterModelConfig,
|
||||
} from 'services/api/types';
|
||||
import type { ControlNetModelConfig, ImageDTO, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import type {
|
||||
@@ -1236,7 +1230,6 @@ function actionsThrottlingFilter(action: UnknownAction) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const $lastCanvasProgressEvent = atom<S['InvocationDenoiseProgressEvent'] | null>(null);
|
||||
/**
|
||||
* The global canvas manager instance.
|
||||
*/
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
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 { selectSelectionCount } from 'features/gallery/store/gallerySelectors';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
type DeleteImageButtonProps = Omit<IconButtonProps, 'aria-label'> & {
|
||||
onClick: () => void;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ButtonGroup, IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
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 { INTERACTION_SCOPES } from 'common/hooks/interactionScopes';
|
||||
@@ -30,7 +29,7 @@ import {
|
||||
PiRulerBold,
|
||||
} from 'react-icons/pi';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import { $progressImage } from 'services/events/setEventListeners';
|
||||
import { $isConnected, $progressImage } from 'services/events/stores';
|
||||
|
||||
const CurrentImageButtons = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -15,7 +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, $isProgressFromCanvas } from 'services/events/setEventListeners';
|
||||
import { $hasProgress, $isProgressFromCanvas } from 'services/events/stores';
|
||||
|
||||
import ProgressImage from './ProgressImage';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { $isProgressFromCanvas, $progressImage } from 'services/events/setEventListeners';
|
||||
import { $isProgressFromCanvas, $progressImage } from 'services/events/stores';
|
||||
|
||||
const selectShouldAntialiasProgressImage = createSelector(
|
||||
selectSystemSlice,
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { CSSProperties, PropsWithChildren } from 'react';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { NodeProps } from 'reactflow';
|
||||
import { $lastProgressEvent } from 'services/events/setEventListeners';
|
||||
import { $lastProgressEvent } from 'services/events/stores';
|
||||
|
||||
const CurrentImageNode = (props: NodeProps) => {
|
||||
const imageDTO = useAppSelector(selectLastSelectedImage);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Flex, Text } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { $isConnected } from 'app/hooks/useSocketIO';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||
@@ -13,6 +12,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold } from 'react-icons/pi';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import type { PostUploadAction } from 'services/api/types';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export const prepareLinearUIBatch = (
|
||||
prepend: boolean,
|
||||
noise: Invocation<'noise' | 'flux_denoise'>,
|
||||
posCond: Invocation<'compel' | 'sdxl_compel_prompt' | 'flux_text_encoder'>,
|
||||
origin: 'generation' | 'workflows' | 'upscaling',
|
||||
origin: 'canvas' | 'workflows' | 'upscaling',
|
||||
destination: 'canvas' | 'gallery'
|
||||
): BatchConfig => {
|
||||
const { iterations, model, shouldRandomizeSeed, seed, shouldConcatPrompts } = state.params;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ConfirmationAlertDialog, Text } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { $isConnected } from 'app/hooks/useSocketIO';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { buildUseBoolean } from 'common/hooks/useBoolean';
|
||||
import { listCursorChanged, listPriorityChanged } from 'features/queue/store/queueSlice';
|
||||
@@ -8,6 +7,7 @@ import { toast } from 'features/toast/toast';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useClearQueueMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
const [useClearQueueConfirmationAlertDialog] = buildUseBoolean(false);
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
export const useCancelBatch = (batch_id: string) => {
|
||||
const isConnected = useStore($isConnected);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
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';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCancelQueueItemMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
export const useCancelCurrentQueueItem = () => {
|
||||
const isConnected = useStore($isConnected);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
export const useCancelQueueItem = (item_id: number) => {
|
||||
const isConnected = useStore($isConnected);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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 { useClearInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
export const useClearInvocationCache = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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 { useDisableInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
export const useDisableInvocationCache = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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 { useEnableInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
export const useEnableInvocationCache = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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, usePauseProcessorMutation } from 'services/api/endpoints/queue';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
export const usePauseProcessor = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
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';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetQueueStatusQuery, usePruneQueueMutation } from 'services/api/endpoints/queue';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
export const usePruneQueue = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
export const useResumeProcessor = () => {
|
||||
const isConnected = useStore($isConnected);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Progress } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { $isConnected } from 'app/hooks/useSocketIO';
|
||||
import { useCurrentDestination } from 'features/queue/hooks/useCurrentDestination';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
|
||||
import { $lastProgressEvent } from 'services/events/setEventListeners';
|
||||
import { $isConnected, $lastProgressEvent } from 'services/events/stores';
|
||||
|
||||
const ProgressBar = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Icon, Tooltip } from '@invoke-ai/ui-library';
|
||||
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';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
const StatusIndicator = () => {
|
||||
const isConnected = useStore($isConnected);
|
||||
|
||||
Reference in New Issue
Block a user