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?
|
# TODO: Does this need any exception handling at all?
|
||||||
pass
|
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(
|
@images_router.patch(
|
||||||
"/{image_name}",
|
"/{image_name}",
|
||||||
|
@ -97,8 +97,8 @@ class ImageRecordStorageBase(ABC):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_many(
|
def get_many(
|
||||||
self,
|
self,
|
||||||
offset: int = 0,
|
offset: Optional[int] = None,
|
||||||
limit: int = 10,
|
limit: Optional[int] = None,
|
||||||
image_origin: Optional[ResourceOrigin] = None,
|
image_origin: Optional[ResourceOrigin] = None,
|
||||||
categories: Optional[list[ImageCategory]] = None,
|
categories: Optional[list[ImageCategory]] = None,
|
||||||
is_intermediate: Optional[bool] = None,
|
is_intermediate: Optional[bool] = None,
|
||||||
@ -322,8 +322,8 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
|||||||
|
|
||||||
def get_many(
|
def get_many(
|
||||||
self,
|
self,
|
||||||
offset: int = 0,
|
offset: Optional[int] = None,
|
||||||
limit: int = 10,
|
limit: Optional[int] = None,
|
||||||
image_origin: Optional[ResourceOrigin] = None,
|
image_origin: Optional[ResourceOrigin] = None,
|
||||||
categories: Optional[list[ImageCategory]] = None,
|
categories: Optional[list[ImageCategory]] = None,
|
||||||
is_intermediate: Optional[bool] = None,
|
is_intermediate: Optional[bool] = None,
|
||||||
@ -392,8 +392,12 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
|||||||
images_query += query_conditions + query_pagination + ";"
|
images_query += query_conditions + query_pagination + ";"
|
||||||
# Add all the parameters
|
# Add all the parameters
|
||||||
images_params = query_params.copy()
|
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
|
# Build the list of images, deserializing each row
|
||||||
self._cursor.execute(images_query, images_params)
|
self._cursor.execute(images_query, images_params)
|
||||||
result = cast(list[sqlite3.Row], self._cursor.fetchall())
|
result = cast(list[sqlite3.Row], self._cursor.fetchall())
|
||||||
|
@ -109,6 +109,13 @@ class ImageServiceABC(ABC):
|
|||||||
"""Deletes an image."""
|
"""Deletes an image."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def delete_many(self, is_intermediate: bool) -> int:
|
||||||
|
"""Deletes many images."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def delete_images_on_board(self, board_id: str):
|
def delete_images_on_board(self, board_id: str):
|
||||||
"""Deletes all images on a board."""
|
"""Deletes all images on a board."""
|
||||||
@ -397,3 +404,28 @@ class ImageService(ImageServiceABC):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._services.logger.error("Problem deleting image records and files")
|
self._services.logger.error("Problem deleting image records and files")
|
||||||
raise e
|
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 { useTranslation } from 'react-i18next';
|
||||||
import { LogLevelName } from 'roarr';
|
import { LogLevelName } from 'roarr';
|
||||||
import SettingsSchedulers from './SettingsSchedulers';
|
import SettingsSchedulers from './SettingsSchedulers';
|
||||||
|
import SettingsClearIntermediates from './SettingsClearIntermediates';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[systemSelector, uiSelector],
|
[systemSelector, uiSelector],
|
||||||
@ -91,6 +92,7 @@ type ConfigOptions = {
|
|||||||
shouldShowResetWebUiText: boolean;
|
shouldShowResetWebUiText: boolean;
|
||||||
shouldShowBetaLayout: boolean;
|
shouldShowBetaLayout: boolean;
|
||||||
shouldShowAdvancedOptionsSettings: boolean;
|
shouldShowAdvancedOptionsSettings: boolean;
|
||||||
|
shouldShowClearIntermediates: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SettingsModalProps = {
|
type SettingsModalProps = {
|
||||||
@ -109,6 +111,8 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
const shouldShowResetWebUiText = config?.shouldShowResetWebUiText ?? true;
|
const shouldShowResetWebUiText = config?.shouldShowResetWebUiText ?? true;
|
||||||
const shouldShowAdvancedOptionsSettings =
|
const shouldShowAdvancedOptionsSettings =
|
||||||
config?.shouldShowAdvancedOptionsSettings ?? true;
|
config?.shouldShowAdvancedOptionsSettings ?? true;
|
||||||
|
const shouldShowClearIntermediates =
|
||||||
|
config?.shouldShowClearIntermediates ?? true;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!shouldShowDeveloperSettings) {
|
if (!shouldShowDeveloperSettings) {
|
||||||
@ -280,6 +284,8 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
</StyledFlex>
|
</StyledFlex>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{shouldShowClearIntermediates && <SettingsClearIntermediates />}
|
||||||
|
|
||||||
<StyledFlex>
|
<StyledFlex>
|
||||||
<Heading size="sm">{t('settings.resetWebUI')}</Heading>
|
<Heading size="sm">{t('settings.resetWebUI')}</Heading>
|
||||||
<IAIButton colorScheme="error" onClick={handleClickResetWebUI}>
|
<IAIButton colorScheme="error" onClick={handleClickResetWebUI}>
|
||||||
@ -328,7 +334,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
|
|
||||||
export default SettingsModal;
|
export default SettingsModal;
|
||||||
|
|
||||||
const StyledFlex = (props: PropsWithChildren) => {
|
export const StyledFlex = (props: PropsWithChildren) => {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -2,6 +2,7 @@ import { ApiFullTagDescription, api } from '..';
|
|||||||
import { components } from '../schema';
|
import { components } from '../schema';
|
||||||
import { ImageDTO } from '../types';
|
import { ImageDTO } from '../types';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an unsafe type; the object inside is not guaranteed to be valid.
|
* 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
|
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"];
|
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": {
|
"/api/v1/images/{image_name}/metadata": {
|
||||||
/**
|
/**
|
||||||
* Get Image Metadata
|
* Get Image Metadata
|
||||||
@ -5298,18 +5305,18 @@ export type components = {
|
|||||||
*/
|
*/
|
||||||
image?: components["schemas"]["ImageField"];
|
image?: components["schemas"]["ImageField"];
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
* StableDiffusion2ModelFormat
|
|
||||||
* @description An enumeration.
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
|
|
||||||
/**
|
/**
|
||||||
* StableDiffusionXLModelFormat
|
* StableDiffusionXLModelFormat
|
||||||
* @description An enumeration.
|
* @description An enumeration.
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
StableDiffusionXLModelFormat: "checkpoint" | "diffusers";
|
StableDiffusionXLModelFormat: "checkpoint" | "diffusers";
|
||||||
|
/**
|
||||||
|
* StableDiffusion2ModelFormat
|
||||||
|
* @description An enumeration.
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
|
||||||
/**
|
/**
|
||||||
* StableDiffusion1ModelFormat
|
* StableDiffusion1ModelFormat
|
||||||
* @description An enumeration.
|
* @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
|
* Get Image Metadata
|
||||||
* @description Gets an image's metadata
|
* @description Gets an image's metadata
|
||||||
|
Loading…
x
Reference in New Issue
Block a user