From 2ddcde13ffc67acde1fd86f1ab70365bd8dfc304 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Fri, 6 Jun 2025 18:29:43 +1000
Subject: [PATCH] refactor(ui): migrate from canceling queue items to
deleteing, make queue hook APIs consistent
---
invokeai/app/api/routers/session_queue.py | 13 +++
.../session_queue/session_queue_base.py | 6 ++
.../session_queue/session_queue_common.py | 6 ++
.../session_queue/session_queue_sqlite.py | 32 +++++++
.../app/components/GlobalModalIsolator.tsx | 2 +
.../web/src/common/hooks/useGlobalHotkeys.ts | 22 ++---
.../components/SimpleSession/context.tsx | 86 ++++++-------------
.../StagingAreaToolbarAcceptButton.tsx | 18 ++--
.../StagingAreaToolbarDiscardAllButton.tsx | 11 +--
...tagingAreaToolbarDiscardSelectedButton.tsx | 9 +-
.../CancelAllExceptCurrentIconButton.tsx | 25 ++++++
...urrentQueueItemConfirmationAlertDialog.tsx | 11 ++-
.../components/ClearInvocationCacheButton.tsx | 8 +-
.../ClearQueueConfirmationAlertDialog.tsx | 11 ++-
.../queue/components/ClearQueueIconButton.tsx | 58 -------------
...urrentQueueItemConfirmationAlertDialog.tsx | 46 ++++++++++
.../DeleteCurrentQueueItemIconButton.tsx | 25 ++++++
.../queue/components/PauseProcessorButton.tsx | 8 +-
.../queue/components/PruneQueueButton.tsx | 11 +--
.../components/QueueActionsMenuButton.tsx | 44 ++++------
.../queue/components/QueueControls.tsx | 19 +++-
.../QueueList/QueueItemComponent.tsx | 26 +++---
.../components/QueueList/QueueItemDetail.tsx | 42 ++++++---
.../components/QueueTabQueueControls.tsx | 4 +-
.../components/ResumeProcessorButton.tsx | 8 +-
.../ToggleInvocationCacheButton.tsx | 24 +++---
.../queue/hooks/useBatchIsCanceled.ts | 20 +++++
.../useCancelAllExceptCurrentQueueItem.ts | 22 ++---
.../features/queue/hooks/useCancelBatch.ts | 60 +++++--------
.../queue/hooks/useCancelCurrentQueueItem.ts | 47 +++-------
.../queue/hooks/useCancelQueueItem.ts | 41 +++++----
.../hooks/useCancelQueueItemsByDestination.ts | 33 +++++++
.../queue/hooks/useClearInvocationCache.ts | 18 ++--
.../src/features/queue/hooks/useClearQueue.ts | 19 ++--
.../queue/hooks/useCurrentDestination.ts | 11 ---
.../queue/hooks/useCurrentQueueItemId.ts | 11 +++
.../useDeleteAllExceptCurrentQueueItem.ts | 38 ++++++++
.../queue/hooks/useDeleteCurrentQueueItem.ts | 20 +++++
.../queue/hooks/useDeleteQueueItem.ts | 33 +++++++
.../hooks/useDeleteQueueItemsByDestination.ts | 33 +++++++
.../queue/hooks/useDisableInvocationCache.ts | 26 +++---
.../queue/hooks/useEnableInvocationCache.ts | 25 +++---
.../features/queue/hooks/usePauseProcessor.ts | 19 ++--
.../src/features/queue/hooks/usePruneQueue.ts | 45 +++++-----
.../queue/hooks/useResumeProcessor.ts | 19 ++--
.../features/queue/hooks/useRetryQueueItem.ts | 45 +++++-----
.../system/components/ProgressBar.tsx | 14 +--
.../components/FloatingLeftPanelButtons.tsx | 32 +++----
.../web/src/services/api/endpoints/queue.ts | 20 ++++-
.../frontend/web/src/services/api/schema.ts | 63 ++++++++++++++
50 files changed, 773 insertions(+), 516 deletions(-)
create mode 100644 invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentIconButton.tsx
delete mode 100644 invokeai/frontend/web/src/features/queue/components/ClearQueueIconButton.tsx
create mode 100644 invokeai/frontend/web/src/features/queue/components/DeleteAllExceptCurrentQueueItemConfirmationAlertDialog.tsx
create mode 100644 invokeai/frontend/web/src/features/queue/components/DeleteCurrentQueueItemIconButton.tsx
create mode 100644 invokeai/frontend/web/src/features/queue/hooks/useBatchIsCanceled.ts
create mode 100644 invokeai/frontend/web/src/features/queue/hooks/useCancelQueueItemsByDestination.ts
delete mode 100644 invokeai/frontend/web/src/features/queue/hooks/useCurrentDestination.ts
create mode 100644 invokeai/frontend/web/src/features/queue/hooks/useCurrentQueueItemId.ts
create mode 100644 invokeai/frontend/web/src/features/queue/hooks/useDeleteAllExceptCurrentQueueItem.ts
create mode 100644 invokeai/frontend/web/src/features/queue/hooks/useDeleteCurrentQueueItem.ts
create mode 100644 invokeai/frontend/web/src/features/queue/hooks/useDeleteQueueItem.ts
create mode 100644 invokeai/frontend/web/src/features/queue/hooks/useDeleteQueueItemsByDestination.ts
diff --git a/invokeai/app/api/routers/session_queue.py b/invokeai/app/api/routers/session_queue.py
index f19f4bc47c..5902a492d4 100644
--- a/invokeai/app/api/routers/session_queue.py
+++ b/invokeai/app/api/routers/session_queue.py
@@ -14,6 +14,7 @@ from invokeai.app.services.session_queue.session_queue_common import (
CancelByBatchIDsResult,
CancelByDestinationResult,
ClearResult,
+ DeleteAllExceptCurrentResult,
DeleteByDestinationResult,
EnqueueBatchResult,
FieldIdentifier,
@@ -146,6 +147,18 @@ async def cancel_all_except_current(
return ApiDependencies.invoker.services.session_queue.cancel_all_except_current(queue_id=queue_id)
+@session_queue_router.put(
+ "/{queue_id}/delete_all_except_current",
+ operation_id="delete_all_except_current",
+ responses={200: {"model": DeleteAllExceptCurrentResult}},
+)
+async def delete_all_except_current(
+ queue_id: str = Path(description="The queue id to perform this operation on"),
+) -> DeleteAllExceptCurrentResult:
+ """Immediately deletes all queue items except in-processing items"""
+ return ApiDependencies.invoker.services.session_queue.delete_all_except_current(queue_id=queue_id)
+
+
@session_queue_router.put(
"/{queue_id}/cancel_by_batch_ids",
operation_id="cancel_by_batch_ids",
diff --git a/invokeai/app/services/session_queue/session_queue_base.py b/invokeai/app/services/session_queue/session_queue_base.py
index 17c31e77a7..aa1126576d 100644
--- a/invokeai/app/services/session_queue/session_queue_base.py
+++ b/invokeai/app/services/session_queue/session_queue_base.py
@@ -10,6 +10,7 @@ from invokeai.app.services.session_queue.session_queue_common import (
CancelByDestinationResult,
CancelByQueueIDResult,
ClearResult,
+ DeleteAllExceptCurrentResult,
DeleteByDestinationResult,
EnqueueBatchResult,
IsEmptyResult,
@@ -129,6 +130,11 @@ class SessionQueueBase(ABC):
"""Cancels all queue items except in-progress items"""
pass
+ @abstractmethod
+ def delete_all_except_current(self, queue_id: str) -> DeleteAllExceptCurrentResult:
+ """Deletes all queue items except in-progress items"""
+ pass
+
@abstractmethod
def list_queue_items(
self,
diff --git a/invokeai/app/services/session_queue/session_queue_common.py b/invokeai/app/services/session_queue/session_queue_common.py
index d41fb44533..9075732901 100644
--- a/invokeai/app/services/session_queue/session_queue_common.py
+++ b/invokeai/app/services/session_queue/session_queue_common.py
@@ -369,6 +369,12 @@ class DeleteByDestinationResult(BaseModel):
deleted: int = Field(..., description="Number of queue items deleted")
+class DeleteAllExceptCurrentResult(DeleteByDestinationResult):
+ """Result of deleting all except current"""
+
+ pass
+
+
class CancelByQueueIDResult(CancelByBatchIDsResult):
"""Result of canceling by queue id"""
diff --git a/invokeai/app/services/session_queue/session_queue_sqlite.py b/invokeai/app/services/session_queue/session_queue_sqlite.py
index c31226581a..36b4f05cf3 100644
--- a/invokeai/app/services/session_queue/session_queue_sqlite.py
+++ b/invokeai/app/services/session_queue/session_queue_sqlite.py
@@ -17,6 +17,7 @@ from invokeai.app.services.session_queue.session_queue_common import (
CancelByDestinationResult,
CancelByQueueIDResult,
ClearResult,
+ DeleteAllExceptCurrentResult,
DeleteByDestinationResult,
EnqueueBatchResult,
IsEmptyResult,
@@ -489,6 +490,37 @@ class SqliteSessionQueue(SessionQueueBase):
raise
return DeleteByDestinationResult(deleted=count)
+ def delete_all_except_current(self, queue_id: str) -> DeleteAllExceptCurrentResult:
+ try:
+ cursor = self._conn.cursor()
+ where = """--sql
+ WHERE
+ queue_id == ?
+ AND status == 'pending'
+ """
+ cursor.execute(
+ f"""--sql
+ SELECT COUNT(*)
+ FROM session_queue
+ {where};
+ """,
+ (queue_id,),
+ )
+ count = cursor.fetchone()[0]
+ cursor.execute(
+ f"""--sql
+ DELETE
+ FROM session_queue
+ {where};
+ """,
+ (queue_id,),
+ )
+ self._conn.commit()
+ except Exception:
+ self._conn.rollback()
+ raise
+ return DeleteAllExceptCurrentResult(deleted=count)
+
def cancel_by_queue_id(self, queue_id: str) -> CancelByQueueIDResult:
try:
cursor = self._conn.cursor()
diff --git a/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx b/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx
index ae83f0c5c2..782b338fe4 100644
--- a/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx
+++ b/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx
@@ -16,6 +16,7 @@ import { ShareWorkflowModal } from 'features/nodes/components/sidePanel/workflow
import { WorkflowLibraryModal } from 'features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryModal';
import { CancelAllExceptCurrentQueueItemConfirmationAlertDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog';
import { ClearQueueConfirmationsAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
+import { DeleteAllExceptCurrentQueueItemConfirmationAlertDialog } from 'features/queue/components/DeleteAllExceptCurrentQueueItemConfirmationAlertDialog';
import { DeleteStylePresetDialog } from 'features/stylePresets/components/DeleteStylePresetDialog';
import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal';
import RefreshAfterResetModal from 'features/system/components/SettingsModal/RefreshAfterResetModal';
@@ -40,6 +41,7 @@ export const GlobalModalIsolator = memo(() => {
+
diff --git a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
index 76c2db9a6d..88fc2bfdfb 100644
--- a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
+++ b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
@@ -1,6 +1,6 @@
import { useAppDispatch } from 'app/store/storeHooks';
-import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
+import { useDeleteCurrentQueueItem } from 'features/queue/hooks/useDeleteCurrentQueueItem';
import { useInvoke } from 'features/queue/hooks/useInvoke';
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
@@ -35,34 +35,30 @@ export const useGlobalHotkeys = () => {
dependencies: [queue],
});
- const {
- cancelQueueItem,
- isDisabled: isDisabledCancelQueueItem,
- isLoading: isLoadingCancelQueueItem,
- } = useCancelCurrentQueueItem();
+ const deleteCurrentQueueItem = useDeleteCurrentQueueItem();
useRegisteredHotkeys({
id: 'cancelQueueItem',
category: 'app',
- callback: cancelQueueItem,
+ callback: deleteCurrentQueueItem.trigger,
options: {
- enabled: !isDisabledCancelQueueItem && !isLoadingCancelQueueItem,
+ enabled: !deleteCurrentQueueItem.isDisabled && !deleteCurrentQueueItem.isLoading,
preventDefault: true,
},
- dependencies: [cancelQueueItem, isDisabledCancelQueueItem, isLoadingCancelQueueItem],
+ dependencies: [deleteCurrentQueueItem],
});
- const { clearQueue, isDisabled: isDisabledClearQueue, isLoading: isLoadingClearQueue } = useClearQueue();
+ const clearQueue = useClearQueue();
useRegisteredHotkeys({
id: 'clearQueue',
category: 'app',
- callback: clearQueue,
+ callback: clearQueue.trigger,
options: {
- enabled: !isDisabledClearQueue && !isLoadingClearQueue,
+ enabled: !clearQueue.isDisabled && !clearQueue.isLoading,
preventDefault: true,
},
- dependencies: [clearQueue, isDisabledClearQueue, isLoadingClearQueue],
+ dependencies: [clearQueue],
});
useRegisteredHotkeys({
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/context.tsx b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/context.tsx
index 33b5fc9cc1..daf843f947 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/context.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/context.tsx
@@ -67,34 +67,6 @@ const setProgress = ($progressData: WritableAtom>,
}
};
-const clearProgressEvent = ($progressData: WritableAtom>, itemId: number) => {
- const progressData = $progressData.get();
- const current = progressData[itemId];
- if (!current) {
- return;
- }
- const next = { ...current };
- next.progressEvent = null;
- $progressData.set({
- ...progressData,
- [itemId]: next,
- });
-};
-
-const clearProgressImage = ($progressData: WritableAtom>, itemId: number) => {
- const progressData = $progressData.get();
- const current = progressData[itemId];
- if (!current) {
- return;
- }
- const next = { ...current };
- next.progressImage = null;
- $progressData.set({
- ...progressData,
- [itemId]: next,
- });
-};
-
type CanvasSessionContextValue = {
session: { id: string; type: 'simple' | 'advanced' };
$items: Atom;
@@ -132,15 +104,11 @@ export const CanvasSessionContextProvider = memo(
const socket = useStore($socket);
/**
- * Manually-synced atom containing the queue items for the current session.
+ * Manually-synced atom containing queue items for the current session. This is populated from the RTK Query cache
+ * and kept in sync with it via a redux subscription.
*/
const $items = useState(() => atom([]))[0];
- /**
- * Manually-synced atom containing the queue items for the current session.
- */
- const $prevItems = useState(() => atom([]))[0];
-
/**
* Whether auto-switch is enabled.
*/
@@ -294,28 +262,16 @@ export const CanvasSessionContextProvider = memo(
setProgress($progressData, data);
};
- const onQueueItemStatusChanged = (data: S['QueueItemStatusChangedEvent']) => {
- if (data.destination !== session.id) {
- return;
- }
-
- if (data.status === 'canceled' || data.status === 'failed') {
- clearProgressEvent($progressData, data.item_id);
- clearProgressImage($progressData, data.item_id);
- }
- };
-
socket.on('invocation_progress', onProgress);
- socket.on('queue_item_status_changed', onQueueItemStatusChanged);
return () => {
socket.off('invocation_progress', onProgress);
- socket.off('queue_item_status_changed', onQueueItemStatusChanged);
};
}, [$autoSwitch, $progressData, $selectedItemId, session.id, socket]);
// Set up state subscriptions and effects
useEffect(() => {
+ let _prevItems: readonly S['SessionQueueItem'][] = [];
// Seed the $items atom with the initial query cache state
$items.set(selectQueueItems(store.getState()));
@@ -324,7 +280,7 @@ export const CanvasSessionContextProvider = memo(
const prevItems = $items.get();
const items = selectQueueItems(store.getState());
if (items !== prevItems) {
- $prevItems.set(prevItems);
+ _prevItems = prevItems;
$items.set(items);
}
});
@@ -344,7 +300,7 @@ export const CanvasSessionContextProvider = memo(
// If an item is selected and it is not in the list of items, un-set it. This effect will run again and we'll
// the above case, selecting the first item if there are any.
if (selectedItemId !== null && items.findIndex(({ item_id }) => item_id === selectedItemId) === -1) {
- let prevIndex = $prevItems.get().findIndex(({ item_id }) => item_id === selectedItemId);
+ let prevIndex = _prevItems.findIndex(({ item_id }) => item_id === selectedItemId);
if (prevIndex >= items.length) {
prevIndex = items.length - 1;
}
@@ -357,19 +313,37 @@ export const CanvasSessionContextProvider = memo(
// Clean up the progress data when a queue item is discarded.
const unsubCleanUpProgressData = $items.listen((items) => {
const progressData = $progressData.get();
+
const toDelete: number[] = [];
+ const toClear: number[] = [];
+
for (const datum of Object.values(progressData)) {
- if (items.findIndex(({ item_id }) => item_id === datum.itemId) === -1) {
+ const item = items.find(({ item_id }) => item_id === datum.itemId);
+ if (!item) {
toDelete.push(datum.itemId);
+ } else if (item.status === 'canceled' || item.status === 'failed') {
+ toClear.push(datum.itemId);
}
}
+
if (toDelete.length === 0) {
return;
}
+
const newProgressData = { ...progressData };
+
for (const itemId of toDelete) {
delete newProgressData[itemId];
}
+
+ for (const itemId of toClear) {
+ const current = newProgressData[itemId];
+ if (current) {
+ current.progressEvent = null;
+ current.progressImage = null;
+ }
+ }
+
$progressData.set(newProgressData);
});
@@ -408,17 +382,7 @@ export const CanvasSessionContextProvider = memo(
$progressData.set({});
$selectedItemId.set(null);
};
- }, [
- $autoSwitch,
- $items,
- $lastLoadedItemId,
- $prevItems,
- $progressData,
- $selectedItemId,
- selectQueueItems,
- session.id,
- store,
- ]);
+ }, [$autoSwitch, $items, $lastLoadedItemId, $progressData, $selectedItemId, selectQueueItems, session.id, store]);
const value = useMemo(
() => ({
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarAcceptButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarAcceptButton.tsx
index b7280327eb..1272887abc 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarAcceptButton.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarAcceptButton.tsx
@@ -9,11 +9,11 @@ import { canvasSessionGenerationFinished } from 'features/controlLayers/store/ca
import { selectBboxRect, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
import { imageNameToImageObject } from 'features/controlLayers/store/util';
+import { useDeleteQueueItemsByDestination } from 'features/queue/hooks/useDeleteQueueItemsByDestination';
import { memo, useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { PiCheckBold } from 'react-icons/pi';
-import { useDeleteQueueItemsByDestinationMutation } from 'services/api/endpoints/queue';
export const StagingAreaToolbarAcceptButton = memo(() => {
const ctx = useCanvasSessionContext();
@@ -24,7 +24,7 @@ export const StagingAreaToolbarAcceptButton = memo(() => {
const shouldShowStagedImage = useStore(canvasManager.stagingArea.$shouldShowStagedImage);
const isCanvasFocused = useIsRegionFocused('canvas');
const selectedItemImageName = useStore(ctx.$selectedItemOutputImageName);
- const [deleteByDestination] = useDeleteQueueItemsByDestinationMutation();
+ const deleteQueueItemsByDestination = useDeleteQueueItemsByDestination();
const { t } = useTranslation();
@@ -41,8 +41,15 @@ export const StagingAreaToolbarAcceptButton = memo(() => {
dispatch(rasterLayerAdded({ overrides, isSelected: selectedEntityIdentifier?.type === 'raster_layer' }));
dispatch(canvasSessionGenerationFinished());
- deleteByDestination({ destination: ctx.session.id });
- }, [selectedItemImageName, bboxRect, dispatch, selectedEntityIdentifier?.type, deleteByDestination, ctx.session.id]);
+ deleteQueueItemsByDestination.trigger(ctx.session.id);
+ }, [
+ selectedItemImageName,
+ bboxRect,
+ dispatch,
+ selectedEntityIdentifier?.type,
+ deleteQueueItemsByDestination,
+ ctx.session.id,
+ ]);
useHotkeys(
['enter'],
@@ -61,7 +68,8 @@ export const StagingAreaToolbarAcceptButton = memo(() => {
icon={}
onClick={acceptSelected}
colorScheme="invokeBlue"
- isDisabled={!selectedItemImageName || !shouldShowStagedImage}
+ isDisabled={!selectedItemImageName || !shouldShowStagedImage || deleteQueueItemsByDestination.isDisabled}
+ isLoading={deleteQueueItemsByDestination.isLoading}
/>
);
});
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardAllButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardAllButton.tsx
index d3cc4b20e4..9c7f653898 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardAllButton.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardAllButton.tsx
@@ -2,21 +2,21 @@ import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { useCanvasSessionContext } from 'features/controlLayers/components/SimpleSession/context';
import { canvasSessionGenerationFinished } from 'features/controlLayers/store/canvasStagingAreaSlice';
+import { useDeleteQueueItemsByDestination } from 'features/queue/hooks/useDeleteQueueItemsByDestination';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiTrashSimpleBold } from 'react-icons/pi';
-import { useDeleteQueueItemsByDestinationMutation } from 'services/api/endpoints/queue';
export const StagingAreaToolbarDiscardAllButton = memo(({ isDisabled }: { isDisabled?: boolean }) => {
const ctx = useCanvasSessionContext();
const dispatch = useAppDispatch();
const { t } = useTranslation();
- const [deleteByDestination] = useDeleteQueueItemsByDestinationMutation();
+ const deleteQueueItemsByDestination = useDeleteQueueItemsByDestination();
const discardAll = useCallback(() => {
- deleteByDestination({ destination: ctx.session.id });
+ deleteQueueItemsByDestination.trigger(ctx.session.id);
dispatch(canvasSessionGenerationFinished());
- }, [deleteByDestination, ctx.session.id, dispatch]);
+ }, [deleteQueueItemsByDestination, ctx.session.id, dispatch]);
return (
);
});
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardSelectedButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardSelectedButton.tsx
index e1c0738fe9..b6fc9d934a 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardSelectedButton.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardSelectedButton.tsx
@@ -3,15 +3,15 @@ import { useStore } from '@nanostores/react';
import { useAppDispatch } from 'app/store/storeHooks';
import { useCanvasSessionContext } from 'features/controlLayers/components/SimpleSession/context';
import { canvasSessionGenerationFinished } from 'features/controlLayers/store/canvasStagingAreaSlice';
+import { useDeleteQueueItem } from 'features/queue/hooks/useDeleteQueueItem';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiXBold } from 'react-icons/pi';
-import { useDeleteQueueItemMutation } from 'services/api/endpoints/queue';
export const StagingAreaToolbarDiscardSelectedButton = memo(({ isDisabled }: { isDisabled?: boolean }) => {
const dispatch = useAppDispatch();
const ctx = useCanvasSessionContext();
- const [deleteQueueItem] = useDeleteQueueItemMutation();
+ const deleteQueueItem = useDeleteQueueItem();
const selectedItemId = useStore(ctx.$selectedItemId);
const { t } = useTranslation();
@@ -21,10 +21,10 @@ export const StagingAreaToolbarDiscardSelectedButton = memo(({ isDisabled }: { i
return;
}
const itemCount = ctx.$itemCount.get();
- deleteQueueItem({ item_id: selectedItemId });
if (itemCount <= 1) {
dispatch(canvasSessionGenerationFinished());
}
+ deleteQueueItem.trigger(selectedItemId);
}, [selectedItemId, ctx.$itemCount, deleteQueueItem, dispatch]);
return (
@@ -35,7 +35,8 @@ export const StagingAreaToolbarDiscardSelectedButton = memo(({ isDisabled }: { i
onClick={discardSelected}
colorScheme="invokeBlue"
fontSize={16}
- isDisabled={selectedItemId === null || isDisabled}
+ isDisabled={selectedItemId === null || deleteQueueItem.isDisabled || isDisabled}
+ isLoading={deleteQueueItem.isLoading}
/>
);
});
diff --git a/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentIconButton.tsx b/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentIconButton.tsx
new file mode 100644
index 0000000000..1d83b409b6
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentIconButton.tsx
@@ -0,0 +1,25 @@
+import { IconButton } from '@invoke-ai/ui-library';
+import { useCancelAllExceptCurrentQueueItemDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiXCircle } from 'react-icons/pi';
+
+export const CancelAllExceptCurrentIconButton = memo(() => {
+ const { t } = useTranslation();
+ const cancelAllExceptCurrent = useCancelAllExceptCurrentQueueItemDialog();
+
+ return (
+ }
+ colorScheme="error"
+ onClick={cancelAllExceptCurrent.openDialog}
+ />
+ );
+});
+
+CancelAllExceptCurrentIconButton.displayName = 'CancelAllExceptCurrentIconButton';
diff --git a/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog.tsx b/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog.tsx
index cdaaebcdf0..a27914a4c1 100644
--- a/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog.tsx
@@ -9,16 +9,15 @@ const [useCancelAllExceptCurrentQueueItemConfirmationAlertDialog] = buildUseBool
export const useCancelAllExceptCurrentQueueItemDialog = () => {
const dialog = useCancelAllExceptCurrentQueueItemConfirmationAlertDialog();
- const { cancelAllExceptCurrentQueueItem, isLoading, isDisabled, queueStatus } = useCancelAllExceptCurrentQueueItem();
+ const cancelAllExceptCurrentQueueItem = useCancelAllExceptCurrentQueueItem();
return {
- cancelAllExceptCurrentQueueItem,
+ trigger: cancelAllExceptCurrentQueueItem.trigger,
isOpen: dialog.isTrue,
openDialog: dialog.setTrue,
closeDialog: dialog.setFalse,
- isLoading,
- queueStatus,
- isDisabled,
+ isLoading: cancelAllExceptCurrentQueueItem.isLoading,
+ isDisabled: cancelAllExceptCurrentQueueItem.isDisabled,
};
};
@@ -32,7 +31,7 @@ export const CancelAllExceptCurrentQueueItemConfirmationAlertDialog = memo(() =>
isOpen={cancelAllExceptCurrentQueueItem.isOpen}
onClose={cancelAllExceptCurrentQueueItem.closeDialog}
title={t('queue.cancelAllExceptCurrentTooltip')}
- acceptCallback={cancelAllExceptCurrentQueueItem.cancelAllExceptCurrentQueueItem}
+ acceptCallback={cancelAllExceptCurrentQueueItem.trigger}
acceptButtonText={t('queue.confirm')}
useInert={false}
>
diff --git a/invokeai/frontend/web/src/features/queue/components/ClearInvocationCacheButton.tsx b/invokeai/frontend/web/src/features/queue/components/ClearInvocationCacheButton.tsx
index 52b1b237e7..6a9f157361 100644
--- a/invokeai/frontend/web/src/features/queue/components/ClearInvocationCacheButton.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/ClearInvocationCacheButton.tsx
@@ -5,10 +5,14 @@ import { useTranslation } from 'react-i18next';
const ClearInvocationCacheButton = () => {
const { t } = useTranslation();
- const { clearInvocationCache, isDisabled, isLoading } = useClearInvocationCache();
+ const clearInvocationCache = useClearInvocationCache();
return (
-
)}
}
colorScheme="error"
diff --git a/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx b/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx
index 17c3ed104f..6432b4e913 100644
--- a/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx
@@ -1,6 +1,6 @@
/* eslint-disable i18next/no-literal-string */
import { ButtonGroup, Flex } from '@invoke-ai/ui-library';
-import { CancelAllExceptCurrentButton } from 'features/queue/components/CancelAllExceptCurrentButton';
+import { DeleteAllExceptCurrentQueueItemConfirmationAlertDialog } from 'features/queue/components/DeleteAllExceptCurrentQueueItemConfirmationAlertDialog';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { memo } from 'react';
@@ -24,7 +24,7 @@ const QueueTabQueueControls = () => {
)}
-
+
diff --git a/invokeai/frontend/web/src/features/queue/components/ResumeProcessorButton.tsx b/invokeai/frontend/web/src/features/queue/components/ResumeProcessorButton.tsx
index cf62f8cb2a..31f073558a 100644
--- a/invokeai/frontend/web/src/features/queue/components/ResumeProcessorButton.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/ResumeProcessorButton.tsx
@@ -11,17 +11,17 @@ type Props = {
const ResumeProcessorButton = ({ asIconButton }: Props) => {
const { t } = useTranslation();
- const { resumeProcessor, isLoading, isDisabled } = useResumeProcessor();
+ const resumeProcessor = useResumeProcessor();
return (
}
- onClick={resumeProcessor}
+ onClick={resumeProcessor.trigger}
colorScheme="green"
/>
);
diff --git a/invokeai/frontend/web/src/features/queue/components/ToggleInvocationCacheButton.tsx b/invokeai/frontend/web/src/features/queue/components/ToggleInvocationCacheButton.tsx
index 1720e6a69d..310ee46ca5 100644
--- a/invokeai/frontend/web/src/features/queue/components/ToggleInvocationCacheButton.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/ToggleInvocationCacheButton.tsx
@@ -9,28 +9,28 @@ const ToggleInvocationCacheButton = () => {
const { t } = useTranslation();
const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
- const {
- enableInvocationCache,
- isDisabled: isEnableDisabled,
- isLoading: isEnableLoading,
- } = useEnableInvocationCache();
+ const enableInvocationCache = useEnableInvocationCache();
- const {
- disableInvocationCache,
- isDisabled: isDisableDisabled,
- isLoading: isDisableLoading,
- } = useDisableInvocationCache();
+ const disableInvocationCache = useDisableInvocationCache();
if (cacheStatus?.enabled) {
return (
-
+
{t('invocationCache.disable')}
);
}
return (
-
+
{t('invocationCache.enable')}
);
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useBatchIsCanceled.ts b/invokeai/frontend/web/src/features/queue/hooks/useBatchIsCanceled.ts
new file mode 100644
index 0000000000..2fd82c5b3b
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/hooks/useBatchIsCanceled.ts
@@ -0,0 +1,20 @@
+import { useGetBatchStatusQuery } from 'services/api/endpoints/queue';
+
+export const useBatchIsCanceled = (batch_id: string) => {
+ const { isCanceled } = useGetBatchStatusQuery(
+ { batch_id },
+ {
+ selectFromResult: ({ data }) => {
+ if (!data) {
+ return { isCanceled: true };
+ }
+
+ return {
+ isCanceled: data?.in_progress === 0 && data?.pending === 0,
+ };
+ },
+ }
+ );
+
+ return isCanceled;
+};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCancelAllExceptCurrentQueueItem.ts b/invokeai/frontend/web/src/features/queue/hooks/useCancelAllExceptCurrentQueueItem.ts
index 3d38891545..8e6c79b96a 100644
--- a/invokeai/frontend/web/src/features/queue/hooks/useCancelAllExceptCurrentQueueItem.ts
+++ b/invokeai/frontend/web/src/features/queue/hooks/useCancelAllExceptCurrentQueueItem.ts
@@ -1,6 +1,6 @@
import { useStore } from '@nanostores/react';
import { toast } from 'features/toast/toast';
-import { useCallback, useMemo } from 'react';
+import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useCancelAllExceptCurrentMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue';
import { $isConnected } from 'services/events/stores';
@@ -9,17 +9,17 @@ export const useCancelAllExceptCurrentQueueItem = () => {
const { t } = useTranslation();
const { data: queueStatus } = useGetQueueStatusQuery();
const isConnected = useStore($isConnected);
- const [trigger, { isLoading }] = useCancelAllExceptCurrentMutation({
+ const [_trigger, { isLoading }] = useCancelAllExceptCurrentMutation({
fixedCacheKey: 'cancelAllExceptCurrent',
});
- const cancelAllExceptCurrentQueueItem = useCallback(async () => {
+ const trigger = useCallback(async () => {
if (!queueStatus?.queue.pending) {
return;
}
try {
- await trigger().unwrap();
+ await _trigger().unwrap();
toast({
id: 'QUEUE_CANCEL_SUCCEEDED',
title: t('queue.cancelSucceeded'),
@@ -32,17 +32,7 @@ export const useCancelAllExceptCurrentQueueItem = () => {
status: 'error',
});
}
- }, [queueStatus?.queue.pending, trigger, t]);
+ }, [queueStatus?.queue.pending, _trigger, t]);
- const isDisabled = useMemo(
- () => !isConnected || !queueStatus?.queue.pending,
- [isConnected, queueStatus?.queue.pending]
- );
-
- return {
- cancelAllExceptCurrentQueueItem,
- isLoading,
- queueStatus,
- isDisabled,
- };
+ return { trigger, isLoading, isDisabled: !isConnected || !queueStatus?.queue.pending } as const;
};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCancelBatch.ts b/invokeai/frontend/web/src/features/queue/hooks/useCancelBatch.ts
index 92d0cbb5a6..ce9202f3cf 100644
--- a/invokeai/frontend/web/src/features/queue/hooks/useCancelBatch.ts
+++ b/invokeai/frontend/web/src/features/queue/hooks/useCancelBatch.ts
@@ -2,48 +2,34 @@ import { useStore } from '@nanostores/react';
import { toast } from 'features/toast/toast';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
-import { useCancelByBatchIdsMutation, useGetBatchStatusQuery } from 'services/api/endpoints/queue';
+import { useCancelByBatchIdsMutation } from 'services/api/endpoints/queue';
import { $isConnected } from 'services/events/stores';
-export const useCancelBatch = (batch_id: string) => {
+export const useCancelBatch = () => {
const isConnected = useStore($isConnected);
- const { isCanceled } = useGetBatchStatusQuery(
- { batch_id },
- {
- selectFromResult: ({ data }) => {
- if (!data) {
- return { isCanceled: true };
- }
-
- return {
- isCanceled: data?.in_progress === 0 && data?.pending === 0,
- };
- },
- }
- );
- const [trigger, { isLoading }] = useCancelByBatchIdsMutation({
+ const [_trigger, { isLoading }] = useCancelByBatchIdsMutation({
fixedCacheKey: 'cancelByBatchIds',
});
const { t } = useTranslation();
- const cancelBatch = useCallback(async () => {
- if (isCanceled) {
- return;
- }
- try {
- await trigger({ batch_ids: [batch_id] }).unwrap();
- toast({
- id: 'CANCEL_BATCH_SUCCEEDED',
- title: t('queue.cancelBatchSucceeded'),
- status: 'success',
- });
- } catch {
- toast({
- id: 'CANCEL_BATCH_FAILED',
- title: t('queue.cancelBatchFailed'),
- status: 'error',
- });
- }
- }, [batch_id, isCanceled, t, trigger]);
+ const trigger = useCallback(
+ async (batch_id: string) => {
+ try {
+ await _trigger({ batch_ids: [batch_id] }).unwrap();
+ toast({
+ id: 'CANCEL_BATCH_SUCCEEDED',
+ title: t('queue.cancelBatchSucceeded'),
+ status: 'success',
+ });
+ } catch {
+ toast({
+ id: 'CANCEL_BATCH_FAILED',
+ title: t('queue.cancelBatchFailed'),
+ status: 'error',
+ });
+ }
+ },
+ [t, _trigger]
+ );
- return { cancelBatch, isLoading, isCanceled, isDisabled: !isConnected };
+ return { trigger, isLoading, isDisabled: !isConnected };
};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCancelCurrentQueueItem.ts b/invokeai/frontend/web/src/features/queue/hooks/useCancelCurrentQueueItem.ts
index 0f9a5c48fe..509efd8a75 100644
--- a/invokeai/frontend/web/src/features/queue/hooks/useCancelCurrentQueueItem.ts
+++ b/invokeai/frontend/web/src/features/queue/hooks/useCancelCurrentQueueItem.ts
@@ -1,43 +1,20 @@
-import { useStore } from '@nanostores/react';
-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';
+import { useCancelQueueItem } from 'features/queue/hooks/useCancelQueueItem';
+import { useCurrentQueueItemId } from 'features/queue/hooks/useCurrentQueueItemId';
+import { useCallback } from 'react';
export const useCancelCurrentQueueItem = () => {
- const isConnected = useStore($isConnected);
- const { data: queueStatus } = useGetQueueStatusQuery();
- const [trigger, { isLoading }] = useCancelQueueItemMutation();
- const { t } = useTranslation();
- const currentQueueItemId = useMemo(() => queueStatus?.queue.item_id, [queueStatus?.queue.item_id]);
- const cancelQueueItem = useCallback(async () => {
- if (currentQueueItemId !== null || currentQueueItemId !== undefined) {
+ const currentQueueItemId = useCurrentQueueItemId();
+ const cancelQueueItem = useCancelQueueItem();
+ const trigger = useCallback(() => {
+ if (currentQueueItemId === null) {
return;
}
- try {
- await trigger({ item_id: currentQueueItemId }).unwrap();
- toast({
- id: 'QUEUE_CANCEL_SUCCEEDED',
- title: t('queue.cancelSucceeded'),
- status: 'success',
- });
- } catch {
- toast({
- id: 'QUEUE_CANCEL_FAILED',
- title: t('queue.cancelFailed'),
- status: 'error',
- });
- }
- }, [currentQueueItemId, t, trigger]);
-
- const isDisabled = useMemo(() => !isConnected || isNil(currentQueueItemId), [isConnected, currentQueueItemId]);
+ cancelQueueItem.trigger(currentQueueItemId);
+ }, [currentQueueItemId, cancelQueueItem]);
return {
- cancelQueueItem,
- isLoading,
- currentQueueItemId,
- isDisabled,
+ trigger,
+ isLoading: cancelQueueItem.isLoading,
+ isDisabled: cancelQueueItem.isDisabled || currentQueueItemId === null,
};
};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCancelQueueItem.ts b/invokeai/frontend/web/src/features/queue/hooks/useCancelQueueItem.ts
index 1e59f8ec46..15de37e030 100644
--- a/invokeai/frontend/web/src/features/queue/hooks/useCancelQueueItem.ts
+++ b/invokeai/frontend/web/src/features/queue/hooks/useCancelQueueItem.ts
@@ -5,26 +5,29 @@ import { useTranslation } from 'react-i18next';
import { useCancelQueueItemMutation } from 'services/api/endpoints/queue';
import { $isConnected } from 'services/events/stores';
-export const useCancelQueueItem = (item_id: number) => {
+export const useCancelQueueItem = () => {
const isConnected = useStore($isConnected);
- const [trigger, { isLoading }] = useCancelQueueItemMutation();
+ const [_trigger, { isLoading }] = useCancelQueueItemMutation();
const { t } = useTranslation();
- const cancelQueueItem = useCallback(async () => {
- try {
- await trigger({ item_id }).unwrap();
- toast({
- id: 'QUEUE_CANCEL_SUCCEEDED',
- title: t('queue.cancelSucceeded'),
- status: 'success',
- });
- } catch {
- toast({
- id: 'QUEUE_CANCEL_FAILED',
- title: t('queue.cancelFailed'),
- status: 'error',
- });
- }
- }, [item_id, t, trigger]);
+ const trigger = useCallback(
+ async (item_id: number) => {
+ try {
+ await _trigger({ item_id }).unwrap();
+ toast({
+ id: 'QUEUE_CANCEL_SUCCEEDED',
+ title: t('queue.cancelSucceeded'),
+ status: 'success',
+ });
+ } catch {
+ toast({
+ id: 'QUEUE_CANCEL_FAILED',
+ title: t('queue.cancelFailed'),
+ status: 'error',
+ });
+ }
+ },
+ [t, _trigger]
+ );
- return { cancelQueueItem, isLoading, isDisabled: !isConnected };
+ return { trigger, isLoading, isDisabled: !isConnected };
};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCancelQueueItemsByDestination.ts b/invokeai/frontend/web/src/features/queue/hooks/useCancelQueueItemsByDestination.ts
new file mode 100644
index 0000000000..0d85a933c7
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/hooks/useCancelQueueItemsByDestination.ts
@@ -0,0 +1,33 @@
+import { useStore } from '@nanostores/react';
+import { toast } from 'features/toast/toast';
+import { useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useCancelQueueItemsByDestinationMutation } from 'services/api/endpoints/queue';
+import { $isConnected } from 'services/events/stores';
+
+export const useCancelQueueItemsByDestination = () => {
+ const isConnected = useStore($isConnected);
+ const [_trigger, { isLoading }] = useCancelQueueItemsByDestinationMutation();
+ const { t } = useTranslation();
+ const trigger = useCallback(
+ async (destination: string) => {
+ try {
+ await _trigger({ destination }).unwrap();
+ toast({
+ id: 'QUEUE_CANCEL_SUCCEEDED',
+ title: t('queue.cancelSucceeded'),
+ status: 'success',
+ });
+ } catch {
+ toast({
+ id: 'QUEUE_CANCEL_FAILED',
+ title: t('queue.cancelFailed'),
+ status: 'error',
+ });
+ }
+ },
+ [t, _trigger]
+ );
+
+ return { trigger, isLoading, isDisabled: !isConnected };
+};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useClearInvocationCache.ts b/invokeai/frontend/web/src/features/queue/hooks/useClearInvocationCache.ts
index 19ab3cf45f..90c5a0dd3a 100644
--- a/invokeai/frontend/web/src/features/queue/hooks/useClearInvocationCache.ts
+++ b/invokeai/frontend/web/src/features/queue/hooks/useClearInvocationCache.ts
@@ -1,6 +1,6 @@
import { useStore } from '@nanostores/react';
import { toast } from 'features/toast/toast';
-import { useCallback, useMemo } from 'react';
+import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useClearInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo';
import { $isConnected } from 'services/events/stores';
@@ -9,19 +9,13 @@ export const useClearInvocationCache = () => {
const { t } = useTranslation();
const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
const isConnected = useStore($isConnected);
- const [trigger, { isLoading }] = useClearInvocationCacheMutation({
+ const [_trigger, { isLoading }] = useClearInvocationCacheMutation({
fixedCacheKey: 'clearInvocationCache',
});
- const isDisabled = useMemo(() => !cacheStatus?.size || !isConnected, [cacheStatus?.size, isConnected]);
-
- const clearInvocationCache = useCallback(async () => {
- if (isDisabled) {
- return;
- }
-
+ const trigger = useCallback(async () => {
try {
- await trigger().unwrap();
+ await _trigger().unwrap();
toast({
id: 'INVOCATION_CACHE_CLEAR_SUCCEEDED',
title: t('invocationCache.clearSucceeded'),
@@ -34,7 +28,7 @@ export const useClearInvocationCache = () => {
status: 'error',
});
}
- }, [isDisabled, trigger, t]);
+ }, [_trigger, t]);
- return { clearInvocationCache, isLoading, cacheStatus, isDisabled };
+ return { trigger, isLoading, isDisabled: !isConnected || !cacheStatus?.size };
};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useClearQueue.ts b/invokeai/frontend/web/src/features/queue/hooks/useClearQueue.ts
index 5d5f7713f2..9c24448ed4 100644
--- a/invokeai/frontend/web/src/features/queue/hooks/useClearQueue.ts
+++ b/invokeai/frontend/web/src/features/queue/hooks/useClearQueue.ts
@@ -2,7 +2,7 @@ import { useStore } from '@nanostores/react';
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 { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useClearQueueMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue';
import { $isConnected } from 'services/events/stores';
@@ -12,17 +12,17 @@ export const useClearQueue = () => {
const dispatch = useAppDispatch();
const { data: queueStatus } = useGetQueueStatusQuery();
const isConnected = useStore($isConnected);
- const [trigger, { isLoading }] = useClearQueueMutation({
+ const [_trigger, { isLoading }] = useClearQueueMutation({
fixedCacheKey: 'clearQueue',
});
- const clearQueue = useCallback(async () => {
+ const trigger = useCallback(async () => {
if (!queueStatus?.queue.total) {
return;
}
try {
- await trigger().unwrap();
+ await _trigger().unwrap();
toast({
id: 'QUEUE_CLEAR_SUCCEEDED',
title: t('queue.clearSucceeded'),
@@ -37,14 +37,7 @@ export const useClearQueue = () => {
status: 'error',
});
}
- }, [queueStatus?.queue.total, trigger, dispatch, t]);
+ }, [queueStatus?.queue.total, _trigger, dispatch, t]);
- const isDisabled = useMemo(() => !isConnected || !queueStatus?.queue.total, [isConnected, queueStatus?.queue.total]);
-
- return {
- clearQueue,
- isLoading,
- queueStatus,
- isDisabled,
- };
+ return { trigger, isLoading, isDisabled: !isConnected || !queueStatus?.queue.total };
};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCurrentDestination.ts b/invokeai/frontend/web/src/features/queue/hooks/useCurrentDestination.ts
deleted file mode 100644
index 773d966634..0000000000
--- a/invokeai/frontend/web/src/features/queue/hooks/useCurrentDestination.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { useGetCurrentQueueItemQuery } from 'services/api/endpoints/queue';
-
-export const useCurrentDestination = () => {
- const { destination } = useGetCurrentQueueItemQuery(undefined, {
- selectFromResult: ({ data }) => ({
- destination: data ? data.destination : null,
- }),
- });
-
- return destination;
-};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCurrentQueueItemId.ts b/invokeai/frontend/web/src/features/queue/hooks/useCurrentQueueItemId.ts
new file mode 100644
index 0000000000..daa3a82704
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/hooks/useCurrentQueueItemId.ts
@@ -0,0 +1,11 @@
+import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
+
+export const useCurrentQueueItemId = () => {
+ const { currentQueueItemId } = useGetQueueStatusQuery(undefined, {
+ selectFromResult: ({ data }) => ({
+ currentQueueItemId: data?.queue.item_id ?? null,
+ }),
+ });
+
+ return currentQueueItemId;
+};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useDeleteAllExceptCurrentQueueItem.ts b/invokeai/frontend/web/src/features/queue/hooks/useDeleteAllExceptCurrentQueueItem.ts
new file mode 100644
index 0000000000..1f34a76d24
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/hooks/useDeleteAllExceptCurrentQueueItem.ts
@@ -0,0 +1,38 @@
+import { useStore } from '@nanostores/react';
+import { toast } from 'features/toast/toast';
+import { useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useDeleteAllExceptCurrentMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue';
+import { $isConnected } from 'services/events/stores';
+
+export const useDeleteAllExceptCurrentQueueItem = () => {
+ const { t } = useTranslation();
+ const { data: queueStatus } = useGetQueueStatusQuery();
+ const isConnected = useStore($isConnected);
+ const [_trigger, { isLoading }] = useDeleteAllExceptCurrentMutation({
+ fixedCacheKey: 'deleteAllExceptCurrent',
+ });
+
+ const trigger = useCallback(async () => {
+ if (!queueStatus?.queue.pending) {
+ return;
+ }
+
+ try {
+ await _trigger().unwrap();
+ toast({
+ id: 'QUEUE_CANCEL_SUCCEEDED',
+ title: t('queue.cancelSucceeded'),
+ status: 'success',
+ });
+ } catch {
+ toast({
+ id: 'QUEUE_CANCEL_FAILED',
+ title: t('queue.cancelFailed'),
+ status: 'error',
+ });
+ }
+ }, [queueStatus?.queue.pending, _trigger, t]);
+
+ return { trigger, isLoading, isDisabled: !isConnected || !queueStatus?.queue.pending } as const;
+};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useDeleteCurrentQueueItem.ts b/invokeai/frontend/web/src/features/queue/hooks/useDeleteCurrentQueueItem.ts
new file mode 100644
index 0000000000..d119311235
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/hooks/useDeleteCurrentQueueItem.ts
@@ -0,0 +1,20 @@
+import { useCurrentQueueItemId } from 'features/queue/hooks/useCurrentQueueItemId';
+import { useDeleteQueueItem } from 'features/queue/hooks/useDeleteQueueItem';
+import { useCallback } from 'react';
+
+export const useDeleteCurrentQueueItem = () => {
+ const currentQueueItemId = useCurrentQueueItemId();
+ const deleteQueueItem = useDeleteQueueItem();
+ const trigger = useCallback(() => {
+ if (currentQueueItemId === null) {
+ return;
+ }
+ deleteQueueItem.trigger(currentQueueItemId);
+ }, [currentQueueItemId, deleteQueueItem]);
+
+ return {
+ trigger,
+ isLoading: deleteQueueItem.isLoading,
+ isDisabled: deleteQueueItem.isDisabled || currentQueueItemId === null,
+ };
+};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useDeleteQueueItem.ts b/invokeai/frontend/web/src/features/queue/hooks/useDeleteQueueItem.ts
new file mode 100644
index 0000000000..8e37d376b4
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/hooks/useDeleteQueueItem.ts
@@ -0,0 +1,33 @@
+import { useStore } from '@nanostores/react';
+import { toast } from 'features/toast/toast';
+import { useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useDeleteQueueItemMutation } from 'services/api/endpoints/queue';
+import { $isConnected } from 'services/events/stores';
+
+export const useDeleteQueueItem = () => {
+ const isConnected = useStore($isConnected);
+ const [_trigger, { isLoading }] = useDeleteQueueItemMutation();
+ const { t } = useTranslation();
+ const trigger = useCallback(
+ async (item_id: number) => {
+ try {
+ await _trigger({ item_id }).unwrap();
+ toast({
+ id: 'QUEUE_CANCEL_SUCCEEDED',
+ title: t('queue.cancelSucceeded'),
+ status: 'success',
+ });
+ } catch {
+ toast({
+ id: 'QUEUE_CANCEL_FAILED',
+ title: t('queue.cancelFailed'),
+ status: 'error',
+ });
+ }
+ },
+ [t, _trigger]
+ );
+
+ return { trigger, isLoading, isDisabled: !isConnected };
+};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useDeleteQueueItemsByDestination.ts b/invokeai/frontend/web/src/features/queue/hooks/useDeleteQueueItemsByDestination.ts
new file mode 100644
index 0000000000..376063cb44
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/hooks/useDeleteQueueItemsByDestination.ts
@@ -0,0 +1,33 @@
+import { useStore } from '@nanostores/react';
+import { toast } from 'features/toast/toast';
+import { useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useDeleteQueueItemsByDestinationMutation } from 'services/api/endpoints/queue';
+import { $isConnected } from 'services/events/stores';
+
+export const useDeleteQueueItemsByDestination = () => {
+ const isConnected = useStore($isConnected);
+ const [_trigger, { isLoading }] = useDeleteQueueItemsByDestinationMutation();
+ const { t } = useTranslation();
+ const trigger = useCallback(
+ async (destination: string) => {
+ try {
+ await _trigger({ destination }).unwrap();
+ toast({
+ id: 'QUEUE_CANCEL_SUCCEEDED',
+ title: t('queue.cancelSucceeded'),
+ status: 'success',
+ });
+ } catch {
+ toast({
+ id: 'QUEUE_CANCEL_FAILED',
+ title: t('queue.cancelFailed'),
+ status: 'error',
+ });
+ }
+ },
+ [t, _trigger]
+ );
+
+ return { trigger, isLoading, isDisabled: !isConnected };
+};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useDisableInvocationCache.ts b/invokeai/frontend/web/src/features/queue/hooks/useDisableInvocationCache.ts
index 703cf8d4cb..17ac6bec44 100644
--- a/invokeai/frontend/web/src/features/queue/hooks/useDisableInvocationCache.ts
+++ b/invokeai/frontend/web/src/features/queue/hooks/useDisableInvocationCache.ts
@@ -1,6 +1,6 @@
import { useStore } from '@nanostores/react';
import { toast } from 'features/toast/toast';
-import { useCallback, useMemo } from 'react';
+import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDisableInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo';
import { $isConnected } from 'services/events/stores';
@@ -9,22 +9,13 @@ export const useDisableInvocationCache = () => {
const { t } = useTranslation();
const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
const isConnected = useStore($isConnected);
- const [trigger, { isLoading }] = useDisableInvocationCacheMutation({
+ const [_trigger, { isLoading }] = useDisableInvocationCacheMutation({
fixedCacheKey: 'disableInvocationCache',
});
- const isDisabled = useMemo(
- () => !cacheStatus?.enabled || !isConnected || cacheStatus?.max_size === 0,
- [cacheStatus?.enabled, cacheStatus?.max_size, isConnected]
- );
-
- const disableInvocationCache = useCallback(async () => {
- if (isDisabled) {
- return;
- }
-
+ const trigger = useCallback(async () => {
try {
- await trigger().unwrap();
+ await _trigger().unwrap();
toast({
id: 'INVOCATION_CACHE_DISABLE_SUCCEEDED',
title: t('invocationCache.disableSucceeded'),
@@ -37,7 +28,12 @@ export const useDisableInvocationCache = () => {
status: 'error',
});
}
- }, [isDisabled, trigger, t]);
+ }, [_trigger, t]);
- return { disableInvocationCache, isLoading, cacheStatus, isDisabled };
+ return {
+ trigger,
+ isLoading,
+ cacheStatus,
+ isDisabled: !cacheStatus?.enabled || !isConnected || cacheStatus?.max_size === 0,
+ };
};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useEnableInvocationCache.ts b/invokeai/frontend/web/src/features/queue/hooks/useEnableInvocationCache.ts
index 2589d50717..02fcf444fa 100644
--- a/invokeai/frontend/web/src/features/queue/hooks/useEnableInvocationCache.ts
+++ b/invokeai/frontend/web/src/features/queue/hooks/useEnableInvocationCache.ts
@@ -1,6 +1,6 @@
import { useStore } from '@nanostores/react';
import { toast } from 'features/toast/toast';
-import { useCallback, useMemo } from 'react';
+import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useEnableInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo';
import { $isConnected } from 'services/events/stores';
@@ -9,22 +9,13 @@ export const useEnableInvocationCache = () => {
const { t } = useTranslation();
const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
const isConnected = useStore($isConnected);
- const [trigger, { isLoading }] = useEnableInvocationCacheMutation({
+ const [_trigger, { isLoading }] = useEnableInvocationCacheMutation({
fixedCacheKey: 'enableInvocationCache',
});
- const isDisabled = useMemo(
- () => cacheStatus?.enabled || !isConnected || cacheStatus?.max_size === 0,
- [cacheStatus?.enabled, cacheStatus?.max_size, isConnected]
- );
-
- const enableInvocationCache = useCallback(async () => {
- if (isDisabled) {
- return;
- }
-
+ const trigger = useCallback(async () => {
try {
- await trigger().unwrap();
+ await _trigger().unwrap();
toast({
id: 'INVOCATION_CACHE_ENABLE_SUCCEEDED',
title: t('invocationCache.enableSucceeded'),
@@ -37,7 +28,11 @@ export const useEnableInvocationCache = () => {
status: 'error',
});
}
- }, [isDisabled, trigger, t]);
+ }, [_trigger, t]);
- return { enableInvocationCache, isLoading, cacheStatus, isDisabled };
+ return {
+ trigger,
+ isLoading,
+ isDisabled: cacheStatus?.enabled || !isConnected || cacheStatus?.max_size === 0,
+ };
};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/usePauseProcessor.ts b/invokeai/frontend/web/src/features/queue/hooks/usePauseProcessor.ts
index d4712ad2b8..9e82576a4f 100644
--- a/invokeai/frontend/web/src/features/queue/hooks/usePauseProcessor.ts
+++ b/invokeai/frontend/web/src/features/queue/hooks/usePauseProcessor.ts
@@ -1,6 +1,6 @@
import { useStore } from '@nanostores/react';
import { toast } from 'features/toast/toast';
-import { useCallback, useMemo } from 'react';
+import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetQueueStatusQuery, usePauseProcessorMutation } from 'services/api/endpoints/queue';
import { $isConnected } from 'services/events/stores';
@@ -9,18 +9,13 @@ export const usePauseProcessor = () => {
const { t } = useTranslation();
const isConnected = useStore($isConnected);
const { data: queueStatus } = useGetQueueStatusQuery();
- const [trigger, { isLoading }] = usePauseProcessorMutation({
+ const [_trigger, { isLoading }] = usePauseProcessorMutation({
fixedCacheKey: 'pauseProcessor',
});
- const isStarted = useMemo(() => Boolean(queueStatus?.processor.is_started), [queueStatus?.processor.is_started]);
-
- const pauseProcessor = useCallback(async () => {
- if (!isStarted) {
- return;
- }
+ const trigger = useCallback(async () => {
try {
- await trigger().unwrap();
+ await _trigger().unwrap();
toast({
id: 'PAUSE_SUCCEEDED',
title: t('queue.pauseSucceeded'),
@@ -33,9 +28,7 @@ export const usePauseProcessor = () => {
status: 'error',
});
}
- }, [isStarted, trigger, t]);
+ }, [_trigger, t]);
- const isDisabled = useMemo(() => !isConnected || !isStarted, [isConnected, isStarted]);
-
- return { pauseProcessor, isLoading, isStarted, isDisabled };
+ return { trigger, isLoading, isDisabled: !isConnected || !queueStatus?.processor.is_started };
};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/usePruneQueue.ts b/invokeai/frontend/web/src/features/queue/hooks/usePruneQueue.ts
index 09e77e23d6..c186db96df 100644
--- a/invokeai/frontend/web/src/features/queue/hooks/usePruneQueue.ts
+++ b/invokeai/frontend/web/src/features/queue/hooks/usePruneQueue.ts
@@ -2,7 +2,7 @@ import { useStore } from '@nanostores/react';
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 { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetQueueStatusQuery, usePruneQueueMutation } from 'services/api/endpoints/queue';
import { $isConnected } from 'services/events/stores';
@@ -11,27 +11,14 @@ export const usePruneQueue = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const isConnected = useStore($isConnected);
- const [trigger, { isLoading }] = usePruneQueueMutation({
+ const finishedCount = useFinishedCount();
+ const [_trigger, { isLoading }] = usePruneQueueMutation({
fixedCacheKey: 'pruneQueue',
});
- const { finishedCount } = useGetQueueStatusQuery(undefined, {
- selectFromResult: ({ data }) => {
- if (!data) {
- return { finishedCount: 0 };
- }
- return {
- finishedCount: data.queue.completed + data.queue.canceled + data.queue.failed,
- };
- },
- });
-
- const pruneQueue = useCallback(async () => {
- if (!finishedCount) {
- return;
- }
+ const trigger = useCallback(async () => {
try {
- const data = await trigger().unwrap();
+ const data = await _trigger().unwrap();
toast({
id: 'PRUNE_SUCCEEDED',
title: t('queue.pruneSucceeded', { item_count: data.deleted }),
@@ -46,9 +33,23 @@ export const usePruneQueue = () => {
status: 'error',
});
}
- }, [finishedCount, trigger, dispatch, t]);
+ }, [_trigger, dispatch, t]);
- const isDisabled = useMemo(() => !isConnected || !finishedCount, [finishedCount, isConnected]);
-
- return { pruneQueue, isLoading, finishedCount, isDisabled };
+ return { trigger, isLoading, isDisabled: !isConnected || !finishedCount };
+};
+
+export const useFinishedCount = () => {
+ const { finishedCount } = useGetQueueStatusQuery(undefined, {
+ selectFromResult: ({ data }) => {
+ if (!data) {
+ return { finishedCount: 0 };
+ }
+
+ return {
+ finishedCount: data.queue.completed + data.queue.canceled + data.queue.failed,
+ };
+ },
+ });
+
+ return finishedCount;
};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useResumeProcessor.ts b/invokeai/frontend/web/src/features/queue/hooks/useResumeProcessor.ts
index 058a3b2b3e..baa02ece03 100644
--- a/invokeai/frontend/web/src/features/queue/hooks/useResumeProcessor.ts
+++ b/invokeai/frontend/web/src/features/queue/hooks/useResumeProcessor.ts
@@ -1,6 +1,6 @@
import { useStore } from '@nanostores/react';
import { toast } from 'features/toast/toast';
-import { useCallback, useMemo } from 'react';
+import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetQueueStatusQuery, useResumeProcessorMutation } from 'services/api/endpoints/queue';
import { $isConnected } from 'services/events/stores';
@@ -9,18 +9,13 @@ export const useResumeProcessor = () => {
const isConnected = useStore($isConnected);
const { data: queueStatus } = useGetQueueStatusQuery();
const { t } = useTranslation();
- const [trigger, { isLoading }] = useResumeProcessorMutation({
+ const [_trigger, { isLoading }] = useResumeProcessorMutation({
fixedCacheKey: 'resumeProcessor',
});
- const isStarted = useMemo(() => Boolean(queueStatus?.processor.is_started), [queueStatus?.processor.is_started]);
-
- const resumeProcessor = useCallback(async () => {
- if (isStarted) {
- return;
- }
+ const trigger = useCallback(async () => {
try {
- await trigger().unwrap();
+ await _trigger().unwrap();
toast({
id: 'PROCESSOR_RESUMED',
title: t('queue.resumeSucceeded'),
@@ -33,9 +28,7 @@ export const useResumeProcessor = () => {
status: 'error',
});
}
- }, [isStarted, trigger, t]);
+ }, [_trigger, t]);
- const isDisabled = useMemo(() => !isConnected || isStarted, [isConnected, isStarted]);
-
- return { resumeProcessor, isLoading, isStarted, isDisabled };
+ return { trigger, isLoading, isDisabled: !isConnected || !queueStatus?.processor.is_started };
};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useRetryQueueItem.ts b/invokeai/frontend/web/src/features/queue/hooks/useRetryQueueItem.ts
index cf3442d448..580f88f9ad 100644
--- a/invokeai/frontend/web/src/features/queue/hooks/useRetryQueueItem.ts
+++ b/invokeai/frontend/web/src/features/queue/hooks/useRetryQueueItem.ts
@@ -5,29 +5,32 @@ import { useTranslation } from 'react-i18next';
import { useRetryItemsByIdMutation } from 'services/api/endpoints/queue';
import { $isConnected } from 'services/events/stores';
-export const useRetryQueueItem = (item_id: number) => {
+export const useRetryQueueItem = () => {
const isConnected = useStore($isConnected);
- const [trigger, { isLoading }] = useRetryItemsByIdMutation();
+ const [_trigger, { isLoading }] = useRetryItemsByIdMutation();
const { t } = useTranslation();
- const retryQueueItem = useCallback(async () => {
- try {
- const result = await trigger([item_id]).unwrap();
- if (!result.retried_item_ids.includes(item_id)) {
- throw new Error('Failed to retry item');
+ const trigger = useCallback(
+ async (item_id: number) => {
+ try {
+ const result = await _trigger([item_id]).unwrap();
+ if (!result.retried_item_ids.includes(item_id)) {
+ throw new Error('Failed to retry item');
+ }
+ toast({
+ id: 'QUEUE_RETRY_SUCCEEDED',
+ title: t('queue.retrySucceeded'),
+ status: 'success',
+ });
+ } catch {
+ toast({
+ id: 'QUEUE_RETRY_FAILED',
+ title: t('queue.retryFailed'),
+ status: 'error',
+ });
}
- toast({
- id: 'QUEUE_RETRY_SUCCEEDED',
- title: t('queue.retrySucceeded'),
- status: 'success',
- });
- } catch {
- toast({
- id: 'QUEUE_RETRY_FAILED',
- title: t('queue.retryFailed'),
- status: 'error',
- });
- }
- }, [item_id, t, trigger]);
+ },
+ [t, _trigger]
+ );
- return { retryQueueItem, isLoading, isDisabled: !isConnected };
+ return { trigger, isLoading, isDisabled: !isConnected };
};
diff --git a/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx b/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx
index 5b688097f3..218ca382b8 100644
--- a/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx
+++ b/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx
@@ -1,6 +1,5 @@
import { Progress } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
-import { useCurrentDestination } from 'features/queue/hooks/useCurrentDestination';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
@@ -8,7 +7,6 @@ import { $isConnected, $lastProgressEvent } from 'services/events/stores';
const ProgressBar = () => {
const { t } = useTranslation();
- const destination = useCurrentDestination();
const { data: queueStatus } = useGetQueueStatusQuery();
const isConnected = useStore($isConnected);
const lastProgressEvent = useStore($lastProgressEvent);
@@ -39,16 +37,6 @@ const ProgressBar = () => {
return false;
}, [isConnected, lastProgressEvent, queueStatus?.queue.in_progress]);
- const colorScheme = useMemo(() => {
- if (destination === 'canvas') {
- return 'invokeGreen';
- } else if (destination === 'gallery') {
- return 'invokeBlue';
- } else {
- return 'base';
- }
- }, [destination]);
-
return (