Maryhipp/clear intermediates (#3820)

* new route to clear intermediates

* UI to clear intermediates from settings modal

* cleanup

* PR feedback

---------

Co-authored-by: Mary Hipp <maryhipp@Marys-MacBook-Air.local>
This commit is contained in:
Mary Hipp Rogers 2023-07-19 10:55:29 -04:00 committed by GitHub
parent aca5c6de9a
commit 9f00e055ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 149 additions and 14 deletions

View File

@ -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}",

View File

@ -97,8 +97,8 @@ class ImageRecordStorageBase(ABC):
@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 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
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 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
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())

View File

@ -109,6 +109,13 @@ class ImageServiceABC(ABC):
"""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 @@ class ImageService(ImageServiceABC):
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

View File

@ -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 (
<StyledFlex>
<Heading size="sm">Clear Intermediates</Heading>
<IAIButton
colorScheme="error"
onClick={handleClickClearIntermediates}
isLoading={isLoadingClearIntermediates}
isDisabled={isDisabled}
>
{isDisabled ? 'Intermediates Cleared' : 'Clear 100 Intermediates'}
</IAIButton>
<Text>
Will permanently delete first 100 intermediates found on disk and in
database
</Text>
<Text>
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.
</Text>
</StyledFlex>
);
}

View File

@ -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) => {
</StyledFlex>
)}
{shouldShowClearIntermediates && <SettingsClearIntermediates />}
<StyledFlex>
<Heading size="sm">{t('settings.resetWebUI')}</Heading>
<IAIButton colorScheme="error" onClick={handleClickResetWebUI}>
@ -328,7 +334,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
export default SettingsModal;
const StyledFlex = (props: PropsWithChildren) => {
export const StyledFlex = (props: PropsWithChildren) => {
return (
<Flex
sx={{

View File

@ -2,6 +2,7 @@ import { ApiFullTagDescription, api } from '..';
import { components } from '../schema';
import { ImageDTO } from '../types';
/**
* This is an unsafe type; the object inside is not guaranteed to be valid.
*/
@ -36,7 +37,10 @@ export const imagesApi = api.injectEndpoints({
},
keepUnusedDataFor: 86400, // 24 hours
}),
clearIntermediates: build.mutation({
query: () => ({ url: `images/clear-intermediates`, method: 'POST' }),
}),
}),
});
export const { useGetImageDTOQuery, useGetImageMetadataQuery } = imagesApi;
export const { useGetImageDTOQuery, useGetImageMetadataQuery, useClearIntermediatesMutation } = imagesApi;

View File

@ -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
@ -5298,18 +5305,18 @@ export type components = {
*/
image?: components["schemas"]["ImageField"];
};
/**
* StableDiffusion2ModelFormat
* @description An enumeration.
* @enum {string}
*/
StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
/**
* StableDiffusionXLModelFormat
* @description An enumeration.
* @enum {string}
*/
StableDiffusionXLModelFormat: "checkpoint" | "diffusers";
/**
* StableDiffusion2ModelFormat
* @description An enumeration.
* @enum {string}
*/
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