(ui) accept upload socket events and show toast

This commit is contained in:
Mary Hipp
2024-10-21 20:07:33 -04:00
parent 0b139ec7df
commit 90ad720bb2
10 changed files with 165 additions and 53 deletions

View File

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

View File

@@ -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;
}

View File

@@ -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(() => {

View File

@@ -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(

View File

@@ -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 />);

View File

@@ -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: () => [

View File

@@ -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;
};

View File

@@ -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'];

View File

@@ -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,
});
});
};

View File

@@ -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;
};