mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Maryhipp/dummy bulk download (#4852)
* UI for bulk downloading boards or groups of images * placeholder route for bulk downloads that does nothing * lint --------- Co-authored-by: Mary Hipp <maryhipp@Marys-MacBook-Air.local>
This commit is contained in:
parent
40f9e49b5e
commit
69937d68d2
@ -322,3 +322,20 @@ async def unstar_images_in_list(
|
|||||||
return ImagesUpdatedFromListResult(updated_image_names=updated_image_names)
|
return ImagesUpdatedFromListResult(updated_image_names=updated_image_names)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise HTTPException(status_code=500, detail="Failed to unstar images")
|
raise HTTPException(status_code=500, detail="Failed to unstar images")
|
||||||
|
|
||||||
|
|
||||||
|
class ImagesDownloaded(BaseModel):
|
||||||
|
response: Optional[str] = Field(
|
||||||
|
description="If defined, the message to display to the user when images begin downloading"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@images_router.post("/download", operation_id="download_images_from_list", response_model=ImagesDownloaded)
|
||||||
|
async def download_images_from_list(
|
||||||
|
image_names: list[str] = Body(description="The list of names of images to download", embed=True),
|
||||||
|
board_id: Optional[str] = Body(
|
||||||
|
default=None, description="The board from which image should be downloaded from", embed=True
|
||||||
|
),
|
||||||
|
) -> ImagesDownloaded:
|
||||||
|
# return ImagesDownloaded(response="Your images are downloading")
|
||||||
|
raise HTTPException(status_code=501, detail="Endpoint is not yet implemented")
|
||||||
|
@ -38,7 +38,8 @@
|
|||||||
"searchBoard": "Search Boards...",
|
"searchBoard": "Search Boards...",
|
||||||
"selectBoard": "Select a Board",
|
"selectBoard": "Select a Board",
|
||||||
"topMessage": "This board contains images used in the following features:",
|
"topMessage": "This board contains images used in the following features:",
|
||||||
"uncategorized": "Uncategorized"
|
"uncategorized": "Uncategorized",
|
||||||
|
"downloadBoard": "Download Board"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"accept": "Accept",
|
"accept": "Accept",
|
||||||
@ -323,7 +324,10 @@
|
|||||||
"showUploads": "Show Uploads",
|
"showUploads": "Show Uploads",
|
||||||
"singleColumnLayout": "Single Column Layout",
|
"singleColumnLayout": "Single Column Layout",
|
||||||
"unableToLoad": "Unable to load Gallery",
|
"unableToLoad": "Unable to load Gallery",
|
||||||
"uploads": "Uploads"
|
"uploads": "Uploads",
|
||||||
|
"downloadSelection": "Download Selection",
|
||||||
|
"preparingDownload": "Preparing Download",
|
||||||
|
"preparingDownloadFailed": "Problem Preparing Download"
|
||||||
},
|
},
|
||||||
"hotkeys": {
|
"hotkeys": {
|
||||||
"acceptStagingImage": {
|
"acceptStagingImage": {
|
||||||
|
@ -22,7 +22,8 @@ export type AppFeature =
|
|||||||
| 'pauseQueue'
|
| 'pauseQueue'
|
||||||
| 'resumeQueue'
|
| 'resumeQueue'
|
||||||
| 'prependQueue'
|
| 'prependQueue'
|
||||||
| 'invocationCache';
|
| 'invocationCache'
|
||||||
|
| 'bulkDownload';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A disable-able Stable Diffusion feature
|
* A disable-able Stable Diffusion feature
|
||||||
|
@ -11,12 +11,15 @@ import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
|||||||
import { BoardId } from 'features/gallery/store/types';
|
import { BoardId } from 'features/gallery/store/types';
|
||||||
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaPlus } from 'react-icons/fa';
|
import { FaDownload, FaPlus } from 'react-icons/fa';
|
||||||
import { useBoardName } from 'services/api/hooks/useBoardName';
|
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||||
import { BoardDTO } from 'services/api/types';
|
import { BoardDTO } from 'services/api/types';
|
||||||
import { menuListMotionProps } from 'theme/components/menu';
|
import { menuListMotionProps } from 'theme/components/menu';
|
||||||
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
|
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
|
||||||
import NoBoardContextMenuItems from './NoBoardContextMenuItems';
|
import NoBoardContextMenuItems from './NoBoardContextMenuItems';
|
||||||
|
import { useFeatureStatus } from '../../../system/hooks/useFeatureStatus';
|
||||||
|
import { useBulkDownloadImagesMutation } from '../../../../services/api/endpoints/images';
|
||||||
|
import { addToast } from '../../../system/store/systemSlice';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
board?: BoardDTO;
|
board?: BoardDTO;
|
||||||
@ -31,6 +34,7 @@ const BoardContextMenu = ({
|
|||||||
setBoardToDelete,
|
setBoardToDelete,
|
||||||
children,
|
children,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const selector = useMemo(
|
const selector = useMemo(
|
||||||
@ -49,17 +53,43 @@ const BoardContextMenu = ({
|
|||||||
|
|
||||||
const { isAutoAdd, autoAssignBoardOnClick } = useAppSelector(selector);
|
const { isAutoAdd, autoAssignBoardOnClick } = useAppSelector(selector);
|
||||||
const boardName = useBoardName(board_id);
|
const boardName = useBoardName(board_id);
|
||||||
|
const isBulkDownloadEnabled =
|
||||||
|
useFeatureStatus('bulkDownload').isFeatureEnabled;
|
||||||
|
|
||||||
|
const [bulkDownload] = useBulkDownloadImagesMutation();
|
||||||
|
|
||||||
const handleSetAutoAdd = useCallback(() => {
|
const handleSetAutoAdd = useCallback(() => {
|
||||||
dispatch(autoAddBoardIdChanged(board_id));
|
dispatch(autoAddBoardIdChanged(board_id));
|
||||||
}, [board_id, dispatch]);
|
}, [board_id, dispatch]);
|
||||||
|
|
||||||
|
const handleBulkDownload = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const response = await bulkDownload({
|
||||||
|
image_names: [],
|
||||||
|
board_id: board_id,
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
addToast({
|
||||||
|
title: t('gallery.preparingDownload'),
|
||||||
|
status: 'success',
|
||||||
|
...(response.response ? { description: response.response } : {}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
dispatch(
|
||||||
|
addToast({
|
||||||
|
title: t('gallery.preparingDownloadFailed'),
|
||||||
|
status: 'error',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [t, board_id, bulkDownload, dispatch]);
|
||||||
|
|
||||||
const skipEvent = useCallback((e: MouseEvent<HTMLDivElement>) => {
|
const skipEvent = useCallback((e: MouseEvent<HTMLDivElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIContextMenu<HTMLDivElement>
|
<IAIContextMenu<HTMLDivElement>
|
||||||
menuProps={{ size: 'sm', isLazy: true }}
|
menuProps={{ size: 'sm', isLazy: true }}
|
||||||
@ -81,6 +111,14 @@ const BoardContextMenu = ({
|
|||||||
>
|
>
|
||||||
{t('boards.menuItemAutoAdd')}
|
{t('boards.menuItemAutoAdd')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
{isBulkDownloadEnabled && (
|
||||||
|
<MenuItem
|
||||||
|
icon={<FaDownload />}
|
||||||
|
onClickCapture={handleBulkDownload}
|
||||||
|
>
|
||||||
|
{t('boards.downloadBoard')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
{!board && <NoBoardContextMenuItems />}
|
{!board && <NoBoardContextMenuItems />}
|
||||||
{board && (
|
{board && (
|
||||||
<GalleryBoardContextMenuItems
|
<GalleryBoardContextMenuItems
|
||||||
|
@ -8,20 +8,29 @@ import {
|
|||||||
} from 'features/changeBoardModal/store/slice';
|
} from 'features/changeBoardModal/store/slice';
|
||||||
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { FaFolder, FaTrash } from 'react-icons/fa';
|
import { FaDownload, FaFolder, FaTrash } from 'react-icons/fa';
|
||||||
import { MdStar, MdStarBorder } from 'react-icons/md';
|
import { MdStar, MdStarBorder } from 'react-icons/md';
|
||||||
import {
|
import {
|
||||||
|
useBulkDownloadImagesMutation,
|
||||||
useStarImagesMutation,
|
useStarImagesMutation,
|
||||||
useUnstarImagesMutation,
|
useUnstarImagesMutation,
|
||||||
} from '../../../../services/api/endpoints/images';
|
} from '../../../../services/api/endpoints/images';
|
||||||
|
import { useFeatureStatus } from '../../../system/hooks/useFeatureStatus';
|
||||||
|
import { addToast } from '../../../system/store/systemSlice';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const MultipleSelectionMenuItems = () => {
|
const MultipleSelectionMenuItems = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const selection = useAppSelector((state) => state.gallery.selection);
|
const selection = useAppSelector((state) => state.gallery.selection);
|
||||||
const customStarUi = useStore($customStarUI);
|
const customStarUi = useStore($customStarUI);
|
||||||
|
|
||||||
|
const isBulkDownloadEnabled =
|
||||||
|
useFeatureStatus('bulkDownload').isFeatureEnabled;
|
||||||
|
|
||||||
const [starImages] = useStarImagesMutation();
|
const [starImages] = useStarImagesMutation();
|
||||||
const [unstarImages] = useUnstarImagesMutation();
|
const [unstarImages] = useUnstarImagesMutation();
|
||||||
|
const [bulkDownload] = useBulkDownloadImagesMutation();
|
||||||
|
|
||||||
const handleChangeBoard = useCallback(() => {
|
const handleChangeBoard = useCallback(() => {
|
||||||
dispatch(imagesToChangeSelected(selection));
|
dispatch(imagesToChangeSelected(selection));
|
||||||
@ -40,6 +49,29 @@ const MultipleSelectionMenuItems = () => {
|
|||||||
unstarImages({ imageDTOs: selection });
|
unstarImages({ imageDTOs: selection });
|
||||||
}, [unstarImages, selection]);
|
}, [unstarImages, selection]);
|
||||||
|
|
||||||
|
const handleBulkDownload = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const response = await bulkDownload({
|
||||||
|
image_names: selection.map((img) => img.image_name),
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
addToast({
|
||||||
|
title: t('gallery.preparingDownload'),
|
||||||
|
status: 'success',
|
||||||
|
...(response.response ? { description: response.response } : {}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
dispatch(
|
||||||
|
addToast({
|
||||||
|
title: t('gallery.preparingDownloadFailed'),
|
||||||
|
status: 'error',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [t, selection, bulkDownload, dispatch]);
|
||||||
|
|
||||||
const areAllStarred = useMemo(() => {
|
const areAllStarred = useMemo(() => {
|
||||||
return selection.every((img) => img.starred);
|
return selection.every((img) => img.starred);
|
||||||
}, [selection]);
|
}, [selection]);
|
||||||
@ -66,6 +98,11 @@ const MultipleSelectionMenuItems = () => {
|
|||||||
{customStarUi ? customStarUi.on.text : `Star All`}
|
{customStarUi ? customStarUi.on.text : `Star All`}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
{isBulkDownloadEnabled && (
|
||||||
|
<MenuItem icon={<FaDownload />} onClickCapture={handleBulkDownload}>
|
||||||
|
{t('gallery.downloadSelection')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
<MenuItem icon={<FaFolder />} onClickCapture={handleChangeBoard}>
|
<MenuItem icon={<FaFolder />} onClickCapture={handleChangeBoard}>
|
||||||
Change Board
|
Change Board
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -7,7 +7,7 @@ export const initialConfigState: AppConfig = {
|
|||||||
shouldUpdateImagesOnConnect: false,
|
shouldUpdateImagesOnConnect: false,
|
||||||
shouldFetchMetadataFromApi: false,
|
shouldFetchMetadataFromApi: false,
|
||||||
disabledTabs: [],
|
disabledTabs: [],
|
||||||
disabledFeatures: ['lightbox', 'faceRestore', 'batches'],
|
disabledFeatures: ['lightbox', 'faceRestore', 'batches', 'bulkDownload'],
|
||||||
disabledSDFeatures: [
|
disabledSDFeatures: [
|
||||||
'variation',
|
'variation',
|
||||||
'symmetry',
|
'symmetry',
|
||||||
|
@ -1599,6 +1599,19 @@ export const imagesApi = api.injectEndpoints({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
bulkDownloadImages: build.mutation<
|
||||||
|
components['schemas']['ImagesDownloaded'],
|
||||||
|
components['schemas']['Body_download_images_from_list']
|
||||||
|
>({
|
||||||
|
query: ({ image_names, board_id }) => ({
|
||||||
|
url: `images/download`,
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
image_names,
|
||||||
|
board_id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1623,4 +1636,5 @@ export const {
|
|||||||
useStarImagesMutation,
|
useStarImagesMutation,
|
||||||
useUnstarImagesMutation,
|
useUnstarImagesMutation,
|
||||||
useGetImageMetadataFromFileQuery,
|
useGetImageMetadataFromFileQuery,
|
||||||
|
useBulkDownloadImagesMutation,
|
||||||
} = imagesApi;
|
} = imagesApi;
|
||||||
|
103
invokeai/frontend/web/src/services/api/schema.d.ts
vendored
103
invokeai/frontend/web/src/services/api/schema.d.ts
vendored
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user