mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
(ui) accept upload socket events and show toast
This commit is contained in:
@@ -324,6 +324,15 @@
|
||||
"bulkDownloadRequestedDesc": "Your download request is being prepared. This may take a few moments.",
|
||||
"bulkDownloadRequestFailed": "Problem Preparing Download",
|
||||
"bulkDownloadFailed": "Download Failed",
|
||||
"bulkUploadRequested": "Preparing Upload",
|
||||
"bulkUploadStarted": "Uploading Images",
|
||||
"bulkUploadStartedDesc": "Starting upload of {{x}} images",
|
||||
"bulkUploadProgressDesc": "Uploading {{y}} of {{x}} images",
|
||||
"bulkUploadComplete": "Upload Complete",
|
||||
"bulkUploadCompleteDesc": "Successfully uploaded {{x}} images.",
|
||||
"bulkUploadRequestFailed": "Problem Preparing Download",
|
||||
"bulkUploadFailed": "Upload Failed or Partially Failed",
|
||||
|
||||
"viewerImage": "Viewer Image",
|
||||
"compareImage": "Compare Image",
|
||||
"openInViewer": "Open in Viewer",
|
||||
|
||||
@@ -78,22 +78,9 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
||||
lastUploadedToastTimeout = window.setTimeout(() => {
|
||||
toastApi.close();
|
||||
}, 3000);
|
||||
/**
|
||||
* We only want to change the board and view if this is the first upload of a batch, else we end up hijacking
|
||||
* the user's gallery board and view selection:
|
||||
* - User uploads multiple images
|
||||
* - A couple uploads finish, but others are pending still
|
||||
* - User changes the board selection
|
||||
* - Pending uploads finish and change the board back to the original board
|
||||
* - User is confused as to why the board changed
|
||||
*
|
||||
* Default to true to not require _all_ image upload handlers to set this value
|
||||
*/
|
||||
const isFirstUploadOfBatch = action.meta.arg.originalArgs.isFirstUploadOfBatch ?? true;
|
||||
if (isFirstUploadOfBatch) {
|
||||
dispatch(boardIdSelected({ boardId }));
|
||||
dispatch(galleryViewChanged('assets'));
|
||||
}
|
||||
|
||||
dispatch(boardIdSelected({ boardId }));
|
||||
dispatch(galleryViewChanged('assets'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { $queueId } from 'app/store/nanostores/queueId';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
||||
@@ -25,7 +26,7 @@ export const useFullscreenDropzone = () => {
|
||||
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
||||
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
||||
const [uploadImage] = useUploadImageMutation();
|
||||
const [bulkUploadImages] = useBulkUploadImagesMutation()
|
||||
const [bulkUploadImages] = useBulkUploadImagesMutation();
|
||||
const activeTabName = useAppSelector(selectActiveTab);
|
||||
const maxImageUploadCount = useAppSelector(selectMaxImageUploadCount);
|
||||
|
||||
@@ -63,19 +64,24 @@ export const useFullscreenDropzone = () => {
|
||||
|
||||
if (acceptedFiles.length > 1) {
|
||||
try {
|
||||
const response = await bulkUploadImages({files: acceptedFiles, board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId}).unwrap()
|
||||
toast({
|
||||
status: "success",
|
||||
title: `Uplaoding ${response.uploading} of ${response.sent}`
|
||||
})
|
||||
id: 'BULK_UPLOAD',
|
||||
title: t('gallery.bulkUploadRequested'),
|
||||
status: 'info',
|
||||
duration: null,
|
||||
});
|
||||
await bulkUploadImages({
|
||||
bulk_upload_id: $queueId.get(),
|
||||
files: acceptedFiles,
|
||||
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
|
||||
}).unwrap();
|
||||
} catch (error) {
|
||||
toast({
|
||||
status: "error",
|
||||
title: "Could not upload images"
|
||||
})
|
||||
throw error
|
||||
status: 'error',
|
||||
title: t('gallery.bulkUploadRequestFailed'),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
} else if (acceptedFiles[0]) {
|
||||
uploadImage({
|
||||
file: acceptedFiles[0],
|
||||
@@ -83,14 +89,12 @@ export const useFullscreenDropzone = () => {
|
||||
is_intermediate: false,
|
||||
postUploadAction: getPostUploadAction(),
|
||||
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
setIsHandlingUpload(false);
|
||||
},
|
||||
[t, maxImageUploadCount, uploadImage, getPostUploadAction, autoAddBoardId]
|
||||
[t, maxImageUploadCount, uploadImage, getPostUploadAction, autoAddBoardId, bulkUploadImages]
|
||||
);
|
||||
|
||||
const onDragOver = useCallback(() => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { $queueId } from 'app/store/nanostores/queueId';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
||||
import { selectMaxImageUploadCount } from 'features/system/store/configSlice';
|
||||
@@ -7,7 +8,7 @@ import { useCallback } from 'react';
|
||||
import type { FileRejection } from 'react-dropzone';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useUploadImageMutation } from 'services/api/endpoints/images';
|
||||
import { useBulkUploadImagesMutation, useUploadImageMutation } from 'services/api/endpoints/images';
|
||||
import type { PostUploadAction } from 'services/api/types';
|
||||
|
||||
type UseImageUploadButtonArgs = {
|
||||
@@ -46,21 +47,41 @@ export const useImageUploadButton = ({
|
||||
const [uploadImage] = useUploadImageMutation();
|
||||
const maxImageUploadCount = useAppSelector(selectMaxImageUploadCount);
|
||||
const { t } = useTranslation();
|
||||
const [bulkUploadImages] = useBulkUploadImagesMutation();
|
||||
|
||||
const onDropAccepted = useCallback(
|
||||
(files: File[]) => {
|
||||
for (const [i, file] of files.entries()) {
|
||||
async (files: File[]) => {
|
||||
if (files.length > 1) {
|
||||
try {
|
||||
toast({
|
||||
id: 'BULK_UPLOAD',
|
||||
title: t('gallery.bulkUploadRequested'),
|
||||
status: 'info',
|
||||
duration: null,
|
||||
});
|
||||
await bulkUploadImages({
|
||||
bulk_upload_id: $queueId.get(),
|
||||
files,
|
||||
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
|
||||
}).unwrap();
|
||||
} catch (error) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('gallery.bulkUploadRequestFailed'),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
} else if (files[0]) {
|
||||
uploadImage({
|
||||
file,
|
||||
file: files[0],
|
||||
image_category: 'user',
|
||||
is_intermediate: false,
|
||||
postUploadAction: postUploadAction ?? { type: 'TOAST' },
|
||||
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
|
||||
isFirstUploadOfBatch: i === 0,
|
||||
});
|
||||
}
|
||||
},
|
||||
[autoAddBoardId, postUploadAction, uploadImage]
|
||||
[autoAddBoardId, postUploadAction, uploadImage, bulkUploadImages, t]
|
||||
);
|
||||
|
||||
const onDropRejected = useCallback(
|
||||
|
||||
@@ -2,6 +2,4 @@ import ReactDOM from 'react-dom/client';
|
||||
|
||||
import InvokeAIUI from './app/components/InvokeAIUI';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<InvokeAIUI />
|
||||
);
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<InvokeAIUI />);
|
||||
|
||||
@@ -273,7 +273,6 @@ export const imagesApi = api.injectEndpoints({
|
||||
board_id?: string;
|
||||
crop_visible?: boolean;
|
||||
metadata?: SerializableObject;
|
||||
isFirstUploadOfBatch?: boolean;
|
||||
}
|
||||
>({
|
||||
query: ({ file, image_category, is_intermediate, session_id, board_id, crop_visible, metadata }) => {
|
||||
@@ -325,28 +324,29 @@ export const imagesApi = api.injectEndpoints({
|
||||
bulkUploadImages: build.mutation<
|
||||
BulkUploadImageResponse,
|
||||
{
|
||||
bulk_upload_id: string;
|
||||
files: File[];
|
||||
board_id?: string;
|
||||
}
|
||||
>({
|
||||
query: ({ files, board_id }) => {
|
||||
query: ({ bulk_upload_id, files, board_id }) => {
|
||||
const formData = new FormData();
|
||||
for(const file of files) {
|
||||
for (const file of files) {
|
||||
formData.append('files', file);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
url: buildImagesUrl('bulk-upload'),
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
params: {
|
||||
bulk_upload_id,
|
||||
board_id: board_id === 'none' ? undefined : board_id,
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
|
||||
deleteBoard: build.mutation<DeleteBoardResult, string>({
|
||||
query: (board_id) => ({ url: buildBoardsUrl(board_id), method: 'DELETE' }),
|
||||
invalidatesTags: () => [
|
||||
|
||||
@@ -432,7 +432,7 @@ export type paths = {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/images/bulk_upload": {
|
||||
"/api/v1/images/bulk-upload": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
@@ -2590,6 +2590,11 @@ export type components = {
|
||||
* @description The timestamp of the event
|
||||
*/
|
||||
timestamp: number;
|
||||
/**
|
||||
* Bulk Upload Id
|
||||
* @description The ID of the bulk image download
|
||||
*/
|
||||
bulk_upload_id: string;
|
||||
/**
|
||||
* Total
|
||||
* @description The total numberof images
|
||||
@@ -2606,6 +2611,11 @@ export type components = {
|
||||
* @description The timestamp of the event
|
||||
*/
|
||||
timestamp: number;
|
||||
/**
|
||||
* Bulk Upload Id
|
||||
* @description The ID of the bulk image download
|
||||
*/
|
||||
bulk_upload_id: string;
|
||||
/**
|
||||
* Error
|
||||
* @description The error message
|
||||
@@ -2629,6 +2639,11 @@ export type components = {
|
||||
* @description The timestamp of the event
|
||||
*/
|
||||
timestamp: number;
|
||||
/**
|
||||
* Bulk Upload Id
|
||||
* @description The ID of the bulk image download
|
||||
*/
|
||||
bulk_upload_id: string;
|
||||
/**
|
||||
* Completed
|
||||
* @description The completed number of images
|
||||
@@ -2639,8 +2654,6 @@ export type components = {
|
||||
* @description The total number of images
|
||||
*/
|
||||
total: number;
|
||||
/** @description The uploaded image */
|
||||
image_DTO: components["schemas"]["ImageDTO"];
|
||||
};
|
||||
/**
|
||||
* BulkUploadStartedEvent
|
||||
@@ -2652,6 +2665,11 @@ export type components = {
|
||||
* @description The timestamp of the event
|
||||
*/
|
||||
timestamp: number;
|
||||
/**
|
||||
* Bulk Upload Id
|
||||
* @description The ID of the bulk image download
|
||||
*/
|
||||
bulk_upload_id: string;
|
||||
/**
|
||||
* Total
|
||||
* @description The total numberof images
|
||||
@@ -18378,7 +18396,8 @@ export interface operations {
|
||||
};
|
||||
bulk_upload: {
|
||||
parameters: {
|
||||
query?: {
|
||||
query: {
|
||||
bulk_upload_id: string;
|
||||
/** @description The board to add this images to, if any */
|
||||
board_id?: string | null;
|
||||
};
|
||||
|
||||
@@ -245,4 +245,4 @@ export type PostUploadAction =
|
||||
export type BoardRecordOrderBy = S['BoardRecordOrderBy'];
|
||||
export type StarterModel = S['StarterModel'];
|
||||
|
||||
export type BulkUploadImageResponse = S['BulkUploadImageResponse']
|
||||
export type BulkUploadImageResponse = S['BulkUploadImageResponse'];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ExternalLink } from '@invoke-ai/ui-library';
|
||||
import { ExternalLink, Flex, Progress, Text } from '@invoke-ai/ui-library';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { socketConnected } from 'app/store/middleware/listenerMiddleware/listeners/socketConnected';
|
||||
import { $baseUrl } from 'app/store/nanostores/baseUrl';
|
||||
@@ -20,8 +20,6 @@ import { queueApi, queueItemsAdapter } from 'services/api/endpoints/queue';
|
||||
import { buildOnInvocationComplete } from 'services/events/onInvocationComplete';
|
||||
import type { ClientToServerEvents, ServerToClientEvents } from 'services/events/types';
|
||||
import type { Socket } from 'socket.io-client';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
|
||||
import { $lastProgressEvent } from './stores';
|
||||
|
||||
@@ -44,7 +42,7 @@ export const setEventListeners = ({ socket, store, setIsConnected }: SetEventLis
|
||||
dispatch(socketConnected());
|
||||
const queue_id = $queueId.get();
|
||||
socket.emit('subscribe_queue', { queue_id });
|
||||
socket.emit('subscribe_bulk_upload', { bulk_upload_id: uuidv4() });
|
||||
socket.emit('subscribe_bulk_upload', { bulk_upload_id: $queueId.get() });
|
||||
if (!$baseUrl.get()) {
|
||||
const bulk_download_id = $bulkDownloadId.get();
|
||||
socket.emit('subscribe_bulk_download', { bulk_download_id });
|
||||
@@ -488,4 +486,80 @@ export const setEventListeners = ({ socket, store, setIsConnected }: SetEventLis
|
||||
duration: null,
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('bulk_upload_started', (data) => {
|
||||
log.debug({ data }, 'Bulk gallery upload started');
|
||||
const { total } = data;
|
||||
|
||||
toast({
|
||||
id: 'BULK_UPLOAD',
|
||||
title: t('gallery.bulkUploadStarted'),
|
||||
status: 'info',
|
||||
updateDescription: true,
|
||||
withCount: false,
|
||||
description: (
|
||||
<Flex flexDir="column" gap={2}>
|
||||
<Text>{t('gallery.bulkUploadStartedDesc', { x: total })}</Text>
|
||||
<Progress value={0} />
|
||||
</Flex>
|
||||
),
|
||||
duration: null,
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('bulk_upload_progress', (data) => {
|
||||
log.debug({ data }, 'Bulk gallery upload ready');
|
||||
const { completed, total } = data;
|
||||
|
||||
toast({
|
||||
id: 'BULK_UPLOAD',
|
||||
title: t('gallery.bulkUploadStarted'),
|
||||
status: 'info',
|
||||
updateDescription: true,
|
||||
withCount: false,
|
||||
description: (
|
||||
<Flex flexDir="column" gap={2}>
|
||||
<Text>{t('gallery.bulkUploadProgressDesc', { x: total, y: completed })}</Text>
|
||||
<Progress value={(completed / total) * 100} />
|
||||
</Flex>
|
||||
),
|
||||
duration: null,
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('bulk_upload_completed', (data) => {
|
||||
log.debug({ data }, 'Bulk gallery upload ready');
|
||||
const { total } = data;
|
||||
|
||||
toast({
|
||||
id: 'BULK_UPLOAD',
|
||||
title: t('gallery.bulkUploadComplete'),
|
||||
status: 'success',
|
||||
updateDescription: true,
|
||||
withCount: false,
|
||||
description: (
|
||||
<Flex flexDir="column" gap={2}>
|
||||
<Text>{t('gallery.bulkUploadCompleteDesc', { x: total })}</Text>
|
||||
<Progress value={100} />
|
||||
</Flex>
|
||||
),
|
||||
duration: null,
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('bulk_upload_error', (data) => {
|
||||
log.error({ data }, 'Bulk gallery download error');
|
||||
|
||||
const { error } = data;
|
||||
|
||||
toast({
|
||||
id: 'BULK_UPLOAD',
|
||||
title: t('gallery.bulkUploadFailed'),
|
||||
status: 'error',
|
||||
updateDescription: true,
|
||||
withCount: false,
|
||||
description: error,
|
||||
duration: null,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ export type ServerToClientEvents = {
|
||||
bulk_download_complete: (payload: S['BulkDownloadCompleteEvent']) => void;
|
||||
bulk_download_error: (payload: S['BulkDownloadErrorEvent']) => void;
|
||||
bulk_upload_started: (payload: S['BulkUploadStartedEvent']) => void;
|
||||
bulk_upload_complete: (payload: S['BulkUploadCompletedEvent']) => void;
|
||||
bulk_upload_completed: (payload: S['BulkUploadCompletedEvent']) => void;
|
||||
bulk_upload_progress: (payload: S['BulkUploadProgressEvent']) => void;
|
||||
bulk_upload_error: (payload: S['BulkUploadErrorEvent']) => void;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user