diff --git a/invokeai/app/api/routers/images.py b/invokeai/app/api/routers/images.py
index 3da94df7f47..57c2a601e7e 100644
--- a/invokeai/app/api/routers/images.py
+++ b/invokeai/app/api/routers/images.py
@@ -84,6 +84,17 @@ async def delete_image(
# TODO: Does this need any exception handling at all?
pass
+@images_router.post("/clear-intermediates", operation_id="clear_intermediates")
+async def clear_intermediates() -> int:
+ """Clears first 100 intermediates"""
+
+ try:
+ count_deleted = ApiDependencies.invoker.services.images.delete_many(is_intermediate=True)
+ return count_deleted
+ except Exception as e:
+ # TODO: Does this need any exception handling at all?
+ pass
+
@images_router.patch(
"/{image_name}",
diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py
index 7b37307ce85..d28a0bc09da 100644
--- a/invokeai/app/services/image_record_storage.py
+++ b/invokeai/app/services/image_record_storage.py
@@ -97,8 +97,8 @@ def update(
@abstractmethod
def get_many(
self,
- offset: int = 0,
- limit: int = 10,
+ offset: Optional[int] = None,
+ limit: Optional[int] = None,
image_origin: Optional[ResourceOrigin] = None,
categories: Optional[list[ImageCategory]] = None,
is_intermediate: Optional[bool] = None,
@@ -322,8 +322,8 @@ def update(
def get_many(
self,
- offset: int = 0,
- limit: int = 10,
+ offset: Optional[int] = None,
+ limit: Optional[int] = None,
image_origin: Optional[ResourceOrigin] = None,
categories: Optional[list[ImageCategory]] = None,
is_intermediate: Optional[bool] = None,
@@ -392,8 +392,12 @@ def get_many(
images_query += query_conditions + query_pagination + ";"
# Add all the parameters
images_params = query_params.copy()
- images_params.append(limit)
- images_params.append(offset)
+
+ if limit is not None:
+ images_params.append(limit)
+ if offset is not None:
+ images_params.append(offset)
+
# Build the list of images, deserializing each row
self._cursor.execute(images_query, images_params)
result = cast(list[sqlite3.Row], self._cursor.fetchall())
diff --git a/invokeai/app/services/images.py b/invokeai/app/services/images.py
index a7d0b6ddeeb..5742a4cb4ba 100644
--- a/invokeai/app/services/images.py
+++ b/invokeai/app/services/images.py
@@ -109,6 +109,13 @@ def delete(self, image_name: str):
"""Deletes an image."""
pass
+ @abstractmethod
+ def delete_many(self, is_intermediate: bool) -> int:
+ """Deletes many images."""
+ pass
+
+
+
@abstractmethod
def delete_images_on_board(self, board_id: str):
"""Deletes all images on a board."""
@@ -397,3 +404,28 @@ def delete_images_on_board(self, board_id: str):
except Exception as e:
self._services.logger.error("Problem deleting image records and files")
raise e
+
+ def delete_many(self, is_intermediate: bool):
+ try:
+ # only clears 100 at a time
+ images = self._services.image_records.get_many(offset=0, limit=100, is_intermediate=is_intermediate,)
+ count = len(images.items)
+ image_name_list = list(
+ map(
+ lambda r: r.image_name,
+ images.items,
+ )
+ )
+ for image_name in image_name_list:
+ self._services.image_files.delete(image_name)
+ self._services.image_records.delete_many(image_name_list)
+ return count
+ except ImageRecordDeleteException:
+ self._services.logger.error(f"Failed to delete image records")
+ raise
+ except ImageFileDeleteException:
+ self._services.logger.error(f"Failed to delete image files")
+ raise
+ except Exception as e:
+ self._services.logger.error("Problem deleting image records and files")
+ raise e
diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsClearIntermediates.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsClearIntermediates.tsx
new file mode 100644
index 00000000000..86b5753cd44
--- /dev/null
+++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsClearIntermediates.tsx
@@ -0,0 +1,57 @@
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { useCallback, useEffect, useState } from 'react';
+import { StyledFlex } from './SettingsModal';
+import { Heading, Text } from '@chakra-ui/react';
+import IAIButton from '../../../../common/components/IAIButton';
+import { useClearIntermediatesMutation } from '../../../../services/api/endpoints/images';
+import { addToast } from '../../store/systemSlice';
+
+export default function SettingsClearIntermediates() {
+ const dispatch = useAppDispatch();
+ const [isDisabled, setIsDisabled] = useState(false);
+
+ const [clearIntermediates, { isLoading: isLoadingClearIntermediates }] =
+ useClearIntermediatesMutation();
+
+ const handleClickClearIntermediates = useCallback(() => {
+ clearIntermediates({})
+ .unwrap()
+ .then((response) => {
+ dispatch(
+ addToast({
+ title:
+ response === 0
+ ? `No intermediates to clear`
+ : `Successfully cleared ${response} intermediates`,
+ status: 'info',
+ })
+ );
+ if (response < 100) {
+ setIsDisabled(true);
+ }
+ });
+ }, [clearIntermediates, dispatch]);
+
+ return (
+
+ Clear Intermediates
+
+ {isDisabled ? 'Intermediates Cleared' : 'Clear 100 Intermediates'}
+
+
+ Will permanently delete first 100 intermediates found on disk and in
+ database
+
+
+ Intermediate images are byproducts of generation, different from the
+ result images in the gallery. Purging intermediates will free disk
+ space. Your gallery images will not be deleted.
+
+
+ );
+}
diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx
index 890ceb1f480..05056891844 100644
--- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx
+++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx
@@ -48,6 +48,7 @@ import {
import { useTranslation } from 'react-i18next';
import { LogLevelName } from 'roarr';
import SettingsSchedulers from './SettingsSchedulers';
+import SettingsClearIntermediates from './SettingsClearIntermediates';
const selector = createSelector(
[systemSelector, uiSelector],
@@ -91,6 +92,7 @@ type ConfigOptions = {
shouldShowResetWebUiText: boolean;
shouldShowBetaLayout: boolean;
shouldShowAdvancedOptionsSettings: boolean;
+ shouldShowClearIntermediates: boolean;
};
type SettingsModalProps = {
@@ -109,6 +111,8 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
const shouldShowResetWebUiText = config?.shouldShowResetWebUiText ?? true;
const shouldShowAdvancedOptionsSettings =
config?.shouldShowAdvancedOptionsSettings ?? true;
+ const shouldShowClearIntermediates =
+ config?.shouldShowClearIntermediates ?? true;
useEffect(() => {
if (!shouldShowDeveloperSettings) {
@@ -280,6 +284,8 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
)}
+ {shouldShowClearIntermediates && }
+
{t('settings.resetWebUI')}
@@ -328,7 +334,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
export default SettingsModal;
-const StyledFlex = (props: PropsWithChildren) => {
+export const StyledFlex = (props: PropsWithChildren) => {
return (
({ url: `images/clear-intermediates`, method: 'POST' }),
+ }),
}),
});
-export const { useGetImageDTOQuery, useGetImageMetadataQuery } = imagesApi;
+export const { useGetImageDTOQuery, useGetImageMetadataQuery, useClearIntermediatesMutation } = imagesApi;
diff --git a/invokeai/frontend/web/src/services/api/schema.d.ts b/invokeai/frontend/web/src/services/api/schema.d.ts
index 805afe54b33..a91a81a7407 100644
--- a/invokeai/frontend/web/src/services/api/schema.d.ts
+++ b/invokeai/frontend/web/src/services/api/schema.d.ts
@@ -164,6 +164,13 @@ export type paths = {
*/
patch: operations["update_image"];
};
+ "/api/v1/images/clear-intermediates": {
+ /**
+ * Clear Intermediates
+ * @description Clears first 100 intermediates
+ */
+ post: operations["clear_intermediates"];
+ };
"/api/v1/images/{image_name}/metadata": {
/**
* Get Image Metadata
@@ -5299,17 +5306,17 @@ export type components = {
image?: components["schemas"]["ImageField"];
};
/**
- * StableDiffusion2ModelFormat
+ * StableDiffusionXLModelFormat
* @description An enumeration.
* @enum {string}
*/
- StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
+ StableDiffusionXLModelFormat: "checkpoint" | "diffusers";
/**
- * StableDiffusionXLModelFormat
+ * StableDiffusion2ModelFormat
* @description An enumeration.
* @enum {string}
*/
- StableDiffusionXLModelFormat: "checkpoint" | "diffusers";
+ StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
/**
* StableDiffusion1ModelFormat
* @description An enumeration.
@@ -6098,6 +6105,20 @@ export type operations = {
};
};
};
+ /**
+ * Clear Intermediates
+ * @description Clears first 100 intermediates
+ */
+ clear_intermediates: {
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ content: {
+ "application/json": unknown;
+ };
+ };
+ };
+ };
/**
* Get Image Metadata
* @description Gets an image's metadata