Merge branch 'main' into release/make-web-dist-startable

This commit is contained in:
Lincoln Stein 2023-05-25 22:49:18 -04:00 committed by GitHub
commit 497a885c85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 1068 additions and 267 deletions

View File

@ -1,5 +1,6 @@
import io
from fastapi import HTTPException, Path, Query, Request, Response, UploadFile
from typing import Optional
from fastapi import Body, HTTPException, Path, Query, Request, Response, UploadFile
from fastapi.routing import APIRouter
from fastapi.responses import FileResponse
from PIL import Image
@ -7,7 +8,11 @@ from invokeai.app.models.image import (
ImageCategory,
ImageType,
)
from invokeai.app.services.models.image_record import ImageDTO, ImageUrlsDTO
from invokeai.app.services.models.image_record import (
ImageDTO,
ImageRecordChanges,
ImageUrlsDTO,
)
from invokeai.app.services.item_storage import PaginatedResults
from ..dependencies import ApiDependencies
@ -27,10 +32,17 @@ images_router = APIRouter(prefix="/v1/images", tags=["images"])
)
async def upload_image(
file: UploadFile,
image_type: ImageType,
request: Request,
response: Response,
image_category: ImageCategory = ImageCategory.GENERAL,
image_category: ImageCategory = Query(
default=ImageCategory.GENERAL, description="The category of the image"
),
is_intermediate: bool = Query(
default=False, description="Whether this is an intermediate image"
),
session_id: Optional[str] = Query(
default=None, description="The session ID associated with this upload, if any"
),
) -> ImageDTO:
"""Uploads an image"""
if not file.content_type.startswith("image"):
@ -46,9 +58,11 @@ async def upload_image(
try:
image_dto = ApiDependencies.invoker.services.images.create(
pil_image,
image_type,
image_category,
image=pil_image,
image_type=ImageType.UPLOAD,
image_category=image_category,
session_id=session_id,
is_intermediate=is_intermediate,
)
response.status_code = 201
@ -61,7 +75,7 @@ async def upload_image(
@images_router.delete("/{image_type}/{image_name}", operation_id="delete_image")
async def delete_image(
image_type: ImageType = Query(description="The type of image to delete"),
image_type: ImageType = Path(description="The type of image to delete"),
image_name: str = Path(description="The name of the image to delete"),
) -> None:
"""Deletes an image"""
@ -73,6 +87,28 @@ async def delete_image(
pass
@images_router.patch(
"/{image_type}/{image_name}",
operation_id="update_image",
response_model=ImageDTO,
)
async def update_image(
image_type: ImageType = Path(description="The type of image to update"),
image_name: str = Path(description="The name of the image to update"),
image_changes: ImageRecordChanges = Body(
description="The changes to apply to the image"
),
) -> ImageDTO:
"""Updates an image"""
try:
return ApiDependencies.invoker.services.images.update(
image_type, image_name, image_changes
)
except Exception as e:
raise HTTPException(status_code=400, detail="Failed to update image")
@images_router.get(
"/{image_type}/{image_name}/metadata",
operation_id="get_image_metadata",
@ -85,9 +121,7 @@ async def get_image_metadata(
"""Gets an image's metadata"""
try:
return ApiDependencies.invoker.services.images.get_dto(
image_type, image_name
)
return ApiDependencies.invoker.services.images.get_dto(image_type, image_name)
except Exception as e:
raise HTTPException(status_code=404)
@ -113,9 +147,7 @@ async def get_image_full(
"""Gets a full-resolution image file"""
try:
path = ApiDependencies.invoker.services.images.get_path(
image_type, image_name
)
path = ApiDependencies.invoker.services.images.get_path(image_type, image_name)
if not ApiDependencies.invoker.services.images.validate_path(path):
raise HTTPException(status_code=404)

View File

@ -78,6 +78,7 @@ class BaseInvocation(ABC, BaseModel):
#fmt: off
id: str = Field(description="The id of this node. Must be unique among all nodes.")
is_intermediate: bool = Field(default=False, description="Whether or not this node is an intermediate node.")
#fmt: on

View File

@ -57,10 +57,11 @@ class CvInpaintInvocation(BaseInvocation, CvInvocationConfig):
image_dto = context.services.images.create(
image=image_inpainted,
image_type=ImageType.INTERMEDIATE,
image_type=ImageType.RESULT,
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(

View File

@ -101,6 +101,7 @@ class TextToImageInvocation(BaseInvocation, SDImageInvocation):
image_category=ImageCategory.GENERAL,
session_id=context.graph_execution_state_id,
node_id=self.id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(
@ -181,6 +182,7 @@ class ImageToImageInvocation(TextToImageInvocation):
image_category=ImageCategory.GENERAL,
session_id=context.graph_execution_state_id,
node_id=self.id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(
@ -296,6 +298,7 @@ class InpaintInvocation(ImageToImageInvocation):
image_category=ImageCategory.GENERAL,
session_id=context.graph_execution_state_id,
node_id=self.id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(

View File

@ -143,6 +143,7 @@ class ImageCropInvocation(BaseInvocation, PILInvocationConfig):
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(
@ -204,6 +205,7 @@ class ImagePasteInvocation(BaseInvocation, PILInvocationConfig):
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(
@ -242,6 +244,7 @@ class MaskFromAlphaInvocation(BaseInvocation, PILInvocationConfig):
image_category=ImageCategory.MASK,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return MaskOutput(
@ -280,6 +283,7 @@ class ImageMultiplyInvocation(BaseInvocation, PILInvocationConfig):
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(
@ -318,6 +322,7 @@ class ImageChannelInvocation(BaseInvocation, PILInvocationConfig):
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(
@ -356,6 +361,7 @@ class ImageConvertInvocation(BaseInvocation, PILInvocationConfig):
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(
@ -397,6 +403,7 @@ class ImageBlurInvocation(BaseInvocation, PILInvocationConfig):
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(
@ -437,6 +444,7 @@ class ImageLerpInvocation(BaseInvocation, PILInvocationConfig):
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(
@ -482,6 +490,7 @@ class ImageInverseLerpInvocation(BaseInvocation, PILInvocationConfig):
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(

View File

@ -149,6 +149,7 @@ class InfillColorInvocation(BaseInvocation):
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(
@ -193,6 +194,7 @@ class InfillTileInvocation(BaseInvocation):
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(
@ -230,6 +232,7 @@ class InfillPatchMatchInvocation(BaseInvocation):
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(

View File

@ -370,6 +370,7 @@ class LatentsToImageInvocation(BaseInvocation):
image_category=ImageCategory.GENERAL,
session_id=context.graph_execution_state_id,
node_id=self.id,
is_intermediate=self.is_intermediate
)
return ImageOutput(

View File

@ -43,10 +43,11 @@ class RestoreFaceInvocation(BaseInvocation):
# TODO: can this return multiple results?
image_dto = context.services.images.create(
image=results[0][0],
image_type=ImageType.INTERMEDIATE,
image_type=ImageType.RESULT,
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(

View File

@ -49,6 +49,7 @@ class UpscaleInvocation(BaseInvocation):
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(

View File

@ -10,7 +10,6 @@ class ImageType(str, Enum, metaclass=MetaEnum):
RESULT = "results"
UPLOAD = "uploads"
INTERMEDIATE = "intermediates"
class InvalidImageTypeException(ValueError):

View File

@ -12,6 +12,7 @@ from invokeai.app.models.image import (
)
from invokeai.app.services.models.image_record import (
ImageRecord,
ImageRecordChanges,
deserialize_image_record,
)
from invokeai.app.services.item_storage import PaginatedResults
@ -49,6 +50,16 @@ class ImageRecordStorageBase(ABC):
"""Gets an image record."""
pass
@abstractmethod
def update(
self,
image_name: str,
image_type: ImageType,
changes: ImageRecordChanges,
) -> None:
"""Updates an image record."""
pass
@abstractmethod
def get_many(
self,
@ -78,6 +89,7 @@ class ImageRecordStorageBase(ABC):
session_id: Optional[str],
node_id: Optional[str],
metadata: Optional[ImageMetadata],
is_intermediate: bool = False,
) -> datetime:
"""Saves an image record."""
pass
@ -125,6 +137,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
session_id TEXT,
node_id TEXT,
metadata TEXT,
is_intermediate BOOLEAN DEFAULT FALSE,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Updated via trigger
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -193,6 +206,42 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
return deserialize_image_record(dict(result))
def update(
self,
image_name: str,
image_type: ImageType,
changes: ImageRecordChanges,
) -> None:
try:
self._lock.acquire()
# Change the category of the image
if changes.image_category is not None:
self._cursor.execute(
f"""--sql
UPDATE images
SET image_category = ?
WHERE image_name = ?;
""",
(changes.image_category, image_name),
)
# Change the session associated with the image
if changes.session_id is not None:
self._cursor.execute(
f"""--sql
UPDATE images
SET session_id = ?
WHERE image_name = ?;
""",
(changes.session_id, image_name),
)
self._conn.commit()
except sqlite3.Error as e:
self._conn.rollback()
raise ImageRecordSaveException from e
finally:
self._lock.release()
def get_many(
self,
image_type: ImageType,
@ -265,6 +314,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
height: int,
node_id: Optional[str],
metadata: Optional[ImageMetadata],
is_intermediate: bool = False,
) -> datetime:
try:
metadata_json = (
@ -281,9 +331,10 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
height,
node_id,
session_id,
metadata
metadata,
is_intermediate
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?);
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
""",
(
image_name,
@ -294,6 +345,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
node_id,
session_id,
metadata_json,
is_intermediate,
),
)
self._conn.commit()

View File

@ -20,6 +20,7 @@ from invokeai.app.services.image_record_storage import (
from invokeai.app.services.models.image_record import (
ImageRecord,
ImageDTO,
ImageRecordChanges,
image_record_to_dto,
)
from invokeai.app.services.image_file_storage import (
@ -31,7 +32,6 @@ from invokeai.app.services.image_file_storage import (
from invokeai.app.services.item_storage import ItemStorageABC, PaginatedResults
from invokeai.app.services.metadata import MetadataServiceBase
from invokeai.app.services.urls import UrlServiceBase
from invokeai.app.util.misc import get_iso_timestamp
if TYPE_CHECKING:
from invokeai.app.services.graph import GraphExecutionState
@ -48,11 +48,21 @@ class ImageServiceABC(ABC):
image_category: ImageCategory,
node_id: Optional[str] = None,
session_id: Optional[str] = None,
metadata: Optional[ImageMetadata] = None,
intermediate: bool = False,
) -> ImageDTO:
"""Creates an image, storing the file and its metadata."""
pass
@abstractmethod
def update(
self,
image_type: ImageType,
image_name: str,
changes: ImageRecordChanges,
) -> ImageDTO:
"""Updates an image."""
pass
@abstractmethod
def get_pil_image(self, image_type: ImageType, image_name: str) -> PILImageType:
"""Gets an image as a PIL image."""
@ -157,6 +167,7 @@ class ImageService(ImageServiceABC):
image_category: ImageCategory,
node_id: Optional[str] = None,
session_id: Optional[str] = None,
is_intermediate: bool = False,
) -> ImageDTO:
if image_type not in ImageType:
raise InvalidImageTypeException
@ -184,6 +195,8 @@ class ImageService(ImageServiceABC):
image_category=image_category,
width=width,
height=height,
# Meta fields
is_intermediate=is_intermediate,
# Nullable fields
node_id=node_id,
session_id=session_id,
@ -217,6 +230,7 @@ class ImageService(ImageServiceABC):
created_at=created_at,
updated_at=created_at, # this is always the same as the created_at at this time
deleted_at=None,
is_intermediate=is_intermediate,
# Extra non-nullable fields for DTO
image_url=image_url,
thumbnail_url=thumbnail_url,
@ -231,6 +245,23 @@ class ImageService(ImageServiceABC):
self._services.logger.error("Problem saving image record and file")
raise e
def update(
self,
image_type: ImageType,
image_name: str,
changes: ImageRecordChanges,
) -> ImageDTO:
try:
self._services.records.update(image_name, image_type, changes)
return self.get_dto(image_type, image_name)
except ImageRecordSaveException:
self._services.logger.error("Failed to update image record")
raise
except Exception as e:
self._services.logger.error("Problem updating image record")
raise e
def get_pil_image(self, image_type: ImageType, image_name: str) -> PILImageType:
try:
return self._services.files.get(image_type, image_name)

View File

@ -1,6 +1,6 @@
import datetime
from typing import Optional, Union
from pydantic import BaseModel, Field
from pydantic import BaseModel, Extra, Field, StrictStr
from invokeai.app.models.image import ImageCategory, ImageType
from invokeai.app.models.metadata import ImageMetadata
from invokeai.app.util.misc import get_iso_timestamp
@ -31,6 +31,8 @@ class ImageRecord(BaseModel):
description="The deleted timestamp of the image."
)
"""The deleted timestamp of the image."""
is_intermediate: bool = Field(description="Whether this is an intermediate image.")
"""Whether this is an intermediate image."""
session_id: Optional[str] = Field(
default=None,
description="The session ID that generated this image, if it is a generated image.",
@ -48,6 +50,25 @@ class ImageRecord(BaseModel):
"""A limited subset of the image's generation metadata. Retrieve the image's session for full metadata."""
class ImageRecordChanges(BaseModel, extra=Extra.forbid):
"""A set of changes to apply to an image record.
Only limited changes are valid:
- `image_category`: change the category of an image
- `session_id`: change the session associated with an image
"""
image_category: Optional[ImageCategory] = Field(
description="The image's new category."
)
"""The image's new category."""
session_id: Optional[StrictStr] = Field(
default=None,
description="The image's new session ID.",
)
"""The image's new session ID."""
class ImageUrlsDTO(BaseModel):
"""The URLs for an image and its thumbnail."""
@ -95,6 +116,7 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord:
created_at = image_dict.get("created_at", get_iso_timestamp())
updated_at = image_dict.get("updated_at", get_iso_timestamp())
deleted_at = image_dict.get("deleted_at", get_iso_timestamp())
is_intermediate = image_dict.get("is_intermediate", False)
raw_metadata = image_dict.get("metadata")
@ -115,4 +137,5 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord:
created_at=created_at,
updated_at=updated_at,
deleted_at=deleted_at,
is_intermediate=is_intermediate,
)

View File

@ -8,7 +8,6 @@ import type { TypedStartListening, TypedAddListener } from '@reduxjs/toolkit';
import type { RootState, AppDispatch } from '../../store';
import { addInitialImageSelectedListener } from './listeners/initialImageSelected';
import { addImageResultReceivedListener } from './listeners/invocationComplete';
import { addImageUploadedListener } from './listeners/imageUploaded';
import { addRequestedImageDeletionListener } from './listeners/imageDeleted';
import { addUserInvokedCanvasListener } from './listeners/userInvokedCanvas';
@ -19,6 +18,16 @@ import { addCanvasSavedToGalleryListener } from './listeners/canvasSavedToGaller
import { addCanvasDownloadedAsImageListener } from './listeners/canvasDownloadedAsImage';
import { addCanvasCopiedToClipboardListener } from './listeners/canvasCopiedToClipboard';
import { addCanvasMergedListener } from './listeners/canvasMerged';
import { addGeneratorProgressListener } from './listeners/socketio/generatorProgress';
import { addGraphExecutionStateCompleteListener } from './listeners/socketio/graphExecutionStateComplete';
import { addInvocationCompleteListener } from './listeners/socketio/invocationComplete';
import { addInvocationErrorListener } from './listeners/socketio/invocationError';
import { addInvocationStartedListener } from './listeners/socketio/invocationStarted';
import { addSocketConnectedListener } from './listeners/socketio/socketConnected';
import { addSocketDisconnectedListener } from './listeners/socketio/socketDisconnected';
import { addSocketSubscribedListener } from './listeners/socketio/socketSubscribed';
import { addSocketUnsubscribedListener } from './listeners/socketio/socketUnsubscribed';
import { addSessionReadyToInvokeListener } from './listeners/sessionReadyToInvoke';
export const listenerMiddleware = createListenerMiddleware();
@ -40,15 +49,27 @@ export type AppListenerEffect = ListenerEffect<
addImageUploadedListener();
addInitialImageSelectedListener();
addImageResultReceivedListener();
addRequestedImageDeletionListener();
addUserInvokedCanvasListener();
addUserInvokedNodesListener();
addUserInvokedTextToImageListener();
addUserInvokedImageToImageListener();
addSessionReadyToInvokeListener();
addCanvasSavedToGalleryListener();
addCanvasDownloadedAsImageListener();
addCanvasCopiedToClipboardListener();
addCanvasMergedListener();
// socketio
addGeneratorProgressListener();
addGraphExecutionStateCompleteListener();
addInvocationCompleteListener();
addInvocationErrorListener();
addInvocationStartedListener();
addSocketConnectedListener();
addSocketDisconnectedListener();
addSocketSubscribedListener();
addSocketUnsubscribedListener();

View File

@ -12,7 +12,7 @@ export const addImageUploadedListener = () => {
startAppListening({
predicate: (action): action is ReturnType<typeof imageUploaded.fulfilled> =>
imageUploaded.fulfilled.match(action) &&
action.payload.response.image_type !== 'intermediates',
action.payload.response.is_intermediate === false,
effect: (action, { dispatch, getState }) => {
const { response: image } = action.payload;

View File

@ -0,0 +1,19 @@
import { startAppListening } from '..';
import { sessionInvoked } from 'services/thunks/session';
import { log } from 'app/logging/useLogger';
import { sessionReadyToInvoke } from 'features/system/store/actions';
const moduleLog = log.child({ namespace: 'invoke' });
export const addSessionReadyToInvokeListener = () => {
startAppListening({
actionCreator: sessionReadyToInvoke,
effect: (action, { getState, dispatch }) => {
const { sessionId } = getState().system;
if (sessionId) {
moduleLog.info({ sessionId }, `Session invoked (${sessionId})})`);
dispatch(sessionInvoked({ sessionId }));
}
},
});
};

View File

@ -0,0 +1,28 @@
import { startAppListening } from '../..';
import { log } from 'app/logging/useLogger';
import { generatorProgress } from 'services/events/actions';
const moduleLog = log.child({ namespace: 'socketio' });
export const addGeneratorProgressListener = () => {
startAppListening({
actionCreator: generatorProgress,
effect: (action, { dispatch, getState }) => {
if (
getState().system.canceledSession ===
action.payload.data.graph_execution_state_id
) {
moduleLog.trace(
action.payload,
'Ignored generator progress for canceled session'
);
return;
}
moduleLog.trace(
action.payload,
`Generator progress (${action.payload.data.node.type})`
);
},
});
};

View File

@ -0,0 +1,17 @@
import { log } from 'app/logging/useLogger';
import { graphExecutionStateComplete } from 'services/events/actions';
import { startAppListening } from '../..';
const moduleLog = log.child({ namespace: 'socketio' });
export const addGraphExecutionStateCompleteListener = () => {
startAppListening({
actionCreator: graphExecutionStateComplete,
effect: (action, { dispatch, getState }) => {
moduleLog.debug(
action.payload,
`Graph execution state complete (${action.payload.data.graph_execution_state_id})`
);
},
});
};

View File

@ -1,40 +1,49 @@
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
import { startAppListening } from '../..';
import { log } from 'app/logging/useLogger';
import { invocationComplete } from 'services/events/actions';
import { isImageOutput } from 'services/types/guards';
import {
imageMetadataReceived,
imageUrlsReceived,
} from 'services/thunks/image';
import { startAppListening } from '..';
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
import { sessionCanceled } from 'services/thunks/session';
import { isImageOutput } from 'services/types/guards';
const moduleLog = log.child({ namespace: 'socketio' });
const nodeDenylist = ['dataURL_image'];
export const addImageResultReceivedListener = () => {
export const addInvocationCompleteListener = () => {
startAppListening({
predicate: (action) => {
if (
invocationComplete.match(action) &&
isImageOutput(action.payload.data.result)
) {
return true;
}
return false;
},
effect: async (action, { getState, dispatch, take }) => {
if (!invocationComplete.match(action)) {
return;
actionCreator: invocationComplete,
effect: async (action, { dispatch, getState, take }) => {
moduleLog.info(
action.payload,
`Invocation complete (${action.payload.data.node.type})`
);
const sessionId = action.payload.data.graph_execution_state_id;
const { cancelType, isCancelScheduled } = getState().system;
// Handle scheduled cancelation
if (cancelType === 'scheduled' && isCancelScheduled) {
dispatch(sessionCanceled({ sessionId }));
}
const { data } = action.payload;
const { result, node, graph_execution_state_id } = data;
// This complete event has an associated image output
if (isImageOutput(result) && !nodeDenylist.includes(node.type)) {
const { image_name, image_type } = result.image;
// Get its URLS
// TODO: is this extraneous? I think so...
dispatch(
imageUrlsReceived({ imageName: image_name, imageType: image_type })
);
// Get its metadata
dispatch(
imageMetadataReceived({
imageName: image_name,

View File

@ -0,0 +1,17 @@
import { startAppListening } from '../..';
import { log } from 'app/logging/useLogger';
import { invocationError } from 'services/events/actions';
const moduleLog = log.child({ namespace: 'socketio' });
export const addInvocationErrorListener = () => {
startAppListening({
actionCreator: invocationError,
effect: (action, { dispatch, getState }) => {
moduleLog.debug(
action.payload,
`Invocation error (${action.payload.data.node.type})`
);
},
});
};

View File

@ -0,0 +1,28 @@
import { startAppListening } from '../..';
import { log } from 'app/logging/useLogger';
import { invocationStarted } from 'services/events/actions';
const moduleLog = log.child({ namespace: 'socketio' });
export const addInvocationStartedListener = () => {
startAppListening({
actionCreator: invocationStarted,
effect: (action, { dispatch, getState }) => {
if (
getState().system.canceledSession ===
action.payload.data.graph_execution_state_id
) {
moduleLog.trace(
action.payload,
'Ignored invocation started for canceled session'
);
return;
}
moduleLog.info(
action.payload,
`Invocation started (${action.payload.data.node.type})`
);
},
});
};

View File

@ -0,0 +1,43 @@
import { startAppListening } from '../..';
import { log } from 'app/logging/useLogger';
import { socketConnected } from 'services/events/actions';
import {
receivedResultImagesPage,
receivedUploadImagesPage,
} from 'services/thunks/gallery';
import { receivedModels } from 'services/thunks/model';
import { receivedOpenAPISchema } from 'services/thunks/schema';
const moduleLog = log.child({ namespace: 'socketio' });
export const addSocketConnectedListener = () => {
startAppListening({
actionCreator: socketConnected,
effect: (action, { dispatch, getState }) => {
const { timestamp } = action.payload;
moduleLog.debug({ timestamp }, 'Connected');
const { results, uploads, models, nodes, config } = getState();
const { disabledTabs } = config;
// These thunks need to be dispatch in middleware; cannot handle in a reducer
if (!results.ids.length) {
dispatch(receivedResultImagesPage());
}
if (!uploads.ids.length) {
dispatch(receivedUploadImagesPage());
}
if (!models.ids.length) {
dispatch(receivedModels());
}
if (!nodes.schema && !disabledTabs.includes('nodes')) {
dispatch(receivedOpenAPISchema());
}
},
});
};

View File

@ -0,0 +1,14 @@
import { startAppListening } from '../..';
import { log } from 'app/logging/useLogger';
import { socketDisconnected } from 'services/events/actions';
const moduleLog = log.child({ namespace: 'socketio' });
export const addSocketDisconnectedListener = () => {
startAppListening({
actionCreator: socketDisconnected,
effect: (action, { dispatch, getState }) => {
moduleLog.debug(action.payload, 'Disconnected');
},
});
};

View File

@ -0,0 +1,17 @@
import { startAppListening } from '../..';
import { log } from 'app/logging/useLogger';
import { socketSubscribed } from 'services/events/actions';
const moduleLog = log.child({ namespace: 'socketio' });
export const addSocketSubscribedListener = () => {
startAppListening({
actionCreator: socketSubscribed,
effect: (action, { dispatch, getState }) => {
moduleLog.debug(
action.payload,
`Subscribed (${action.payload.sessionId}))`
);
},
});
};

View File

@ -0,0 +1,17 @@
import { startAppListening } from '../..';
import { log } from 'app/logging/useLogger';
import { socketUnsubscribed } from 'services/events/actions';
const moduleLog = log.child({ namespace: 'socketio' });
export const addSocketUnsubscribedListener = () => {
startAppListening({
actionCreator: socketUnsubscribed,
effect: (action, { dispatch, getState }) => {
moduleLog.debug(
action.payload,
`Unsubscribed (${action.payload.sessionId})`
);
},
});
};

View File

@ -1,9 +1,9 @@
import { startAppListening } from '..';
import { sessionCreated, sessionInvoked } from 'services/thunks/session';
import { sessionCreated } from 'services/thunks/session';
import { buildCanvasGraphComponents } from 'features/nodes/util/graphBuilders/buildCanvasGraph';
import { log } from 'app/logging/useLogger';
import { canvasGraphBuilt } from 'features/nodes/store/actions';
import { imageUploaded } from 'services/thunks/image';
import { imageUpdated, imageUploaded } from 'services/thunks/image';
import { v4 as uuidv4 } from 'uuid';
import { Graph } from 'services/api';
import {
@ -15,12 +15,22 @@ import { getCanvasData } from 'features/canvas/util/getCanvasData';
import { getCanvasGenerationMode } from 'features/canvas/util/getCanvasGenerationMode';
import { blobToDataURL } from 'features/canvas/util/blobToDataURL';
import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
import { sessionReadyToInvoke } from 'features/system/store/actions';
const moduleLog = log.child({ namespace: 'invoke' });
/**
* This listener is responsible for building the canvas graph and blobs when the user invokes the canvas.
* It is also responsible for uploading the base and mask layers to the server.
* This listener is responsible invoking the canvas. This involves a number of steps:
*
* 1. Generate image blobs from the canvas layers
* 2. Determine the generation mode from the layers (txt2img, img2img, inpaint)
* 3. Build the canvas graph
* 4. Create the session with the graph
* 5. Upload the init image if necessary
* 6. Upload the mask image if necessary
* 7. Update the init and mask images with the session ID
* 8. Initialize the staging area if not yet initialized
* 9. Dispatch the sessionReadyToInvoke action to invoke the session
*/
export const addUserInvokedCanvasListener = () => {
startAppListening({
@ -70,63 +80,7 @@ export const addUserInvokedCanvasListener = () => {
const { rangeNode, iterateNode, baseNode, edges } = graphComponents;
// Upload the base layer, to be used as init image
const baseFilename = `${uuidv4()}.png`;
dispatch(
imageUploaded({
imageType: 'intermediates',
formData: {
file: new File([baseBlob], baseFilename, { type: 'image/png' }),
},
})
);
if (baseNode.type === 'img2img' || baseNode.type === 'inpaint') {
const [{ payload: basePayload }] = await take(
(action): action is ReturnType<typeof imageUploaded.fulfilled> =>
imageUploaded.fulfilled.match(action) &&
action.meta.arg.formData.file.name === baseFilename
);
const { image_name: baseName, image_type: baseType } =
basePayload.response;
baseNode.image = {
image_name: baseName,
image_type: baseType,
};
}
// Upload the mask layer image
const maskFilename = `${uuidv4()}.png`;
if (baseNode.type === 'inpaint') {
dispatch(
imageUploaded({
imageType: 'intermediates',
formData: {
file: new File([maskBlob], maskFilename, { type: 'image/png' }),
},
})
);
const [{ payload: maskPayload }] = await take(
(action): action is ReturnType<typeof imageUploaded.fulfilled> =>
imageUploaded.fulfilled.match(action) &&
action.meta.arg.formData.file.name === maskFilename
);
const { image_name: maskName, image_type: maskType } =
maskPayload.response;
baseNode.mask = {
image_name: maskName,
image_type: maskType,
};
}
// Assemble!
// Assemble! Note that this graph *does not have the init or mask image set yet!*
const nodes: Graph['nodes'] = {
[rangeNode.id]: rangeNode,
[iterateNode.id]: iterateNode,
@ -136,15 +90,96 @@ export const addUserInvokedCanvasListener = () => {
const graph = { nodes, edges };
dispatch(canvasGraphBuilt(graph));
moduleLog({ data: graph }, 'Canvas graph built');
// Actually create the session
// If we are generating img2img or inpaint, we need to upload the init images
if (baseNode.type === 'img2img' || baseNode.type === 'inpaint') {
const baseFilename = `${uuidv4()}.png`;
dispatch(
imageUploaded({
formData: {
file: new File([baseBlob], baseFilename, { type: 'image/png' }),
},
isIntermediate: true,
})
);
// Wait for the image to be uploaded
const [{ payload: basePayload }] = await take(
(action): action is ReturnType<typeof imageUploaded.fulfilled> =>
imageUploaded.fulfilled.match(action) &&
action.meta.arg.formData.file.name === baseFilename
);
// Update the base node with the image name and type
const { image_name: baseName, image_type: baseType } =
basePayload.response;
baseNode.image = {
image_name: baseName,
image_type: baseType,
};
}
// For inpaint, we also need to upload the mask layer
if (baseNode.type === 'inpaint') {
const maskFilename = `${uuidv4()}.png`;
dispatch(
imageUploaded({
formData: {
file: new File([maskBlob], maskFilename, { type: 'image/png' }),
},
isIntermediate: true,
})
);
// Wait for the mask to be uploaded
const [{ payload: maskPayload }] = await take(
(action): action is ReturnType<typeof imageUploaded.fulfilled> =>
imageUploaded.fulfilled.match(action) &&
action.meta.arg.formData.file.name === maskFilename
);
// Update the base node with the image name and type
const { image_name: maskName, image_type: maskType } =
maskPayload.response;
baseNode.mask = {
image_name: maskName,
image_type: maskType,
};
}
// Create the session and wait for response
dispatch(sessionCreated({ graph }));
const [sessionCreatedAction] = await take(sessionCreated.fulfilled.match);
const sessionId = sessionCreatedAction.payload.id;
// Wait for the session to be invoked (this is just the HTTP request to start processing)
const [{ meta }] = await take(sessionInvoked.fulfilled.match);
// Associate the init image with the session, now that we have the session ID
if (
(baseNode.type === 'img2img' || baseNode.type === 'inpaint') &&
baseNode.image
) {
dispatch(
imageUpdated({
imageName: baseNode.image.image_name,
imageType: baseNode.image.image_type,
requestBody: { session_id: sessionId },
})
);
}
const { sessionId } = meta.arg;
// Associate the mask image with the session, now that we have the session ID
if (baseNode.type === 'inpaint' && baseNode.mask) {
dispatch(
imageUpdated({
imageName: baseNode.mask.image_name,
imageType: baseNode.mask.image_type,
requestBody: { session_id: sessionId },
})
);
}
if (!state.canvas.layerState.stagingArea.boundingBox) {
dispatch(
@ -158,7 +193,11 @@ export const addUserInvokedCanvasListener = () => {
);
}
// Flag the session with the canvas session ID
dispatch(canvasSessionIdChanged(sessionId));
// We are ready to invoke the session!
dispatch(sessionReadyToInvoke());
},
});
};

View File

@ -4,6 +4,7 @@ import { sessionCreated } from 'services/thunks/session';
import { log } from 'app/logging/useLogger';
import { imageToImageGraphBuilt } from 'features/nodes/store/actions';
import { userInvoked } from 'app/store/actions';
import { sessionReadyToInvoke } from 'features/system/store/actions';
const moduleLog = log.child({ namespace: 'invoke' });
@ -11,7 +12,7 @@ export const addUserInvokedImageToImageListener = () => {
startAppListening({
predicate: (action): action is ReturnType<typeof userInvoked> =>
userInvoked.match(action) && action.payload === 'img2img',
effect: (action, { getState, dispatch }) => {
effect: async (action, { getState, dispatch, take }) => {
const state = getState();
const graph = buildImageToImageGraph(state);
@ -19,6 +20,10 @@ export const addUserInvokedImageToImageListener = () => {
moduleLog({ data: graph }, 'Image to Image graph built');
dispatch(sessionCreated({ graph }));
await take(sessionCreated.fulfilled.match);
dispatch(sessionReadyToInvoke());
},
});
};

View File

@ -4,6 +4,7 @@ import { buildNodesGraph } from 'features/nodes/util/graphBuilders/buildNodesGra
import { log } from 'app/logging/useLogger';
import { nodesGraphBuilt } from 'features/nodes/store/actions';
import { userInvoked } from 'app/store/actions';
import { sessionReadyToInvoke } from 'features/system/store/actions';
const moduleLog = log.child({ namespace: 'invoke' });
@ -11,7 +12,7 @@ export const addUserInvokedNodesListener = () => {
startAppListening({
predicate: (action): action is ReturnType<typeof userInvoked> =>
userInvoked.match(action) && action.payload === 'nodes',
effect: (action, { getState, dispatch }) => {
effect: async (action, { getState, dispatch, take }) => {
const state = getState();
const graph = buildNodesGraph(state);
@ -19,6 +20,10 @@ export const addUserInvokedNodesListener = () => {
moduleLog({ data: graph }, 'Nodes graph built');
dispatch(sessionCreated({ graph }));
await take(sessionCreated.fulfilled.match);
dispatch(sessionReadyToInvoke());
},
});
};

View File

@ -4,6 +4,7 @@ import { sessionCreated } from 'services/thunks/session';
import { log } from 'app/logging/useLogger';
import { textToImageGraphBuilt } from 'features/nodes/store/actions';
import { userInvoked } from 'app/store/actions';
import { sessionReadyToInvoke } from 'features/system/store/actions';
const moduleLog = log.child({ namespace: 'invoke' });
@ -11,14 +12,20 @@ export const addUserInvokedTextToImageListener = () => {
startAppListening({
predicate: (action): action is ReturnType<typeof userInvoked> =>
userInvoked.match(action) && action.payload === 'txt2img',
effect: (action, { getState, dispatch }) => {
effect: async (action, { getState, dispatch, take }) => {
const state = getState();
const graph = buildTextToImageGraph(state);
dispatch(textToImageGraphBuilt(graph));
moduleLog({ data: graph }, 'Text to Image graph built');
dispatch(sessionCreated({ graph }));
await take(sessionCreated.fulfilled.match);
dispatch(sessionReadyToInvoke());
},
});
};

View File

@ -16,6 +16,7 @@ import lightboxReducer from 'features/lightbox/store/lightboxSlice';
import generationReducer from 'features/parameters/store/generationSlice';
import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
import systemReducer from 'features/system/store/systemSlice';
// import sessionReducer from 'features/system/store/sessionSlice';
import configReducer from 'features/system/store/configSlice';
import uiReducer from 'features/ui/store/uiSlice';
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
@ -46,6 +47,7 @@ const allReducers = {
ui: uiReducer,
uploads: uploadsReducer,
hotkeys: hotkeysReducer,
// session: sessionReducer,
};
const rootReducer = combineReducers(allReducers);

View File

@ -13,7 +13,9 @@ import {
buildOutputFieldTemplates,
} from './fieldTemplateBuilders';
const invocationDenylist = ['Graph'];
const RESERVED_FIELD_NAMES = ['id', 'type', 'meta'];
const invocationDenylist = ['Graph', 'InvocationMeta'];
export const parseSchema = (openAPI: OpenAPIV3.Document) => {
// filter out non-invocation schemas, plus some tricky invocations for now
@ -73,7 +75,7 @@ export const parseSchema = (openAPI: OpenAPIV3.Document) => {
(inputsAccumulator, property, propertyName) => {
if (
// `type` and `id` are not valid inputs/outputs
!['type', 'id'].includes(propertyName) &&
!RESERVED_FIELD_NAMES.includes(propertyName) &&
isSchemaObject(property)
) {
const field: InputFieldTemplate | undefined =

View File

@ -0,0 +1,3 @@
import { createAction } from '@reduxjs/toolkit';
export const sessionReadyToInvoke = createAction('system/sessionReadyToInvoke');

View File

@ -0,0 +1,62 @@
// TODO: split system slice inot this
// import type { PayloadAction } from '@reduxjs/toolkit';
// import { createSlice } from '@reduxjs/toolkit';
// import { socketSubscribed, socketUnsubscribed } from 'services/events/actions';
// export type SessionState = {
// /**
// * The current socket session id
// */
// sessionId: string;
// /**
// * Whether the current session is a canvas session. Needed to manage the staging area.
// */
// isCanvasSession: boolean;
// /**
// * When a session is canceled, its ID is stored here until a new session is created.
// */
// canceledSessionId: string;
// };
// export const initialSessionState: SessionState = {
// sessionId: '',
// isCanvasSession: false,
// canceledSessionId: '',
// };
// export const sessionSlice = createSlice({
// name: 'session',
// initialState: initialSessionState,
// reducers: {
// sessionIdChanged: (state, action: PayloadAction<string>) => {
// state.sessionId = action.payload;
// },
// isCanvasSessionChanged: (state, action: PayloadAction<boolean>) => {
// state.isCanvasSession = action.payload;
// },
// },
// extraReducers: (builder) => {
// /**
// * Socket Subscribed
// */
// builder.addCase(socketSubscribed, (state, action) => {
// state.sessionId = action.payload.sessionId;
// state.canceledSessionId = '';
// });
// /**
// * Socket Unsubscribed
// */
// builder.addCase(socketUnsubscribed, (state) => {
// state.sessionId = '';
// });
// },
// });
// export const { sessionIdChanged, isCanvasSessionChanged } =
// sessionSlice.actions;
// export default sessionSlice.reducer;
export default {};

View File

@ -7,7 +7,6 @@ export { OpenAPI } from './core/OpenAPI';
export type { OpenAPIConfig } from './core/OpenAPI';
export type { AddInvocation } from './models/AddInvocation';
export type { BlurInvocation } from './models/BlurInvocation';
export type { Body_upload_image } from './models/Body_upload_image';
export type { CkptModelInfo } from './models/CkptModelInfo';
export type { CollectInvocation } from './models/CollectInvocation';
@ -17,7 +16,6 @@ export type { CompelInvocation } from './models/CompelInvocation';
export type { CompelOutput } from './models/CompelOutput';
export type { ConditioningField } from './models/ConditioningField';
export type { CreateModelRequest } from './models/CreateModelRequest';
export type { CropImageInvocation } from './models/CropImageInvocation';
export type { CvInpaintInvocation } from './models/CvInpaintInvocation';
export type { DiffusersModelInfo } from './models/DiffusersModelInfo';
export type { DivideInvocation } from './models/DivideInvocation';
@ -28,11 +26,20 @@ export type { GraphExecutionState } from './models/GraphExecutionState';
export type { GraphInvocation } from './models/GraphInvocation';
export type { GraphInvocationOutput } from './models/GraphInvocationOutput';
export type { HTTPValidationError } from './models/HTTPValidationError';
export type { ImageBlurInvocation } from './models/ImageBlurInvocation';
export type { ImageCategory } from './models/ImageCategory';
export type { ImageChannelInvocation } from './models/ImageChannelInvocation';
export type { ImageConvertInvocation } from './models/ImageConvertInvocation';
export type { ImageCropInvocation } from './models/ImageCropInvocation';
export type { ImageDTO } from './models/ImageDTO';
export type { ImageField } from './models/ImageField';
export type { ImageInverseLerpInvocation } from './models/ImageInverseLerpInvocation';
export type { ImageLerpInvocation } from './models/ImageLerpInvocation';
export type { ImageMetadata } from './models/ImageMetadata';
export type { ImageMultiplyInvocation } from './models/ImageMultiplyInvocation';
export type { ImageOutput } from './models/ImageOutput';
export type { ImagePasteInvocation } from './models/ImagePasteInvocation';
export type { ImageRecordChanges } from './models/ImageRecordChanges';
export type { ImageToImageInvocation } from './models/ImageToImageInvocation';
export type { ImageToLatentsInvocation } from './models/ImageToLatentsInvocation';
export type { ImageType } from './models/ImageType';
@ -43,14 +50,12 @@ export type { InfillTileInvocation } from './models/InfillTileInvocation';
export type { InpaintInvocation } from './models/InpaintInvocation';
export type { IntCollectionOutput } from './models/IntCollectionOutput';
export type { IntOutput } from './models/IntOutput';
export type { InverseLerpInvocation } from './models/InverseLerpInvocation';
export type { IterateInvocation } from './models/IterateInvocation';
export type { IterateInvocationOutput } from './models/IterateInvocationOutput';
export type { LatentsField } from './models/LatentsField';
export type { LatentsOutput } from './models/LatentsOutput';
export type { LatentsToImageInvocation } from './models/LatentsToImageInvocation';
export type { LatentsToLatentsInvocation } from './models/LatentsToLatentsInvocation';
export type { LerpInvocation } from './models/LerpInvocation';
export type { LoadImageInvocation } from './models/LoadImageInvocation';
export type { MaskFromAlphaInvocation } from './models/MaskFromAlphaInvocation';
export type { MaskOutput } from './models/MaskOutput';
@ -61,7 +66,6 @@ export type { NoiseOutput } from './models/NoiseOutput';
export type { PaginatedResults_GraphExecutionState_ } from './models/PaginatedResults_GraphExecutionState_';
export type { PaginatedResults_ImageDTO_ } from './models/PaginatedResults_ImageDTO_';
export type { ParamIntInvocation } from './models/ParamIntInvocation';
export type { PasteImageInvocation } from './models/PasteImageInvocation';
export type { PromptOutput } from './models/PromptOutput';
export type { RandomIntInvocation } from './models/RandomIntInvocation';
export type { RandomRangeInvocation } from './models/RandomRangeInvocation';

View File

@ -10,6 +10,10 @@ export type AddInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'add';
/**
* The first number

View File

@ -10,6 +10,10 @@ export type CollectInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'collect';
/**
* The item to collect (all inputs must be of the same type)

View File

@ -10,6 +10,10 @@ export type CompelInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'compel';
/**
* Prompt

View File

@ -12,6 +12,10 @@ export type CvInpaintInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'cv_inpaint';
/**
* The image to inpaint

View File

@ -10,6 +10,10 @@ export type DivideInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'div';
/**
* The first number

View File

@ -3,31 +3,34 @@
/* eslint-disable */
import type { AddInvocation } from './AddInvocation';
import type { BlurInvocation } from './BlurInvocation';
import type { CollectInvocation } from './CollectInvocation';
import type { CompelInvocation } from './CompelInvocation';
import type { CropImageInvocation } from './CropImageInvocation';
import type { CvInpaintInvocation } from './CvInpaintInvocation';
import type { DivideInvocation } from './DivideInvocation';
import type { Edge } from './Edge';
import type { GraphInvocation } from './GraphInvocation';
import type { ImageBlurInvocation } from './ImageBlurInvocation';
import type { ImageChannelInvocation } from './ImageChannelInvocation';
import type { ImageConvertInvocation } from './ImageConvertInvocation';
import type { ImageCropInvocation } from './ImageCropInvocation';
import type { ImageInverseLerpInvocation } from './ImageInverseLerpInvocation';
import type { ImageLerpInvocation } from './ImageLerpInvocation';
import type { ImageMultiplyInvocation } from './ImageMultiplyInvocation';
import type { ImagePasteInvocation } from './ImagePasteInvocation';
import type { ImageToImageInvocation } from './ImageToImageInvocation';
import type { ImageToLatentsInvocation } from './ImageToLatentsInvocation';
import type { InfillColorInvocation } from './InfillColorInvocation';
import type { InfillPatchMatchInvocation } from './InfillPatchMatchInvocation';
import type { InfillTileInvocation } from './InfillTileInvocation';
import type { InpaintInvocation } from './InpaintInvocation';
import type { InverseLerpInvocation } from './InverseLerpInvocation';
import type { IterateInvocation } from './IterateInvocation';
import type { LatentsToImageInvocation } from './LatentsToImageInvocation';
import type { LatentsToLatentsInvocation } from './LatentsToLatentsInvocation';
import type { LerpInvocation } from './LerpInvocation';
import type { LoadImageInvocation } from './LoadImageInvocation';
import type { MaskFromAlphaInvocation } from './MaskFromAlphaInvocation';
import type { MultiplyInvocation } from './MultiplyInvocation';
import type { NoiseInvocation } from './NoiseInvocation';
import type { ParamIntInvocation } from './ParamIntInvocation';
import type { PasteImageInvocation } from './PasteImageInvocation';
import type { RandomIntInvocation } from './RandomIntInvocation';
import type { RandomRangeInvocation } from './RandomRangeInvocation';
import type { RangeInvocation } from './RangeInvocation';
@ -49,7 +52,7 @@ export type Graph = {
/**
* The nodes in this graph
*/
nodes?: Record<string, (LoadImageInvocation | ShowImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation)>;
nodes?: Record<string, (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation)>;
/**
* The connections between nodes and their fields in this graph
*/

View File

@ -5,14 +5,17 @@
import type { Graph } from './Graph';
/**
* A node to process inputs and produce outputs.
* May use dependency injection in __init__ to receive providers.
* Execute a graph
*/
export type GraphInvocation = {
/**
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'graph';
/**
* The graph to run

View File

@ -7,12 +7,16 @@ import type { ImageField } from './ImageField';
/**
* Blurs an image
*/
export type BlurInvocation = {
export type ImageBlurInvocation = {
/**
* The id of this node. Must be unique among all nodes.
*/
id: string;
type?: 'blur';
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'img_blur';
/**
* The image to blur
*/

View File

@ -5,4 +5,4 @@
/**
* The category of an image. Use ImageCategory.OTHER for non-default categories.
*/
export type ImageCategory = 'general' | 'control' | 'other';
export type ImageCategory = 'general' | 'control' | 'mask' | 'other';

View File

@ -0,0 +1,29 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ImageField } from './ImageField';
/**
* Gets a channel from an image.
*/
export type ImageChannelInvocation = {
/**
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'img_chan';
/**
* The image to get the channel from
*/
image?: ImageField;
/**
* The channel to get
*/
channel?: 'A' | 'R' | 'G' | 'B';
};

View File

@ -0,0 +1,29 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ImageField } from './ImageField';
/**
* Converts an image to a different mode.
*/
export type ImageConvertInvocation = {
/**
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'img_conv';
/**
* The image to convert
*/
image?: ImageField;
/**
* The mode to convert to
*/
mode?: 'L' | 'RGB' | 'RGBA' | 'CMYK' | 'YCbCr' | 'LAB' | 'HSV' | 'I' | 'F';
};

View File

@ -7,12 +7,16 @@ import type { ImageField } from './ImageField';
/**
* Crops an image to a specified box. The box can be outside of the image.
*/
export type CropImageInvocation = {
export type ImageCropInvocation = {
/**
* The id of this node. Must be unique among all nodes.
*/
id: string;
type?: 'crop';
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'img_crop';
/**
* The image to crop
*/

View File

@ -50,6 +50,10 @@ export type ImageDTO = {
* The deleted timestamp of the image.
*/
deleted_at?: string;
/**
* Whether this is an intermediate image.
*/
is_intermediate: boolean;
/**
* The session ID that generated this image, if it is a generated image.
*/

View File

@ -7,12 +7,16 @@ import type { ImageField } from './ImageField';
/**
* Inverse linear interpolation of all pixels of an image
*/
export type InverseLerpInvocation = {
export type ImageInverseLerpInvocation = {
/**
* The id of this node. Must be unique among all nodes.
*/
id: string;
type?: 'ilerp';
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'img_ilerp';
/**
* The image to lerp
*/

View File

@ -7,12 +7,16 @@ import type { ImageField } from './ImageField';
/**
* Linear interpolation of all pixels of an image
*/
export type LerpInvocation = {
export type ImageLerpInvocation = {
/**
* The id of this node. Must be unique among all nodes.
*/
id: string;
type?: 'lerp';
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'img_lerp';
/**
* The image to lerp
*/

View File

@ -0,0 +1,29 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ImageField } from './ImageField';
/**
* Multiplies two images together using `PIL.ImageChops.multiply()`.
*/
export type ImageMultiplyInvocation = {
/**
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'img_mul';
/**
* The first image to multiply
*/
image1?: ImageField;
/**
* The second image to multiply
*/
image2?: ImageField;
};

View File

@ -7,12 +7,16 @@ import type { ImageField } from './ImageField';
/**
* Pastes an image into another image.
*/
export type PasteImageInvocation = {
export type ImagePasteInvocation = {
/**
* The id of this node. Must be unique among all nodes.
*/
id: string;
type?: 'paste';
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'img_paste';
/**
* The base image
*/

View File

@ -0,0 +1,24 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ImageCategory } from './ImageCategory';
/**
* A set of changes to apply to an image record.
*
* Only limited changes are valid:
* - `image_category`: change the category of an image
* - `session_id`: change the session associated with an image
*/
export type ImageRecordChanges = {
/**
* The image's new category.
*/
image_category?: ImageCategory;
/**
* The image's new session ID.
*/
session_id?: string;
};

View File

@ -12,6 +12,10 @@ export type ImageToImageInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'img2img';
/**
* The prompt to generate an image from

View File

@ -12,6 +12,10 @@ export type ImageToLatentsInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'i2l';
/**
* The image to encode

View File

@ -5,4 +5,4 @@
/**
* The type of an image.
*/
export type ImageType = 'results' | 'uploads' | 'intermediates';
export type ImageType = 'results' | 'uploads';

View File

@ -13,6 +13,10 @@ export type InfillColorInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'infill_rgba';
/**
* The image to infill

View File

@ -12,6 +12,10 @@ export type InfillPatchMatchInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'infill_patchmatch';
/**
* The image to infill

View File

@ -12,6 +12,10 @@ export type InfillTileInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'infill_tile';
/**
* The image to infill

View File

@ -13,6 +13,10 @@ export type InpaintInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'inpaint';
/**
* The prompt to generate an image from

View File

@ -3,14 +3,17 @@
/* eslint-disable */
/**
* A node to process inputs and produce outputs.
* May use dependency injection in __init__ to receive providers.
* Iterates over a list of items
*/
export type IterateInvocation = {
/**
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'iterate';
/**
* The list of items to iterate over

View File

@ -12,6 +12,10 @@ export type LatentsToImageInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'l2i';
/**
* The latents to generate an image from

View File

@ -13,6 +13,10 @@ export type LatentsToLatentsInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'l2l';
/**
* Positive conditioning for generation
@ -42,14 +46,6 @@ export type LatentsToLatentsInvocation = {
* The model to use (currently ignored)
*/
model?: string;
/**
* Whether or not to generate an image that can tile without seams
*/
seamless?: boolean;
/**
* The axes to tile the image on, 'x' and/or 'y'
*/
seamless_axes?: string;
/**
* The latents to use as a base image
*/

View File

@ -2,7 +2,7 @@
/* tslint:disable */
/* eslint-disable */
import type { ImageType } from './ImageType';
import type { ImageField } from './ImageField';
/**
* Load an image and provide it as output.
@ -12,14 +12,14 @@ export type LoadImageInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'load_image';
/**
* The type of the image
* The image to load
*/
image_type: ImageType;
/**
* The name of the image
*/
image_name: string;
image?: ImageField;
};

View File

@ -12,6 +12,10 @@ export type MaskFromAlphaInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'tomask';
/**
* The image to create the mask from

View File

@ -10,6 +10,10 @@ export type MultiplyInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'mul';
/**
* The first number

View File

@ -10,6 +10,10 @@ export type NoiseInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'noise';
/**
* The seed to use

View File

@ -10,6 +10,10 @@ export type ParamIntInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'param_int';
/**
* The integer value

View File

@ -10,6 +10,10 @@ export type RandomIntInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'rand_int';
/**
* The inclusive low value

View File

@ -10,6 +10,10 @@ export type RandomRangeInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'random_range';
/**
* The inclusive low value

View File

@ -10,6 +10,10 @@ export type RangeInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'range';
/**
* The start of the range

View File

@ -10,6 +10,10 @@ export type RangeOfSizeInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'range_of_size';
/**
* The start of the range

View File

@ -12,6 +12,10 @@ export type ResizeLatentsInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'lresize';
/**
* The latents to resize

View File

@ -12,6 +12,10 @@ export type RestoreFaceInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'restore_face';
/**
* The input image

View File

@ -12,6 +12,10 @@ export type ScaleLatentsInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'lscale';
/**
* The latents to scale

View File

@ -12,6 +12,10 @@ export type ShowImageInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'show_image';
/**
* The image to show

View File

@ -10,6 +10,10 @@ export type SubtractInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'sub';
/**
* The first number

View File

@ -10,6 +10,10 @@ export type TextToImageInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'txt2img';
/**
* The prompt to generate an image from

View File

@ -13,6 +13,10 @@ export type TextToLatentsInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 't2l';
/**
* Positive conditioning for generation
@ -42,13 +46,5 @@ export type TextToLatentsInvocation = {
* The model to use (currently ignored)
*/
model?: string;
/**
* Whether or not to generate an image that can tile without seams
*/
seamless?: boolean;
/**
* The axes to tile the image on, 'x' and/or 'y'
*/
seamless_axes?: string;
};

View File

@ -12,6 +12,10 @@ export type UpscaleInvocation = {
* The id of this node. Must be unique among all nodes.
*/
id: string;
/**
* Whether or not this node is an intermediate node.
*/
is_intermediate?: boolean;
type?: 'upscale';
/**
* The input image

View File

@ -4,6 +4,7 @@
import type { Body_upload_image } from '../models/Body_upload_image';
import type { ImageCategory } from '../models/ImageCategory';
import type { ImageDTO } from '../models/ImageDTO';
import type { ImageRecordChanges } from '../models/ImageRecordChanges';
import type { ImageType } from '../models/ImageType';
import type { ImageUrlsDTO } from '../models/ImageUrlsDTO';
import type { PaginatedResults_ImageDTO_ } from '../models/PaginatedResults_ImageDTO_';
@ -65,20 +66,32 @@ export class ImagesService {
* @throws ApiError
*/
public static uploadImage({
imageType,
formData,
imageCategory,
isIntermediate = false,
sessionId,
}: {
imageType: ImageType,
formData: Body_upload_image,
/**
* The category of the image
*/
imageCategory?: ImageCategory,
/**
* Whether this is an intermediate image
*/
isIntermediate?: boolean,
/**
* The session ID associated with this upload, if any
*/
sessionId?: string,
}): CancelablePromise<ImageDTO> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/images/',
query: {
'image_type': imageType,
'image_category': imageCategory,
'is_intermediate': isIntermediate,
'session_id': sessionId,
},
formData: formData,
mediaType: 'multipart/form-data',
@ -132,6 +145,9 @@ export class ImagesService {
imageType,
imageName,
}: {
/**
* The type of image to delete
*/
imageType: ImageType,
/**
* The name of the image to delete
@ -151,6 +167,42 @@ export class ImagesService {
});
}
/**
* Update Image
* Updates an image
* @returns ImageDTO Successful Response
* @throws ApiError
*/
public static updateImage({
imageType,
imageName,
requestBody,
}: {
/**
* The type of image to update
*/
imageType: ImageType,
/**
* The name of the image to update
*/
imageName: string,
requestBody: ImageRecordChanges,
}): CancelablePromise<ImageDTO> {
return __request(OpenAPI, {
method: 'PATCH',
url: '/api/v1/images/{image_type}/{image_name}',
path: {
'image_type': imageType,
'image_name': imageName,
},
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Get Image Metadata
* Gets an image's metadata

View File

@ -2,34 +2,37 @@
/* tslint:disable */
/* eslint-disable */
import type { AddInvocation } from '../models/AddInvocation';
import type { BlurInvocation } from '../models/BlurInvocation';
import type { CollectInvocation } from '../models/CollectInvocation';
import type { CompelInvocation } from '../models/CompelInvocation';
import type { CropImageInvocation } from '../models/CropImageInvocation';
import type { CvInpaintInvocation } from '../models/CvInpaintInvocation';
import type { DivideInvocation } from '../models/DivideInvocation';
import type { Edge } from '../models/Edge';
import type { Graph } from '../models/Graph';
import type { GraphExecutionState } from '../models/GraphExecutionState';
import type { GraphInvocation } from '../models/GraphInvocation';
import type { ImageBlurInvocation } from '../models/ImageBlurInvocation';
import type { ImageChannelInvocation } from '../models/ImageChannelInvocation';
import type { ImageConvertInvocation } from '../models/ImageConvertInvocation';
import type { ImageCropInvocation } from '../models/ImageCropInvocation';
import type { ImageInverseLerpInvocation } from '../models/ImageInverseLerpInvocation';
import type { ImageLerpInvocation } from '../models/ImageLerpInvocation';
import type { ImageMultiplyInvocation } from '../models/ImageMultiplyInvocation';
import type { ImagePasteInvocation } from '../models/ImagePasteInvocation';
import type { ImageToImageInvocation } from '../models/ImageToImageInvocation';
import type { ImageToLatentsInvocation } from '../models/ImageToLatentsInvocation';
import type { InfillColorInvocation } from '../models/InfillColorInvocation';
import type { InfillPatchMatchInvocation } from '../models/InfillPatchMatchInvocation';
import type { InfillTileInvocation } from '../models/InfillTileInvocation';
import type { InpaintInvocation } from '../models/InpaintInvocation';
import type { InverseLerpInvocation } from '../models/InverseLerpInvocation';
import type { IterateInvocation } from '../models/IterateInvocation';
import type { LatentsToImageInvocation } from '../models/LatentsToImageInvocation';
import type { LatentsToLatentsInvocation } from '../models/LatentsToLatentsInvocation';
import type { LerpInvocation } from '../models/LerpInvocation';
import type { LoadImageInvocation } from '../models/LoadImageInvocation';
import type { MaskFromAlphaInvocation } from '../models/MaskFromAlphaInvocation';
import type { MultiplyInvocation } from '../models/MultiplyInvocation';
import type { NoiseInvocation } from '../models/NoiseInvocation';
import type { PaginatedResults_GraphExecutionState_ } from '../models/PaginatedResults_GraphExecutionState_';
import type { ParamIntInvocation } from '../models/ParamIntInvocation';
import type { PasteImageInvocation } from '../models/PasteImageInvocation';
import type { RandomIntInvocation } from '../models/RandomIntInvocation';
import type { RandomRangeInvocation } from '../models/RandomRangeInvocation';
import type { RangeInvocation } from '../models/RangeInvocation';
@ -151,7 +154,7 @@ export class SessionsService {
* The id of the session
*/
sessionId: string,
requestBody: (LoadImageInvocation | ShowImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation),
requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation),
}): CancelablePromise<string> {
return __request(OpenAPI, {
method: 'POST',
@ -188,7 +191,7 @@ export class SessionsService {
* The path to the node in the graph
*/
nodePath: string,
requestBody: (LoadImageInvocation | ShowImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation),
requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation),
}): CancelablePromise<GraphExecutionState> {
return __request(OpenAPI, {
method: 'PUT',

View File

@ -8,7 +8,11 @@ import {
import { socketSubscribed, socketUnsubscribed } from './actions';
import { AppThunkDispatch, RootState } from 'app/store/store';
import { getTimestamp } from 'common/util/getTimestamp';
import { sessionInvoked, sessionCreated } from 'services/thunks/session';
import {
sessionInvoked,
sessionCreated,
sessionWithoutGraphCreated,
} from 'services/thunks/session';
import { OpenAPI } from 'services/api';
import { setEventListeners } from 'services/events/util/setEventListeners';
import { log } from 'app/logging/useLogger';
@ -62,17 +66,14 @@ export const socketMiddleware = () => {
socket.connect();
}
if (sessionCreated.fulfilled.match(action)) {
if (
sessionCreated.fulfilled.match(action) ||
sessionWithoutGraphCreated.fulfilled.match(action)
) {
const sessionId = action.payload.id;
const sessionLog = socketioLog.child({ sessionId });
const oldSessionId = getState().system.sessionId;
if (oldSessionId) {
sessionLog.debug(
{ oldSessionId },
`Unsubscribed from old session (${oldSessionId})`
);
socket.emit('unsubscribe', {
session: oldSessionId,
});
@ -85,8 +86,6 @@ export const socketMiddleware = () => {
);
}
sessionLog.debug(`Subscribe to new session (${sessionId})`);
socket.emit('subscribe', { session: sessionId });
dispatch(
@ -95,9 +94,6 @@ export const socketMiddleware = () => {
timestamp: getTimestamp(),
})
);
// Finally we actually invoke the session, starting processing
dispatch(sessionInvoked({ sessionId }));
}
next(action);

View File

@ -1,7 +1,6 @@
import { MiddlewareAPI } from '@reduxjs/toolkit';
import { AppDispatch, RootState } from 'app/store/store';
import { getTimestamp } from 'common/util/getTimestamp';
import { sessionCanceled } from 'services/thunks/session';
import { Socket } from 'socket.io-client';
import {
generatorProgress,
@ -16,12 +15,6 @@ import {
import { ClientToServerEvents, ServerToClientEvents } from '../types';
import { Logger } from 'roarr';
import { JsonObject } from 'roarr/dist/types';
import {
receivedResultImagesPage,
receivedUploadImagesPage,
} from 'services/thunks/gallery';
import { receivedModels } from 'services/thunks/model';
import { receivedOpenAPISchema } from 'services/thunks/schema';
import { makeToast } from '../../../app/components/Toaster';
import { addToast } from '../../../features/system/store/systemSlice';
@ -43,37 +36,13 @@ export const setEventListeners = (arg: SetEventListenersArg) => {
dispatch(socketConnected({ timestamp: getTimestamp() }));
const { results, uploads, models, nodes, config, system } = getState();
const { sessionId } = getState().system;
const { disabledTabs } = config;
// These thunks need to be dispatch in middleware; cannot handle in a reducer
if (!results.ids.length) {
dispatch(receivedResultImagesPage());
}
if (!uploads.ids.length) {
dispatch(receivedUploadImagesPage());
}
if (!models.ids.length) {
dispatch(receivedModels());
}
if (!nodes.schema && !disabledTabs.includes('nodes')) {
dispatch(receivedOpenAPISchema());
}
if (system.sessionId) {
log.debug(
{ sessionId: system.sessionId },
`Subscribed to existing session (${system.sessionId})`
);
socket.emit('subscribe', { session: system.sessionId });
if (sessionId) {
socket.emit('subscribe', { session: sessionId });
dispatch(
socketSubscribed({
sessionId: system.sessionId,
sessionId,
timestamp: getTimestamp(),
})
);
@ -101,7 +70,6 @@ export const setEventListeners = (arg: SetEventListenersArg) => {
* Disconnect
*/
socket.on('disconnect', () => {
log.debug('Disconnected');
dispatch(socketDisconnected({ timestamp: getTimestamp() }));
});
@ -109,18 +77,6 @@ export const setEventListeners = (arg: SetEventListenersArg) => {
* Invocation started
*/
socket.on('invocation_started', (data) => {
if (getState().system.canceledSession === data.graph_execution_state_id) {
log.trace(
{ data, sessionId: data.graph_execution_state_id },
`Ignored invocation started (${data.node.type}) for canceled session (${data.graph_execution_state_id})`
);
return;
}
log.info(
{ data, sessionId: data.graph_execution_state_id },
`Invocation started (${data.node.type})`
);
dispatch(invocationStarted({ data, timestamp: getTimestamp() }));
});
@ -128,18 +84,6 @@ export const setEventListeners = (arg: SetEventListenersArg) => {
* Generator progress
*/
socket.on('generator_progress', (data) => {
if (getState().system.canceledSession === data.graph_execution_state_id) {
log.trace(
{ data, sessionId: data.graph_execution_state_id },
`Ignored generator progress (${data.node.type}) for canceled session (${data.graph_execution_state_id})`
);
return;
}
log.trace(
{ data, sessionId: data.graph_execution_state_id },
`Generator progress (${data.node.type})`
);
dispatch(generatorProgress({ data, timestamp: getTimestamp() }));
});
@ -147,10 +91,6 @@ export const setEventListeners = (arg: SetEventListenersArg) => {
* Invocation error
*/
socket.on('invocation_error', (data) => {
log.error(
{ data, sessionId: data.graph_execution_state_id },
`Invocation error (${data.node.type})`
);
dispatch(invocationError({ data, timestamp: getTimestamp() }));
});
@ -158,19 +98,6 @@ export const setEventListeners = (arg: SetEventListenersArg) => {
* Invocation complete
*/
socket.on('invocation_complete', (data) => {
log.info(
{ data, sessionId: data.graph_execution_state_id },
`Invocation complete (${data.node.type})`
);
const sessionId = data.graph_execution_state_id;
const { cancelType, isCancelScheduled } = getState().system;
// Handle scheduled cancelation
if (cancelType === 'scheduled' && isCancelScheduled) {
dispatch(sessionCanceled({ sessionId }));
}
dispatch(
invocationComplete({
data,
@ -183,10 +110,6 @@ export const setEventListeners = (arg: SetEventListenersArg) => {
* Graph complete
*/
socket.on('graph_execution_state_complete', (data) => {
log.info(
{ data, sessionId: data.graph_execution_state_id },
`Graph execution state complete (${data.graph_execution_state_id})`
);
dispatch(graphExecutionStateComplete({ data, timestamp: getTimestamp() }));
});
};

View File

@ -12,7 +12,7 @@ export const receivedResultImagesPage = createAppAsyncThunk(
const { page, pages, nextPage } = getState().results;
if (nextPage === page) {
rejectWithValue([]);
return rejectWithValue([]);
}
const response = await ImagesService.listImagesWithMetadata({
@ -30,7 +30,13 @@ export const receivedResultImagesPage = createAppAsyncThunk(
export const receivedUploadImagesPage = createAppAsyncThunk(
'uploads/receivedUploadImagesPage',
async (_arg, { getState }) => {
async (_arg, { getState, rejectWithValue }) => {
const { page, pages, nextPage } = getState().uploads;
if (nextPage === page) {
return rejectWithValue([]);
}
const response = await ImagesService.listImagesWithMetadata({
imageType: 'uploads',
imageCategory: 'general',

View File

@ -76,3 +76,19 @@ export const imageDeleted = createAppAsyncThunk(
return response;
}
);
type ImageUpdatedArg = Parameters<(typeof ImagesService)['updateImage']>[0];
/**
* `ImagesService.deleteImage()` thunk
*/
export const imageUpdated = createAppAsyncThunk(
'api/imageUpdated',
async (arg: ImageUpdatedArg) => {
const response = await ImagesService.updateImage(arg);
imagesLog.debug({ arg, response }, 'Image updated');
return response;
}
);

View File

@ -35,6 +35,28 @@ export const sessionCreated = createAppAsyncThunk(
}
);
/**
* `SessionsService.createSession()` without graph thunk
*/
export const sessionWithoutGraphCreated = createAppAsyncThunk(
'api/sessionWithoutGraphCreated',
async (_, { rejectWithValue }) => {
try {
const response = await SessionsService.createSession({});
sessionLog.info({ response }, `Session created (${response.id})`);
return response;
} catch (err: any) {
sessionLog.error(
{
error: serializeError(err),
},
'Problem creating session'
);
return rejectWithValue(err.message);
}
}
);
type NodeAddedArg = Parameters<(typeof SessionsService)['addNode']>[0];
/**
@ -57,6 +79,29 @@ export const nodeAdded = createAppAsyncThunk(
}
);
type NodeUpdatedArg = Parameters<(typeof SessionsService)['updateNode']>[0];
/**
* `SessionsService.addNode()` thunk
*/
export const nodeUpdated = createAppAsyncThunk(
'api/nodeUpdated',
async (
arg: { node: NodeUpdatedArg['requestBody']; sessionId: string },
_thunkApi
) => {
const response = await SessionsService.updateNode({
requestBody: arg.node,
sessionId: arg.sessionId,
nodePath: arg.node.id,
});
sessionLog.info({ arg, response }, `Node updated (${response})`);
return response;
}
);
/**
* `SessionsService.invokeSession()` thunk
*/