mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
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:
parent
aca5c6de9a
commit
9f00e055ac
@ -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}",
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
@ -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={{
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user