Compare commits

..

1 Commits

Author SHA1 Message Date
Ryan Dick
0fb3b53446 Add test state dict for a FLUX DoRA model. 2024-11-14 19:13:40 +00:00
21 changed files with 1225 additions and 245 deletions

View File

@@ -1675,7 +1675,7 @@
"clearCaches": "Clear Caches",
"recalculateRects": "Recalculate Rects",
"clipToBbox": "Clip Strokes to Bbox",
"outputOnlyMaskedRegions": "Output Only Generated Regions",
"outputOnlyMaskedRegions": "Output Only Masked Regions",
"addLayer": "Add Layer",
"duplicate": "Duplicate",
"moveToFront": "Move to Front",

View File

@@ -41,33 +41,29 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
log.debug({ imageDTO }, 'Image uploaded');
if (action.meta.arg.originalArgs.silent || imageDTO.is_intermediate) {
// When a "silent" upload is requested, or the image is intermediate, we can skip all post-upload actions,
// like toasts and switching the gallery view
return;
}
const boardId = imageDTO.board_id ?? 'none';
const DEFAULT_UPLOADED_TOAST = {
id: 'IMAGE_UPLOADED',
title: t('toast.imageUploaded'),
status: 'success',
} as const;
if (action.meta.arg.originalArgs.withToast) {
const DEFAULT_UPLOADED_TOAST = {
id: 'IMAGE_UPLOADED',
title: t('toast.imageUploaded'),
status: 'success',
} as const;
// default action - just upload and alert user
if (lastUploadedToastTimeout !== null) {
window.clearTimeout(lastUploadedToastTimeout);
// default action - just upload and alert user
if (lastUploadedToastTimeout !== null) {
window.clearTimeout(lastUploadedToastTimeout);
}
const toastApi = toast({
...DEFAULT_UPLOADED_TOAST,
title: DEFAULT_UPLOADED_TOAST.title,
description: getUploadedToastDescription(boardId, state),
duration: null, // we will close the toast manually
});
lastUploadedToastTimeout = window.setTimeout(() => {
toastApi.close();
}, 3000);
}
const toastApi = toast({
...DEFAULT_UPLOADED_TOAST,
title: DEFAULT_UPLOADED_TOAST.title,
description: getUploadedToastDescription(boardId, state),
duration: null, // we will close the toast manually
});
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

View File

@@ -61,11 +61,6 @@ export const useImageUploadButton = ({ onUpload, isDisabled, allowMultiple }: Us
log.warn('Multiple files dropped but only one allowed');
return;
}
if (files.length === 0) {
// Should never happen
log.warn('No files dropped');
return;
}
const file = files[0];
assert(file !== undefined); // should never happen
const imageDTO = await uploadImage({
@@ -73,20 +68,18 @@ export const useImageUploadButton = ({ onUpload, isDisabled, allowMultiple }: Us
image_category: 'user',
is_intermediate: false,
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
silent: true,
}).unwrap();
if (onUpload) {
onUpload(imageDTO);
}
} else {
//
const imageDTOs = await uploadImages(
files.map((file, i) => ({
files.map((file) => ({
file,
image_category: 'user',
is_intermediate: false,
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
silent: false,
isFirstUploadOfBatch: i === 0,
}))
);
if (onUpload) {

View File

@@ -4,7 +4,6 @@ import { attachClosestEdge, extractClosestEdge } from '@atlaskit/pragmatic-drag-
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { singleCanvasEntityDndSource } from 'features/dnd/dnd';
import { type DndListTargetState, idle } from 'features/dnd/types';
import { firefoxDndFix } from 'features/dnd/util';
import type { RefObject } from 'react';
import { useEffect, useState } from 'react';
@@ -18,7 +17,6 @@ export const useCanvasEntityListDnd = (ref: RefObject<HTMLElement>, entityIdenti
return;
}
return combine(
firefoxDndFix(element),
draggable({
element,
getInitialData() {

View File

@@ -1,19 +1,17 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { withResultAsync } from 'common/util/result';
import { selectSelectedImage } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
import { toast } from 'features/toast/toast';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiFloppyDiskBold } from 'react-icons/pi';
import { uploadImage } from 'services/api/endpoints/images';
const TOAST_ID = 'SAVE_STAGING_AREA_IMAGE_TO_GALLERY';
import { useAddImagesToBoardMutation, useChangeImageIsIntermediateMutation } from 'services/api/endpoints/images';
export const StagingAreaToolbarSaveSelectedToGalleryButton = memo(() => {
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
const selectedImage = useAppSelector(selectSelectedImage);
const [addImageToBoard] = useAddImagesToBoardMutation();
const [changeIsImageIntermediate] = useChangeImageIsIntermediateMutation();
const { t } = useTranslation();
@@ -21,42 +19,21 @@ export const StagingAreaToolbarSaveSelectedToGalleryButton = memo(() => {
if (!selectedImage) {
return;
}
// To save the image to gallery, we will download it and re-upload it. This allows the user to delete the image
// the gallery without borking the canvas, which may need this image to exist.
const result = await withResultAsync(async () => {
// Download the image
const res = await fetch(selectedImage.imageDTO.image_url);
const blob = await res.blob();
// Create a new file with the same name, which we will upload
const file = new File([blob], `copy_of_${selectedImage.imageDTO.image_name}`, { type: 'image/png' });
await uploadImage({
file,
// Image should show up in the Images tab
image_category: 'general',
if (autoAddBoardId !== 'none') {
await addImageToBoard({ imageDTOs: [selectedImage.imageDTO], board_id: autoAddBoardId }).unwrap();
// The changeIsImageIntermediate request will use the board_id on this specific imageDTO object, so we need to
// update it before making the request - else the optimistic board updates will get out of whack.
changeIsImageIntermediate({
imageDTO: { ...selectedImage.imageDTO, board_id: autoAddBoardId },
is_intermediate: false,
// TODO(psyche): Maybe this should just save to the currently-selected board?
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
// We will do our own toast - opt out of the default handling
silent: true,
});
});
if (result.isOk()) {
toast({
id: TOAST_ID,
title: t('controlLayers.savedToGalleryOk'),
status: 'success',
});
} else {
toast({
id: TOAST_ID,
title: t('controlLayers.savedToGalleryError'),
status: 'error',
changeIsImageIntermediate({
imageDTO: selectedImage.imageDTO,
is_intermediate: false,
});
}
}, [autoAddBoardId, selectedImage, t]);
}, [addImageToBoard, autoAddBoardId, changeIsImageIntermediate, selectedImage]);
return (
<IconButton
@@ -65,7 +42,7 @@ export const StagingAreaToolbarSaveSelectedToGalleryButton = memo(() => {
icon={<PiFloppyDiskBold />}
onClick={saveSelectedImageToGallery}
colorScheme="invokeBlue"
isDisabled={!selectedImage}
isDisabled={!selectedImage || !selectedImage.imageDTO.is_intermediate}
/>
);
});

View File

@@ -25,8 +25,6 @@ import type {
RegionalGuidanceReferenceImageState,
} from 'features/controlLayers/store/types';
import { imageDTOToImageObject, imageDTOToImageWithDims, initialControlNet } from 'features/controlLayers/store/util';
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
import type { BoardId } from 'features/gallery/store/types';
import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -72,11 +70,6 @@ const useSaveCanvas = ({ region, saveToGallery, toastOk, toastError, onSave, wit
metadata = selectCanvasMetadata(store.getState());
}
let boardId: BoardId | undefined = undefined;
if (saveToGallery) {
boardId = selectAutoAddBoardId(store.getState());
}
const result = await withResultAsync(() => {
const rasterAdapters = canvasManager.compositor.getVisibleAdaptersOfType('raster_layer');
return canvasManager.compositor.getCompositeImageDTO(
@@ -85,8 +78,6 @@ const useSaveCanvas = ({ region, saveToGallery, toastOk, toastError, onSave, wit
{
is_intermediate: !saveToGallery,
metadata,
board_id: boardId,
silent: true,
},
undefined,
true // force upload the image to ensure it gets added to the gallery
@@ -231,8 +222,8 @@ export const useNewRasterLayerFromBbox = () => {
toastError: t('controlLayers.newRasterLayerError'),
};
}, [dispatch, t]);
const func = useSaveCanvas(arg);
return func;
const newRasterLayerFromBbox = useSaveCanvas(arg);
return newRasterLayerFromBbox;
};
export const useNewControlLayerFromBbox = () => {

View File

@@ -28,17 +28,19 @@ import type {
} from 'features/controlLayers/store/types';
import { getEntityIdentifier } from 'features/controlLayers/store/types';
import { imageDTOToImageObject } from 'features/controlLayers/store/util';
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
import { toast } from 'features/toast/toast';
import { t } from 'i18next';
import { atom, computed } from 'nanostores';
import type { Logger } from 'roarr';
import { serializeError } from 'serialize-error';
import type { UploadImageArg } from 'services/api/endpoints/images';
import { getImageDTOSafe, uploadImage } from 'services/api/endpoints/images';
import type { ImageDTO, UploadImageArg } from 'services/api/types';
import type { ImageDTO } from 'services/api/types';
import stableHash from 'stable-hash';
import type { Equals } from 'tsafe';
import { assert } from 'tsafe';
import type { JsonObject, SetOptional } from 'type-fest';
import type { JsonObject } from 'type-fest';
type CompositingOptions = {
/**
@@ -257,7 +259,7 @@ export class CanvasCompositorModule extends CanvasModuleBase {
getCompositeImageDTO = async (
adapters: CanvasEntityAdapter[],
rect: Rect,
uploadOptions: SetOptional<Omit<UploadImageArg, 'file'>, 'image_category'>,
uploadOptions: Pick<UploadImageArg, 'is_intermediate' | 'metadata'>,
compositingOptions?: CompositingOptions,
forceUpload?: boolean
): Promise<ImageDTO> => {
@@ -297,7 +299,10 @@ export class CanvasCompositorModule extends CanvasModuleBase {
uploadImage({
file: new File([blob], 'canvas-composite.png', { type: 'image/png' }),
image_category: 'general',
...uploadOptions,
is_intermediate: uploadOptions.is_intermediate,
board_id: uploadOptions.is_intermediate ? undefined : selectAutoAddBoardId(this.manager.store.getState()),
metadata: uploadOptions.metadata,
withToast: false,
})
);
this.$isUploading.set(false);

View File

@@ -493,7 +493,7 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase {
file: new File([blob], `${this.id}_rasterized.png`, { type: 'image/png' }),
image_category: 'other',
is_intermediate: true,
silent: true,
withToast: false,
});
const imageObject = imageDTOToImageObject(imageDTO);
if (replaceObjects) {

View File

@@ -90,7 +90,7 @@ const initialState: CanvasSettingsState = {
invertScrollForToolWidth: false,
color: { r: 31, g: 160, b: 224, a: 1 }, // invokeBlue.500
sendToCanvas: false,
outputOnlyMaskedRegions: true,
outputOnlyMaskedRegions: false,
autoProcess: true,
snapToGrid: true,
showProgressOnCanvas: true,

View File

@@ -1,4 +1,3 @@
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import type { ImageProps, SystemStyleObject } from '@invoke-ai/ui-library';
import { Image } from '@invoke-ai/ui-library';
@@ -6,7 +5,6 @@ import { useAppStore } from 'app/store/nanostores/store';
import { singleImageDndSource } from 'features/dnd/dnd';
import type { DndDragPreviewSingleImageState } from 'features/dnd/DndDragPreviewSingleImage';
import { createSingleImageDragPreview, setSingleImageDragPreview } from 'features/dnd/DndDragPreviewSingleImage';
import { firefoxDndFix } from 'features/dnd/util';
import { useImageContextMenu } from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
import { memo, useEffect, useState } from 'react';
import type { ImageDTO } from 'services/api/types';
@@ -37,28 +35,25 @@ export const DndImage = memo(({ imageDTO, asThumbnail, ...rest }: Props) => {
if (!element) {
return;
}
return combine(
firefoxDndFix(element),
draggable({
element,
getInitialData: () => singleImageDndSource.getData({ imageDTO }, imageDTO.image_name),
onDragStart: () => {
setIsDragging(true);
},
onDrop: () => {
setIsDragging(false);
},
onGenerateDragPreview: (args) => {
if (singleImageDndSource.typeGuard(args.source.data)) {
setSingleImageDragPreview({
singleImageDndData: args.source.data,
onGenerateDragPreviewArgs: args,
setDragPreviewState,
});
}
},
})
);
return draggable({
element,
getInitialData: () => singleImageDndSource.getData({ imageDTO }, imageDTO.image_name),
onDragStart: () => {
setIsDragging(true);
},
onDrop: () => {
setIsDragging(false);
},
onGenerateDragPreview: (args) => {
if (singleImageDndSource.typeGuard(args.source.data)) {
setSingleImageDragPreview({
singleImageDndData: args.source.data,
onGenerateDragPreviewArgs: args,
setDragPreviewState,
});
}
},
});
}, [imageDTO, element, store]);
useImageContextMenu(imageDTO, element);

View File

@@ -13,9 +13,8 @@ import { selectMaxImageUploadCount } from 'features/system/store/configSlice';
import { toast } from 'features/toast/toast';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { uploadImages } from 'services/api/endpoints/images';
import { type UploadImageArg, uploadImages } from 'services/api/endpoints/images';
import { useBoardName } from 'services/api/hooks/useBoardName';
import type { UploadImageArg } from 'services/api/types';
import { z } from 'zod';
const ACCEPTED_IMAGE_TYPES = ['image/png', 'image/jpg', 'image/jpeg'];
@@ -95,12 +94,11 @@ export const FullscreenDropzone = memo(() => {
}
const autoAddBoardId = selectAutoAddBoardId(getState());
const uploadArgs: UploadImageArg[] = files.map((file, i) => ({
const uploadArgs: UploadImageArg[] = files.map((file) => ({
file,
image_category: 'user',
is_intermediate: false,
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
isFirstUploadOfBatch: i === 0,
}));
uploadImages(uploadArgs);

View File

@@ -1,7 +1,6 @@
import type { GetOffsetFn } from '@atlaskit/pragmatic-drag-and-drop/dist/types/public-utils/element/custom-native-drag-preview/types';
import type { Input } from '@atlaskit/pragmatic-drag-and-drop/types';
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { noop } from 'lodash-es';
import type { CSSProperties } from 'react';
/**
@@ -45,67 +44,3 @@ export function triggerPostMoveFlash(element: HTMLElement, backgroundColor: CSSP
iterations: 1,
});
}
/**
* Firefox has a bug where input or textarea elements with draggable parents do not allow selection of their text.
*
* This helper function implements a workaround by setting the draggable attribute to false when the mouse is over a
* input or textarea child of the draggable. It reverts the attribute on mouse out.
*
* The fix is only applied for Firefox, and should be used in every `pragmatic-drag-and-drop` `draggable`.
*
* See:
* - https://github.com/atlassian/pragmatic-drag-and-drop/issues/111
* - https://bugzilla.mozilla.org/show_bug.cgi?id=1853069
*
* @example
* ```tsx
* useEffect(() => {
* const element = ref.current;
* if (!element) {
* return;
* }
* return combine(
* firefoxDndFix(element),
* // The rest of the draggable setup is the same
* draggable({
* element,
* // ...
* }),
* );
*```
* @param element The draggable element
* @returns A cleanup function that removes the event listeners
*/
export const firefoxDndFix = (element: HTMLElement): (() => void) => {
if (!navigator.userAgent.includes('Firefox')) {
return noop;
}
const abortController = new AbortController();
element.addEventListener(
'mouseover',
(event) => {
if (event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLInputElement) {
element.setAttribute('draggable', 'false');
}
},
{ signal: abortController.signal }
);
element.addEventListener(
'mouseout',
(event) => {
if (event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLInputElement) {
element.setAttribute('draggable', 'true');
}
},
{ signal: abortController.signal }
);
return () => {
element.setAttribute('draggable', 'true');
abortController.abort();
};
};

View File

@@ -12,7 +12,6 @@ import type { DndDragPreviewMultipleImageState } from 'features/dnd/DndDragPrevi
import { createMultipleImageDragPreview, setMultipleImageDragPreview } from 'features/dnd/DndDragPreviewMultipleImage';
import type { DndDragPreviewSingleImageState } from 'features/dnd/DndDragPreviewSingleImage';
import { createSingleImageDragPreview, setSingleImageDragPreview } from 'features/dnd/DndDragPreviewSingleImage';
import { firefoxDndFix } from 'features/dnd/util';
import { useImageContextMenu } from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
import { GalleryImageHoverIcons } from 'features/gallery/components/ImageGrid/GalleryImageHoverIcons';
import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId';
@@ -67,7 +66,7 @@ const galleryImageContainerSX = {
},
'&:hover::before': {
boxShadow:
'inset 0px 0px 0px 1px var(--invoke-colors-invokeBlue-300), inset 0px 0px 0px 2px var(--invoke-colors-invokeBlue-800)',
'inset 0px 0px 0px 2px var(--invoke-colors-invokeBlue-300), inset 0px 0px 0px 3px var(--invoke-colors-invokeBlue-800)',
},
'&:hover[data-selected=true]::before': {
boxShadow:
@@ -116,17 +115,13 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
return;
}
return combine(
firefoxDndFix(element),
draggable({
element,
getInitialData: () => {
const { gallery } = store.getState();
// When we have multiple images selected, and the dragged image is part of the selection, initiate a
// multi-image drag.
if (
gallery.selection.length > 1 &&
gallery.selection.find(({ image_name }) => image_name === imageDTO.image_name) !== undefined
) {
if (gallery.selection.length > 1 && gallery.selection.includes(imageDTO)) {
return multipleImageDndSource.getData({
imageDTOs: gallery.selection,
boardId: gallery.selectedBoardId,

View File

@@ -4,7 +4,6 @@ import { attachClosestEdge, extractClosestEdge } from '@atlaskit/pragmatic-drag-
import { singleWorkflowFieldDndSource } from 'features/dnd/dnd';
import type { DndListTargetState } from 'features/dnd/types';
import { idle } from 'features/dnd/types';
import { firefoxDndFix } from 'features/dnd/util';
import type { FieldIdentifier } from 'features/nodes/types/field';
import type { RefObject } from 'react';
import { useEffect, useState } from 'react';
@@ -19,7 +18,6 @@ export const useLinearViewFieldDnd = (ref: RefObject<HTMLElement>, fieldIdentifi
return;
}
return combine(
firefoxDndFix(element),
draggable({
element,
getInitialData() {

View File

@@ -36,10 +36,7 @@ export const addImageToImage = async ({
}: AddImageToImageArg): Promise<Invocation<'img_resize' | 'l2i' | 'flux_vae_decode' | 'sd3_l2i'>> => {
denoise.denoising_start = denoising_start;
const adapters = manager.compositor.getVisibleAdaptersOfType('raster_layer');
const { image_name } = await manager.compositor.getCompositeImageDTO(adapters, bbox.rect, {
is_intermediate: true,
silent: true,
});
const { image_name } = await manager.compositor.getCompositeImageDTO(adapters, bbox.rect, { is_intermediate: true });
if (!isEqual(scaledSize, originalSize)) {
// Resize the initial image to the scaled size, denoise, then resize back to the original size

View File

@@ -51,13 +51,11 @@ export const addInpaint = async ({
const rasterAdapters = manager.compositor.getVisibleAdaptersOfType('raster_layer');
const initialImage = await manager.compositor.getCompositeImageDTO(rasterAdapters, bbox.rect, {
is_intermediate: true,
silent: true,
});
const inpaintMaskAdapters = manager.compositor.getVisibleAdaptersOfType('inpaint_mask');
const maskImage = await manager.compositor.getCompositeImageDTO(inpaintMaskAdapters, bbox.rect, {
is_intermediate: true,
silent: true,
});
if (!isEqual(scaledSize, originalSize)) {

View File

@@ -52,13 +52,11 @@ export const addOutpaint = async ({
const rasterAdapters = manager.compositor.getVisibleAdaptersOfType('raster_layer');
const initialImage = await manager.compositor.getCompositeImageDTO(rasterAdapters, bbox.rect, {
is_intermediate: true,
silent: true,
});
const inpaintMaskAdapters = manager.compositor.getVisibleAdaptersOfType('inpaint_mask');
const maskImage = await manager.compositor.getCompositeImageDTO(inpaintMaskAdapters, bbox.rect, {
is_intermediate: true,
silent: true,
});
const infill = getInfill(g, params);

View File

@@ -6,10 +6,10 @@ import type { components, paths } from 'services/api/schema';
import type {
DeleteBoardResult,
GraphAndWorkflowResponse,
ImageCategory,
ImageDTO,
ListImagesArgs,
ListImagesResponse,
UploadImageArg,
} from 'services/api/types';
import { getCategories, getListImagesUrl } from 'services/api/util';
import type { JsonObject } from 'type-fest';
@@ -260,7 +260,20 @@ export const imagesApi = api.injectEndpoints({
return [];
},
}),
uploadImage: build.mutation<ImageDTO, UploadImageArg>({
uploadImage: build.mutation<
ImageDTO,
{
file: File;
image_category: ImageCategory;
is_intermediate: boolean;
session_id?: string;
board_id?: string;
crop_visible?: boolean;
metadata?: JsonObject;
isFirstUploadOfBatch?: boolean;
withToast?: boolean;
}
>({
query: ({ file, image_category, is_intermediate, session_id, board_id, crop_visible, metadata }) => {
const formData = new FormData();
formData.append('file', file);
@@ -545,6 +558,7 @@ export const {
useClearIntermediatesMutation,
useAddImagesToBoardMutation,
useRemoveImagesFromBoardMutation,
useChangeImageIsIntermediateMutation,
useDeleteBoardAndImagesMutation,
useDeleteBoardMutation,
useStarImagesMutation,
@@ -608,17 +622,79 @@ export const getImageMetadata = (
return req.unwrap();
};
export type UploadImageArg = {
file: File;
image_category: ImageCategory;
is_intermediate: boolean;
session_id?: string;
board_id?: string;
crop_visible?: boolean;
metadata?: JsonObject;
withToast?: boolean;
};
export const uploadImage = (arg: UploadImageArg): Promise<ImageDTO> => {
const {
file,
image_category,
is_intermediate,
crop_visible = false,
board_id,
metadata,
session_id,
withToast = true,
} = arg;
const { dispatch } = getStore();
const req = dispatch(imagesApi.endpoints.uploadImage.initiate(arg, { track: false }));
const req = dispatch(
imagesApi.endpoints.uploadImage.initiate(
{
file,
image_category,
is_intermediate,
crop_visible,
board_id,
metadata,
session_id,
withToast,
},
{ track: false }
)
);
return req.unwrap();
};
export const uploadImages = async (args: UploadImageArg[]): Promise<ImageDTO[]> => {
const { dispatch } = getStore();
const results = await Promise.allSettled(
args.map((arg) => {
const req = dispatch(imagesApi.endpoints.uploadImage.initiate(arg, { track: false }));
args.map((arg, i) => {
const {
file,
image_category,
is_intermediate,
crop_visible = false,
board_id,
metadata,
session_id,
withToast = true,
} = arg;
const req = dispatch(
imagesApi.endpoints.uploadImage.initiate(
{
file,
image_category,
is_intermediate,
crop_visible,
board_id,
metadata,
session_id,
isFirstUploadOfBatch: i === 0,
withToast,
},
{ track: false }
)
);
return req.unwrap();
})
);

View File

@@ -1,5 +1,5 @@
import type { components, paths } from 'services/api/schema';
import type { JsonObject, SetRequired } from 'type-fest';
import type { SetRequired } from 'type-fest';
export type S = components['schemas'];
@@ -287,42 +287,3 @@ export type SetHFTokenResponse = NonNullable<
export type SetHFTokenArg = NonNullable<
paths['/api/v2/models/hf_login']['post']['requestBody']['content']['application/json']
>;
export type UploadImageArg = {
/**
* The file object to upload
*/
file: File;
/**
* THe category of image to upload
*/
image_category: ImageCategory;
/**
* Whether the uploaded image is an intermediate image (intermediate images are not shown int he gallery)
*/
is_intermediate: boolean;
/**
* The session with which to associate the uploaded image
*/
session_id?: string;
/**
* The board id to add the image to
*/
board_id?: string;
/**
* Whether or not to crop the image to its bounding box before saving
*/
crop_visible?: boolean;
/**
* Metadata to embed in the image when saving it
*/
metadata?: JsonObject;
/**
* Whether this upload should be "silent" (no toast on upload, no changing of gallery view)
*/
silent?: boolean;
/**
* Whether this is the first upload of a batch (used when displaying user feedback with toasts - ignored if the upload is silent)
*/
isFirstUploadOfBatch?: boolean;
};

View File

@@ -1 +1 @@
__version__ = "5.4.1"
__version__ = "5.4.1rc2"