mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): POST upload working
This commit is contained in:
parent
3722f055fb
commit
e2114a1da5
@ -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
|
||||
)
|
||||
},
|
||||
)
|
||||
|
14
invokeai/frontend/web/src/app/invokeai.d.ts
vendored
14
invokeai/frontend/web/src/app/invokeai.d.ts
vendored
@ -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.
|
||||
|
@ -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 () => {
|
||||
|
@ -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));
|
||||
}
|
||||
};
|
@ -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`,
|
||||
|
@ -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'
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user