diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index 10197fef1ff..96bd9bce291 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -209,9 +209,13 @@
"pauseSucceeded": "Processor Paused",
"pauseFailed": "Problem Pausing Processor",
"cancel": "Cancel",
+ "cancelAllExceptCurrentQueueItemAlertDialog": "Canceling all queue items except the current one will stop pending items but allow the in-progress one to finish.",
+ "cancelAllExceptCurrentQueueItemAlertDialog2": "Are you sure you want to cancel all pending queue items?",
+ "cancelAllExceptCurrentTooltip": "Cancel All Except Current Item",
"cancelTooltip": "Cancel Current Item",
"cancelSucceeded": "Item Canceled",
"cancelFailed": "Problem Canceling Item",
+ "confirm": "Confirm",
"prune": "Prune",
"pruneTooltip": "Prune {{item_count}} Completed Items",
"pruneSucceeded": "Pruned {{item_count}} Completed Items from Queue",
diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx
index 2902c344adb..7bef8d59a6d 100644
--- a/invokeai/frontend/web/src/app/components/App.tsx
+++ b/invokeai/frontend/web/src/app/components/App.tsx
@@ -23,6 +23,7 @@ import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModa
import { ImageContextMenu } from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterModelsToast';
import { ShareWorkflowModal } from 'features/nodes/components/sidePanel/WorkflowListMenu/ShareWorkflowModal';
+import { CancelAllExceptCurrentQueueItemConfirmationAlertDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog';
import { ClearQueueConfirmationsAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
import { DeleteStylePresetDialog } from 'features/stylePresets/components/DeleteStylePresetDialog';
import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal';
@@ -97,6 +98,7 @@ const App = ({ config = DEFAULT_CONFIG, studioInitAction }: Props) => {
+
diff --git a/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog.tsx b/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog.tsx
new file mode 100644
index 00000000000..cdaaebcdf0e
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog.tsx
@@ -0,0 +1,47 @@
+import { ConfirmationAlertDialog, Text } from '@invoke-ai/ui-library';
+import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
+import { buildUseBoolean } from 'common/hooks/useBoolean';
+import { useCancelAllExceptCurrentQueueItem } from 'features/queue/hooks/useCancelAllExceptCurrentQueueItem';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+
+const [useCancelAllExceptCurrentQueueItemConfirmationAlertDialog] = buildUseBoolean(false);
+
+export const useCancelAllExceptCurrentQueueItemDialog = () => {
+ const dialog = useCancelAllExceptCurrentQueueItemConfirmationAlertDialog();
+ const { cancelAllExceptCurrentQueueItem, isLoading, isDisabled, queueStatus } = useCancelAllExceptCurrentQueueItem();
+
+ return {
+ cancelAllExceptCurrentQueueItem,
+ isOpen: dialog.isTrue,
+ openDialog: dialog.setTrue,
+ closeDialog: dialog.setFalse,
+ isLoading,
+ queueStatus,
+ isDisabled,
+ };
+};
+
+export const CancelAllExceptCurrentQueueItemConfirmationAlertDialog = memo(() => {
+ useAssertSingleton('CancelAllExceptCurrentQueueItemConfirmationAlertDialog');
+ const { t } = useTranslation();
+ const cancelAllExceptCurrentQueueItem = useCancelAllExceptCurrentQueueItemDialog();
+
+ return (
+
+ {t('queue.cancelAllExceptCurrentQueueItemAlertDialog')}
+
+ {t('queue.cancelAllExceptCurrentQueueItemAlertDialog2')}
+
+ );
+});
+
+CancelAllExceptCurrentQueueItemConfirmationAlertDialog.displayName =
+ 'CancelAllExceptCurrentQueueItemConfirmationAlertDialog';
diff --git a/invokeai/frontend/web/src/features/queue/components/QueueActionsMenuButton.tsx b/invokeai/frontend/web/src/features/queue/components/QueueActionsMenuButton.tsx
index 27672cec839..2a9f89b0f33 100644
--- a/invokeai/frontend/web/src/features/queue/components/QueueActionsMenuButton.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/QueueActionsMenuButton.tsx
@@ -1,6 +1,7 @@
import { IconButton, Menu, MenuButton, MenuGroup, MenuItem, MenuList } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { SessionMenuItems } from 'common/components/SessionMenuItems';
+import { useCancelAllExceptCurrentQueueItemDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog';
import { useClearQueueDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
import { QueueCountBadge } from 'features/queue/components/QueueCountBadge';
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
@@ -10,7 +11,15 @@ import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
-import { PiListBold, PiPauseFill, PiPlayFill, PiQueueBold, PiTrashSimpleBold, PiXBold } from 'react-icons/pi';
+import {
+ PiListBold,
+ PiPauseFill,
+ PiPlayFill,
+ PiQueueBold,
+ PiTrashSimpleBold,
+ PiXBold,
+ PiXCircle,
+} from 'react-icons/pi';
export const QueueActionsMenuButton = memo(() => {
const ref = useRef(null);
@@ -18,6 +27,7 @@ export const QueueActionsMenuButton = memo(() => {
const { t } = useTranslation();
const isPauseEnabled = useFeatureStatus('pauseQueue');
const isResumeEnabled = useFeatureStatus('resumeQueue');
+ const cancelAllExceptCurrent = useCancelAllExceptCurrentQueueItemDialog();
const cancelCurrent = useCancelCurrentQueueItem();
const clearQueue = useClearQueueDialog();
const {
@@ -52,6 +62,15 @@ export const QueueActionsMenuButton = memo(() => {
>
{t('queue.cancelTooltip')}
+ }
+ onClick={cancelAllExceptCurrent.openDialog}
+ isLoading={cancelAllExceptCurrent.isLoading}
+ isDisabled={cancelAllExceptCurrent.isDisabled}
+ >
+ {t('queue.cancelAllExceptCurrentTooltip')}
+
}
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCancelAllExceptCurrentQueueItem.ts b/invokeai/frontend/web/src/features/queue/hooks/useCancelAllExceptCurrentQueueItem.ts
new file mode 100644
index 00000000000..3d388915457
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/hooks/useCancelAllExceptCurrentQueueItem.ts
@@ -0,0 +1,48 @@
+import { useStore } from '@nanostores/react';
+import { toast } from 'features/toast/toast';
+import { useCallback, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useCancelAllExceptCurrentMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue';
+import { $isConnected } from 'services/events/stores';
+
+export const useCancelAllExceptCurrentQueueItem = () => {
+ const { t } = useTranslation();
+ const { data: queueStatus } = useGetQueueStatusQuery();
+ const isConnected = useStore($isConnected);
+ const [trigger, { isLoading }] = useCancelAllExceptCurrentMutation({
+ fixedCacheKey: 'cancelAllExceptCurrent',
+ });
+
+ const cancelAllExceptCurrentQueueItem = 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]);
+
+ const isDisabled = useMemo(
+ () => !isConnected || !queueStatus?.queue.pending,
+ [isConnected, queueStatus?.queue.pending]
+ );
+
+ return {
+ cancelAllExceptCurrentQueueItem,
+ isLoading,
+ queueStatus,
+ isDisabled,
+ };
+};
diff --git a/invokeai/frontend/web/src/services/api/endpoints/queue.ts b/invokeai/frontend/web/src/services/api/endpoints/queue.ts
index c2af0d8aa9e..05e5862ee74 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/queue.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/queue.ts
@@ -348,6 +348,25 @@ export const queueApi = api.injectEndpoints({
return ['SessionQueueStatus', 'BatchStatus', { type: 'QueueCountsByDestination', id: destination }];
},
}),
+ cancelAllExceptCurrent: build.mutation<
+ paths['/api/v1/queue/{queue_id}/cancel_all_except_current']['put']['responses']['200']['content']['application/json'],
+ void
+ >({
+ query: () => ({
+ url: buildQueueUrl('cancel_all_except_current'),
+ method: 'PUT',
+ }),
+ onQueryStarted: async (arg, api) => {
+ const { dispatch, queryFulfilled } = api;
+ try {
+ await queryFulfilled;
+ resetListQueryData(dispatch);
+ } catch {
+ // no-op
+ }
+ },
+ invalidatesTags: ['SessionQueueStatus', 'BatchStatus', 'QueueCountsByDestination'],
+ }),
listQueueItems: build.query<
EntityState & {
has_more: boolean;
@@ -390,6 +409,7 @@ export const queueApi = api.injectEndpoints({
});
export const {
+ useCancelAllExceptCurrentMutation,
useCancelByBatchIdsMutation,
useEnqueueBatchMutation,
usePauseProcessorMutation,