feat(ui): POST upload working

This commit is contained in:
Mary Hipp 2023-04-03 14:32:43 -04:00
parent 3722f055fb
commit e2114a1da5
8 changed files with 68 additions and 77 deletions

View File

@ -1,5 +1,5 @@
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654)
import io
from datetime import datetime, timezone
from fastapi import Path, Request, UploadFile
@ -48,19 +48,19 @@ async def upload_image(file: UploadFile, request: Request):
contents = await file.read()
try:
im = Image.open(contents)
im = Image.open(io.BytesIO(contents))
except:
# Error opening the image
return Response(status_code=415)
filename = f"{str(int(datetime.now(timezone.utc).timestamp()))}.png"
ApiDependencies.invoker.services.images.save(ImageType.UPLOAD, filename, im)
ApiDependencies.invoker.services.images.save("uploads", filename, im)
return Response(
status_code=201,
headers={
"Location": request.url_for(
"get_image", image_type=ImageType.UPLOAD, image_name=filename
"get_image", image_type="uploads", image_name=filename
)
},
)

View File

@ -124,20 +124,6 @@ export declare type Image = {
category: GalleryCategory;
isBase64?: boolean;
dreamPrompt?: 'string';
name?: string;
};
/**
* ResultImage
*/
export declare type ResultImage = {
name: string;
url: string;
thumbnail: string;
width: number;
height: number;
timestamp: number;
metadata?: Metadata;
};
// GalleryImages is an array of Image.

View File

@ -2,7 +2,6 @@ import { Box, useToast } from '@chakra-ui/react';
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import useImageUploader from 'common/hooks/useImageUploader';
import { uploadImage } from 'features/gallery/store/thunks/uploadImage';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { ResourceKey } from 'i18next';
import {
@ -15,6 +14,7 @@ import {
} from 'react';
import { FileRejection, useDropzone } from 'react-dropzone';
import { useTranslation } from 'react-i18next';
import { uploadImage } from 'services/thunks/image';
import ImageUploadOverlay from './ImageUploadOverlay';
type ImageUploaderProps = {
@ -49,7 +49,7 @@ const ImageUploader = (props: ImageUploaderProps) => {
const fileAcceptedCallback = useCallback(
async (file: File) => {
dispatch(uploadImage({ imageFile: file }));
dispatch(uploadImage({ formData: { file } }));
},
[dispatch]
);
@ -124,7 +124,7 @@ const ImageUploader = (props: ImageUploaderProps) => {
return;
}
dispatch(uploadImage({ imageFile: file }));
dispatch(uploadImage({ formData: { file } }));
};
document.addEventListener('paste', pasteImageListener);
return () => {

View File

@ -1,54 +0,0 @@
import { AnyAction, ThunkAction } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/invokeai';
import { RootState } from 'app/store';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { setInitialImage } from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { v4 as uuidv4 } from 'uuid';
import { addImage } from '../gallerySlice';
type UploadImageConfig = {
imageFile: File;
};
export const uploadImage =
(
config: UploadImageConfig
): ThunkAction<void, RootState, unknown, AnyAction> =>
async (dispatch, getState) => {
const { imageFile } = config;
const state = getState() as RootState;
const activeTabName = activeTabNameSelector(state);
const formData = new FormData();
formData.append('file', imageFile, imageFile.name);
formData.append(
'data',
JSON.stringify({
kind: 'init',
})
);
const response = await fetch(`${window.location.origin}/upload`, {
method: 'POST',
body: formData,
});
const image = (await response.json()) as InvokeAI.ImageUploadResponse;
const newImage: InvokeAI.Image = {
uuid: uuidv4(),
category: 'user',
...image,
};
dispatch(addImage({ image: newImage, category: 'user' }));
if (activeTabName === 'unifiedCanvas') {
dispatch(setInitialCanvasImage(newImage));
} else if (activeTabName === 'img2img') {
dispatch(setInitialImage(newImage));
}
};

View File

@ -89,6 +89,7 @@ export class ImagesService {
url: '/api/v1/images/uploads/',
formData: formData,
mediaType: 'multipart/form-data',
responseHeader: "location",
errors: {
404: `Session not found`,
422: `Validation Error`,

View File

@ -1,8 +1,11 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import { v4 as uuidv4 } from 'uuid';
import { ProgressImage } from './events/types';
import { createSession, invokeSession } from 'services/thunks/session';
import { getImage } from './thunks/image';
import { getImage, uploadImage } from './thunks/image';
import * as InvokeAI from 'app/invokeai';
import { addImage } from 'features/gallery/store/gallerySlice';
/**
* Just temp until we work out better statuses
@ -93,6 +96,19 @@ export const apiSlice = createSlice({
// !HTTP 200
// state.networkStatus = 'idle'
});
builder.addCase(uploadImage.fulfilled, (state, action) => {
// !HTTP 200
console.log(action.payload);
// state.networkStatus = 'idle'
});
builder.addCase(uploadImage.pending, (state, action) => {
// HTTP request pending
// state.networkStatus = 'busy'
});
builder.addCase(uploadImage.rejected, (state, action) => {
// !HTTP 200
// state.networkStatus = 'idle'
});
},
});

View File

@ -1,8 +1,16 @@
import { isFulfilled, Middleware, MiddlewareAPI } from '@reduxjs/toolkit';
import { v4 as uuidv4 } from 'uuid';
import { emitSubscribe } from 'app/nodesSocketio/actions';
import { AppDispatch, RootState } from 'app/store';
import { setSessionId } from './apiSlice';
import { uploadImage } from './thunks/image';
import { createSession, invokeSession } from './thunks/session';
import * as InvokeAI from 'app/invokeai';
import { addImage } from 'features/gallery/store/gallerySlice';
import { tabMap } from 'features/ui/store/tabMap';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { setInitialImage } from 'features/parameters/store/generationSlice';
/**
* `redux-toolkit` provides nice matching utilities, which can be used as type guards
@ -10,10 +18,11 @@ import { createSession, invokeSession } from './thunks/session';
*/
const isFulfilledCreateSession = isFulfilled(createSession);
const isFulfilledUploadImage = isFulfilled(uploadImage);
export const invokeMiddleware: Middleware =
(store: MiddlewareAPI<AppDispatch, RootState>) => (next) => (action) => {
const { dispatch } = store;
const { dispatch, getState } = store;
if (isFulfilledCreateSession(action)) {
const sessionId = action.payload.id;
@ -22,6 +31,29 @@ export const invokeMiddleware: Middleware =
dispatch(setSessionId(sessionId));
dispatch(emitSubscribe(sessionId));
dispatch(invokeSession({ sessionId }));
} else if (isFulfilledUploadImage(action)) {
const uploadLocation = action.payload;
console.log('uploadImage.fulfilled');
const newImage: InvokeAI.Image = {
uuid: uuidv4(),
category: 'user',
url: uploadLocation,
width: 512,
height: 512,
mtime: new Date().getTime(),
thumbnail: uploadLocation,
};
dispatch(addImage({ image: newImage, category: 'user' }));
const { activeTab } = getState().ui;
const activeTabName = tabMap[activeTab];
if (activeTabName === 'unifiedCanvas') {
dispatch(setInitialCanvasImage(newImage));
} else if (activeTabName === 'img2img') {
dispatch(setInitialImage(newImage));
}
} else {
next(action);
}

View File

@ -11,3 +11,13 @@ export const getImage = createAppAsyncThunk(
return response;
}
);
type UploadImageArg = Parameters<(typeof ImagesService)['uploadImage']>[0];
export const uploadImage = createAppAsyncThunk(
'api/uploadImage',
async (arg: UploadImageArg, _thunkApi) => {
const response = await ImagesService.uploadImage(arg);
return response;
}
);