From e0b9b5cc6c3af933beab6cfb0bbacc5fc269a4fd Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 2 May 2023 20:09:56 +1000 Subject: [PATCH 01/66] feat(nodes): add dataURL to image node --- invokeai/app/invocations/image.py | 51 +++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/invokeai/app/invocations/image.py b/invokeai/app/invocations/image.py index 883ef63f69..7c1465f4e3 100644 --- a/invokeai/app/invocations/image.py +++ b/invokeai/app/invocations/image.py @@ -1,10 +1,12 @@ # Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) +import io from typing import Literal, Optional import numpy from PIL import Image, ImageFilter, ImageOps -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, validator +from w3lib.url import parse_data_uri from ..models.image import ImageField, ImageType from .baseinvocation import ( @@ -37,9 +39,7 @@ class ImageOutput(BaseInvocationOutput): # fmt: on class Config: - schema_extra = { - "required": ["type", "image", "width", "height", "mode"] - } + schema_extra = {"required": ["type", "image", "width", "height", "mode"]} def build_image_output( @@ -119,6 +119,45 @@ class ShowImageInvocation(BaseInvocation): ) +class DataURLToImageInvocation(BaseInvocation, PILInvocationConfig): + """Outputs an image from a data URL.""" + + type: Literal["dataURL_image"] = "dataURL_image" + + # Inputs + dataURL: str = Field(description="The b64 data URL") + + @validator("dataURL") + def must_be_valid_image_dataURL(cls, v): + try: + result = parse_data_uri(v) + img = Image.open(io.BytesIO(result.data)) + img.verify() + except Exception: + raise ValueError("Invalid image dataURL") + return v + + def invoke(self, context: InvocationContext) -> ImageOutput: + # TODO: Figure out how to use pydantic validator to also transform into a different type, + # bc this is just the same logic as we use to validate the dataURL. + result = parse_data_uri(self.dataURL) + image = Image.open(io.BytesIO(result.data)) + + image_name = context.services.images.create_name( + context.graph_execution_state_id, self.id + ) + + metadata = context.services.metadata.build_metadata( + session_id=context.graph_execution_state_id, node=self + ) + + context.services.images.save(ImageType.RESULT, image_name, image, metadata) + + return build_image_output( + image_type=ImageType.RESULT, image_name=image_name, image=image + ) + + class CropImageInvocation(BaseInvocation, PILInvocationConfig): """Crops an image to a specified box. The box can be outside of the image.""" @@ -151,7 +190,7 @@ class CropImageInvocation(BaseInvocation, PILInvocationConfig): metadata = context.services.metadata.build_metadata( session_id=context.graph_execution_state_id, node=self ) - + context.services.images.save(image_type, image_name, image_crop, metadata) return build_image_output( image_type=image_type, @@ -209,7 +248,7 @@ class PasteImageInvocation(BaseInvocation, PILInvocationConfig): metadata = context.services.metadata.build_metadata( session_id=context.graph_execution_state_id, node=self ) - + context.services.images.save(image_type, image_name, new_image, metadata) return build_image_output( image_type=image_type, From ff5e2a9a8c0fe6faf3b8d7e5a176f7311c7b0eee Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 2 May 2023 20:10:18 +1000 Subject: [PATCH 02/66] chore(ui): regen api client --- .../frontend/web/src/services/api/index.ts | 2 ++ .../api/models/DataURLToImageInvocation.ts | 19 +++++++++++++++++ .../web/src/services/api/models/Graph.ts | 3 ++- .../api/schemas/$DataURLToImageInvocation.ts | 21 +++++++++++++++++++ .../web/src/services/api/schemas/$Graph.ts | 2 ++ .../services/api/services/SessionsService.ts | 5 +++-- 6 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 invokeai/frontend/web/src/services/api/models/DataURLToImageInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/schemas/$DataURLToImageInvocation.ts diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index 2a34837715..f452de3ce9 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -15,6 +15,7 @@ export type { CollectInvocationOutput } from './models/CollectInvocationOutput'; export type { CreateModelRequest } from './models/CreateModelRequest'; export type { CropImageInvocation } from './models/CropImageInvocation'; export type { CvInpaintInvocation } from './models/CvInpaintInvocation'; +export type { DataURLToImageInvocation } from './models/DataURLToImageInvocation'; export type { DiffusersModelInfo } from './models/DiffusersModelInfo'; export type { DivideInvocation } from './models/DivideInvocation'; export type { Edge } from './models/Edge'; @@ -78,6 +79,7 @@ export { $CollectInvocationOutput } from './schemas/$CollectInvocationOutput'; export { $CreateModelRequest } from './schemas/$CreateModelRequest'; export { $CropImageInvocation } from './schemas/$CropImageInvocation'; export { $CvInpaintInvocation } from './schemas/$CvInpaintInvocation'; +export { $DataURLToImageInvocation } from './schemas/$DataURLToImageInvocation'; export { $DiffusersModelInfo } from './schemas/$DiffusersModelInfo'; export { $DivideInvocation } from './schemas/$DivideInvocation'; export { $Edge } from './schemas/$Edge'; diff --git a/invokeai/frontend/web/src/services/api/models/DataURLToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/DataURLToImageInvocation.ts new file mode 100644 index 0000000000..86b49e6cab --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/DataURLToImageInvocation.ts @@ -0,0 +1,19 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Outputs an image from a base 64 data URL. + */ +export type DataURLToImageInvocation = { + /** + * The id of this node. Must be unique among all nodes. + */ + id: string; + type?: 'dataURL_image'; + /** + * The b64 data URL + */ + dataURL: string; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index 57a9178290..f252f8d2f1 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -7,6 +7,7 @@ import type { BlurInvocation } from './BlurInvocation'; import type { CollectInvocation } from './CollectInvocation'; import type { CropImageInvocation } from './CropImageInvocation'; import type { CvInpaintInvocation } from './CvInpaintInvocation'; +import type { DataURLToImageInvocation } from './DataURLToImageInvocation'; import type { DivideInvocation } from './DivideInvocation'; import type { Edge } from './Edge'; import type { GraphInvocation } from './GraphInvocation'; @@ -42,7 +43,7 @@ export type Graph = { /** * The nodes in this graph */ - nodes?: Record; + nodes?: Record; /** * The connections between nodes and their fields in this graph */ diff --git a/invokeai/frontend/web/src/services/api/schemas/$DataURLToImageInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$DataURLToImageInvocation.ts new file mode 100644 index 0000000000..34a9fa23c0 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/schemas/$DataURLToImageInvocation.ts @@ -0,0 +1,21 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $DataURLToImageInvocation = { + description: `Outputs an image from a base 64 data URL.`, + properties: { + id: { + type: 'string', + description: `The id of this node. Must be unique among all nodes.`, + isRequired: true, + }, + type: { + type: 'Enum', + }, + dataURL: { + type: 'string', + description: `The b64 data URL`, + isRequired: true, + }, + }, +} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$Graph.ts b/invokeai/frontend/web/src/services/api/schemas/$Graph.ts index 6fd8117db8..a41ffd8cc5 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$Graph.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$Graph.ts @@ -15,6 +15,8 @@ export const $Graph = { type: 'LoadImageInvocation', }, { type: 'ShowImageInvocation', + }, { + type: 'DataURLToImageInvocation', }, { type: 'CropImageInvocation', }, { diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index dad455fc80..f0eb38662d 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -6,6 +6,7 @@ import type { BlurInvocation } from '../models/BlurInvocation'; import type { CollectInvocation } from '../models/CollectInvocation'; import type { CropImageInvocation } from '../models/CropImageInvocation'; import type { CvInpaintInvocation } from '../models/CvInpaintInvocation'; +import type { DataURLToImageInvocation } from '../models/DataURLToImageInvocation'; import type { DivideInvocation } from '../models/DivideInvocation'; import type { Edge } from '../models/Edge'; import type { Graph } from '../models/Graph'; @@ -144,7 +145,7 @@ export class SessionsService { * The id of the session */ sessionId: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | ParamIntInvocation | CvInpaintInvocation | RangeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | DataURLToImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | ParamIntInvocation | CvInpaintInvocation | RangeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -181,7 +182,7 @@ export class SessionsService { * The path to the node in the graph */ nodePath: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | ParamIntInvocation | CvInpaintInvocation | RangeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | DataURLToImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | ParamIntInvocation | CvInpaintInvocation | RangeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'PUT', From 08ec12b3919bc0c768c702a8f3fefc6dcc135d5a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 2 May 2023 20:11:12 +1000 Subject: [PATCH 03/66] feat(ui): wip canvas nodes migration --- .../frontend/web/src/app/socketio/emitters.ts | 380 +++++++++--------- .../web/src/common/util/arrayBuffer.ts | 59 +++ .../src/common/util/parameterTranslation.ts | 16 +- .../canvas/hooks/usePrepareCanvasState.ts | 39 ++ .../src/features/canvas/store/canvasSlice.ts | 26 +- .../src/features/canvas/util/generateMask.ts | 16 +- .../features/canvas/util/getCanvasDataURLs.ts | 123 ++++++ .../gallery/components/HoverableImage.tsx | 9 +- .../gallery/store/galleryPersistDenylist.ts | 4 - .../gallery/store/resultsPersistDenylist.ts | 2 +- .../gallery/store/uploadsPersistDenylist.ts | 2 +- .../src/features/nodes/util/getNodeType.ts | 19 + .../buildImageToImageNode.ts | 21 +- .../linearGraphBuilder/buildLinearGraph.ts | 90 ++++- .../buildTextToImageNode.ts | 8 +- .../Canvas/InfillAndScalingSettings.tsx | 8 +- .../ProcessButtons/InvokeButton.tsx | 12 +- .../src/features/system/store/systemSlice.ts | 10 + .../web/src/services/thunks/session.ts | 49 ++- 19 files changed, 652 insertions(+), 241 deletions(-) create mode 100644 invokeai/frontend/web/src/common/util/arrayBuffer.ts create mode 100644 invokeai/frontend/web/src/features/canvas/hooks/usePrepareCanvasState.ts create mode 100644 invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts create mode 100644 invokeai/frontend/web/src/features/nodes/util/getNodeType.ts diff --git a/invokeai/frontend/web/src/app/socketio/emitters.ts b/invokeai/frontend/web/src/app/socketio/emitters.ts index ad7979503f..8ed46cbc82 100644 --- a/invokeai/frontend/web/src/app/socketio/emitters.ts +++ b/invokeai/frontend/web/src/app/socketio/emitters.ts @@ -1,209 +1,209 @@ -// import { AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit'; -// import * as InvokeAI from 'app/types/invokeai'; -// import type { RootState } from 'app/store/store'; -// import { -// frontendToBackendParameters, -// FrontendToBackendParametersConfig, -// } from 'common/util/parameterTranslation'; -// import dateFormat from 'dateformat'; -// import { -// GalleryCategory, -// GalleryState, -// removeImage, -// } from 'features/gallery/store/gallerySlice'; -// import { -// generationRequested, -// modelChangeRequested, -// modelConvertRequested, -// modelMergingRequested, -// setIsProcessing, -// } from 'features/system/store/systemSlice'; -// import { InvokeTabName } from 'features/ui/store/tabMap'; -// import { Socket } from 'socket.io-client'; +import { AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit'; +import * as InvokeAI from 'app/types/invokeai'; +import type { RootState } from 'app/store/store'; +import { + frontendToBackendParameters, + FrontendToBackendParametersConfig, +} from 'common/util/parameterTranslation'; +import dateFormat from 'dateformat'; +import { + GalleryCategory, + GalleryState, + removeImage, +} from 'features/gallery/store/gallerySlice'; +import { + generationRequested, + modelChangeRequested, + modelConvertRequested, + modelMergingRequested, + setIsProcessing, +} from 'features/system/store/systemSlice'; +import { InvokeTabName } from 'features/ui/store/tabMap'; +import { Socket } from 'socket.io-client'; -// /** -// * Returns an object containing all functions which use `socketio.emit()`. -// * i.e. those which make server requests. -// */ -// const makeSocketIOEmitters = ( -// store: MiddlewareAPI, RootState>, -// socketio: Socket -// ) => { -// // We need to dispatch actions to redux and get pieces of state from the store. -// const { dispatch, getState } = store; +/** + * Returns an object containing all functions which use `socketio.emit()`. + * i.e. those which make server requests. + */ +const makeSocketIOEmitters = ( + store: MiddlewareAPI, RootState>, + socketio: Socket +) => { + // We need to dispatch actions to redux and get pieces of state from the store. + const { dispatch, getState } = store; -// return { -// emitGenerateImage: (generationMode: InvokeTabName) => { -// dispatch(setIsProcessing(true)); + return { + emitGenerateImage: (generationMode: InvokeTabName) => { + dispatch(setIsProcessing(true)); -// const state: RootState = getState(); + const state: RootState = getState(); -// const { -// generation: generationState, -// postprocessing: postprocessingState, -// system: systemState, -// canvas: canvasState, -// } = state; + const { + generation: generationState, + postprocessing: postprocessingState, + system: systemState, + canvas: canvasState, + } = state; -// const frontendToBackendParametersConfig: FrontendToBackendParametersConfig = -// { -// generationMode, -// generationState, -// postprocessingState, -// canvasState, -// systemState, -// }; + const frontendToBackendParametersConfig: FrontendToBackendParametersConfig = + { + generationMode, + generationState, + postprocessingState, + canvasState, + systemState, + }; -// dispatch(generationRequested()); + dispatch(generationRequested()); -// const { generationParameters, esrganParameters, facetoolParameters } = -// frontendToBackendParameters(frontendToBackendParametersConfig); + const { generationParameters, esrganParameters, facetoolParameters } = + frontendToBackendParameters(frontendToBackendParametersConfig); -// socketio.emit( -// 'generateImage', -// generationParameters, -// esrganParameters, -// facetoolParameters -// ); + socketio.emit( + 'generateImage', + generationParameters, + esrganParameters, + facetoolParameters + ); -// // we need to truncate the init_mask base64 else it takes up the whole log -// // TODO: handle maintaining masks for reproducibility in future -// if (generationParameters.init_mask) { -// generationParameters.init_mask = generationParameters.init_mask -// .substr(0, 64) -// .concat('...'); -// } -// if (generationParameters.init_img) { -// generationParameters.init_img = generationParameters.init_img -// .substr(0, 64) -// .concat('...'); -// } + // we need to truncate the init_mask base64 else it takes up the whole log + // TODO: handle maintaining masks for reproducibility in future + if (generationParameters.init_mask) { + generationParameters.init_mask = generationParameters.init_mask + .substr(0, 64) + .concat('...'); + } + if (generationParameters.init_img) { + generationParameters.init_img = generationParameters.init_img + .substr(0, 64) + .concat('...'); + } -// dispatch( -// addLogEntry({ -// timestamp: dateFormat(new Date(), 'isoDateTime'), -// message: `Image generation requested: ${JSON.stringify({ -// ...generationParameters, -// ...esrganParameters, -// ...facetoolParameters, -// })}`, -// }) -// ); -// }, -// emitRunESRGAN: (imageToProcess: InvokeAI._Image) => { -// dispatch(setIsProcessing(true)); + dispatch( + addLogEntry({ + timestamp: dateFormat(new Date(), 'isoDateTime'), + message: `Image generation requested: ${JSON.stringify({ + ...generationParameters, + ...esrganParameters, + ...facetoolParameters, + })}`, + }) + ); + }, + emitRunESRGAN: (imageToProcess: InvokeAI._Image) => { + dispatch(setIsProcessing(true)); -// const { -// postprocessing: { -// upscalingLevel, -// upscalingDenoising, -// upscalingStrength, -// }, -// } = getState(); + const { + postprocessing: { + upscalingLevel, + upscalingDenoising, + upscalingStrength, + }, + } = getState(); -// const esrganParameters = { -// upscale: [upscalingLevel, upscalingDenoising, upscalingStrength], -// }; -// socketio.emit('runPostprocessing', imageToProcess, { -// type: 'esrgan', -// ...esrganParameters, -// }); -// dispatch( -// addLogEntry({ -// timestamp: dateFormat(new Date(), 'isoDateTime'), -// message: `ESRGAN upscale requested: ${JSON.stringify({ -// file: imageToProcess.url, -// ...esrganParameters, -// })}`, -// }) -// ); -// }, -// emitRunFacetool: (imageToProcess: InvokeAI._Image) => { -// dispatch(setIsProcessing(true)); + const esrganParameters = { + upscale: [upscalingLevel, upscalingDenoising, upscalingStrength], + }; + socketio.emit('runPostprocessing', imageToProcess, { + type: 'esrgan', + ...esrganParameters, + }); + dispatch( + addLogEntry({ + timestamp: dateFormat(new Date(), 'isoDateTime'), + message: `ESRGAN upscale requested: ${JSON.stringify({ + file: imageToProcess.url, + ...esrganParameters, + })}`, + }) + ); + }, + emitRunFacetool: (imageToProcess: InvokeAI._Image) => { + dispatch(setIsProcessing(true)); -// const { -// postprocessing: { facetoolType, facetoolStrength, codeformerFidelity }, -// } = getState(); + const { + postprocessing: { facetoolType, facetoolStrength, codeformerFidelity }, + } = getState(); -// const facetoolParameters: Record = { -// facetool_strength: facetoolStrength, -// }; + const facetoolParameters: Record = { + facetool_strength: facetoolStrength, + }; -// if (facetoolType === 'codeformer') { -// facetoolParameters.codeformer_fidelity = codeformerFidelity; -// } + if (facetoolType === 'codeformer') { + facetoolParameters.codeformer_fidelity = codeformerFidelity; + } -// socketio.emit('runPostprocessing', imageToProcess, { -// type: facetoolType, -// ...facetoolParameters, -// }); -// dispatch( -// addLogEntry({ -// timestamp: dateFormat(new Date(), 'isoDateTime'), -// message: `Face restoration (${facetoolType}) requested: ${JSON.stringify( -// { -// file: imageToProcess.url, -// ...facetoolParameters, -// } -// )}`, -// }) -// ); -// }, -// emitDeleteImage: (imageToDelete: InvokeAI._Image) => { -// const { url, uuid, category, thumbnail } = imageToDelete; -// dispatch(removeImage(imageToDelete)); -// socketio.emit('deleteImage', url, thumbnail, uuid, category); -// }, -// emitRequestImages: (category: GalleryCategory) => { -// const gallery: GalleryState = getState().gallery; -// const { earliest_mtime } = gallery.categories[category]; -// socketio.emit('requestImages', category, earliest_mtime); -// }, -// emitRequestNewImages: (category: GalleryCategory) => { -// const gallery: GalleryState = getState().gallery; -// const { latest_mtime } = gallery.categories[category]; -// socketio.emit('requestLatestImages', category, latest_mtime); -// }, -// emitCancelProcessing: () => { -// socketio.emit('cancel'); -// }, -// emitRequestSystemConfig: () => { -// socketio.emit('requestSystemConfig'); -// }, -// emitSearchForModels: (modelFolder: string) => { -// socketio.emit('searchForModels', modelFolder); -// }, -// emitAddNewModel: (modelConfig: InvokeAI.InvokeModelConfigProps) => { -// socketio.emit('addNewModel', modelConfig); -// }, -// emitDeleteModel: (modelName: string) => { -// socketio.emit('deleteModel', modelName); -// }, -// emitConvertToDiffusers: ( -// modelToConvert: InvokeAI.InvokeModelConversionProps -// ) => { -// dispatch(modelConvertRequested()); -// socketio.emit('convertToDiffusers', modelToConvert); -// }, -// emitMergeDiffusersModels: ( -// modelMergeInfo: InvokeAI.InvokeModelMergingProps -// ) => { -// dispatch(modelMergingRequested()); -// socketio.emit('mergeDiffusersModels', modelMergeInfo); -// }, -// emitRequestModelChange: (modelName: string) => { -// dispatch(modelChangeRequested()); -// socketio.emit('requestModelChange', modelName); -// }, -// emitSaveStagingAreaImageToGallery: (url: string) => { -// socketio.emit('requestSaveStagingAreaImageToGallery', url); -// }, -// emitRequestEmptyTempFolder: () => { -// socketio.emit('requestEmptyTempFolder'); -// }, -// }; -// }; + socketio.emit('runPostprocessing', imageToProcess, { + type: facetoolType, + ...facetoolParameters, + }); + dispatch( + addLogEntry({ + timestamp: dateFormat(new Date(), 'isoDateTime'), + message: `Face restoration (${facetoolType}) requested: ${JSON.stringify( + { + file: imageToProcess.url, + ...facetoolParameters, + } + )}`, + }) + ); + }, + emitDeleteImage: (imageToDelete: InvokeAI._Image) => { + const { url, uuid, category, thumbnail } = imageToDelete; + dispatch(removeImage(imageToDelete)); + socketio.emit('deleteImage', url, thumbnail, uuid, category); + }, + emitRequestImages: (category: GalleryCategory) => { + const gallery: GalleryState = getState().gallery; + const { earliest_mtime } = gallery.categories[category]; + socketio.emit('requestImages', category, earliest_mtime); + }, + emitRequestNewImages: (category: GalleryCategory) => { + const gallery: GalleryState = getState().gallery; + const { latest_mtime } = gallery.categories[category]; + socketio.emit('requestLatestImages', category, latest_mtime); + }, + emitCancelProcessing: () => { + socketio.emit('cancel'); + }, + emitRequestSystemConfig: () => { + socketio.emit('requestSystemConfig'); + }, + emitSearchForModels: (modelFolder: string) => { + socketio.emit('searchForModels', modelFolder); + }, + emitAddNewModel: (modelConfig: InvokeAI.InvokeModelConfigProps) => { + socketio.emit('addNewModel', modelConfig); + }, + emitDeleteModel: (modelName: string) => { + socketio.emit('deleteModel', modelName); + }, + emitConvertToDiffusers: ( + modelToConvert: InvokeAI.InvokeModelConversionProps + ) => { + dispatch(modelConvertRequested()); + socketio.emit('convertToDiffusers', modelToConvert); + }, + emitMergeDiffusersModels: ( + modelMergeInfo: InvokeAI.InvokeModelMergingProps + ) => { + dispatch(modelMergingRequested()); + socketio.emit('mergeDiffusersModels', modelMergeInfo); + }, + emitRequestModelChange: (modelName: string) => { + dispatch(modelChangeRequested()); + socketio.emit('requestModelChange', modelName); + }, + emitSaveStagingAreaImageToGallery: (url: string) => { + socketio.emit('requestSaveStagingAreaImageToGallery', url); + }, + emitRequestEmptyTempFolder: () => { + socketio.emit('requestEmptyTempFolder'); + }, + }; +}; -// export default makeSocketIOEmitters; +export default makeSocketIOEmitters; export default {}; diff --git a/invokeai/frontend/web/src/common/util/arrayBuffer.ts b/invokeai/frontend/web/src/common/util/arrayBuffer.ts new file mode 100644 index 0000000000..da0fe38d35 --- /dev/null +++ b/invokeai/frontend/web/src/common/util/arrayBuffer.ts @@ -0,0 +1,59 @@ +export const getIsImageDataPartiallyTransparent = (imageData: ImageData) => { + let hasTransparency = false; + let isFullyTransparent = true; + const len = imageData.data.length; + let i = 3; + for (i; i < len; i += 4) { + if (imageData.data[i] !== 0) { + isFullyTransparent = false; + } else { + hasTransparency = true; + } + } + return { hasTransparency, isFullyTransparent }; +}; + +export const getImageDataTransparency = (imageData: ImageData) => { + let isFullyTransparent = true; + let isPartiallyTransparent = false; + const len = imageData.data.length; + let i = 3; + for (i; i < len; i += 4) { + if (imageData.data[i] === 255) { + isFullyTransparent = false; + } else { + isPartiallyTransparent = true; + } + if (!isFullyTransparent && isPartiallyTransparent) { + return { isFullyTransparent, isPartiallyTransparent }; + } + } + return { isFullyTransparent, isPartiallyTransparent }; +}; + +export const areAnyPixelsBlack = (imageData: ImageData) => { + const len = imageData.data.length; + let i = 0; + for (i; i < len; ) { + if ( + imageData.data[i++] === 255 && + imageData.data[i++] === 255 && + imageData.data[i++] === 255 && + imageData.data[i++] === 255 + ) { + return true; + } + } + return false; +}; + +export const getIsImageDataWhite = (imageData: ImageData) => { + const len = imageData.data.length; + let i = 0; + for (i; i < len; ) { + if (imageData.data[i++] !== 255) { + return false; + } + } + return true; +}; diff --git a/invokeai/frontend/web/src/common/util/parameterTranslation.ts b/invokeai/frontend/web/src/common/util/parameterTranslation.ts index 07b8ac8ea1..de25ae7b71 100644 --- a/invokeai/frontend/web/src/common/util/parameterTranslation.ts +++ b/invokeai/frontend/web/src/common/util/parameterTranslation.ts @@ -19,6 +19,7 @@ import { InvokeTabName } from 'features/ui/store/tabMap'; import openBase64ImageInTab from './openBase64ImageInTab'; import randomInt from './randomInt'; import { stringToSeedWeightsArray } from './seedWeightPairs'; +import { getIsImageDataTransparent, getIsImageDataWhite } from './arrayBuffer'; export type FrontendToBackendParametersConfig = { generationMode: InvokeTabName; @@ -256,7 +257,7 @@ export const frontendToBackendParameters = ( ...boundingBoxDimensions, }; - const maskDataURL = generateMask( + const { dataURL: maskDataURL, imageData: maskImageData } = generateMask( isMaskEnabled ? objects.filter(isCanvasMaskLine) : [], boundingBox ); @@ -287,6 +288,19 @@ export const frontendToBackendParameters = ( height: boundingBox.height, }); + const ctx = canvasBaseLayer.getContext(); + const imageData = ctx.getImageData( + boundingBox.x + absPos.x, + boundingBox.y + absPos.y, + boundingBox.width, + boundingBox.height + ); + + const doesBaseHaveTransparency = getIsImageDataTransparent(imageData); + const doesMaskHaveTransparency = getIsImageDataWhite(maskImageData); + + console.log(doesBaseHaveTransparency, doesMaskHaveTransparency); + if (enableImageDebugging) { openBase64ImageInTab([ { base64: maskDataURL, caption: 'mask sent as init_mask' }, diff --git a/invokeai/frontend/web/src/features/canvas/hooks/usePrepareCanvasState.ts b/invokeai/frontend/web/src/features/canvas/hooks/usePrepareCanvasState.ts new file mode 100644 index 0000000000..061979376b --- /dev/null +++ b/invokeai/frontend/web/src/features/canvas/hooks/usePrepareCanvasState.ts @@ -0,0 +1,39 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useAppSelector } from 'app/store/storeHooks'; +import { + FrontendToBackendParametersConfig, + frontendToBackendParameters, +} from 'common/util/parameterTranslation'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors'; +import { systemSelector } from 'features/system/store/systemSelectors'; +import { canvasSelector } from '../store/canvasSelectors'; +import { useCallback, useMemo } from 'react'; + +const selector = createSelector( + [generationSelector, postprocessingSelector, systemSelector, canvasSelector], + (generation, postprocessing, system, canvas) => { + const frontendToBackendParametersConfig: FrontendToBackendParametersConfig = + { + generationMode: 'unifiedCanvas', + generationState: generation, + postprocessingState: postprocessing, + canvasState: canvas, + systemState: system, + }; + + return frontendToBackendParametersConfig; + } +); + +export const usePrepareCanvasState = () => { + const frontendToBackendParametersConfig = useAppSelector(selector); + + const getGenerationParameters = useCallback(() => { + const { generationParameters, esrganParameters, facetoolParameters } = + frontendToBackendParameters(frontendToBackendParametersConfig); + console.log(generationParameters); + }, [frontendToBackendParametersConfig]); + + return getGenerationParameters; +}; diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index ab3ab0c4e9..5ff8ea4295 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -156,22 +156,20 @@ export const canvasSlice = createSlice({ setCursorPosition: (state, action: PayloadAction) => { state.cursorPosition = action.payload; }, - setInitialCanvasImage: (state, action: PayloadAction) => { + setInitialCanvasImage: (state, action: PayloadAction) => { const image = action.payload; + const { width, height } = image.metadata; const { stageDimensions } = state; const newBoundingBoxDimensions = { - width: roundDownToMultiple(clamp(image.width, 64, 512), 64), - height: roundDownToMultiple(clamp(image.height, 64, 512), 64), + width: roundDownToMultiple(clamp(width, 64, 512), 64), + height: roundDownToMultiple(clamp(height, 64, 512), 64), }; const newBoundingBoxCoordinates = { - x: roundToMultiple( - image.width / 2 - newBoundingBoxDimensions.width / 2, - 64 - ), + x: roundToMultiple(width / 2 - newBoundingBoxDimensions.width / 2, 64), y: roundToMultiple( - image.height / 2 - newBoundingBoxDimensions.height / 2, + height / 2 - newBoundingBoxDimensions.height / 2, 64 ), }; @@ -196,8 +194,8 @@ export const canvasSlice = createSlice({ layer: 'base', x: 0, y: 0, - width: image.width, - height: image.height, + width: width, + height: height, image: image, }, ], @@ -208,8 +206,8 @@ export const canvasSlice = createSlice({ const newScale = calculateScale( stageDimensions.width, stageDimensions.height, - image.width, - image.height, + width, + height, STAGE_PADDING_PERCENTAGE ); @@ -218,8 +216,8 @@ export const canvasSlice = createSlice({ stageDimensions.height, 0, 0, - image.width, - image.height, + width, + height, newScale ); state.stageScale = newScale; diff --git a/invokeai/frontend/web/src/features/canvas/util/generateMask.ts b/invokeai/frontend/web/src/features/canvas/util/generateMask.ts index 9187ac2ac7..32e0fecfd0 100644 --- a/invokeai/frontend/web/src/features/canvas/util/generateMask.ts +++ b/invokeai/frontend/web/src/features/canvas/util/generateMask.ts @@ -12,7 +12,10 @@ import { IRect } from 'konva/lib/types'; * drawing the mask and compositing everything correctly to output a valid * mask image. */ -const generateMask = (lines: CanvasMaskLine[], boundingBox: IRect): string => { +const generateMask = ( + lines: CanvasMaskLine[], + boundingBox: IRect +): { dataURL: string; imageData: ImageData } => { // create an offscreen canvas and add the mask to it const { width, height } = boundingBox; @@ -55,10 +58,19 @@ const generateMask = (lines: CanvasMaskLine[], boundingBox: IRect): string => { stage.add(maskLayer); const dataURL = stage.toDataURL({ ...boundingBox }); + const imageData = stage + .toCanvas() + .getContext('2d') + ?.getImageData( + boundingBox.x, + boundingBox.y, + boundingBox.width, + boundingBox.height + ); offscreenContainer.remove(); - return dataURL; + return { dataURL, imageData }; }; export default generateMask; diff --git a/invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts b/invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts new file mode 100644 index 0000000000..fdc03bc48a --- /dev/null +++ b/invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts @@ -0,0 +1,123 @@ +import { RootState } from 'app/store/store'; +import { getCanvasBaseLayer, getCanvasStage } from './konvaInstanceProvider'; +import { isCanvasMaskLine } from '../store/canvasTypes'; +import generateMask from './generateMask'; +import { log } from 'app/logging/useLogger'; +import { + areAnyPixelsBlack, + getImageDataTransparency, + getIsImageDataWhite, +} from 'common/util/arrayBuffer'; +import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; + +export const getCanvasDataURLs = (state: RootState) => { + const canvasBaseLayer = getCanvasBaseLayer(); + const canvasStage = getCanvasStage(); + + if (!canvasBaseLayer || !canvasStage) { + log.error( + { namespace: 'getCanvasDataURLs' }, + 'Unable to find canvas / stage' + ); + return; + } + + const { + layerState: { objects }, + boundingBoxCoordinates, + boundingBoxDimensions, + stageScale, + isMaskEnabled, + shouldPreserveMaskedArea, + boundingBoxScaleMethod: boundingBoxScale, + scaledBoundingBoxDimensions, + } = state.canvas; + + const boundingBox = { + ...boundingBoxCoordinates, + ...boundingBoxDimensions, + }; + + // generationParameters.fit = false; + + // generationParameters.strength = img2imgStrength; + + // generationParameters.invert_mask = shouldPreserveMaskedArea; + + // generationParameters.bounding_box = boundingBox; + + const tempScale = canvasBaseLayer.scale(); + + canvasBaseLayer.scale({ + x: 1 / stageScale, + y: 1 / stageScale, + }); + + const absPos = canvasBaseLayer.getAbsolutePosition(); + + const { dataURL: maskDataURL, imageData: maskImageData } = generateMask( + isMaskEnabled ? objects.filter(isCanvasMaskLine) : [], + { + x: boundingBox.x + absPos.x, + y: boundingBox.y + absPos.y, + width: boundingBox.width, + height: boundingBox.height, + } + ); + + const baseDataURL = canvasBaseLayer.toDataURL({ + x: boundingBox.x + absPos.x, + y: boundingBox.y + absPos.y, + width: boundingBox.width, + height: boundingBox.height, + }); + + const ctx = canvasBaseLayer.getContext(); + + const baseImageData = ctx.getImageData( + boundingBox.x + absPos.x, + boundingBox.y + absPos.y, + boundingBox.width, + boundingBox.height + ); + + const { + isPartiallyTransparent: baseIsPartiallyTransparent, + isFullyTransparent: baseIsFullyTransparent, + } = getImageDataTransparency(baseImageData); + + const doesMaskHaveBlackPixels = areAnyPixelsBlack(maskImageData); + + if (state.system.enableImageDebugging) { + openBase64ImageInTab([ + { base64: maskDataURL, caption: 'mask sent as init_mask' }, + { base64: baseDataURL, caption: 'image sent as init_img' }, + ]); + } + + canvasBaseLayer.scale(tempScale); + + // generationParameters.init_img = imageDataURL; + // generationParameters.progress_images = false; + + // if (boundingBoxScale !== 'none') { + // generationParameters.inpaint_width = scaledBoundingBoxDimensions.width; + // generationParameters.inpaint_height = scaledBoundingBoxDimensions.height; + // } + + // generationParameters.seam_size = seamSize; + // generationParameters.seam_blur = seamBlur; + // generationParameters.seam_strength = seamStrength; + // generationParameters.seam_steps = seamSteps; + // generationParameters.tile_size = tileSize; + // generationParameters.infill_method = infillMethod; + // generationParameters.force_outpaint = false; + + return { + baseDataURL, + maskDataURL, + baseIsPartiallyTransparent, + baseIsFullyTransparent, + doesMaskHaveBlackPixels, + }; +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index 032784fbf9..ba100ecacc 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -17,7 +17,10 @@ import { FaCheck, FaExpand, FaImage, FaShare, FaTrash } from 'react-icons/fa'; import DeleteImageModal from './DeleteImageModal'; import { ContextMenu } from 'chakra-ui-contextmenu'; import * as InvokeAI from 'app/types/invokeai'; -import { resizeAndScaleCanvas } from 'features/canvas/store/canvasSlice'; +import { + resizeAndScaleCanvas, + setInitialCanvasImage, +} from 'features/canvas/store/canvasSlice'; import { gallerySelector } from 'features/gallery/store/gallerySelectors'; import { setActiveTab } from 'features/ui/store/uiSlice'; import { useTranslation } from 'react-i18next'; @@ -159,7 +162,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { * TODO: the rest of these */ const handleSendToCanvas = () => { - // dispatch(setInitialCanvasImage(image)); + dispatch(setInitialCanvasImage(image)); dispatch(resizeAndScaleCanvas()); @@ -315,6 +318,8 @@ const HoverableImage = memo((props: HoverableImageProps) => { sx={{ width: '50%', height: '50%', + maxWidth: '4rem', + maxHeight: '4rem', fill: 'ok.500', }} /> diff --git a/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts b/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts index 243fe26dd4..8c91a79dfb 100644 --- a/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts @@ -4,12 +4,8 @@ import { GalleryState } from './gallerySlice'; * Gallery slice persist denylist */ const itemsToDenylist: (keyof GalleryState)[] = [ - 'categories', 'currentCategory', - 'currentImage', - 'currentImageUuid', 'shouldAutoSwitchToNewImages', - 'intermediateImage', ]; export const galleryDenylist = itemsToDenylist.map( diff --git a/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts b/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts index b62a199b33..ef21f4b7b2 100644 --- a/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts @@ -5,7 +5,7 @@ import { ResultsState } from './resultsSlice'; * * Currently denylisting results slice entirely, see persist config in store.ts */ -const itemsToDenylist: (keyof ResultsState)[] = ['isLoading']; +const itemsToDenylist: (keyof ResultsState)[] = []; export const resultsDenylist = itemsToDenylist.map( (denylistItem) => `results.${denylistItem}` diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts index 6e2ac1c3aa..ec4248e99c 100644 --- a/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts @@ -5,7 +5,7 @@ import { UploadsState } from './uploadsSlice'; * * Currently denylisting uploads slice entirely, see persist config in store.ts */ -const itemsToDenylist: (keyof UploadsState)[] = ['isLoading']; +const itemsToDenylist: (keyof UploadsState)[] = []; export const uploadsDenylist = itemsToDenylist.map( (denylistItem) => `uploads.${denylistItem}` diff --git a/invokeai/frontend/web/src/features/nodes/util/getNodeType.ts b/invokeai/frontend/web/src/features/nodes/util/getNodeType.ts new file mode 100644 index 0000000000..d26bff393f --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/getNodeType.ts @@ -0,0 +1,19 @@ +export const getNodeType = ( + baseIsPartiallyTransparent: boolean, + baseIsFullyTransparent: boolean, + doesMaskHaveBlackPixels: boolean +): 'txt2img' | `img2img` | 'inpaint' | 'outpaint' => { + if (baseIsPartiallyTransparent) { + if (baseIsFullyTransparent) { + return 'txt2img'; + } + + return 'outpaint'; + } else { + if (doesMaskHaveBlackPixels) { + return 'inpaint'; + } + + return 'img2img'; + } +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildImageToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildImageToImageNode.ts index f9213dfeae..53736cbe42 100644 --- a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildImageToImageNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildImageToImageNode.ts @@ -5,10 +5,13 @@ import { ImageToImageInvocation, TextToImageInvocation, } from 'services/api'; -import { _Image } from 'app/types/invokeai'; import { initialImageSelector } from 'features/parameters/store/generationSelectors'; +import { O } from 'ts-toolbelt'; -export const buildImg2ImgNode = (state: RootState): ImageToImageInvocation => { +export const buildImg2ImgNode = ( + state: RootState, + overrides: O.Partial = {} +): ImageToImageInvocation => { const nodeId = uuidv4(); const { generation, system, models } = state; @@ -33,7 +36,7 @@ export const buildImg2ImgNode = (state: RootState): ImageToImageInvocation => { if (!initialImage) { // TODO: handle this - throw 'no initial image'; + // throw 'no initial image'; } const imageToImageNode: ImageToImageInvocation = { @@ -48,10 +51,12 @@ export const buildImg2ImgNode = (state: RootState): ImageToImageInvocation => { seamless, model: selectedModelName, progress_images: true, - image: { - image_name: initialImage.name, - image_type: initialImage.type, - }, + image: initialImage + ? { + image_name: initialImage.name, + image_type: initialImage.type, + } + : undefined, strength, fit, }; @@ -60,6 +65,8 @@ export const buildImg2ImgNode = (state: RootState): ImageToImageInvocation => { imageToImageNode.seed = seed; } + Object.assign(imageToImageNode, overrides); + return imageToImageNode; }; diff --git a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildLinearGraph.ts index 3e638c8239..e0dd2f4843 100644 --- a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildLinearGraph.ts @@ -1,10 +1,15 @@ import { RootState } from 'app/store/store'; -import { Graph } from 'services/api'; +import { DataURLToImageInvocation, Graph } from 'services/api'; import { buildImg2ImgNode } from './buildImageToImageNode'; import { buildTxt2ImgNode } from './buildTextToImageNode'; import { buildRangeNode } from './buildRangeNode'; import { buildIterateNode } from './buildIterateNode'; import { buildEdges } from './buildEdges'; +import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; +import { getCanvasDataURLs } from 'features/canvas/util/getCanvasDataURLs'; +import { log } from 'console'; +import { getNodeType } from '../getNodeType'; +import { v4 as uuidv4 } from 'uuid'; /** * Builds the Linear workflow graph. @@ -37,3 +42,86 @@ export const buildLinearGraph = (state: RootState): Graph => { return graph; }; + +/** + * Builds the Linear workflow graph. + */ +export const buildCanvasGraph = (state: RootState): Graph => { + const c = getCanvasDataURLs(state); + + if (!c) { + throw 'problm creating canvas graph'; + } + + const { + baseDataURL, + maskDataURL, + baseIsPartiallyTransparent, + baseIsFullyTransparent, + doesMaskHaveBlackPixels, + } = c; + + console.log({ + baseDataURL, + maskDataURL, + baseIsPartiallyTransparent, + baseIsFullyTransparent, + doesMaskHaveBlackPixels, + }); + + const nodeType = getNodeType( + baseIsPartiallyTransparent, + baseIsFullyTransparent, + doesMaskHaveBlackPixels + ); + + console.log(nodeType); + + // The base node is either a txt2img or img2img node + const baseNode = + nodeType === 'img2img' + ? buildImg2ImgNode(state, state.canvas.boundingBoxDimensions) + : buildTxt2ImgNode(state, state.canvas.boundingBoxDimensions); + + const dataURLNode: DataURLToImageInvocation = { + id: uuidv4(), + type: 'dataURL_image', + dataURL: baseDataURL, + }; + + // We always range and iterate nodes, no matter the iteration count + // This is required to provide the correct seeds to the backend engine + const rangeNode = buildRangeNode(state); + const iterateNode = buildIterateNode(); + + // Build the edges for the nodes selected. + const edges = buildEdges(baseNode, rangeNode, iterateNode); + + if (baseNode.type === 'img2img') { + edges.push({ + source: { + node_id: dataURLNode.id, + field: 'image', + }, + destination: { + node_id: baseNode.id, + field: 'image', + }, + }); + } + + // Assemble! + const graph = { + nodes: { + [dataURLNode.id]: dataURLNode, + [rangeNode.id]: rangeNode, + [iterateNode.id]: iterateNode, + [baseNode.id]: baseNode, + }, + edges, + }; + + // TODO: hires fix requires latent space upscaling; we don't have nodes for this yet + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildTextToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildTextToImageNode.ts index 08952bcfb1..42c0c12c1a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildTextToImageNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildTextToImageNode.ts @@ -1,8 +1,12 @@ import { v4 as uuidv4 } from 'uuid'; import { RootState } from 'app/store/store'; import { TextToImageInvocation } from 'services/api'; +import { O } from 'ts-toolbelt'; -export const buildTxt2ImgNode = (state: RootState): TextToImageInvocation => { +export const buildTxt2ImgNode = ( + state: RootState, + overrides: O.Partial = {} +): TextToImageInvocation => { const nodeId = uuidv4(); const { generation, models } = state; @@ -39,5 +43,7 @@ export const buildTxt2ImgNode = (state: RootState): TextToImageInvocation => { textToImageNode.seed = seed; } + Object.assign(textToImageNode, overrides); + return textToImageNode; }; diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/InfillAndScalingSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/InfillAndScalingSettings.tsx index c18934e22b..d578361624 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/InfillAndScalingSettings.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/InfillAndScalingSettings.tsx @@ -28,7 +28,7 @@ const selector = createSelector( (parameters, system, canvas) => { const { tileSize, infillMethod } = parameters; - const { infill_methods: availableInfillMethods } = system; + const { infillMethods } = system; const { boundingBoxScaleMethod: boundingBoxScale, @@ -40,7 +40,7 @@ const selector = createSelector( scaledBoundingBoxDimensions, tileSize, infillMethod, - availableInfillMethods, + infillMethods, isManual: boundingBoxScale === 'manual', }; }, @@ -56,7 +56,7 @@ const InfillAndScalingSettings = () => { const { tileSize, infillMethod, - availableInfillMethods, + infillMethods, boundingBoxScale, isManual, scaledBoundingBoxDimensions, @@ -147,7 +147,7 @@ const InfillAndScalingSettings = () => { dispatch(setInfillMethod(e.target.value))} /> { @@ -23,11 +24,16 @@ export default function InvokeButton(props: InvokeButton) { const dispatch = useAppDispatch(); const { isReady } = useAppSelector(readinessSelector); const activeTabName = useAppSelector(activeTabNameSelector); + // const getGenerationParameters = usePrepareCanvasState(); const handleInvoke = useCallback(() => { dispatch(clampSymmetrySteps()); - dispatch(generateGraphBuilt()); - }, [dispatch]); + if (activeTabName === 'unifiedCanvas') { + dispatch(canvasGraphBuilt()); + } else { + dispatch(generateGraphBuilt()); + } + }, [dispatch, activeTabName]); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index a8c8da9bfb..7d8d4978a0 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -27,6 +27,8 @@ import { t } from 'i18next'; export type CancelStrategy = 'immediate' | 'scheduled'; +export type InfillMethod = 'tile' | 'patchmatch'; + export interface SystemState { isGFPGANAvailable: boolean; isESRGANAvailable: boolean; @@ -79,7 +81,14 @@ export interface SystemState { consoleLogLevel: InvokeLogLevel; shouldLogToConsole: boolean; statusTranslationKey: TFuncKey; + /** + * When a session is canceled, its ID is stored here until a new session is created. + */ canceledSession: string; + /** + * TODO: get this from backend + */ + infillMethods: InfillMethod[]; } const initialSystemState: SystemState = { @@ -111,6 +120,7 @@ const initialSystemState: SystemState = { shouldLogToConsole: true, statusTranslationKey: 'common.statusDisconnected', canceledSession: '', + infillMethods: ['tile'], }; export const systemSlice = createSlice({ diff --git a/invokeai/frontend/web/src/services/thunks/session.ts b/invokeai/frontend/web/src/services/thunks/session.ts index 6bd7f01c26..903bb3a2de 100644 --- a/invokeai/frontend/web/src/services/thunks/session.ts +++ b/invokeai/frontend/web/src/services/thunks/session.ts @@ -1,6 +1,9 @@ import { createAppAsyncThunk } from 'app/store/storeUtils'; import { SessionsService } from 'services/api'; -import { buildLinearGraph as buildGenerateGraph } from 'features/nodes/util/linearGraphBuilder/buildLinearGraph'; +import { + buildCanvasGraph, + buildLinearGraph as buildGenerateGraph, +} from 'features/nodes/util/linearGraphBuilder/buildLinearGraph'; import { isAnyOf, isFulfilled } from '@reduxjs/toolkit'; import { buildNodesGraph } from 'features/nodes/util/nodesGraphBuilder/buildNodesGraph'; import { log } from 'app/logging/useLogger'; @@ -42,9 +45,27 @@ export const nodesGraphBuilt = createAppAsyncThunk( } ); +export const canvasGraphBuilt = createAppAsyncThunk( + 'api/canvasGraphBuilt', + async (_, { dispatch, getState, rejectWithValue }) => { + try { + const graph = buildCanvasGraph(getState()); + dispatch(sessionCreated({ graph })); + return graph; + } catch (err: any) { + sessionLog.error( + { error: serializeError(err) }, + 'Problem building graph' + ); + return rejectWithValue(err.message); + } + } +); + export const isFulfilledAnyGraphBuilt = isAnyOf( generateGraphBuilt.fulfilled, - nodesGraphBuilt.fulfilled + nodesGraphBuilt.fulfilled, + canvasGraphBuilt.fulfilled ); type SessionCreatedArg = { @@ -58,14 +79,22 @@ type SessionCreatedArg = { */ export const sessionCreated = createAppAsyncThunk( 'api/sessionCreated', - async (arg: SessionCreatedArg, { dispatch, getState }) => { - const response = await SessionsService.createSession({ - requestBody: arg.graph, - }); - - sessionLog.info({ arg, response }, `Session created (${response.id})`); - - return response; + async (arg: SessionCreatedArg, { rejectWithValue }) => { + try { + const response = await SessionsService.createSession({ + requestBody: arg.graph, + }); + sessionLog.info({ arg, response }, `Session created (${response.id})`); + return response; + } catch (err: any) { + sessionLog.error( + { + error: serializeError(err), + }, + 'Problem creating session' + ); + return rejectWithValue(err.message); + } } ); From cee21ca0822309aecc549508b4c1f3cdcc33bd3c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 May 2023 08:55:36 +1000 Subject: [PATCH 04/66] feat(ui): wip canvas nodes migration 2 --- .../web/src/common/util/arrayBuffer.ts | 26 ----- .../canvas/hooks/useGetCanvasNodeType.ts | 30 ++++++ .../src/features/canvas/util/generateMask.ts | 62 +++++++---- .../features/canvas/util/getCanvasDataURLs.ts | 64 +++++++---- .../features/canvas/util/getCanvasNodeType.ts | 102 ++++++++++++++++++ .../gallery/components/HoverableImage.tsx | 1 + .../UnifiedCanvas/UnifiedCanvasWorkarea.tsx | 1 + 7 files changed, 220 insertions(+), 66 deletions(-) create mode 100644 invokeai/frontend/web/src/features/canvas/hooks/useGetCanvasNodeType.ts create mode 100644 invokeai/frontend/web/src/features/canvas/util/getCanvasNodeType.ts diff --git a/invokeai/frontend/web/src/common/util/arrayBuffer.ts b/invokeai/frontend/web/src/common/util/arrayBuffer.ts index da0fe38d35..779d9b1b17 100644 --- a/invokeai/frontend/web/src/common/util/arrayBuffer.ts +++ b/invokeai/frontend/web/src/common/util/arrayBuffer.ts @@ -1,18 +1,3 @@ -export const getIsImageDataPartiallyTransparent = (imageData: ImageData) => { - let hasTransparency = false; - let isFullyTransparent = true; - const len = imageData.data.length; - let i = 3; - for (i; i < len; i += 4) { - if (imageData.data[i] !== 0) { - isFullyTransparent = false; - } else { - hasTransparency = true; - } - } - return { hasTransparency, isFullyTransparent }; -}; - export const getImageDataTransparency = (imageData: ImageData) => { let isFullyTransparent = true; let isPartiallyTransparent = false; @@ -46,14 +31,3 @@ export const areAnyPixelsBlack = (imageData: ImageData) => { } return false; }; - -export const getIsImageDataWhite = (imageData: ImageData) => { - const len = imageData.data.length; - let i = 0; - for (i; i < len; ) { - if (imageData.data[i++] !== 255) { - return false; - } - } - return true; -}; diff --git a/invokeai/frontend/web/src/features/canvas/hooks/useGetCanvasNodeType.ts b/invokeai/frontend/web/src/features/canvas/hooks/useGetCanvasNodeType.ts new file mode 100644 index 0000000000..91f8e50d29 --- /dev/null +++ b/invokeai/frontend/web/src/features/canvas/hooks/useGetCanvasNodeType.ts @@ -0,0 +1,30 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useAppSelector } from 'app/store/storeHooks'; +import { canvasSelector } from '../store/canvasSelectors'; +import { useMemo } from 'react'; +import { getCanvasNodeType } from '../util/getCanvasNodeType'; + +const selector = createSelector(canvasSelector, (canvas) => { + const { + layerState: { objects }, + boundingBoxCoordinates, + boundingBoxDimensions, + stageScale, + isMaskEnabled, + } = canvas; + return { + objects, + boundingBoxCoordinates, + boundingBoxDimensions, + stageScale, + isMaskEnabled, + }; +}); + +export const useGetCanvasNodeType = () => { + const data = useAppSelector(selector); + + const nodeType = useMemo(() => getCanvasNodeType(data), [data]); + + return nodeType; +}; diff --git a/invokeai/frontend/web/src/features/canvas/util/generateMask.ts b/invokeai/frontend/web/src/features/canvas/util/generateMask.ts index 32e0fecfd0..db88dd1ad3 100644 --- a/invokeai/frontend/web/src/features/canvas/util/generateMask.ts +++ b/invokeai/frontend/web/src/features/canvas/util/generateMask.ts @@ -1,5 +1,6 @@ import { CanvasMaskLine } from 'features/canvas/store/canvasTypes'; import Konva from 'konva'; +import { Stage } from 'konva/lib/Stage'; import { IRect } from 'konva/lib/types'; /** @@ -12,10 +13,50 @@ import { IRect } from 'konva/lib/types'; * drawing the mask and compositing everything correctly to output a valid * mask image. */ -const generateMask = ( +export const getStageDataURL = (stage: Stage, boundingBox: IRect): string => { + // create an offscreen canvas and add the mask to it + // const { stage, offscreenContainer } = buildMaskStage(lines, boundingBox); + + const dataURL = stage.toDataURL({ ...boundingBox }); + + // const imageData = stage + // .toCanvas() + // .getContext('2d') + // ?.getImageData( + // boundingBox.x, + // boundingBox.y, + // boundingBox.width, + // boundingBox.height + // ); + + // offscreenContainer.remove(); + + // return { dataURL, imageData }; + + return dataURL; +}; + +export const getStageImageData = ( + stage: Stage, + boundingBox: IRect +): ImageData | undefined => { + const imageData = stage + .toCanvas() + .getContext('2d') + ?.getImageData( + boundingBox.x, + boundingBox.y, + boundingBox.width, + boundingBox.height + ); + + return imageData; +}; + +export const buildMaskStage = ( lines: CanvasMaskLine[], boundingBox: IRect -): { dataURL: string; imageData: ImageData } => { +): { stage: Stage; offscreenContainer: HTMLDivElement } => { // create an offscreen canvas and add the mask to it const { width, height } = boundingBox; @@ -57,20 +98,5 @@ const generateMask = ( stage.add(baseLayer); stage.add(maskLayer); - const dataURL = stage.toDataURL({ ...boundingBox }); - const imageData = stage - .toCanvas() - .getContext('2d') - ?.getImageData( - boundingBox.x, - boundingBox.y, - boundingBox.width, - boundingBox.height - ); - - offscreenContainer.remove(); - - return { dataURL, imageData }; + return { stage, offscreenContainer }; }; - -export default generateMask; diff --git a/invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts b/invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts index fdc03bc48a..e309057eee 100644 --- a/invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts +++ b/invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts @@ -1,24 +1,27 @@ import { RootState } from 'app/store/store'; import { getCanvasBaseLayer, getCanvasStage } from './konvaInstanceProvider'; import { isCanvasMaskLine } from '../store/canvasTypes'; -import generateMask from './generateMask'; +import { + buildMaskStage, + getStageDataURL, + getStageImageData, +} from './generateMask'; import { log } from 'app/logging/useLogger'; import { areAnyPixelsBlack, getImageDataTransparency, - getIsImageDataWhite, } from 'common/util/arrayBuffer'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; +import { masks } from 'dateformat'; + +const moduleLog = log.child({ namespace: 'getCanvasDataURLs' }); export const getCanvasDataURLs = (state: RootState) => { const canvasBaseLayer = getCanvasBaseLayer(); const canvasStage = getCanvasStage(); if (!canvasBaseLayer || !canvasStage) { - log.error( - { namespace: 'getCanvasDataURLs' }, - 'Unable to find canvas / stage' - ); + moduleLog.error('Unable to find canvas / stage'); return; } @@ -55,30 +58,46 @@ export const getCanvasDataURLs = (state: RootState) => { const absPos = canvasBaseLayer.getAbsolutePosition(); - const { dataURL: maskDataURL, imageData: maskImageData } = generateMask( - isMaskEnabled ? objects.filter(isCanvasMaskLine) : [], - { - x: boundingBox.x + absPos.x, - y: boundingBox.y + absPos.y, - width: boundingBox.width, - height: boundingBox.height, - } - ); - - const baseDataURL = canvasBaseLayer.toDataURL({ + const offsetBoundingBox = { x: boundingBox.x + absPos.x, y: boundingBox.y + absPos.y, width: boundingBox.width, height: boundingBox.height, - }); + }; + + const { stage: maskStage, offscreenContainer } = buildMaskStage( + isMaskEnabled ? objects.filter(isCanvasMaskLine) : [], + offsetBoundingBox + ); + + const maskDataURL = maskStage.toDataURL(offsetBoundingBox); + + const maskImageData = maskStage + .toCanvas() + .getContext('2d') + ?.getImageData( + offsetBoundingBox.x, + offsetBoundingBox.y, + offsetBoundingBox.width, + offsetBoundingBox.height + ); + + offscreenContainer.remove(); + + if (!maskImageData) { + moduleLog.error('Unable to get mask stage context'); + return; + } + + const baseDataURL = canvasBaseLayer.toDataURL(offsetBoundingBox); const ctx = canvasBaseLayer.getContext(); const baseImageData = ctx.getImageData( - boundingBox.x + absPos.x, - boundingBox.y + absPos.y, - boundingBox.width, - boundingBox.height + offsetBoundingBox.x, + offsetBoundingBox.y, + offsetBoundingBox.width, + offsetBoundingBox.height ); const { @@ -86,6 +105,7 @@ export const getCanvasDataURLs = (state: RootState) => { isFullyTransparent: baseIsFullyTransparent, } = getImageDataTransparency(baseImageData); + // const doesMaskHaveBlackPixels = false; const doesMaskHaveBlackPixels = areAnyPixelsBlack(maskImageData); if (state.system.enableImageDebugging) { diff --git a/invokeai/frontend/web/src/features/canvas/util/getCanvasNodeType.ts b/invokeai/frontend/web/src/features/canvas/util/getCanvasNodeType.ts new file mode 100644 index 0000000000..baa250031b --- /dev/null +++ b/invokeai/frontend/web/src/features/canvas/util/getCanvasNodeType.ts @@ -0,0 +1,102 @@ +import { RootState } from 'app/store/store'; +import { getCanvasBaseLayer, getCanvasStage } from './konvaInstanceProvider'; +import { + CanvasObject, + Dimensions, + isCanvasMaskLine, +} from '../store/canvasTypes'; +import { buildMaskStage, getStageImageData } from './generateMask'; +import { log } from 'app/logging/useLogger'; +import { + areAnyPixelsBlack, + getImageDataTransparency, +} from 'common/util/arrayBuffer'; +import { getNodeType } from 'features/nodes/util/getNodeType'; +import { Vector2d } from 'konva/lib/types'; + +const moduleLog = log.child({ namespace: 'getCanvasNodeTypes' }); + +export type GetCanvasNodeTypeArg = { + objects: CanvasObject[]; + boundingBoxCoordinates: Vector2d; + boundingBoxDimensions: Dimensions; + stageScale: number; + isMaskEnabled: boolean; +}; + +export const getCanvasNodeType = (arg: GetCanvasNodeTypeArg) => { + const canvasBaseLayer = getCanvasBaseLayer(); + const canvasStage = getCanvasStage(); + + if (!canvasBaseLayer || !canvasStage) { + moduleLog.error('Unable to find canvas / stage'); + return; + } + + const { + objects, + boundingBoxCoordinates, + boundingBoxDimensions, + stageScale, + isMaskEnabled, + } = arg; + + const boundingBox = { + ...boundingBoxCoordinates, + ...boundingBoxDimensions, + }; + + const tempScale = canvasBaseLayer.scale(); + + canvasBaseLayer.scale({ + x: 1 / stageScale, + y: 1 / stageScale, + }); + + const absPos = canvasBaseLayer.getAbsolutePosition(); + + const scaledBoundingBox = { + x: boundingBox.x + absPos.x, + y: boundingBox.y + absPos.y, + width: boundingBox.width, + height: boundingBox.height, + }; + + const { stage: maskStage, offscreenContainer } = buildMaskStage( + isMaskEnabled ? objects.filter(isCanvasMaskLine) : [], + scaledBoundingBox + ); + + const maskImageData = getStageImageData(maskStage, scaledBoundingBox); + + offscreenContainer.remove(); + + if (!maskImageData) { + moduleLog.error('Unable to get mask stage context'); + return; + } + + const ctx = canvasBaseLayer.getContext(); + + const baseImageData = ctx.getImageData( + boundingBox.x + absPos.x, + boundingBox.y + absPos.y, + boundingBox.width, + boundingBox.height + ); + + canvasBaseLayer.scale(tempScale); + + const { + isPartiallyTransparent: baseIsPartiallyTransparent, + isFullyTransparent: baseIsFullyTransparent, + } = getImageDataTransparency(baseImageData); + + const doesMaskHaveBlackPixels = areAnyPixelsBlack(maskImageData); + + return getNodeType( + baseIsPartiallyTransparent, + baseIsFullyTransparent, + doesMaskHaveBlackPixels + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index ba100ecacc..80d2facdcf 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -134,6 +134,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { const handleDragStart = useCallback( (e: DragEvent) => { + console.log('dragging'); e.dataTransfer.setData('invokeai/imageName', image.name); e.dataTransfer.setData('invokeai/imageType', image.type); e.dataTransfer.effectAllowed = 'move'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx index dd32295f3c..19e8f372b2 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx @@ -34,6 +34,7 @@ import ParametersSlide from '../../common/ParametersSlide'; import UnifiedCanvasParameters from './UnifiedCanvasParameters'; import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; import UnifiedCanvasContent from './UnifiedCanvasContent'; +import { useGetCanvasNodeType } from 'features/canvas/hooks/useGetCanvasNodeType'; const CanvasWorkspace = () => { const shouldPinParametersPanel = useAppSelector( From f7bbc4004acb59123a5dda03175d0dda130da206 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 May 2023 14:35:47 +1000 Subject: [PATCH 05/66] feat(ui): wip canvas nodes migration 3 --- .../canvas/hooks/useGetCanvasNodeType.ts | 30 ------ .../src/features/canvas/store/canvasSlice.ts | 8 +- .../src/features/canvas/store/canvasTypes.ts | 1 + ...{getCanvasDataURLs.ts => getCanvasData.ts} | 3 +- .../features/canvas/util/getCanvasNodeType.ts | 102 ------------------ .../nodes/components/NodeGraphOverlay.tsx | 2 +- ...uildLinearGraph.ts => buildCanvasGraph.ts} | 71 ++++-------- .../features/nodes/util/buildLinearGraph.ts | 39 +++++++ .../buildNodesGraph.ts | 0 .../UnifiedCanvas/UnifiedCanvasWorkarea.tsx | 27 ----- .../web/src/services/thunks/session.ts | 8 +- 11 files changed, 70 insertions(+), 221 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/canvas/hooks/useGetCanvasNodeType.ts rename invokeai/frontend/web/src/features/canvas/util/{getCanvasDataURLs.ts => getCanvasData.ts} (97%) delete mode 100644 invokeai/frontend/web/src/features/canvas/util/getCanvasNodeType.ts rename invokeai/frontend/web/src/features/nodes/util/{linearGraphBuilder/buildLinearGraph.ts => buildCanvasGraph.ts} (51%) create mode 100644 invokeai/frontend/web/src/features/nodes/util/buildLinearGraph.ts rename invokeai/frontend/web/src/features/nodes/util/{nodesGraphBuilder => }/buildNodesGraph.ts (100%) diff --git a/invokeai/frontend/web/src/features/canvas/hooks/useGetCanvasNodeType.ts b/invokeai/frontend/web/src/features/canvas/hooks/useGetCanvasNodeType.ts deleted file mode 100644 index 91f8e50d29..0000000000 --- a/invokeai/frontend/web/src/features/canvas/hooks/useGetCanvasNodeType.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { useAppSelector } from 'app/store/storeHooks'; -import { canvasSelector } from '../store/canvasSelectors'; -import { useMemo } from 'react'; -import { getCanvasNodeType } from '../util/getCanvasNodeType'; - -const selector = createSelector(canvasSelector, (canvas) => { - const { - layerState: { objects }, - boundingBoxCoordinates, - boundingBoxDimensions, - stageScale, - isMaskEnabled, - } = canvas; - return { - objects, - boundingBoxCoordinates, - boundingBoxDimensions, - stageScale, - isMaskEnabled, - }; -}); - -export const useGetCanvasNodeType = () => { - const data = useAppSelector(selector); - - const nodeType = useMemo(() => getCanvasNodeType(data), [data]); - - return nodeType; -}; diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index 5ff8ea4295..22d4270e18 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -29,6 +29,7 @@ import { isCanvasBaseImage, isCanvasMaskLine, } from './canvasTypes'; +import { invocationComplete } from 'services/events/actions'; export const initialLayerState: CanvasLayerState = { objects: [], @@ -289,7 +290,7 @@ export const canvasSlice = createSlice({ state, action: PayloadAction<{ boundingBox: IRect; - image: InvokeAI._Image; + image: InvokeAI.Image; }> ) => { const { boundingBox, image } = action.payload; @@ -815,6 +816,11 @@ export const canvasSlice = createSlice({ state.isTransformingBoundingBox = false; }, }, + extraReducers(builder) { + builder.addCase(invocationComplete, (state, action) => { + // + }); + }, }); export const { diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts index 2eec0e9bed..df24e9ee4d 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts @@ -162,4 +162,5 @@ export interface CanvasState { stageDimensions: Dimensions; stageScale: number; tool: CanvasTool; + pendingBoundingBox?: IRect; } diff --git a/invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts b/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts similarity index 97% rename from invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts rename to invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts index e309057eee..c6192f0a09 100644 --- a/invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts +++ b/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts @@ -16,7 +16,7 @@ import { masks } from 'dateformat'; const moduleLog = log.child({ namespace: 'getCanvasDataURLs' }); -export const getCanvasDataURLs = (state: RootState) => { +export const getCanvasData = (state: RootState) => { const canvasBaseLayer = getCanvasBaseLayer(); const canvasStage = getCanvasStage(); @@ -85,7 +85,6 @@ export const getCanvasDataURLs = (state: RootState) => { offscreenContainer.remove(); if (!maskImageData) { - moduleLog.error('Unable to get mask stage context'); return; } diff --git a/invokeai/frontend/web/src/features/canvas/util/getCanvasNodeType.ts b/invokeai/frontend/web/src/features/canvas/util/getCanvasNodeType.ts deleted file mode 100644 index baa250031b..0000000000 --- a/invokeai/frontend/web/src/features/canvas/util/getCanvasNodeType.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { RootState } from 'app/store/store'; -import { getCanvasBaseLayer, getCanvasStage } from './konvaInstanceProvider'; -import { - CanvasObject, - Dimensions, - isCanvasMaskLine, -} from '../store/canvasTypes'; -import { buildMaskStage, getStageImageData } from './generateMask'; -import { log } from 'app/logging/useLogger'; -import { - areAnyPixelsBlack, - getImageDataTransparency, -} from 'common/util/arrayBuffer'; -import { getNodeType } from 'features/nodes/util/getNodeType'; -import { Vector2d } from 'konva/lib/types'; - -const moduleLog = log.child({ namespace: 'getCanvasNodeTypes' }); - -export type GetCanvasNodeTypeArg = { - objects: CanvasObject[]; - boundingBoxCoordinates: Vector2d; - boundingBoxDimensions: Dimensions; - stageScale: number; - isMaskEnabled: boolean; -}; - -export const getCanvasNodeType = (arg: GetCanvasNodeTypeArg) => { - const canvasBaseLayer = getCanvasBaseLayer(); - const canvasStage = getCanvasStage(); - - if (!canvasBaseLayer || !canvasStage) { - moduleLog.error('Unable to find canvas / stage'); - return; - } - - const { - objects, - boundingBoxCoordinates, - boundingBoxDimensions, - stageScale, - isMaskEnabled, - } = arg; - - const boundingBox = { - ...boundingBoxCoordinates, - ...boundingBoxDimensions, - }; - - const tempScale = canvasBaseLayer.scale(); - - canvasBaseLayer.scale({ - x: 1 / stageScale, - y: 1 / stageScale, - }); - - const absPos = canvasBaseLayer.getAbsolutePosition(); - - const scaledBoundingBox = { - x: boundingBox.x + absPos.x, - y: boundingBox.y + absPos.y, - width: boundingBox.width, - height: boundingBox.height, - }; - - const { stage: maskStage, offscreenContainer } = buildMaskStage( - isMaskEnabled ? objects.filter(isCanvasMaskLine) : [], - scaledBoundingBox - ); - - const maskImageData = getStageImageData(maskStage, scaledBoundingBox); - - offscreenContainer.remove(); - - if (!maskImageData) { - moduleLog.error('Unable to get mask stage context'); - return; - } - - const ctx = canvasBaseLayer.getContext(); - - const baseImageData = ctx.getImageData( - boundingBox.x + absPos.x, - boundingBox.y + absPos.y, - boundingBox.width, - boundingBox.height - ); - - canvasBaseLayer.scale(tempScale); - - const { - isPartiallyTransparent: baseIsPartiallyTransparent, - isFullyTransparent: baseIsFullyTransparent, - } = getImageDataTransparency(baseImageData); - - const doesMaskHaveBlackPixels = areAnyPixelsBlack(maskImageData); - - return getNodeType( - baseIsPartiallyTransparent, - baseIsFullyTransparent, - doesMaskHaveBlackPixels - ); -}; diff --git a/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx b/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx index 90c8e1396f..bcf02eabf5 100644 --- a/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx @@ -2,7 +2,7 @@ import { Box } from '@chakra-ui/react'; import { RootState } from 'app/store/store'; import { useAppSelector } from 'app/store/storeHooks'; import { memo } from 'react'; -import { buildNodesGraph } from '../util/nodesGraphBuilder/buildNodesGraph'; +import { buildNodesGraph } from '../util/buildNodesGraph'; const NodeGraphOverlay = () => { const state = useAppSelector((state: RootState) => state); diff --git a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts similarity index 51% rename from invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildLinearGraph.ts rename to invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts index e0dd2f4843..24c057c04a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts @@ -1,58 +1,31 @@ import { RootState } from 'app/store/store'; import { DataURLToImageInvocation, Graph } from 'services/api'; -import { buildImg2ImgNode } from './buildImageToImageNode'; -import { buildTxt2ImgNode } from './buildTextToImageNode'; -import { buildRangeNode } from './buildRangeNode'; -import { buildIterateNode } from './buildIterateNode'; -import { buildEdges } from './buildEdges'; +import { buildImg2ImgNode } from './linearGraphBuilder/buildImageToImageNode'; +import { buildTxt2ImgNode } from './linearGraphBuilder/buildTextToImageNode'; +import { buildRangeNode } from './linearGraphBuilder/buildRangeNode'; +import { buildIterateNode } from './linearGraphBuilder/buildIterateNode'; +import { buildEdges } from './linearGraphBuilder/buildEdges'; import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; -import { getCanvasDataURLs } from 'features/canvas/util/getCanvasDataURLs'; -import { log } from 'console'; -import { getNodeType } from '../getNodeType'; +import { getCanvasData } from 'features/canvas/util/getCanvasData'; +import { getNodeType } from './getNodeType'; import { v4 as uuidv4 } from 'uuid'; +import { log } from 'app/logging/useLogger'; + +const moduleLog = log.child({ namespace: 'buildCanvasGraph' }); /** - * Builds the Linear workflow graph. + * Builds the Canvas workflow graph. */ -export const buildLinearGraph = (state: RootState): Graph => { - // The base node is either a txt2img or img2img node - const baseNode = state.generation.isImageToImageEnabled - ? buildImg2ImgNode(state) - : buildTxt2ImgNode(state); - - // We always range and iterate nodes, no matter the iteration count - // This is required to provide the correct seeds to the backend engine - const rangeNode = buildRangeNode(state); - const iterateNode = buildIterateNode(); - - // Build the edges for the nodes selected. - const edges = buildEdges(baseNode, rangeNode, iterateNode); - - // Assemble! - const graph = { - nodes: { - [rangeNode.id]: rangeNode, - [iterateNode.id]: iterateNode, - [baseNode.id]: baseNode, - }, - edges, - }; - - // TODO: hires fix requires latent space upscaling; we don't have nodes for this yet - - return graph; -}; - -/** - * Builds the Linear workflow graph. - */ -export const buildCanvasGraph = (state: RootState): Graph => { - const c = getCanvasDataURLs(state); +export const buildCanvasGraph = (state: RootState): Graph | undefined => { + const c = getCanvasData(state); if (!c) { - throw 'problm creating canvas graph'; + moduleLog.error('Unable to create canvas graph'); + return; } + moduleLog.debug({ data: c }, 'Built canvas data'); + const { baseDataURL, maskDataURL, @@ -61,21 +34,13 @@ export const buildCanvasGraph = (state: RootState): Graph => { doesMaskHaveBlackPixels, } = c; - console.log({ - baseDataURL, - maskDataURL, - baseIsPartiallyTransparent, - baseIsFullyTransparent, - doesMaskHaveBlackPixels, - }); - const nodeType = getNodeType( baseIsPartiallyTransparent, baseIsFullyTransparent, doesMaskHaveBlackPixels ); - console.log(nodeType); + moduleLog.debug(`Node type ${nodeType}`); // The base node is either a txt2img or img2img node const baseNode = diff --git a/invokeai/frontend/web/src/features/nodes/util/buildLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/buildLinearGraph.ts new file mode 100644 index 0000000000..f247964c72 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/buildLinearGraph.ts @@ -0,0 +1,39 @@ +import { RootState } from 'app/store/store'; +import { Graph } from 'services/api'; +import { buildImg2ImgNode } from './linearGraphBuilder/buildImageToImageNode'; +import { buildTxt2ImgNode } from './linearGraphBuilder/buildTextToImageNode'; +import { buildRangeNode } from './linearGraphBuilder/buildRangeNode'; +import { buildIterateNode } from './linearGraphBuilder/buildIterateNode'; +import { buildEdges } from './linearGraphBuilder/buildEdges'; + +/** + * Builds the Linear workflow graph. + */ +export const buildLinearGraph = (state: RootState): Graph => { + // The base node is either a txt2img or img2img node + const baseNode = state.generation.isImageToImageEnabled + ? buildImg2ImgNode(state) + : buildTxt2ImgNode(state); + + // We always range and iterate nodes, no matter the iteration count + // This is required to provide the correct seeds to the backend engine + const rangeNode = buildRangeNode(state); + const iterateNode = buildIterateNode(); + + // Build the edges for the nodes selected. + const edges = buildEdges(baseNode, rangeNode, iterateNode); + + // Assemble! + const graph = { + nodes: { + [rangeNode.id]: rangeNode, + [iterateNode.id]: iterateNode, + [baseNode.id]: baseNode, + }, + edges, + }; + + // TODO: hires fix requires latent space upscaling; we don't have nodes for this yet + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/nodesGraphBuilder/buildNodesGraph.ts b/invokeai/frontend/web/src/features/nodes/util/buildNodesGraph.ts similarity index 100% rename from invokeai/frontend/web/src/features/nodes/util/nodesGraphBuilder/buildNodesGraph.ts rename to invokeai/frontend/web/src/features/nodes/util/buildNodesGraph.ts diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx index 19e8f372b2..ee7f4d242d 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx @@ -1,29 +1,3 @@ -// import { RootState } from 'app/store/store'; -// import { useAppSelector } from 'app/store/storeHooks'; -// import InvokeWorkarea from 'features/ui/components/InvokeWorkarea'; -// import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; -// import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; -// import UnifiedCanvasContent from './UnifiedCanvasContent'; -// import UnifiedCanvasParameters from './UnifiedCanvasParameters'; - -// export default function UnifiedCanvasWorkarea() { -// const shouldUseCanvasBetaLayout = useAppSelector( -// (state: RootState) => state.ui.shouldUseCanvasBetaLayout -// ); - -// const activeTabName = useAppSelector(activeTabNameSelector); - -// return ( -// }> -// {activeTabName === 'unifiedCanvas' && -// (shouldUseCanvasBetaLayout ? ( -// -// ) : ( -// -// ))} -// -// ); -// } import { Box, Flex } from '@chakra-ui/react'; import { useAppSelector } from 'app/store/storeHooks'; import { memo } from 'react'; @@ -34,7 +8,6 @@ import ParametersSlide from '../../common/ParametersSlide'; import UnifiedCanvasParameters from './UnifiedCanvasParameters'; import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; import UnifiedCanvasContent from './UnifiedCanvasContent'; -import { useGetCanvasNodeType } from 'features/canvas/hooks/useGetCanvasNodeType'; const CanvasWorkspace = () => { const shouldPinParametersPanel = useAppSelector( diff --git a/invokeai/frontend/web/src/services/thunks/session.ts b/invokeai/frontend/web/src/services/thunks/session.ts index 903bb3a2de..c92b303ff7 100644 --- a/invokeai/frontend/web/src/services/thunks/session.ts +++ b/invokeai/frontend/web/src/services/thunks/session.ts @@ -1,11 +1,9 @@ import { createAppAsyncThunk } from 'app/store/storeUtils'; import { SessionsService } from 'services/api'; -import { - buildCanvasGraph, - buildLinearGraph as buildGenerateGraph, -} from 'features/nodes/util/linearGraphBuilder/buildLinearGraph'; +import { buildLinearGraph as buildGenerateGraph } from 'features/nodes/util/buildLinearGraph'; +import { buildCanvasGraph } from 'features/nodes/util/buildCanvasGraph'; import { isAnyOf, isFulfilled } from '@reduxjs/toolkit'; -import { buildNodesGraph } from 'features/nodes/util/nodesGraphBuilder/buildNodesGraph'; +import { buildNodesGraph } from 'features/nodes/util/buildNodesGraph'; import { log } from 'app/logging/useLogger'; import { serializeError } from 'serialize-error'; From a75148cb167a2506bbe7313f97792c2890a53120 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 May 2023 19:27:06 +1000 Subject: [PATCH 06/66] feat(nodes): free gpu mem after invocation --- invokeai/app/services/processor.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/invokeai/app/services/processor.py b/invokeai/app/services/processor.py index 35cbcd5068..0c2ad28bf5 100644 --- a/invokeai/app/services/processor.py +++ b/invokeai/app/services/processor.py @@ -1,11 +1,15 @@ +import gc import traceback from threading import Event, Thread, BoundedSemaphore +import torch + from ..invocations.baseinvocation import InvocationContext from .invocation_queue import InvocationQueueItem from .invoker import InvocationProcessorABC, Invoker from ..models.exceptions import CanceledException + class DefaultInvocationProcessor(InvocationProcessorABC): __invoker_thread: Thread __stop_event: Event @@ -22,9 +26,7 @@ class DefaultInvocationProcessor(InvocationProcessorABC): target=self.__process, kwargs=dict(stop_event=self.__stop_event), ) - self.__invoker_thread.daemon = ( - True # TODO: make async and do not use threads - ) + self.__invoker_thread.daemon = True # TODO: make async and do not use threads self.__invoker_thread.start() def stop(self, *args, **kwargs) -> None: @@ -48,13 +50,15 @@ class DefaultInvocationProcessor(InvocationProcessorABC): ) # get the source node id to provide to clients (the prepared node id is not as useful) - source_node_id = graph_execution_state.prepared_source_mapping[invocation.id] + source_node_id = graph_execution_state.prepared_source_mapping[ + invocation.id + ] # Send starting event self.__invoker.services.events.emit_invocation_started( graph_execution_state_id=graph_execution_state.id, node=invocation.dict(), - source_node_id=source_node_id + source_node_id=source_node_id, ) # Invoke @@ -114,11 +118,12 @@ class DefaultInvocationProcessor(InvocationProcessorABC): ) pass + finally: + gc.collect() + torch.cuda.empty_cache() # Check queue to see if this is canceled, and skip if so - if self.__invoker.services.queue.is_canceled( - graph_execution_state.id - ): + if self.__invoker.services.queue.is_canceled(graph_execution_state.id): continue # Queue any further commands if invoking all From 6ab5d28cf3179bda664dfbfebe6ed1ea63fefdfe Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 May 2023 19:27:29 +1000 Subject: [PATCH 07/66] feat(ui): wip canvas migration, createListenerMiddleware --- invokeai/frontend/web/package.json | 1 + invokeai/frontend/web/public/locales/en.json | 2 +- .../middleware/listenerMiddleware/index.ts | 59 +++++++++++++++ .../listeners/imageUploadedListener.ts | 19 +++++ .../listeners/initialImageListener.ts | 53 +++++++++++++ .../listeners/invocationCompleteListener.ts | 75 +++++++++++++++++++ invokeai/frontend/web/src/app/store/store.ts | 13 +++- .../frontend/web/src/app/store/storeHooks.ts | 4 +- .../frontend/web/src/app/types/invokeai.ts | 9 +++ .../src/features/canvas/store/canvasSlice.ts | 6 -- .../components/CurrentImageButtons.tsx | 7 +- .../components/CurrentImagePreview.tsx | 15 +++- .../gallery/components/HoverableImage.tsx | 1 - .../features/gallery/store/gallerySlice.ts | 20 +---- .../features/gallery/store/resultsSlice.ts | 64 ++++++++-------- .../features/gallery/store/uploadsSlice.ts | 13 +--- .../ImageToImage/InitialImagePreview.tsx | 34 ++++----- .../parameters/hooks/useParameters.ts | 27 ++----- .../src/features/parameters/store/actions.ts | 12 +++ .../parameters/store/generationSlice.ts | 11 +-- .../src/features/system/store/systemSlice.ts | 9 +-- .../features/ui/components/InvokeWorkarea.tsx | 2 +- .../api/models/DataURLToImageInvocation.ts | 2 +- .../api/schemas/$DataURLToImageInvocation.ts | 2 +- .../web/src/services/events/actions.ts | 2 +- .../web/src/services/events/middleware.ts | 38 ++-------- .../services/events/util/setEventListeners.ts | 6 +- invokeai/frontend/web/tsconfig.json | 7 +- invokeai/frontend/web/yarn.lock | 14 +++- 29 files changed, 352 insertions(+), 175 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploadedListener.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageListener.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/invocationCompleteListener.ts create mode 100644 invokeai/frontend/web/src/features/parameters/store/actions.ts diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index ef61f65cf4..da5e58c9c9 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -118,6 +118,7 @@ "@types/node": "^18.16.2", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.1", + "@types/react-redux": "^7.1.25", "@types/react-transition-group": "^4.4.5", "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.59.1", diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 876cd96b39..18f506d5bf 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -550,7 +550,7 @@ "imageCopied": "Image Copied", "imageLinkCopied": "Image Link Copied", "imageNotLoaded": "No Image Loaded", - "imageNotLoadedDesc": "No image found to send to image to image module", + "imageNotLoadedDesc": "Could not find image", "imageSavedToGallery": "Image Saved to Gallery", "canvasMerged": "Canvas Merged", "sentToImageToImage": "Sent To Image To Image", diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts new file mode 100644 index 0000000000..563f12ba02 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -0,0 +1,59 @@ +import { + createListenerMiddleware, + addListener, + ListenerEffect, + AnyAction, +} from '@reduxjs/toolkit'; +import type { TypedStartListening, TypedAddListener } from '@reduxjs/toolkit'; + +import type { RootState, AppDispatch } from '../../store'; +import { initialImageSelected } from 'features/parameters/store/actions'; +import { initialImageListener } from './listeners/initialImageListener'; +import { + imageResultReceivedListener, + imageResultReceivedPrediate, +} from './listeners/invocationCompleteListener'; +import { imageUploaded } from 'services/thunks/image'; +import { imageUploadedListener } from './listeners/imageUploadedListener'; + +export const listenerMiddleware = createListenerMiddleware(); + +export type AppStartListening = TypedStartListening; + +export const startAppListening = + listenerMiddleware.startListening as AppStartListening; + +export const addAppListener = addListener as TypedAddListener< + RootState, + AppDispatch +>; + +export type AppListenerEffect = ListenerEffect< + AnyAction, + RootState, + AppDispatch +>; + +/** + * Initial image selected + */ +startAppListening({ + actionCreator: initialImageSelected, + effect: initialImageListener, +}); + +/** + * Image Result received + */ +startAppListening({ + predicate: imageResultReceivedPrediate, + effect: imageResultReceivedListener, +}); + +/** + * Image Uploaded + */ +startAppListening({ + actionCreator: imageUploaded.fulfilled, + effect: imageUploadedListener, +}); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploadedListener.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploadedListener.ts new file mode 100644 index 0000000000..5ae24f315b --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploadedListener.ts @@ -0,0 +1,19 @@ +import { deserializeImageResponse } from 'services/util/deserializeImageResponse'; +import { AppListenerEffect } from '..'; +import { uploadAdded } from 'features/gallery/store/uploadsSlice'; +import { imageSelected } from 'features/gallery/store/gallerySlice'; + +export const imageUploadedListener: AppListenerEffect = ( + action, + { dispatch, getState } +) => { + const { response } = action.payload; + const state = getState(); + const image = deserializeImageResponse(response); + + dispatch(uploadAdded(image)); + + if (state.gallery.shouldAutoSwitchToNewImages) { + dispatch(imageSelected(image)); + } +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageListener.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageListener.ts new file mode 100644 index 0000000000..231e1153ed --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageListener.ts @@ -0,0 +1,53 @@ +import { initialImageChanged } from 'features/parameters/store/generationSlice'; +import { Image, isInvokeAIImage } from 'app/types/invokeai'; +import { selectResultsById } from 'features/gallery/store/resultsSlice'; +import { selectUploadsById } from 'features/gallery/store/uploadsSlice'; +import { makeToast } from 'features/system/hooks/useToastWatcher'; +import { t } from 'i18next'; +import { addToast } from 'features/system/store/systemSlice'; +import { AnyAction, ListenerEffect } from '@reduxjs/toolkit'; +import { AppDispatch, RootState } from 'app/store/store'; + +export const initialImageListener: ListenerEffect< + AnyAction, + RootState, + AppDispatch +> = (action, { getState, dispatch }) => { + if (!action.payload) { + dispatch( + addToast( + makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' }) + ) + ); + return; + } + + if (isInvokeAIImage(action.payload)) { + dispatch(initialImageChanged(action.payload)); + dispatch(addToast(makeToast(t('toast.sentToImageToImage')))); + return; + } + + const { name, type } = action.payload; + + let image: Image | undefined; + const state = getState(); + + if (type === 'results') { + image = selectResultsById(state, name); + } else if (type === 'uploads') { + image = selectUploadsById(state, name); + } + + if (!image) { + dispatch( + addToast( + makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' }) + ) + ); + return; + } + + dispatch(initialImageChanged(image)); + dispatch(addToast(makeToast(t('toast.sentToImageToImage')))); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/invocationCompleteListener.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/invocationCompleteListener.ts new file mode 100644 index 0000000000..4b6c2e060b --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/invocationCompleteListener.ts @@ -0,0 +1,75 @@ +import { AnyListenerPredicate } from '@reduxjs/toolkit'; +import { invocationComplete } from 'services/events/actions'; +import { isImageOutput } from 'services/types/guards'; +import { RootState } from 'app/store/store'; +import { + buildImageUrls, + extractTimestampFromImageName, +} from 'services/util/deserializeImageField'; +import { Image } from 'app/types/invokeai'; +import { resultAdded } from 'features/gallery/store/resultsSlice'; +import { imageReceived, thumbnailReceived } from 'services/thunks/image'; +import { AppListenerEffect } from '..'; + +export const imageResultReceivedPrediate: AnyListenerPredicate = ( + action, + _currentState, + _originalState +) => { + if ( + invocationComplete.match(action) && + isImageOutput(action.payload.data.result) + ) { + return true; + } + return false; +}; + +export const imageResultReceivedListener: AppListenerEffect = ( + action, + { getState, dispatch } +) => { + const { data, shouldFetchImages } = action.payload; + const { result, node, graph_execution_state_id } = data; + + if (isImageOutput(result)) { + const name = result.image.image_name; + const type = result.image.image_type; + const state = getState(); + + // if we need to refetch, set URLs to placeholder for now + const { url, thumbnail } = shouldFetchImages + ? { url: '', thumbnail: '' } + : buildImageUrls(type, name); + + const timestamp = extractTimestampFromImageName(name); + + const image: Image = { + name, + type, + url, + thumbnail, + metadata: { + created: timestamp, + width: result.width, + height: result.height, + invokeai: { + session_id: graph_execution_state_id, + ...(node ? { node } : {}), + }, + }, + }; + + dispatch(resultAdded(image)); + + if (state.config.shouldFetchImages) { + dispatch(imageReceived({ imageName: name, imageType: type })); + dispatch( + thumbnailReceived({ + thumbnailName: name, + thumbnailType: type, + }) + ); + } + } +}; diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index b0f73a759e..2663adfb6b 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -1,4 +1,9 @@ -import { combineReducers, configureStore } from '@reduxjs/toolkit'; +import { + AnyAction, + ThunkDispatch, + combineReducers, + configureStore, +} from '@reduxjs/toolkit'; import { persistReducer } from 'redux-persist'; import storage from 'redux-persist/lib/storage'; // defaults to localStorage for web @@ -30,6 +35,7 @@ import { systemDenylist } from 'features/system/store/systemPersistDenylist'; import { uiDenylist } from 'features/ui/store/uiPersistDenylist'; import { resultsDenylist } from 'features/gallery/store/resultsPersistDenylist'; import { uploadsDenylist } from 'features/gallery/store/uploadsPersistDenylist'; +import { listenerMiddleware } from './middleware/listenerMiddleware'; /** * redux-persist provides an easy and reliable way to persist state across reloads. @@ -101,7 +107,9 @@ export const store = configureStore({ getDefaultMiddleware({ immutableCheck: false, serializableCheck: false, - }).concat(dynamicMiddlewares), + }) + .concat(dynamicMiddlewares) + .prepend(listenerMiddleware.middleware), devTools: { // Uncommenting these very rapidly called actions makes the redux dev tools output much more readable actionsDenylist: [ @@ -120,4 +128,5 @@ export const store = configureStore({ export type AppGetState = typeof store.getState; export type RootState = ReturnType; +export type AppThunkDispatch = ThunkDispatch; export type AppDispatch = typeof store.dispatch; diff --git a/invokeai/frontend/web/src/app/store/storeHooks.ts b/invokeai/frontend/web/src/app/store/storeHooks.ts index 387bc7ea68..2d2d632caa 100644 --- a/invokeai/frontend/web/src/app/store/storeHooks.ts +++ b/invokeai/frontend/web/src/app/store/storeHooks.ts @@ -1,6 +1,6 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; -import { AppDispatch, RootState } from 'app/store/store'; +import { AppDispatch, AppThunkDispatch, RootState } from 'app/store/store'; // Use throughout your app instead of plain `useDispatch` and `useSelector` -export const useAppDispatch: () => AppDispatch = useDispatch; +export const useAppDispatch = () => useDispatch(); export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 05e6e088d6..f2be887c93 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -13,6 +13,7 @@ */ import { GalleryCategory } from 'features/gallery/store/gallerySlice'; +import { SelectedImage } from 'features/parameters/store/actions'; import { FacetoolType } from 'features/parameters/store/postprocessingSlice'; import { InvokeTabName } from 'features/ui/store/tabMap'; import { IRect } from 'konva/lib/types'; @@ -126,6 +127,14 @@ export type Image = { metadata: ImageResponseMetadata; }; +export const isInvokeAIImage = (obj: Image | SelectedImage): obj is Image => { + if ('url' in obj && 'thumbnail' in obj) { + return true; + } + + return false; +}; + /** * Types related to the system status. */ diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index 22d4270e18..5eaca8274f 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -29,7 +29,6 @@ import { isCanvasBaseImage, isCanvasMaskLine, } from './canvasTypes'; -import { invocationComplete } from 'services/events/actions'; export const initialLayerState: CanvasLayerState = { objects: [], @@ -816,11 +815,6 @@ export const canvasSlice = createSlice({ state.isTransformingBoundingBox = false; }, }, - extraReducers(builder) { - builder.addCase(invocationComplete, (state, action) => { - // - }); - }, }); export const { diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index 4fef811d46..94f54d1f3e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -22,7 +22,7 @@ import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice'; import FaceRestoreSettings from 'features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreSettings'; import UpscaleSettings from 'features/parameters/components/AdvancedParameters/Upscale/UpscaleSettings'; import { - initialImageSelected, + initialImageChanged, setAllParameters, // setInitialImage, setSeed, @@ -68,6 +68,7 @@ import { useGetUrl } from 'common/util/getUrl'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { imageDeleted } from 'services/thunks/image'; import { useParameters } from 'features/parameters/hooks/useParameters'; +import { initialImageSelected } from 'features/parameters/store/actions'; const currentImageButtonsSelector = createSelector( [ @@ -264,8 +265,8 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { useHotkeys('p', handleUsePrompt, [image]); const handleSendToImageToImage = useCallback(() => { - sendToImageToImage(image); - }, [image, sendToImageToImage]); + dispatch(initialImageSelected(image)); + }, [dispatch, image]); useHotkeys('shift+i', handleSendToImageToImage, [image]); diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index f6cfa99237..61a6bd9279 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -11,7 +11,7 @@ import CurrentImageFallback from './CurrentImageFallback'; import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer'; import NextPrevImageButtons from './NextPrevImageButtons'; import CurrentImageHidden from './CurrentImageHidden'; -import { memo } from 'react'; +import { DragEvent, memo, useCallback } from 'react'; export const imagesSelector = createSelector( [uiSelector, selectedImageSelector, systemSelector], @@ -36,6 +36,18 @@ const CurrentImagePreview = () => { useAppSelector(imagesSelector); const { getUrl } = useGetUrl(); + const handleDragStart = useCallback( + (e: DragEvent) => { + if (!image) { + return; + } + e.dataTransfer.setData('invokeai/imageName', image.name); + e.dataTransfer.setData('invokeai/imageType', image.type); + e.dataTransfer.effectAllowed = 'move'; + }, + [image] + ); + return ( { > {image && ( { const handleDragStart = useCallback( (e: DragEvent) => { - console.log('dragging'); e.dataTransfer.setData('invokeai/imageName', image.name); e.dataTransfer.setData('invokeai/imageType', image.type); e.dataTransfer.effectAllowed = 'move'; diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index 47c2c4e0fd..4a56eb68d7 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -4,7 +4,7 @@ import { invocationComplete } from 'services/events/actions'; import { isImageOutput } from 'services/types/guards'; import { deserializeImageResponse } from 'services/util/deserializeImageResponse'; import { imageUploaded } from 'services/thunks/image'; -import { SelectedImage } from 'features/parameters/store/generationSlice'; +import { Image } from 'app/types/invokeai'; type GalleryImageObjectFitType = 'contain' | 'cover'; @@ -12,7 +12,7 @@ export interface GalleryState { /** * The selected image */ - selectedImage?: SelectedImage; + selectedImage?: Image; galleryImageMinimumWidth: number; galleryImageObjectFit: GalleryImageObjectFitType; shouldAutoSwitchToNewImages: boolean; @@ -22,7 +22,6 @@ export interface GalleryState { } const initialState: GalleryState = { - selectedImage: undefined, galleryImageMinimumWidth: 64, galleryImageObjectFit: 'cover', shouldAutoSwitchToNewImages: true, @@ -35,10 +34,7 @@ export const gallerySlice = createSlice({ name: 'gallery', initialState, reducers: { - imageSelected: ( - state, - action: PayloadAction - ) => { + imageSelected: (state, action: PayloadAction) => { state.selectedImage = action.payload; // TODO: if the user selects an image, disable the auto switch? // state.shouldAutoSwitchToNewImages = false; @@ -84,16 +80,6 @@ export const gallerySlice = createSlice({ }; } }); - - /** - * Upload Image - FULFILLED - */ - builder.addCase(imageUploaded.fulfilled, (state, action) => { - const { response } = action.payload; - - const uploadedImage = deserializeImageResponse(response); - state.selectedImage = { name: uploadedImage.name, type: 'uploads' }; - }); }, }); diff --git a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts index 26af366e03..056b92887d 100644 --- a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts @@ -73,43 +73,43 @@ const resultsSlice = createSlice({ state.isLoading = false; }); - /** - * Invocation Complete - */ - builder.addCase(invocationComplete, (state, action) => { - const { data, shouldFetchImages } = action.payload; - const { result, node, graph_execution_state_id } = data; + // /** + // * Invocation Complete + // */ + // builder.addCase(invocationComplete, (state, action) => { + // const { data, shouldFetchImages } = action.payload; + // const { result, node, graph_execution_state_id } = data; - if (isImageOutput(result)) { - const name = result.image.image_name; - const type = result.image.image_type; + // if (isImageOutput(result)) { + // const name = result.image.image_name; + // const type = result.image.image_type; - // if we need to refetch, set URLs to placeholder for now - const { url, thumbnail } = shouldFetchImages - ? { url: '', thumbnail: '' } - : buildImageUrls(type, name); + // // if we need to refetch, set URLs to placeholder for now + // const { url, thumbnail } = shouldFetchImages + // ? { url: '', thumbnail: '' } + // : buildImageUrls(type, name); - const timestamp = extractTimestampFromImageName(name); + // const timestamp = extractTimestampFromImageName(name); - const image: Image = { - name, - type, - url, - thumbnail, - metadata: { - created: timestamp, - width: result.width, - height: result.height, - invokeai: { - session_id: graph_execution_state_id, - ...(node ? { node } : {}), - }, - }, - }; + // const image: Image = { + // name, + // type, + // url, + // thumbnail, + // metadata: { + // created: timestamp, + // width: result.width, + // height: result.height, + // invokeai: { + // session_id: graph_execution_state_id, + // ...(node ? { node } : {}), + // }, + // }, + // }; - resultsAdapter.setOne(state, image); - } - }); + // resultsAdapter.setOne(state, image); + // } + // }); /** * Image Received - FULFILLED diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts index bb77844f42..e1d8e5ea8f 100644 --- a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts @@ -35,7 +35,7 @@ const uploadsSlice = createSlice({ name: 'uploads', initialState: initialUploadsState, reducers: { - uploadAdded: uploadsAdapter.addOne, + uploadAdded: uploadsAdapter.upsertOne, }, extraReducers: (builder) => { /** @@ -61,17 +61,6 @@ const uploadsSlice = createSlice({ state.isLoading = false; }); - /** - * Upload Image - FULFILLED - */ - builder.addCase(imageUploaded.fulfilled, (state, action) => { - const { location, response } = action.payload; - - const uploadedImage = deserializeImageResponse(response); - - uploadsAdapter.setOne(state, uploadedImage); - }); - /** * Delete Image - FULFILLED */ diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx index 9682d2eb0b..ddfdb622ef 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx @@ -5,9 +5,9 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import SelectImagePlaceholder from 'common/components/SelectImagePlaceholder'; import { useGetUrl } from 'common/util/getUrl'; import useGetImageByNameAndType from 'features/gallery/hooks/useGetImageByName'; -import { +import generationSlice, { clearInitialImage, - initialImageSelected, + initialImageChanged, } from 'features/parameters/store/generationSlice'; import { addToast } from 'features/system/store/systemSlice'; import { isEqual } from 'lodash-es'; @@ -15,23 +15,26 @@ import { DragEvent, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ImageType } from 'services/api'; import ImageToImageOverlay from 'common/components/ImageToImageOverlay'; -import { initialImageSelector } from 'features/parameters/store/generationSelectors'; +import { + generationSelector, + initialImageSelector, +} from 'features/parameters/store/generationSelectors'; +import { initialImageSelected } from 'features/parameters/store/actions'; const selector = createSelector( - [initialImageSelector], - (initialImage) => { + [generationSelector], + (generation) => { + const { initialImage, isImageToImageEnabled } = generation; return { initialImage, + isImageToImageEnabled, }; }, { memoizeOptions: { resultEqualityCheck: isEqual } } ); const InitialImagePreview = () => { - const isImageToImageEnabled = useAppSelector( - (state: RootState) => state.generation.isImageToImageEnabled - ); - const { initialImage } = useAppSelector(selector); + const { initialImage, isImageToImageEnabled } = useAppSelector(selector); const { getUrl } = useGetUrl(); const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -55,22 +58,13 @@ const InitialImagePreview = () => { const handleDrop = useCallback( (e: DragEvent) => { setIsLoaded(false); + const name = e.dataTransfer.getData('invokeai/imageName'); const type = e.dataTransfer.getData('invokeai/imageType') as ImageType; - if (!name || !type) { - return; - } - - const image = getImageByNameAndType(name, type); - - if (!image) { - return; - } - dispatch(initialImageSelected({ name, type })); }, - [getImageByNameAndType, dispatch] + [dispatch] ); return ( diff --git a/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts b/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts index 7c45f159b2..66f9287c1f 100644 --- a/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts +++ b/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts @@ -4,9 +4,11 @@ import { isFinite, isString } from 'lodash-es'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import useSetBothPrompts from './usePrompt'; -import { initialImageSelected, setSeed } from '../store/generationSlice'; +import { initialImageChanged, setSeed } from '../store/generationSlice'; import { isImage, isImageField } from 'services/types/guards'; import { NUMPY_RAND_MAX } from 'app/constants'; +import { initialImageSelected } from '../store/actions'; +import { Image } from 'app/types/invokeai'; export const useParameters = () => { const dispatch = useAppDispatch(); @@ -86,7 +88,7 @@ export const useParameters = () => { } dispatch( - initialImageSelected({ name: image.image_name, type: image.image_type }) + initialImageChanged({ name: image.image_name, type: image.image_type }) ); toast({ title: t('toast.initialImageSet'), @@ -102,27 +104,10 @@ export const useParameters = () => { * Sets image as initial image with toast */ const sendToImageToImage = useCallback( - (image: unknown) => { - if (!isImage(image)) { - toast({ - title: t('toast.imageNotLoaded'), - description: t('toast.imageNotLoadedDesc'), - status: 'warning', - duration: 2500, - isClosable: true, - }); - return; - } - + (image: Image) => { dispatch(initialImageSelected({ name: image.name, type: image.type })); - toast({ - title: t('toast.sentToImageToImage'), - status: 'info', - duration: 2500, - isClosable: true, - }); }, - [t, toast, dispatch] + [dispatch] ); return { recallPrompt, recallSeed, recallInitialImage, sendToImageToImage }; diff --git a/invokeai/frontend/web/src/features/parameters/store/actions.ts b/invokeai/frontend/web/src/features/parameters/store/actions.ts new file mode 100644 index 0000000000..4b261d7783 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/store/actions.ts @@ -0,0 +1,12 @@ +import { createAction } from '@reduxjs/toolkit'; +import { Image } from 'app/types/invokeai'; +import { ImageType } from 'services/api'; + +export type SelectedImage = { + name: string; + type: ImageType; +}; + +export const initialImageSelected = createAction< + Image | SelectedImage | undefined +>('generation/initialImageSelected'); diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 9d9d689cb0..d889dd88e1 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -7,17 +7,12 @@ import { seedWeightsToString } from 'common/util/seedWeightPairs'; import { clamp } from 'lodash-es'; import { ImageField, ImageType } from 'services/api'; -export type SelectedImage = { - name: string; - type: ImageType; -}; - export interface GenerationState { cfgScale: number; height: number; img2imgStrength: number; infillMethod: string; - initialImage?: SelectedImage; // can be an Image or url + initialImage?: InvokeAI.Image; // can be an Image or url iterations: number; maskPath: string; perlin: number; @@ -351,7 +346,7 @@ export const generationSlice = createSlice({ setVerticalSymmetrySteps: (state, action: PayloadAction) => { state.verticalSymmetrySteps = action.payload; }, - initialImageSelected: (state, action: PayloadAction) => { + initialImageChanged: (state, action: PayloadAction) => { state.initialImage = action.payload; state.isImageToImageEnabled = true; }, @@ -399,7 +394,7 @@ export const { setShouldUseSymmetry, setHorizontalSymmetrySteps, setVerticalSymmetrySteps, - initialImageSelected, + initialImageChanged, isImageToImageEnabledChanged, } = generationSlice.actions; diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 7d8d4978a0..75aa758198 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -15,7 +15,7 @@ import { } from 'services/events/actions'; import { ProgressImage } from 'services/events/types'; -import { initialImageSelected } from 'features/parameters/store/generationSlice'; +import { initialImageChanged } from 'features/parameters/store/generationSlice'; import { makeToast } from '../hooks/useToastWatcher'; import { sessionCanceled, sessionInvoked } from 'services/thunks/session'; import { receivedModels } from 'services/thunks/model'; @@ -434,13 +434,6 @@ export const systemSlice = createSlice({ state.statusTranslationKey = 'common.statusConnected'; }); - /** - * Initial Image Selected - */ - builder.addCase(initialImageSelected, (state) => { - state.toastQueue.push(makeToast(t('toast.sentToImageToImage'))); - }); - /** * Received available models from the backend */ diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeWorkarea.tsx index f59028c8ca..a9701bfee7 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeWorkarea.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeWorkarea.tsx @@ -1,7 +1,7 @@ import { Box, BoxProps, Grid, GridItem } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { initialImageSelected } from 'features/parameters/store/generationSlice'; +import { initialImageChanged } from 'features/parameters/store/generationSlice'; import { activeTabNameSelector, uiSelector, diff --git a/invokeai/frontend/web/src/services/api/models/DataURLToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/DataURLToImageInvocation.ts index 86b49e6cab..b1e35d9e0c 100644 --- a/invokeai/frontend/web/src/services/api/models/DataURLToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/DataURLToImageInvocation.ts @@ -3,7 +3,7 @@ /* eslint-disable */ /** - * Outputs an image from a base 64 data URL. + * Outputs an image from a data URL. */ export type DataURLToImageInvocation = { /** diff --git a/invokeai/frontend/web/src/services/api/schemas/$DataURLToImageInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$DataURLToImageInvocation.ts index 34a9fa23c0..f875cd0a11 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$DataURLToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$DataURLToImageInvocation.ts @@ -2,7 +2,7 @@ /* tslint:disable */ /* eslint-disable */ export const $DataURLToImageInvocation = { - description: `Outputs an image from a base 64 data URL.`, + description: `Outputs an image from a data URL.`, properties: { id: { type: 'string', diff --git a/invokeai/frontend/web/src/services/events/actions.ts b/invokeai/frontend/web/src/services/events/actions.ts index 84268773a9..192061aa73 100644 --- a/invokeai/frontend/web/src/services/events/actions.ts +++ b/invokeai/frontend/web/src/services/events/actions.ts @@ -1,4 +1,4 @@ -import { createAction } from '@reduxjs/toolkit'; +import { AnyAction, createAction } from '@reduxjs/toolkit'; import { GeneratorProgressEvent, GraphExecutionStateCompleteEvent, diff --git a/invokeai/frontend/web/src/services/events/middleware.ts b/invokeai/frontend/web/src/services/events/middleware.ts index 65652ae7ee..bc2e180da8 100644 --- a/invokeai/frontend/web/src/services/events/middleware.ts +++ b/invokeai/frontend/web/src/services/events/middleware.ts @@ -5,20 +5,14 @@ import { ClientToServerEvents, ServerToClientEvents, } from 'services/events/types'; -import { - invocationComplete, - socketSubscribed, - socketUnsubscribed, -} from './actions'; -import { AppDispatch, RootState } from 'app/store/store'; +import { socketSubscribed, socketUnsubscribed } from './actions'; +import { AppThunkDispatch, RootState } from 'app/store/store'; import { getTimestamp } from 'common/util/getTimestamp'; import { sessionInvoked, isFulfilledSessionCreatedAction, } from 'services/thunks/session'; import { OpenAPI } from 'services/api'; -import { isImageOutput } from 'services/types/guards'; -import { imageReceived, thumbnailReceived } from 'services/thunks/image'; import { setEventListeners } from 'services/events/util/setEventListeners'; import { log } from 'app/logging/useLogger'; @@ -56,13 +50,15 @@ export const socketMiddleware = () => { ); const middleware: Middleware = - (store: MiddlewareAPI) => (next) => (action) => { - const { dispatch, getState } = store; + (storeApi: MiddlewareAPI) => + (next) => + (action) => { + const { dispatch, getState } = storeApi; // Set listeners for `connect` and `disconnect` events once // Must happen in middleware to get access to `dispatch` if (!areListenersSet) { - setEventListeners({ store, socket, log: socketioLog }); + setEventListeners({ storeApi, socket, log: socketioLog }); areListenersSet = true; @@ -107,26 +103,6 @@ export const socketMiddleware = () => { dispatch(sessionInvoked({ sessionId })); } - if (invocationComplete.match(action)) { - const { config } = getState(); - - if (config.shouldFetchImages) { - const { result } = action.payload.data; - if (isImageOutput(result)) { - const imageName = result.image.image_name; - const imageType = result.image.image_type; - - dispatch(imageReceived({ imageName, imageType })); - dispatch( - thumbnailReceived({ - thumbnailName: imageName, - thumbnailType: imageType, - }) - ); - } - } - } - next(action); }; diff --git a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts index 581363e446..1cd06c51ff 100644 --- a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts +++ b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts @@ -27,13 +27,13 @@ import { addToast } from '../../../features/system/store/systemSlice'; type SetEventListenersArg = { socket: Socket; - store: MiddlewareAPI; + storeApi: MiddlewareAPI; log: Logger; }; export const setEventListeners = (arg: SetEventListenersArg) => { - const { socket, store, log } = arg; - const { dispatch, getState } = store; + const { socket, storeApi, log } = arg; + const { dispatch, getState } = storeApi; /** * Connect diff --git a/invokeai/frontend/web/tsconfig.json b/invokeai/frontend/web/tsconfig.json index 8276f461eb..fa8f1fd262 100644 --- a/invokeai/frontend/web/tsconfig.json +++ b/invokeai/frontend/web/tsconfig.json @@ -20,7 +20,12 @@ "*": ["./src/*"] } }, - "include": ["src/**/*.ts", "src/**/*.tsx", "*.d.ts"], + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "*.d.ts", + "src/app/store/middleware/listenerMiddleware" + ], "exclude": ["src/services/fixtures/*", "node_modules", "dist"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/invokeai/frontend/web/yarn.lock b/invokeai/frontend/web/yarn.lock index 59a2607db7..3901903bd4 100644 --- a/invokeai/frontend/web/yarn.lock +++ b/invokeai/frontend/web/yarn.lock @@ -1836,7 +1836,7 @@ resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249" integrity sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA== -"@types/hoist-non-react-statics@^3.3.1": +"@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== @@ -1907,6 +1907,16 @@ dependencies: "@types/react" "*" +"@types/react-redux@^7.1.25": + version "7.1.25" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88" + integrity sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + "@types/react-transition-group@^4.4.5": version "4.4.5" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416" @@ -5687,7 +5697,7 @@ redux-thunk@^2.4.2: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b" integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q== -redux@^4.2.1: +redux@^4.0.0, redux@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== From ed1f096a6fff1adf2614e7956cbf11f9a0bf2a28 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 May 2023 23:38:00 +1000 Subject: [PATCH 08/66] feat(ui): wip canvas migration 4 --- .../middleware/listenerMiddleware/index.ts | 62 +++++++------ .../listeners/imageDeleted.ts | 59 +++++++++++++ .../listeners/imageUploaded.ts | 22 +++++ .../listeners/imageUploadedListener.ts | 19 ---- .../listeners/initialImageListener.ts | 53 ----------- .../listeners/initialImageSelected.ts | 54 ++++++++++++ .../listeners/invocationComplete.ts | 88 +++++++++++++++++++ .../listeners/invocationCompleteListener.ts | 75 ---------------- .../components/IAICanvasStagingArea.tsx | 10 ++- .../src/features/canvas/store/canvasSlice.ts | 48 +++++++--- .../src/features/canvas/store/canvasTypes.ts | 8 +- .../components/CurrentImageButtons.tsx | 4 +- .../gallery/components/HoverableImage.tsx | 9 +- .../web/src/features/gallery/store/actions.ts | 7 ++ .../features/gallery/store/gallerySlice.ts | 18 ---- .../features/gallery/store/resultsSlice.ts | 49 +---------- .../features/gallery/store/uploadsSlice.ts | 5 +- .../parameters/hooks/useParameters.ts | 2 +- .../web/src/services/events/middleware.ts | 3 +- .../frontend/web/src/services/thunks/image.ts | 54 +----------- 20 files changed, 336 insertions(+), 313 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts delete mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploadedListener.ts delete mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageListener.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/invocationComplete.ts delete mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/invocationCompleteListener.ts create mode 100644 invokeai/frontend/web/src/features/gallery/store/actions.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 563f12ba02..603a6ca423 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -7,14 +7,20 @@ import { import type { TypedStartListening, TypedAddListener } from '@reduxjs/toolkit'; import type { RootState, AppDispatch } from '../../store'; -import { initialImageSelected } from 'features/parameters/store/actions'; -import { initialImageListener } from './listeners/initialImageListener'; +import { addInitialImageSelectedListener } from './listeners/initialImageSelected'; +import { addImageResultReceivedListener } from './listeners/invocationComplete'; +import { addImageUploadedListener } from './listeners/imageUploaded'; +import { addRequestedImageDeletionListener } from './listeners/imageDeleted'; import { - imageResultReceivedListener, - imageResultReceivedPrediate, -} from './listeners/invocationCompleteListener'; -import { imageUploaded } from 'services/thunks/image'; -import { imageUploadedListener } from './listeners/imageUploadedListener'; + canvasGraphBuilt, + sessionCreated, + sessionInvoked, +} from 'services/thunks/session'; +import { tabMap } from 'features/ui/store/tabMap'; +import { + canvasSessionIdChanged, + stagingAreaInitialized, +} from 'features/canvas/store/canvasSlice'; export const listenerMiddleware = createListenerMiddleware(); @@ -34,26 +40,30 @@ export type AppListenerEffect = ListenerEffect< AppDispatch >; -/** - * Initial image selected - */ -startAppListening({ - actionCreator: initialImageSelected, - effect: initialImageListener, -}); +addImageUploadedListener(); +addInitialImageSelectedListener(); +addImageResultReceivedListener(); +addRequestedImageDeletionListener(); -/** - * Image Result received - */ startAppListening({ - predicate: imageResultReceivedPrediate, - effect: imageResultReceivedListener, -}); + actionCreator: canvasGraphBuilt.fulfilled, + effect: async (action, { dispatch, getState, condition, fork, take }) => { + const [{ meta }] = await take(sessionInvoked.fulfilled.match); + const { sessionId } = meta.arg; + const state = getState(); -/** - * Image Uploaded - */ -startAppListening({ - actionCreator: imageUploaded.fulfilled, - effect: imageUploadedListener, + if (!state.canvas.layerState.stagingArea.boundingBox) { + dispatch( + stagingAreaInitialized({ + sessionId, + boundingBox: { + ...state.canvas.boundingBoxCoordinates, + ...state.canvas.boundingBoxDimensions, + }, + }) + ); + } + + dispatch(canvasSessionIdChanged(sessionId)); + }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts new file mode 100644 index 0000000000..00cbf86527 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts @@ -0,0 +1,59 @@ +import { requestedImageDeletion } from 'features/gallery/store/actions'; +import { startAppListening } from '..'; +import { imageDeleted } from 'services/thunks/image'; +import { log } from 'app/logging/useLogger'; +import { clamp } from 'lodash-es'; +import { imageSelected } from 'features/gallery/store/gallerySlice'; + +const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' }); + +export const addRequestedImageDeletionListener = () => { + startAppListening({ + actionCreator: requestedImageDeletion, + effect: (action, { dispatch, getState }) => { + const image = action.payload; + if (!image) { + moduleLog.warn('No image provided'); + return; + } + + const { name, type } = image; + + if (type !== 'uploads' && type !== 'results') { + moduleLog.warn({ data: image }, `Invalid image type ${type}`); + return; + } + + const selectedImageName = getState().gallery.selectedImage?.name; + + if (selectedImageName === name) { + const allIds = getState()[type].ids; + const allEntities = getState()[type].entities; + + const deletedImageIndex = allIds.findIndex( + (result) => result.toString() === name + ); + + const filteredIds = allIds.filter((id) => id.toString() !== name); + + const newSelectedImageIndex = clamp( + deletedImageIndex, + 0, + filteredIds.length - 1 + ); + + const newSelectedImageId = filteredIds[newSelectedImageIndex]; + + const newSelectedImage = allEntities[newSelectedImageId]; + + if (newSelectedImageId) { + dispatch(imageSelected(newSelectedImage)); + } else { + dispatch(imageSelected()); + } + } + + dispatch(imageDeleted({ imageName: name, imageType: type })); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts new file mode 100644 index 0000000000..7d578356f4 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -0,0 +1,22 @@ +import { deserializeImageResponse } from 'services/util/deserializeImageResponse'; +import { startAppListening } from '..'; +import { uploadAdded } from 'features/gallery/store/uploadsSlice'; +import { imageSelected } from 'features/gallery/store/gallerySlice'; +import { imageUploaded } from 'services/thunks/image'; + +export const addImageUploadedListener = () => { + startAppListening({ + actionCreator: imageUploaded.fulfilled, + effect: (action, { dispatch, getState }) => { + const { response } = action.payload; + const state = getState(); + const image = deserializeImageResponse(response); + + dispatch(uploadAdded(image)); + + if (state.gallery.shouldAutoSwitchToNewImages) { + dispatch(imageSelected(image)); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploadedListener.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploadedListener.ts deleted file mode 100644 index 5ae24f315b..0000000000 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploadedListener.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { deserializeImageResponse } from 'services/util/deserializeImageResponse'; -import { AppListenerEffect } from '..'; -import { uploadAdded } from 'features/gallery/store/uploadsSlice'; -import { imageSelected } from 'features/gallery/store/gallerySlice'; - -export const imageUploadedListener: AppListenerEffect = ( - action, - { dispatch, getState } -) => { - const { response } = action.payload; - const state = getState(); - const image = deserializeImageResponse(response); - - dispatch(uploadAdded(image)); - - if (state.gallery.shouldAutoSwitchToNewImages) { - dispatch(imageSelected(image)); - } -}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageListener.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageListener.ts deleted file mode 100644 index 231e1153ed..0000000000 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageListener.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { initialImageChanged } from 'features/parameters/store/generationSlice'; -import { Image, isInvokeAIImage } from 'app/types/invokeai'; -import { selectResultsById } from 'features/gallery/store/resultsSlice'; -import { selectUploadsById } from 'features/gallery/store/uploadsSlice'; -import { makeToast } from 'features/system/hooks/useToastWatcher'; -import { t } from 'i18next'; -import { addToast } from 'features/system/store/systemSlice'; -import { AnyAction, ListenerEffect } from '@reduxjs/toolkit'; -import { AppDispatch, RootState } from 'app/store/store'; - -export const initialImageListener: ListenerEffect< - AnyAction, - RootState, - AppDispatch -> = (action, { getState, dispatch }) => { - if (!action.payload) { - dispatch( - addToast( - makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' }) - ) - ); - return; - } - - if (isInvokeAIImage(action.payload)) { - dispatch(initialImageChanged(action.payload)); - dispatch(addToast(makeToast(t('toast.sentToImageToImage')))); - return; - } - - const { name, type } = action.payload; - - let image: Image | undefined; - const state = getState(); - - if (type === 'results') { - image = selectResultsById(state, name); - } else if (type === 'uploads') { - image = selectUploadsById(state, name); - } - - if (!image) { - dispatch( - addToast( - makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' }) - ) - ); - return; - } - - dispatch(initialImageChanged(image)); - dispatch(addToast(makeToast(t('toast.sentToImageToImage')))); -}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts new file mode 100644 index 0000000000..6bc2f9e9bc --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts @@ -0,0 +1,54 @@ +import { initialImageChanged } from 'features/parameters/store/generationSlice'; +import { Image, isInvokeAIImage } from 'app/types/invokeai'; +import { selectResultsById } from 'features/gallery/store/resultsSlice'; +import { selectUploadsById } from 'features/gallery/store/uploadsSlice'; +import { makeToast } from 'features/system/hooks/useToastWatcher'; +import { t } from 'i18next'; +import { addToast } from 'features/system/store/systemSlice'; +import { startAppListening } from '..'; +import { initialImageSelected } from 'features/parameters/store/actions'; + +export const addInitialImageSelectedListener = () => { + startAppListening({ + actionCreator: initialImageSelected, + effect: (action, { getState, dispatch }) => { + if (!action.payload) { + dispatch( + addToast( + makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' }) + ) + ); + return; + } + + if (isInvokeAIImage(action.payload)) { + dispatch(initialImageChanged(action.payload)); + dispatch(addToast(makeToast(t('toast.sentToImageToImage')))); + return; + } + + const { name, type } = action.payload; + + let image: Image | undefined; + const state = getState(); + + if (type === 'results') { + image = selectResultsById(state, name); + } else if (type === 'uploads') { + image = selectUploadsById(state, name); + } + + if (!image) { + dispatch( + addToast( + makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' }) + ) + ); + return; + } + + dispatch(initialImageChanged(image)); + dispatch(addToast(makeToast(t('toast.sentToImageToImage')))); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/invocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/invocationComplete.ts new file mode 100644 index 0000000000..9d84b2cbf0 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/invocationComplete.ts @@ -0,0 +1,88 @@ +import { invocationComplete } from 'services/events/actions'; +import { isImageOutput } from 'services/types/guards'; +import { + buildImageUrls, + extractTimestampFromImageName, +} from 'services/util/deserializeImageField'; +import { Image } from 'app/types/invokeai'; +import { resultAdded } from 'features/gallery/store/resultsSlice'; +import { imageReceived, thumbnailReceived } from 'services/thunks/image'; +import { startAppListening } from '..'; +import { imageSelected } from 'features/gallery/store/gallerySlice'; +import { addImageToStagingArea } from 'features/canvas/store/canvasSlice'; + +const nodeDenylist = ['dataURL_image']; + +export const addImageResultReceivedListener = () => { + startAppListening({ + predicate: (action) => { + if ( + invocationComplete.match(action) && + isImageOutput(action.payload.data.result) + ) { + return true; + } + return false; + }, + effect: (action, { getState, dispatch }) => { + if (!invocationComplete.match(action)) { + return; + } + + const { data, shouldFetchImages } = action.payload; + const { result, node, graph_execution_state_id } = data; + + if (isImageOutput(result) && !nodeDenylist.includes(node.type)) { + const name = result.image.image_name; + const type = result.image.image_type; + const state = getState(); + + // if we need to refetch, set URLs to placeholder for now + const { url, thumbnail } = shouldFetchImages + ? { url: '', thumbnail: '' } + : buildImageUrls(type, name); + + const timestamp = extractTimestampFromImageName(name); + + const image: Image = { + name, + type, + url, + thumbnail, + metadata: { + created: timestamp, + width: result.width, + height: result.height, + invokeai: { + session_id: graph_execution_state_id, + ...(node ? { node } : {}), + }, + }, + }; + + dispatch(resultAdded(image)); + + if (state.gallery.shouldAutoSwitchToNewImages) { + dispatch(imageSelected(image)); + } + + if (state.config.shouldFetchImages) { + dispatch(imageReceived({ imageName: name, imageType: type })); + dispatch( + thumbnailReceived({ + thumbnailName: name, + thumbnailType: type, + }) + ); + } + + if ( + graph_execution_state_id === + state.canvas.layerState.stagingArea.sessionId + ) { + dispatch(addImageToStagingArea(image)); + } + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/invocationCompleteListener.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/invocationCompleteListener.ts deleted file mode 100644 index 4b6c2e060b..0000000000 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/invocationCompleteListener.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { AnyListenerPredicate } from '@reduxjs/toolkit'; -import { invocationComplete } from 'services/events/actions'; -import { isImageOutput } from 'services/types/guards'; -import { RootState } from 'app/store/store'; -import { - buildImageUrls, - extractTimestampFromImageName, -} from 'services/util/deserializeImageField'; -import { Image } from 'app/types/invokeai'; -import { resultAdded } from 'features/gallery/store/resultsSlice'; -import { imageReceived, thumbnailReceived } from 'services/thunks/image'; -import { AppListenerEffect } from '..'; - -export const imageResultReceivedPrediate: AnyListenerPredicate = ( - action, - _currentState, - _originalState -) => { - if ( - invocationComplete.match(action) && - isImageOutput(action.payload.data.result) - ) { - return true; - } - return false; -}; - -export const imageResultReceivedListener: AppListenerEffect = ( - action, - { getState, dispatch } -) => { - const { data, shouldFetchImages } = action.payload; - const { result, node, graph_execution_state_id } = data; - - if (isImageOutput(result)) { - const name = result.image.image_name; - const type = result.image.image_type; - const state = getState(); - - // if we need to refetch, set URLs to placeholder for now - const { url, thumbnail } = shouldFetchImages - ? { url: '', thumbnail: '' } - : buildImageUrls(type, name); - - const timestamp = extractTimestampFromImageName(name); - - const image: Image = { - name, - type, - url, - thumbnail, - metadata: { - created: timestamp, - width: result.width, - height: result.height, - invokeai: { - session_id: graph_execution_state_id, - ...(node ? { node } : {}), - }, - }, - }; - - dispatch(resultAdded(image)); - - if (state.config.shouldFetchImages) { - dispatch(imageReceived({ imageName: name, imageType: type })); - dispatch( - thumbnailReceived({ - thumbnailName: name, - thumbnailType: type, - }) - ); - } - } -}; diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasStagingArea.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasStagingArea.tsx index 7bd4782840..f84a5b0e49 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasStagingArea.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasStagingArea.tsx @@ -12,18 +12,20 @@ const selector = createSelector( [canvasSelector], (canvas) => { const { - layerState: { - stagingArea: { images, selectedImageIndex }, - }, + layerState, shouldShowStagingImage, shouldShowStagingOutline, boundingBoxCoordinates: { x, y }, boundingBoxDimensions: { width, height }, } = canvas; + const { selectedImageIndex, images } = layerState.stagingArea; + return { currentStagingAreaImage: - images.length > 0 ? images[selectedImageIndex] : undefined, + images.length > 0 && selectedImageIndex !== undefined + ? images[selectedImageIndex] + : undefined, isOnFirstImage: selectedImageIndex === 0, isOnLastImage: selectedImageIndex === images.length - 1, shouldShowStagingImage, diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index 5eaca8274f..438f4df9e1 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -22,6 +22,7 @@ import { CanvasLayer, CanvasLayerState, CanvasMaskLine, + CanvasSession, CanvasState, CanvasTool, Dimensions, @@ -29,6 +30,7 @@ import { isCanvasBaseImage, isCanvasMaskLine, } from './canvasTypes'; +import { stringToArray } from 'konva/lib/shapes/Text'; export const initialLayerState: CanvasLayerState = { objects: [], @@ -285,16 +287,28 @@ export const canvasSlice = createSlice({ setIsMoveStageKeyHeld: (state, action: PayloadAction) => { state.isMoveStageKeyHeld = action.payload; }, - addImageToStagingArea: ( + canvasSessionIdChanged: (state, action: PayloadAction) => { + state.layerState.stagingArea.sessionId = action.payload; + }, + stagingAreaInitialized: ( state, - action: PayloadAction<{ - boundingBox: IRect; - image: InvokeAI.Image; - }> + action: PayloadAction<{ sessionId: string; boundingBox: IRect }> ) => { - const { boundingBox, image } = action.payload; + const { sessionId, boundingBox } = action.payload; - if (!boundingBox || !image) return; + state.layerState.stagingArea = { + boundingBox, + sessionId, + images: [], + selectedImageIndex: -1, + }; + }, + addImageToStagingArea: (state, action: PayloadAction) => { + const image = action.payload; + + if (!image || !state.layerState.stagingArea.boundingBox) { + return; + } state.pastLayerStates.push(cloneDeep(state.layerState)); @@ -305,7 +319,7 @@ export const canvasSlice = createSlice({ state.layerState.stagingArea.images.push({ kind: 'image', layer: 'base', - ...boundingBox, + ...state.layerState.stagingArea.boundingBox, image, }); @@ -321,9 +335,7 @@ export const canvasSlice = createSlice({ state.pastLayerStates.shift(); } - state.layerState.stagingArea = { - ...initialLayerState.stagingArea, - }; + state.layerState.stagingArea = { ...initialLayerState.stagingArea }; state.futureLayerStates = []; state.shouldShowStagingOutline = true; @@ -661,6 +673,10 @@ export const canvasSlice = createSlice({ } }, nextStagingAreaImage: (state) => { + if (!state.layerState.stagingArea.images.length) { + return; + } + const currentIndex = state.layerState.stagingArea.selectedImageIndex; const length = state.layerState.stagingArea.images.length; @@ -670,6 +686,10 @@ export const canvasSlice = createSlice({ ); }, prevStagingAreaImage: (state) => { + if (!state.layerState.stagingArea.images.length) { + return; + } + const currentIndex = state.layerState.stagingArea.selectedImageIndex; state.layerState.stagingArea.selectedImageIndex = Math.max( @@ -678,6 +698,10 @@ export const canvasSlice = createSlice({ ); }, commitStagingAreaImage: (state) => { + if (!state.layerState.stagingArea.images.length) { + return; + } + const { images, selectedImageIndex } = state.layerState.stagingArea; state.pastLayerStates.push(cloneDeep(state.layerState)); @@ -883,6 +907,8 @@ export const { undo, setScaledBoundingBoxDimensions, setShouldRestrictStrokesToBox, + stagingAreaInitialized, + canvasSessionIdChanged, } = canvasSlice.actions; export default canvasSlice.reducer; diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts index df24e9ee4d..9194f065b2 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts @@ -90,9 +90,16 @@ export type CanvasLayerState = { stagingArea: { images: CanvasImage[]; selectedImageIndex: number; + sessionId?: string; + boundingBox?: IRect; }; }; +export type CanvasSession = { + sessionId: string; + boundingBox: IRect; +}; + // type guards export const isCanvasMaskLine = (obj: CanvasObject): obj is CanvasMaskLine => obj.kind === 'line' && obj.layer === 'mask'; @@ -162,5 +169,4 @@ export interface CanvasState { stageDimensions: Dimensions; stageScale: number; tool: CanvasTool; - pendingBoundingBox?: IRect; } diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index 94f54d1f3e..31f36a3761 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -66,9 +66,9 @@ import { useCallback } from 'react'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import { useGetUrl } from 'common/util/getUrl'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; -import { imageDeleted } from 'services/thunks/image'; import { useParameters } from 'features/parameters/hooks/useParameters'; import { initialImageSelected } from 'features/parameters/store/actions'; +import { requestedImageDeletion } from '../store/actions'; const currentImageButtonsSelector = createSelector( [ @@ -376,7 +376,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { const handleDelete = useCallback(() => { if (canDeleteImage && image) { - dispatch(imageDeleted({ imageType: image.type, imageName: image.name })); + dispatch(requestedImageDeletion(image)); } }, [image, canDeleteImage, dispatch]); diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index ba100ecacc..52bb6856e1 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -28,7 +28,6 @@ import IAIIconButton from 'common/components/IAIIconButton'; import { useGetUrl } from 'common/util/getUrl'; import { ExternalLinkIcon } from '@chakra-ui/icons'; import { IoArrowUndoCircleOutline } from 'react-icons/io5'; -import { imageDeleted } from 'services/thunks/image'; import { createSelector } from '@reduxjs/toolkit'; import { systemSelector } from 'features/system/store/systemSelectors'; import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; @@ -36,6 +35,8 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { isEqual } from 'lodash-es'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useParameters } from 'features/parameters/hooks/useParameters'; +import { initialImageSelected } from 'features/parameters/store/actions'; +import { requestedImageDeletion } from '../store/actions'; export const selector = createSelector( [gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector], @@ -115,7 +116,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { // Immediately deletes an image const handleDelete = useCallback(() => { if (canDeleteImage && image) { - dispatch(imageDeleted({ imageType: image.type, imageName: image.name })); + dispatch(requestedImageDeletion(image)); } }, [dispatch, image, canDeleteImage]); @@ -151,8 +152,8 @@ const HoverableImage = memo((props: HoverableImageProps) => { }, [image, recallSeed]); const handleSendToImageToImage = useCallback(() => { - sendToImageToImage(image); - }, [image, sendToImageToImage]); + dispatch(initialImageSelected(image)); + }, [dispatch, image]); const handleRecallInitialImage = useCallback(() => { recallInitialImage(image.metadata.invokeai?.node?.image); diff --git a/invokeai/frontend/web/src/features/gallery/store/actions.ts b/invokeai/frontend/web/src/features/gallery/store/actions.ts new file mode 100644 index 0000000000..55c974b169 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/store/actions.ts @@ -0,0 +1,7 @@ +import { createAction } from '@reduxjs/toolkit'; +import { Image } from 'app/types/invokeai'; +import { SelectedImage } from 'features/parameters/store/actions'; + +export const requestedImageDeletion = createAction< + Image | SelectedImage | undefined +>('gallery/requestedImageDeletion'); diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index 4a56eb68d7..630bd0d6b3 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -1,9 +1,5 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import { invocationComplete } from 'services/events/actions'; -import { isImageOutput } from 'services/types/guards'; -import { deserializeImageResponse } from 'services/util/deserializeImageResponse'; -import { imageUploaded } from 'services/thunks/image'; import { Image } from 'app/types/invokeai'; type GalleryImageObjectFitType = 'contain' | 'cover'; @@ -67,20 +63,6 @@ export const gallerySlice = createSlice({ state.shouldUseSingleGalleryColumn = action.payload; }, }, - extraReducers(builder) { - /** - * Invocation Complete - */ - builder.addCase(invocationComplete, (state, action) => { - const { data } = action.payload; - if (isImageOutput(data.result) && state.shouldAutoSwitchToNewImages) { - state.selectedImage = { - name: data.result.image.image_name, - type: 'results', - }; - } - }); - }, }); export const { diff --git a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts index 056b92887d..f1286137a9 100644 --- a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts @@ -1,17 +1,11 @@ import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; import { Image } from 'app/types/invokeai'; -import { invocationComplete } from 'services/events/actions'; import { RootState } from 'app/store/store'; import { receivedResultImagesPage, IMAGES_PER_PAGE, } from 'services/thunks/gallery'; -import { isImageOutput } from 'services/types/guards'; -import { - buildImageUrls, - extractTimestampFromImageName, -} from 'services/util/deserializeImageField'; import { deserializeImageResponse } from 'services/util/deserializeImageResponse'; import { imageDeleted, @@ -73,44 +67,6 @@ const resultsSlice = createSlice({ state.isLoading = false; }); - // /** - // * Invocation Complete - // */ - // builder.addCase(invocationComplete, (state, action) => { - // const { data, shouldFetchImages } = action.payload; - // const { result, node, graph_execution_state_id } = data; - - // if (isImageOutput(result)) { - // const name = result.image.image_name; - // const type = result.image.image_type; - - // // if we need to refetch, set URLs to placeholder for now - // const { url, thumbnail } = shouldFetchImages - // ? { url: '', thumbnail: '' } - // : buildImageUrls(type, name); - - // const timestamp = extractTimestampFromImageName(name); - - // const image: Image = { - // name, - // type, - // url, - // thumbnail, - // metadata: { - // created: timestamp, - // width: result.width, - // height: result.height, - // invokeai: { - // session_id: graph_execution_state_id, - // ...(node ? { node } : {}), - // }, - // }, - // }; - - // resultsAdapter.setOne(state, image); - // } - // }); - /** * Image Received - FULFILLED */ @@ -142,9 +98,10 @@ const resultsSlice = createSlice({ }); /** - * Delete Image - FULFILLED + * Delete Image - PENDING + * Pre-emptively remove the image from the gallery */ - builder.addCase(imageDeleted.fulfilled, (state, action) => { + builder.addCase(imageDeleted.pending, (state, action) => { const { imageType, imageName } = action.meta.arg; if (imageType === 'results') { diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts index e1d8e5ea8f..6e39733f1b 100644 --- a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts @@ -62,9 +62,10 @@ const uploadsSlice = createSlice({ }); /** - * Delete Image - FULFILLED + * Delete Image - pending + * Pre-emptively remove the image from the gallery */ - builder.addCase(imageDeleted.fulfilled, (state, action) => { + builder.addCase(imageDeleted.pending, (state, action) => { const { imageType, imageName } = action.meta.arg; if (imageType === 'uploads') { diff --git a/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts b/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts index 66f9287c1f..23e969b0eb 100644 --- a/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts +++ b/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts @@ -88,7 +88,7 @@ export const useParameters = () => { } dispatch( - initialImageChanged({ name: image.image_name, type: image.image_type }) + initialImageSelected({ name: image.image_name, type: image.image_type }) ); toast({ title: t('toast.initialImageSet'), diff --git a/invokeai/frontend/web/src/services/events/middleware.ts b/invokeai/frontend/web/src/services/events/middleware.ts index bc2e180da8..9ece44665a 100644 --- a/invokeai/frontend/web/src/services/events/middleware.ts +++ b/invokeai/frontend/web/src/services/events/middleware.ts @@ -11,6 +11,7 @@ import { getTimestamp } from 'common/util/getTimestamp'; import { sessionInvoked, isFulfilledSessionCreatedAction, + sessionCreated, } from 'services/thunks/session'; import { OpenAPI } from 'services/api'; import { setEventListeners } from 'services/events/util/setEventListeners'; @@ -65,7 +66,7 @@ export const socketMiddleware = () => { socket.connect(); } - if (isFulfilledSessionCreatedAction(action)) { + if (sessionCreated.fulfilled.match(action)) { const sessionId = action.payload.id; const sessionLog = socketioLog.child({ sessionId }); const oldSessionId = getState().system.sessionId; diff --git a/invokeai/frontend/web/src/services/thunks/image.ts b/invokeai/frontend/web/src/services/thunks/image.ts index a0d8f504b7..de1361be38 100644 --- a/invokeai/frontend/web/src/services/thunks/image.ts +++ b/invokeai/frontend/web/src/services/thunks/image.ts @@ -1,8 +1,5 @@ -import { isFulfilled, isRejected } from '@reduxjs/toolkit'; import { log } from 'app/logging/useLogger'; import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { imageSelected } from 'features/gallery/store/gallerySlice'; -import { clamp, isString } from 'lodash-es'; import { ImagesService } from 'services/api'; import { getHeaders } from 'services/util/getHeaders'; @@ -15,7 +12,7 @@ type ImageReceivedArg = Parameters<(typeof ImagesService)['getImage']>[0]; */ export const imageReceived = createAppAsyncThunk( 'api/imageReceived', - async (arg: ImageReceivedArg, _thunkApi) => { + async (arg: ImageReceivedArg) => { const response = await ImagesService.getImage(arg); imagesLog.info({ arg, response }, 'Received image'); @@ -33,7 +30,7 @@ type ThumbnailReceivedArg = Parameters< */ export const thumbnailReceived = createAppAsyncThunk( 'api/thumbnailReceived', - async (arg: ThumbnailReceivedArg, _thunkApi) => { + async (arg: ThumbnailReceivedArg) => { const response = await ImagesService.getThumbnail(arg); imagesLog.info({ arg, response }, 'Received thumbnail'); @@ -49,7 +46,7 @@ type ImageUploadedArg = Parameters<(typeof ImagesService)['uploadImage']>[0]; */ export const imageUploaded = createAppAsyncThunk( 'api/imageUploaded', - async (arg: ImageUploadedArg, _thunkApi) => { + async (arg: ImageUploadedArg) => { const response = await ImagesService.uploadImage(arg); const { location } = getHeaders(response); @@ -62,11 +59,6 @@ export const imageUploaded = createAppAsyncThunk( } ); -/** - * Function to check if an action is a fulfilled `ImagesService.uploadImage()` thunk - */ -export const isFulfilledImageUploadedAction = isFulfilled(imageUploaded); - type ImageDeletedArg = Parameters<(typeof ImagesService)['deleteImage']>[0]; /** @@ -74,45 +66,7 @@ type ImageDeletedArg = Parameters<(typeof ImagesService)['deleteImage']>[0]; */ export const imageDeleted = createAppAsyncThunk( 'api/imageDeleted', - async (arg: ImageDeletedArg, { getState, dispatch }) => { - const { imageType, imageName } = arg; - - if (imageType !== 'uploads' && imageType !== 'results') { - return; - } - - // TODO: move this logic to another thunk? - // Determine which image should replace the deleted image, if the deleted image is the selected image. - // Unfortunately, we have to do this here, because the resultsSlice and uploadsSlice cannot change - // the selected image. - const selectedImageName = getState().gallery.selectedImage?.name; - - if (selectedImageName === imageName) { - const allIds = getState()[imageType].ids; - - const deletedImageIndex = allIds.findIndex( - (result) => result.toString() === imageName - ); - - const filteredIds = allIds.filter((id) => id.toString() !== imageName); - - const newSelectedImageIndex = clamp( - deletedImageIndex, - 0, - filteredIds.length - 1 - ); - - const newSelectedImageId = filteredIds[newSelectedImageIndex]; - - if (newSelectedImageId) { - dispatch( - imageSelected({ name: newSelectedImageId as string, type: imageType }) - ); - } else { - dispatch(imageSelected()); - } - } - + async (arg: ImageDeletedArg) => { const response = await ImagesService.deleteImage(arg); imagesLog.info( From c7303adb0d821b008e7514c49b3cd7e2cf753e61 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 4 May 2023 13:27:21 +1000 Subject: [PATCH 09/66] feat(ui): fix generation mode logic --- .../web/src/common/util/arrayBuffer.ts | 19 ++- .../canvas/util/dataURLToUint8ClampedArray.ts | 26 +++ .../src/features/canvas/util/generateMask.ts | 157 +++++++++++++----- .../src/features/canvas/util/getCanvasData.ts | 69 +++----- .../features/nodes/util/buildCanvasGraph.ts | 6 +- .../web/src/services/thunks/session.ts | 2 +- 6 files changed, 178 insertions(+), 101 deletions(-) create mode 100644 invokeai/frontend/web/src/features/canvas/util/dataURLToUint8ClampedArray.ts diff --git a/invokeai/frontend/web/src/common/util/arrayBuffer.ts b/invokeai/frontend/web/src/common/util/arrayBuffer.ts index 779d9b1b17..885fc05177 100644 --- a/invokeai/frontend/web/src/common/util/arrayBuffer.ts +++ b/invokeai/frontend/web/src/common/util/arrayBuffer.ts @@ -1,10 +1,11 @@ -export const getImageDataTransparency = (imageData: ImageData) => { +export const getImageDataTransparency = (pixels: Uint8ClampedArray) => { + console.log(pixels); let isFullyTransparent = true; let isPartiallyTransparent = false; - const len = imageData.data.length; + const len = pixels.length; let i = 3; for (i; i < len; i += 4) { - if (imageData.data[i] === 255) { + if (pixels[i] === 255) { isFullyTransparent = false; } else { isPartiallyTransparent = true; @@ -16,15 +17,15 @@ export const getImageDataTransparency = (imageData: ImageData) => { return { isFullyTransparent, isPartiallyTransparent }; }; -export const areAnyPixelsBlack = (imageData: ImageData) => { - const len = imageData.data.length; +export const areAnyPixelsBlack = (pixels: Uint8ClampedArray) => { + const len = pixels.length; let i = 0; for (i; i < len; ) { if ( - imageData.data[i++] === 255 && - imageData.data[i++] === 255 && - imageData.data[i++] === 255 && - imageData.data[i++] === 255 + pixels[i++] === 0 && + pixels[i++] === 0 && + pixels[i++] === 0 && + pixels[i++] === 255 ) { return true; } diff --git a/invokeai/frontend/web/src/features/canvas/util/dataURLToUint8ClampedArray.ts b/invokeai/frontend/web/src/features/canvas/util/dataURLToUint8ClampedArray.ts new file mode 100644 index 0000000000..2652d294fc --- /dev/null +++ b/invokeai/frontend/web/src/features/canvas/util/dataURLToUint8ClampedArray.ts @@ -0,0 +1,26 @@ +export const dataURLToImageData = async ( + dataURL: string, + width: number, + height: number +): Promise => + new Promise((resolve, reject) => { + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + const image = new Image(); + + if (!ctx) { + canvas.remove(); + reject('Unable to get context'); + return; + } + + image.onload = function () { + ctx.drawImage(image, 0, 0); + canvas.remove(); + resolve(ctx.getImageData(0, 0, width, height)); + }; + + image.src = dataURL; + }); diff --git a/invokeai/frontend/web/src/features/canvas/util/generateMask.ts b/invokeai/frontend/web/src/features/canvas/util/generateMask.ts index db88dd1ad3..f3cc1fb237 100644 --- a/invokeai/frontend/web/src/features/canvas/util/generateMask.ts +++ b/invokeai/frontend/web/src/features/canvas/util/generateMask.ts @@ -1,6 +1,108 @@ +// import { CanvasMaskLine } from 'features/canvas/store/canvasTypes'; +// import Konva from 'konva'; +// import { Stage } from 'konva/lib/Stage'; +// import { IRect } from 'konva/lib/types'; + +// /** +// * Generating a mask image from InpaintingCanvas.tsx is not as simple +// * as calling toDataURL() on the canvas, because the mask may be represented +// * by colored lines or transparency, or the user may have inverted the mask +// * display. +// * +// * So we need to regenerate the mask image by creating an offscreen canvas, +// * drawing the mask and compositing everything correctly to output a valid +// * mask image. +// */ +// export const getStageDataURL = (stage: Stage, boundingBox: IRect): string => { +// // create an offscreen canvas and add the mask to it +// // const { stage, offscreenContainer } = buildMaskStage(lines, boundingBox); + +// const dataURL = stage.toDataURL({ ...boundingBox }); + +// // const imageData = stage +// // .toCanvas() +// // .getContext('2d') +// // ?.getImageData( +// // boundingBox.x, +// // boundingBox.y, +// // boundingBox.width, +// // boundingBox.height +// // ); + +// // offscreenContainer.remove(); + +// // return { dataURL, imageData }; + +// return dataURL; +// }; + +// export const getStageImageData = ( +// stage: Stage, +// boundingBox: IRect +// ): ImageData | undefined => { +// const imageData = stage +// .toCanvas() +// .getContext('2d') +// ?.getImageData( +// boundingBox.x, +// boundingBox.y, +// boundingBox.width, +// boundingBox.height +// ); + +// return imageData; +// }; + +// export const buildMaskStage = ( +// lines: CanvasMaskLine[], +// boundingBox: IRect +// ): { stage: Stage; offscreenContainer: HTMLDivElement } => { +// // create an offscreen canvas and add the mask to it +// const { width, height } = boundingBox; + +// const offscreenContainer = document.createElement('div'); + +// const stage = new Konva.Stage({ +// container: offscreenContainer, +// width: width, +// height: height, +// }); + +// const baseLayer = new Konva.Layer(); +// const maskLayer = new Konva.Layer(); + +// // composite the image onto the mask layer +// baseLayer.add( +// new Konva.Rect({ +// ...boundingBox, +// fill: 'white', +// }) +// ); + +// lines.forEach((line) => +// maskLayer.add( +// new Konva.Line({ +// points: line.points, +// stroke: 'black', +// strokeWidth: line.strokeWidth * 2, +// tension: 0, +// lineCap: 'round', +// lineJoin: 'round', +// shadowForStrokeEnabled: false, +// globalCompositeOperation: +// line.tool === 'brush' ? 'source-over' : 'destination-out', +// }) +// ) +// ); + +// stage.add(baseLayer); +// stage.add(maskLayer); + +// return { stage, offscreenContainer }; +// }; + import { CanvasMaskLine } from 'features/canvas/store/canvasTypes'; import Konva from 'konva'; -import { Stage } from 'konva/lib/Stage'; import { IRect } from 'konva/lib/types'; /** @@ -13,50 +115,7 @@ import { IRect } from 'konva/lib/types'; * drawing the mask and compositing everything correctly to output a valid * mask image. */ -export const getStageDataURL = (stage: Stage, boundingBox: IRect): string => { - // create an offscreen canvas and add the mask to it - // const { stage, offscreenContainer } = buildMaskStage(lines, boundingBox); - - const dataURL = stage.toDataURL({ ...boundingBox }); - - // const imageData = stage - // .toCanvas() - // .getContext('2d') - // ?.getImageData( - // boundingBox.x, - // boundingBox.y, - // boundingBox.width, - // boundingBox.height - // ); - - // offscreenContainer.remove(); - - // return { dataURL, imageData }; - - return dataURL; -}; - -export const getStageImageData = ( - stage: Stage, - boundingBox: IRect -): ImageData | undefined => { - const imageData = stage - .toCanvas() - .getContext('2d') - ?.getImageData( - boundingBox.x, - boundingBox.y, - boundingBox.width, - boundingBox.height - ); - - return imageData; -}; - -export const buildMaskStage = ( - lines: CanvasMaskLine[], - boundingBox: IRect -): { stage: Stage; offscreenContainer: HTMLDivElement } => { +const generateMask = (lines: CanvasMaskLine[], boundingBox: IRect): string => { // create an offscreen canvas and add the mask to it const { width, height } = boundingBox; @@ -98,5 +157,11 @@ export const buildMaskStage = ( stage.add(baseLayer); stage.add(maskLayer); - return { stage, offscreenContainer }; + const dataURL = stage.toDataURL({ ...boundingBox }); + + offscreenContainer.remove(); + + return dataURL; }; + +export default generateMask; diff --git a/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts b/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts index c6192f0a09..af4ea42561 100644 --- a/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts +++ b/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts @@ -1,22 +1,18 @@ import { RootState } from 'app/store/store'; import { getCanvasBaseLayer, getCanvasStage } from './konvaInstanceProvider'; import { isCanvasMaskLine } from '../store/canvasTypes'; -import { - buildMaskStage, - getStageDataURL, - getStageImageData, -} from './generateMask'; import { log } from 'app/logging/useLogger'; import { areAnyPixelsBlack, getImageDataTransparency, } from 'common/util/arrayBuffer'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; -import { masks } from 'dateformat'; +import generateMask from './generateMask'; +import { dataURLToImageData } from './dataURLToUint8ClampedArray'; const moduleLog = log.child({ namespace: 'getCanvasDataURLs' }); -export const getCanvasData = (state: RootState) => { +export const getCanvasData = async (state: RootState) => { const canvasBaseLayer = getCanvasBaseLayer(); const canvasStage = getCanvasStage(); @@ -65,57 +61,44 @@ export const getCanvasData = (state: RootState) => { height: boundingBox.height, }; - const { stage: maskStage, offscreenContainer } = buildMaskStage( - isMaskEnabled ? objects.filter(isCanvasMaskLine) : [], - offsetBoundingBox - ); - - const maskDataURL = maskStage.toDataURL(offsetBoundingBox); - - const maskImageData = maskStage - .toCanvas() - .getContext('2d') - ?.getImageData( - offsetBoundingBox.x, - offsetBoundingBox.y, - offsetBoundingBox.width, - offsetBoundingBox.height - ); - - offscreenContainer.remove(); - - if (!maskImageData) { - return; - } - const baseDataURL = canvasBaseLayer.toDataURL(offsetBoundingBox); - const ctx = canvasBaseLayer.getContext(); + canvasBaseLayer.scale(tempScale); - const baseImageData = ctx.getImageData( - offsetBoundingBox.x, - offsetBoundingBox.y, - offsetBoundingBox.width, - offsetBoundingBox.height + const maskDataURL = generateMask( + isMaskEnabled ? objects.filter(isCanvasMaskLine) : [], + boundingBox ); + const baseImageData = await dataURLToImageData( + baseDataURL, + boundingBox.width, + boundingBox.height + ); + + const maskImageData = await dataURLToImageData( + maskDataURL, + boundingBox.width, + boundingBox.height + ); + + console.log('baseImageData', baseImageData); + console.log('maskImageData', maskImageData); + const { isPartiallyTransparent: baseIsPartiallyTransparent, isFullyTransparent: baseIsFullyTransparent, - } = getImageDataTransparency(baseImageData); + } = getImageDataTransparency(baseImageData.data); - // const doesMaskHaveBlackPixels = false; - const doesMaskHaveBlackPixels = areAnyPixelsBlack(maskImageData); + const doesMaskHaveBlackPixels = areAnyPixelsBlack(maskImageData.data); if (state.system.enableImageDebugging) { openBase64ImageInTab([ - { base64: maskDataURL, caption: 'mask sent as init_mask' }, - { base64: baseDataURL, caption: 'image sent as init_img' }, + { base64: maskDataURL, caption: 'mask b64' }, + { base64: baseDataURL, caption: 'image b64' }, ]); } - canvasBaseLayer.scale(tempScale); - // generationParameters.init_img = imageDataURL; // generationParameters.progress_images = false; diff --git a/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts b/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts index 24c057c04a..8182241843 100644 --- a/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts @@ -16,8 +16,10 @@ const moduleLog = log.child({ namespace: 'buildCanvasGraph' }); /** * Builds the Canvas workflow graph. */ -export const buildCanvasGraph = (state: RootState): Graph | undefined => { - const c = getCanvasData(state); +export const buildCanvasGraph = async ( + state: RootState +): Promise => { + const c = await getCanvasData(state); if (!c) { moduleLog.error('Unable to create canvas graph'); diff --git a/invokeai/frontend/web/src/services/thunks/session.ts b/invokeai/frontend/web/src/services/thunks/session.ts index c92b303ff7..8e0b59b1d4 100644 --- a/invokeai/frontend/web/src/services/thunks/session.ts +++ b/invokeai/frontend/web/src/services/thunks/session.ts @@ -47,7 +47,7 @@ export const canvasGraphBuilt = createAppAsyncThunk( 'api/canvasGraphBuilt', async (_, { dispatch, getState, rejectWithValue }) => { try { - const graph = buildCanvasGraph(getState()); + const graph = await buildCanvasGraph(getState()); dispatch(sessionCreated({ graph })); return graph; } catch (err: any) { From 5e09dd380dce3c14e86bb06ef647f5359f9297c9 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 4 May 2023 14:10:03 +1000 Subject: [PATCH 10/66] Revert "feat(nodes): free gpu mem after invocation" This reverts commit 99cb33f477306d5dcc455efe04053ce41b8d85bd. --- invokeai/app/services/processor.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/invokeai/app/services/processor.py b/invokeai/app/services/processor.py index 0c2ad28bf5..35cbcd5068 100644 --- a/invokeai/app/services/processor.py +++ b/invokeai/app/services/processor.py @@ -1,15 +1,11 @@ -import gc import traceback from threading import Event, Thread, BoundedSemaphore -import torch - from ..invocations.baseinvocation import InvocationContext from .invocation_queue import InvocationQueueItem from .invoker import InvocationProcessorABC, Invoker from ..models.exceptions import CanceledException - class DefaultInvocationProcessor(InvocationProcessorABC): __invoker_thread: Thread __stop_event: Event @@ -26,7 +22,9 @@ class DefaultInvocationProcessor(InvocationProcessorABC): target=self.__process, kwargs=dict(stop_event=self.__stop_event), ) - self.__invoker_thread.daemon = True # TODO: make async and do not use threads + self.__invoker_thread.daemon = ( + True # TODO: make async and do not use threads + ) self.__invoker_thread.start() def stop(self, *args, **kwargs) -> None: @@ -50,15 +48,13 @@ class DefaultInvocationProcessor(InvocationProcessorABC): ) # get the source node id to provide to clients (the prepared node id is not as useful) - source_node_id = graph_execution_state.prepared_source_mapping[ - invocation.id - ] + source_node_id = graph_execution_state.prepared_source_mapping[invocation.id] # Send starting event self.__invoker.services.events.emit_invocation_started( graph_execution_state_id=graph_execution_state.id, node=invocation.dict(), - source_node_id=source_node_id, + source_node_id=source_node_id ) # Invoke @@ -118,12 +114,11 @@ class DefaultInvocationProcessor(InvocationProcessorABC): ) pass - finally: - gc.collect() - torch.cuda.empty_cache() # Check queue to see if this is canceled, and skip if so - if self.__invoker.services.queue.is_canceled(graph_execution_state.id): + if self.__invoker.services.queue.is_canceled( + graph_execution_state.id + ): continue # Queue any further commands if invoking all From 0b49997bb6c3a6c681270aae5fc79c15727df5ed Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 4 May 2023 22:07:14 +1000 Subject: [PATCH 11/66] feat(nodes): allow uploaded images to be any ImageType (eg intermediates) --- invokeai/app/api/routers/images.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/invokeai/app/api/routers/images.py b/invokeai/app/api/routers/images.py index 6220e98ca2..0b7891e0f2 100644 --- a/invokeai/app/api/routers/images.py +++ b/invokeai/app/api/routers/images.py @@ -83,7 +83,7 @@ async def get_thumbnail( status_code=201, ) async def upload_image( - file: UploadFile, request: Request, response: Response + file: UploadFile, image_type: ImageType, request: Request, response: Response ) -> ImageResponse: if not file.content_type.startswith("image"): raise HTTPException(status_code=415, detail="Not an image") @@ -99,21 +99,21 @@ async def upload_image( filename = f"{uuid.uuid4()}_{str(int(datetime.now(timezone.utc).timestamp()))}.png" saved_image = ApiDependencies.invoker.services.images.save( - ImageType.UPLOAD, filename, img + image_type, filename, img ) invokeai_metadata = ApiDependencies.invoker.services.metadata.get_metadata(img) image_url = ApiDependencies.invoker.services.images.get_uri( - ImageType.UPLOAD, saved_image.image_name + image_type, saved_image.image_name ) thumbnail_url = ApiDependencies.invoker.services.images.get_uri( - ImageType.UPLOAD, saved_image.image_name, True + image_type, saved_image.image_name, True ) res = ImageResponse( - image_type=ImageType.UPLOAD, + image_type=image_type, image_name=saved_image.image_name, image_url=image_url, thumbnail_url=thumbnail_url, From 357cee2849b87779a0fdc88d7d65be2af0db5239 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 4 May 2023 22:13:10 +1000 Subject: [PATCH 12/66] fix(nodes): fix cfg scale min value --- invokeai/app/invocations/generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/app/invocations/generate.py b/invokeai/app/invocations/generate.py index 580df3987d..74985bd1ae 100644 --- a/invokeai/app/invocations/generate.py +++ b/invokeai/app/invocations/generate.py @@ -48,7 +48,7 @@ class TextToImageInvocation(BaseInvocation, SDImageInvocation): steps: int = Field(default=10, gt=0, description="The number of steps to use to generate the image") width: int = Field(default=512, multiple_of=8, gt=0, description="The width of the resulting image", ) height: int = Field(default=512, multiple_of=8, gt=0, description="The height of the resulting image", ) - cfg_scale: float = Field(default=7.5, gt=0, description="The Classifier-Free Guidance, higher values may result in a result closer to the prompt", ) + cfg_scale: float = Field(default=7.5, ge=1, description="The Classifier-Free Guidance, higher values may result in a result closer to the prompt", ) scheduler: SAMPLER_NAME_VALUES = Field(default="k_lms", description="The scheduler to use" ) seamless: bool = Field(default=False, description="Whether or not to generate an image that can tile without seams", ) model: str = Field(default="", description="The model to use (currently ignored)") From 206e6b173044c70455791c937c221a8517215173 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 May 2023 00:06:34 +1000 Subject: [PATCH 13/66] feat(nodes): wip inpaint node --- invokeai/app/invocations/generate.py | 23 +++++++++++++++++------ invokeai/app/models/image.py | 7 +++++++ invokeai/app/services/metadata.py | 11 ++++++++++- invokeai/backend/generator/base.py | 6 +++--- invokeai/backend/generator/inpaint.py | 6 +++--- 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/invokeai/app/invocations/generate.py b/invokeai/app/invocations/generate.py index 74985bd1ae..eeb0d86477 100644 --- a/invokeai/app/invocations/generate.py +++ b/invokeai/app/invocations/generate.py @@ -1,15 +1,16 @@ # Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) from functools import partial -from typing import Literal, Optional, Union +from typing import Literal, Optional, Union, get_args import numpy as np from torch import Tensor from pydantic import BaseModel, Field -from invokeai.app.models.image import ImageField, ImageType +from invokeai.app.models.image import ColorField, ImageField, ImageType from invokeai.app.invocations.util.choose_model import choose_model +from invokeai.backend.generator.inpaint import infill_methods from .baseinvocation import BaseInvocation, InvocationContext, InvocationConfig from .image import ImageOutput, build_image_output from ...backend.generator import Txt2Img, Img2Img, Inpaint, InvokeAIGenerator @@ -17,7 +18,9 @@ from ...backend.stable_diffusion import PipelineIntermediateState from ..util.step_callback import stable_diffusion_step_callback SAMPLER_NAME_VALUES = Literal[tuple(InvokeAIGenerator.schedulers())] +INFILL_METHODS = Literal[tuple(infill_methods())] +DEFAULT_INFILL_METHOD = 'patchmatch' if 'patchmatch' in get_args(INFILL_METHODS) else 'tile' class SDImageInvocation(BaseModel): """Helper class to provide all Stable Diffusion raster image invocations with additional config""" @@ -45,7 +48,7 @@ class TextToImageInvocation(BaseInvocation, SDImageInvocation): # fmt: off prompt: Optional[str] = Field(description="The prompt to generate an image from") seed: int = Field(default=-1,ge=-1, le=np.iinfo(np.uint32).max, description="The seed to use (-1 for a random seed)", ) - steps: int = Field(default=10, gt=0, description="The number of steps to use to generate the image") + steps: int = Field(default=30, gt=0, description="The number of steps to use to generate the image") width: int = Field(default=512, multiple_of=8, gt=0, description="The width of the resulting image", ) height: int = Field(default=512, multiple_of=8, gt=0, description="The height of the resulting image", ) cfg_scale: float = Field(default=7.5, ge=1, description="The Classifier-Free Guidance, higher values may result in a result closer to the prompt", ) @@ -148,7 +151,6 @@ class ImageToImageInvocation(TextToImageInvocation): self.image.image_type, self.image.image_name ) ) - mask = None if self.fit: image = image.resize((self.width, self.height)) @@ -165,7 +167,6 @@ class ImageToImageInvocation(TextToImageInvocation): outputs = Img2Img(model).generate( prompt=self.prompt, init_image=image, - init_mask=mask, step_callback=partial(self.dispatch_progress, context, source_node_id), **self.dict( exclude={"prompt", "image", "mask"} @@ -197,7 +198,6 @@ class ImageToImageInvocation(TextToImageInvocation): image=result_image, ) - class InpaintInvocation(ImageToImageInvocation): """Generates an image using inpaint.""" @@ -205,6 +205,17 @@ class InpaintInvocation(ImageToImageInvocation): # Inputs mask: Union[ImageField, None] = Field(description="The mask") + seam_size: int = Field(default=96, ge=1, description="The seam inpaint size (px)") + seam_blur: int = Field(default=16, ge=0, description="The seam inpaint blur radius (px)") + seam_strength: float = Field( + default=0.75, gt=0, le=1, description="The seam inpaint strength" + ) + seam_steps: int = Field(default=30, ge=1, description="The number of steps to use for seam inpaint") + tile_size: int = Field(default=32, ge=1, description="The tile infill method size (px)") + infill_method: INFILL_METHODS = Field(default=DEFAULT_INFILL_METHOD, description="The method used to infill empty regions (px)") + inpaint_width: Optional[int] = Field(default=None, multiple_of=8, gt=0, description="The width of the inpaint region (px)") + inpaint_height: Optional[int] = Field(default=None, multiple_of=8, gt=0, description="The height of the inpaint region (px)") + inpaint_fill: Optional[ColorField] = Field(default=ColorField(r=127, g=127, b=127, a=255), description="The solid infill method color") inpaint_replace: float = Field( default=0.0, ge=0.0, diff --git a/invokeai/app/models/image.py b/invokeai/app/models/image.py index 5ef1ab0d35..e707343aa7 100644 --- a/invokeai/app/models/image.py +++ b/invokeai/app/models/image.py @@ -27,3 +27,10 @@ class ImageField(BaseModel): class Config: schema_extra = {"required": ["image_type", "image_name"]} + + +class ColorField(BaseModel): + r: int = Field(ge=0, le=255, description="The red component") + g: int = Field(ge=0, le=255, description="The green component") + b: int = Field(ge=0, le=255, description="The blue component") + a: Optional[int] = Field(default=255, ge=0, le=255, description="The alpha component") diff --git a/invokeai/app/services/metadata.py b/invokeai/app/services/metadata.py index 2c8bb0d26b..fd50ddad20 100644 --- a/invokeai/app/services/metadata.py +++ b/invokeai/app/services/metadata.py @@ -20,9 +20,18 @@ class MetadataLatentsField(TypedDict): latents_name: str +class MetadataColorField(TypedDict): + """Pydantic-less ColorField, used for metadata parsing""" + r: int + g: int + b: int + a: int + + + # TODO: This is a placeholder for `InvocationsUnion` pending resolution of circular imports NodeMetadata = Dict[ - str, str | int | float | bool | MetadataImageField | MetadataLatentsField + str, None | str | int | float | bool | MetadataImageField | MetadataLatentsField ] diff --git a/invokeai/backend/generator/base.py b/invokeai/backend/generator/base.py index 8ad9dec026..9887434e90 100644 --- a/invokeai/backend/generator/base.py +++ b/invokeai/backend/generator/base.py @@ -226,10 +226,10 @@ class Inpaint(Img2Img): def generate(self, mask_image: Image.Image | torch.FloatTensor, # Seam settings - when 0, doesn't fill seam - seam_size: int = 0, - seam_blur: int = 0, + seam_size: int = 96, + seam_blur: int = 16, seam_strength: float = 0.7, - seam_steps: int = 10, + seam_steps: int = 30, tile_size: int = 32, inpaint_replace=False, infill_method=None, diff --git a/invokeai/backend/generator/inpaint.py b/invokeai/backend/generator/inpaint.py index 138779c864..9cdc52fd29 100644 --- a/invokeai/backend/generator/inpaint.py +++ b/invokeai/backend/generator/inpaint.py @@ -211,10 +211,10 @@ class Inpaint(Img2Img): strength: float, mask_blur_radius: int = 8, # Seam settings - when 0, doesn't fill seam - seam_size: int = 0, - seam_blur: int = 0, + seam_size: int = 96, + seam_blur: int = 16, seam_strength: float = 0.7, - seam_steps: int = 10, + seam_steps: int = 30, tile_size: int = 32, step_callback=None, inpaint_replace=False, From 1c9429a6eaa31e75da77dc1f06f8ed0d87f69c61 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 May 2023 00:06:50 +1000 Subject: [PATCH 14/66] feat(ui): wip canvas --- .../frontend/web/src/app/store/actions.ts | 0 .../middleware/listenerMiddleware/index.ts | 41 +---- .../listeners/canvasGraphBuilt.ts | 31 ++++ .../listeners/userInvoked.ts | 167 ++++++++++++++++++ invokeai/frontend/web/src/app/store/store.ts | 53 +++++- .../src/common/components/ImageUploader.tsx | 4 +- .../web/src/common/util/arrayBuffer.ts | 1 - .../src/common/util/parameterTranslation.ts | 2 - .../canvas/hooks/usePrepareCanvasState.ts | 39 ---- .../src/features/canvas/util/canvasToBlob.ts | 13 ++ ...8ClampedArray.ts => dataURLToImageData.ts} | 3 + .../src/features/canvas/util/generateMask.ts | 9 +- .../src/features/canvas/util/getCanvasData.ts | 13 +- .../components/panels/TopCenterPanel.tsx | 4 +- .../web/src/features/nodes/store/actions.ts | 12 ++ .../src/features/nodes/store/nodesSlice.ts | 9 +- .../features/nodes/util/buildCanvasGraph.ts | 153 +++++++++++----- .../{getNodeType.ts => getGenerationMode.ts} | 2 +- .../util/linearGraphBuilder/buildEdges.ts | 3 +- .../linearGraphBuilder/buildInpaintNode.ts | 72 ++++++++ .../ProcessButtons/InvokeButton.tsx | 10 +- .../components/PromptInput/PromptInput.tsx | 23 +-- .../src/features/system/store/systemSlice.ts | 2 +- .../frontend/web/src/services/api/index.ts | 2 + .../web/src/services/api/models/ColorField.ts | 23 +++ .../services/api/models/InpaintInvocation.ts | 37 ++++ .../src/services/api/schemas/$ColorField.ts | 30 ++++ .../api/schemas/$ImageToImageInvocation.ts | 5 +- .../api/schemas/$InpaintInvocation.ts | 49 ++++- .../services/api/schemas/$NoiseInvocation.ts | 4 +- .../api/schemas/$TextToImageInvocation.ts | 5 +- .../services/api/services/ImagesService.ts | 5 + .../web/src/services/thunks/session.ts | 108 +++++------ 33 files changed, 712 insertions(+), 222 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/actions.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasGraphBuilt.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvoked.ts delete mode 100644 invokeai/frontend/web/src/features/canvas/hooks/usePrepareCanvasState.ts create mode 100644 invokeai/frontend/web/src/features/canvas/util/canvasToBlob.ts rename invokeai/frontend/web/src/features/canvas/util/{dataURLToUint8ClampedArray.ts => dataURLToImageData.ts} (87%) create mode 100644 invokeai/frontend/web/src/features/nodes/store/actions.ts rename invokeai/frontend/web/src/features/nodes/util/{getNodeType.ts => getGenerationMode.ts} (91%) create mode 100644 invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildInpaintNode.ts create mode 100644 invokeai/frontend/web/src/services/api/models/ColorField.ts create mode 100644 invokeai/frontend/web/src/services/api/schemas/$ColorField.ts diff --git a/invokeai/frontend/web/src/app/store/actions.ts b/invokeai/frontend/web/src/app/store/actions.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 603a6ca423..5869038d6a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -12,15 +12,11 @@ import { addImageResultReceivedListener } from './listeners/invocationComplete'; import { addImageUploadedListener } from './listeners/imageUploaded'; import { addRequestedImageDeletionListener } from './listeners/imageDeleted'; import { - canvasGraphBuilt, - sessionCreated, - sessionInvoked, -} from 'services/thunks/session'; -import { tabMap } from 'features/ui/store/tabMap'; -import { - canvasSessionIdChanged, - stagingAreaInitialized, -} from 'features/canvas/store/canvasSlice'; + addUserInvokedCanvasListener, + addUserInvokedCreateListener, + addUserInvokedNodesListener, +} from './listeners/userInvoked'; +import { addCanvasGraphBuiltListener } from './listeners/canvasGraphBuilt'; export const listenerMiddleware = createListenerMiddleware(); @@ -44,26 +40,7 @@ addImageUploadedListener(); addInitialImageSelectedListener(); addImageResultReceivedListener(); addRequestedImageDeletionListener(); - -startAppListening({ - actionCreator: canvasGraphBuilt.fulfilled, - effect: async (action, { dispatch, getState, condition, fork, take }) => { - const [{ meta }] = await take(sessionInvoked.fulfilled.match); - const { sessionId } = meta.arg; - const state = getState(); - - if (!state.canvas.layerState.stagingArea.boundingBox) { - dispatch( - stagingAreaInitialized({ - sessionId, - boundingBox: { - ...state.canvas.boundingBoxCoordinates, - ...state.canvas.boundingBoxDimensions, - }, - }) - ); - } - - dispatch(canvasSessionIdChanged(sessionId)); - }, -}); +addUserInvokedCanvasListener(); +addUserInvokedCreateListener(); +addUserInvokedNodesListener(); +// addCanvasGraphBuiltListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasGraphBuilt.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasGraphBuilt.ts new file mode 100644 index 0000000000..532bac3eee --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasGraphBuilt.ts @@ -0,0 +1,31 @@ +import { canvasGraphBuilt } from 'features/nodes/store/actions'; +import { startAppListening } from '..'; +import { + canvasSessionIdChanged, + stagingAreaInitialized, +} from 'features/canvas/store/canvasSlice'; +import { sessionInvoked } from 'services/thunks/session'; + +export const addCanvasGraphBuiltListener = () => + startAppListening({ + actionCreator: canvasGraphBuilt, + effect: async (action, { dispatch, getState, take }) => { + const [{ meta }] = await take(sessionInvoked.fulfilled.match); + const { sessionId } = meta.arg; + const state = getState(); + + if (!state.canvas.layerState.stagingArea.boundingBox) { + dispatch( + stagingAreaInitialized({ + sessionId, + boundingBox: { + ...state.canvas.boundingBoxCoordinates, + ...state.canvas.boundingBoxDimensions, + }, + }) + ); + } + + dispatch(canvasSessionIdChanged(sessionId)); + }, + }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvoked.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvoked.ts new file mode 100644 index 0000000000..63da80440e --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvoked.ts @@ -0,0 +1,167 @@ +import { createAction } from '@reduxjs/toolkit'; +import { startAppListening } from '..'; +import { InvokeTabName } from 'features/ui/store/tabMap'; +import { buildLinearGraph } from 'features/nodes/util/buildLinearGraph'; +import { sessionCreated, sessionInvoked } from 'services/thunks/session'; +import { buildCanvasGraphAndBlobs } from 'features/nodes/util/buildCanvasGraph'; +import { buildNodesGraph } from 'features/nodes/util/buildNodesGraph'; +import { log } from 'app/logging/useLogger'; +import { + canvasGraphBuilt, + createGraphBuilt, + nodesGraphBuilt, +} from 'features/nodes/store/actions'; +import { imageUploaded } from 'services/thunks/image'; +import { v4 as uuidv4 } from 'uuid'; +import { Graph } from 'services/api'; +import { + canvasSessionIdChanged, + stagingAreaInitialized, +} from 'features/canvas/store/canvasSlice'; + +const moduleLog = log.child({ namespace: 'invoke' }); + +export const userInvoked = createAction('app/userInvoked'); + +export const addUserInvokedCreateListener = () => { + startAppListening({ + predicate: (action): action is ReturnType => + userInvoked.match(action) && action.payload === 'generate', + effect: (action, { getState, dispatch }) => { + const state = getState(); + + const graph = buildLinearGraph(state); + dispatch(createGraphBuilt(graph)); + moduleLog({ data: graph }, 'Create graph built'); + + dispatch(sessionCreated({ graph })); + }, + }); +}; + +export const addUserInvokedCanvasListener = () => { + startAppListening({ + predicate: (action): action is ReturnType => + userInvoked.match(action) && action.payload === 'unifiedCanvas', + effect: async (action, { getState, dispatch, take }) => { + const state = getState(); + + const data = await buildCanvasGraphAndBlobs(state); + + if (!data) { + moduleLog.error('Problem building graph'); + return; + } + + const { + rangeNode, + iterateNode, + baseNode, + edges, + baseBlob, + maskBlob, + generationMode, + } = data; + + const baseFilename = `${uuidv4()}.png`; + const maskFilename = `${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 => + 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, + }; + } + + 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 => + 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! + const nodes: Graph['nodes'] = { + [rangeNode.id]: rangeNode, + [iterateNode.id]: iterateNode, + [baseNode.id]: baseNode, + }; + + const graph = { nodes, edges }; + + dispatch(canvasGraphBuilt(graph)); + moduleLog({ data: graph }, 'Canvas graph built'); + + dispatch(sessionCreated({ graph })); + + const [{ meta }] = await take(sessionInvoked.fulfilled.match); + const { sessionId } = meta.arg; + + if (!state.canvas.layerState.stagingArea.boundingBox) { + dispatch( + stagingAreaInitialized({ + sessionId, + boundingBox: { + ...state.canvas.boundingBoxCoordinates, + ...state.canvas.boundingBoxDimensions, + }, + }) + ); + } + + dispatch(canvasSessionIdChanged(sessionId)); + }, + }); +}; + +export const addUserInvokedNodesListener = () => { + startAppListening({ + predicate: (action): action is ReturnType => + userInvoked.match(action) && action.payload === 'nodes', + effect: (action, { getState, dispatch }) => { + const state = getState(); + + const graph = buildNodesGraph(state); + dispatch(nodesGraphBuilt(graph)); + moduleLog({ data: graph }, 'Nodes graph built'); + + dispatch(sessionCreated({ graph })); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 2663adfb6b..15eb045405 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -1,8 +1,10 @@ import { + Action, AnyAction, ThunkDispatch, combineReducers, configureStore, + isAnyOf, } from '@reduxjs/toolkit'; import { persistReducer } from 'redux-persist'; @@ -33,9 +35,10 @@ import { nodesDenylist } from 'features/nodes/store/nodesPersistDenylist'; import { postprocessingDenylist } from 'features/parameters/store/postprocessingPersistDenylist'; import { systemDenylist } from 'features/system/store/systemPersistDenylist'; import { uiDenylist } from 'features/ui/store/uiPersistDenylist'; -import { resultsDenylist } from 'features/gallery/store/resultsPersistDenylist'; -import { uploadsDenylist } from 'features/gallery/store/uploadsPersistDenylist'; import { listenerMiddleware } from './middleware/listenerMiddleware'; +import { isAnyGraphBuilt } from 'features/nodes/store/actions'; +import { forEach } from 'lodash-es'; +import { Graph } from 'services/api'; /** * redux-persist provides an easy and reliable way to persist state across reloads. @@ -101,6 +104,27 @@ const persistedReducer = persistReducer(rootPersistConfig, rootReducer); // } // } +// const actionSanitizer = (action: AnyAction): AnyAction => { +// if (isAnyGraphBuilt(action)) { +// if (action.payload.nodes) { +// const sanitizedNodes: Graph['nodes'] = {}; +// forEach(action.payload.nodes, (node, key) => { +// if (node.type === 'dataURL_image') { +// const { dataURL, ...rest } = node; +// sanitizedNodes[key] = { ...rest, dataURL: '<>' }; +// } +// }); +// const sanitizedAction: AnyAction = { +// ...action, +// payload: { ...action.payload, nodes: sanitizedNodes }, +// }; +// return sanitizedAction; +// } +// } + +// return action; +// }; + export const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => @@ -123,6 +147,31 @@ export const store = configureStore({ 'canvas/addPointToCurrentLine', 'socket/generatorProgress', ], + actionSanitizer: (action) => { + if (isAnyGraphBuilt(action)) { + if (action.payload.nodes) { + const sanitizedNodes: Graph['nodes'] = {}; + + forEach(action.payload.nodes, (node, key) => { + if (node.type === 'dataURL_image') { + const { dataURL, ...rest } = node; + sanitizedNodes[key] = { ...rest, dataURL: '<>' }; + } else { + sanitizedNodes[key] = { ...node }; + } + }); + + return { + ...action, + payload: { ...action.payload, nodes: sanitizedNodes }, + }; + } + } + + return action; + }, + // stateSanitizer: (state) => + // state.data ? { ...state, data: '<>' } : state, }, }); diff --git a/invokeai/frontend/web/src/common/components/ImageUploader.tsx b/invokeai/frontend/web/src/common/components/ImageUploader.tsx index 8ff88c7ecf..ee3b9d135e 100644 --- a/invokeai/frontend/web/src/common/components/ImageUploader.tsx +++ b/invokeai/frontend/web/src/common/components/ImageUploader.tsx @@ -49,7 +49,7 @@ const ImageUploader = (props: ImageUploaderProps) => { const fileAcceptedCallback = useCallback( async (file: File) => { - dispatch(imageUploaded({ formData: { file } })); + dispatch(imageUploaded({ imageType: 'uploads', formData: { file } })); }, [dispatch] ); @@ -124,7 +124,7 @@ const ImageUploader = (props: ImageUploaderProps) => { return; } - dispatch(imageUploaded({ formData: { file } })); + dispatch(imageUploaded({ imageType: 'uploads', formData: { file } })); }; document.addEventListener('paste', pasteImageListener); return () => { diff --git a/invokeai/frontend/web/src/common/util/arrayBuffer.ts b/invokeai/frontend/web/src/common/util/arrayBuffer.ts index 885fc05177..cdfe84fccc 100644 --- a/invokeai/frontend/web/src/common/util/arrayBuffer.ts +++ b/invokeai/frontend/web/src/common/util/arrayBuffer.ts @@ -1,5 +1,4 @@ export const getImageDataTransparency = (pixels: Uint8ClampedArray) => { - console.log(pixels); let isFullyTransparent = true; let isPartiallyTransparent = false; const len = pixels.length; diff --git a/invokeai/frontend/web/src/common/util/parameterTranslation.ts b/invokeai/frontend/web/src/common/util/parameterTranslation.ts index de25ae7b71..83df66aab2 100644 --- a/invokeai/frontend/web/src/common/util/parameterTranslation.ts +++ b/invokeai/frontend/web/src/common/util/parameterTranslation.ts @@ -299,8 +299,6 @@ export const frontendToBackendParameters = ( const doesBaseHaveTransparency = getIsImageDataTransparent(imageData); const doesMaskHaveTransparency = getIsImageDataWhite(maskImageData); - console.log(doesBaseHaveTransparency, doesMaskHaveTransparency); - if (enableImageDebugging) { openBase64ImageInTab([ { base64: maskDataURL, caption: 'mask sent as init_mask' }, diff --git a/invokeai/frontend/web/src/features/canvas/hooks/usePrepareCanvasState.ts b/invokeai/frontend/web/src/features/canvas/hooks/usePrepareCanvasState.ts deleted file mode 100644 index 061979376b..0000000000 --- a/invokeai/frontend/web/src/features/canvas/hooks/usePrepareCanvasState.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { useAppSelector } from 'app/store/storeHooks'; -import { - FrontendToBackendParametersConfig, - frontendToBackendParameters, -} from 'common/util/parameterTranslation'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors'; -import { systemSelector } from 'features/system/store/systemSelectors'; -import { canvasSelector } from '../store/canvasSelectors'; -import { useCallback, useMemo } from 'react'; - -const selector = createSelector( - [generationSelector, postprocessingSelector, systemSelector, canvasSelector], - (generation, postprocessing, system, canvas) => { - const frontendToBackendParametersConfig: FrontendToBackendParametersConfig = - { - generationMode: 'unifiedCanvas', - generationState: generation, - postprocessingState: postprocessing, - canvasState: canvas, - systemState: system, - }; - - return frontendToBackendParametersConfig; - } -); - -export const usePrepareCanvasState = () => { - const frontendToBackendParametersConfig = useAppSelector(selector); - - const getGenerationParameters = useCallback(() => { - const { generationParameters, esrganParameters, facetoolParameters } = - frontendToBackendParameters(frontendToBackendParametersConfig); - console.log(generationParameters); - }, [frontendToBackendParametersConfig]); - - return getGenerationParameters; -}; diff --git a/invokeai/frontend/web/src/features/canvas/util/canvasToBlob.ts b/invokeai/frontend/web/src/features/canvas/util/canvasToBlob.ts new file mode 100644 index 0000000000..44220c8ba4 --- /dev/null +++ b/invokeai/frontend/web/src/features/canvas/util/canvasToBlob.ts @@ -0,0 +1,13 @@ +/** + * Gets a Blob from a canvas. + */ +export const canvasToBlob = async (canvas: HTMLCanvasElement): Promise => + new Promise((resolve, reject) => { + canvas.toBlob((blob) => { + if (blob) { + resolve(blob); + return; + } + reject('Unable to create Blob'); + }); + }); diff --git a/invokeai/frontend/web/src/features/canvas/util/dataURLToUint8ClampedArray.ts b/invokeai/frontend/web/src/features/canvas/util/dataURLToImageData.ts similarity index 87% rename from invokeai/frontend/web/src/features/canvas/util/dataURLToUint8ClampedArray.ts rename to invokeai/frontend/web/src/features/canvas/util/dataURLToImageData.ts index 2652d294fc..739240a79d 100644 --- a/invokeai/frontend/web/src/features/canvas/util/dataURLToUint8ClampedArray.ts +++ b/invokeai/frontend/web/src/features/canvas/util/dataURLToImageData.ts @@ -1,3 +1,6 @@ +/** + * Gets an ImageData object from an image dataURL by drawing it to a canvas. + */ export const dataURLToImageData = async ( dataURL: string, width: number, diff --git a/invokeai/frontend/web/src/features/canvas/util/generateMask.ts b/invokeai/frontend/web/src/features/canvas/util/generateMask.ts index f3cc1fb237..a5cd41ad10 100644 --- a/invokeai/frontend/web/src/features/canvas/util/generateMask.ts +++ b/invokeai/frontend/web/src/features/canvas/util/generateMask.ts @@ -104,6 +104,7 @@ import { CanvasMaskLine } from 'features/canvas/store/canvasTypes'; import Konva from 'konva'; import { IRect } from 'konva/lib/types'; +import { canvasToBlob } from './canvasToBlob'; /** * Generating a mask image from InpaintingCanvas.tsx is not as simple @@ -115,7 +116,7 @@ import { IRect } from 'konva/lib/types'; * drawing the mask and compositing everything correctly to output a valid * mask image. */ -const generateMask = (lines: CanvasMaskLine[], boundingBox: IRect): string => { +const generateMask = async (lines: CanvasMaskLine[], boundingBox: IRect) => { // create an offscreen canvas and add the mask to it const { width, height } = boundingBox; @@ -157,11 +158,13 @@ const generateMask = (lines: CanvasMaskLine[], boundingBox: IRect): string => { stage.add(baseLayer); stage.add(maskLayer); - const dataURL = stage.toDataURL({ ...boundingBox }); + const maskDataURL = stage.toDataURL(boundingBox); + + const maskBlob = await canvasToBlob(stage.toCanvas(boundingBox)); offscreenContainer.remove(); - return dataURL; + return { maskDataURL, maskBlob }; }; export default generateMask; diff --git a/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts b/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts index af4ea42561..131b109f55 100644 --- a/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts +++ b/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts @@ -8,7 +8,8 @@ import { } from 'common/util/arrayBuffer'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import generateMask from './generateMask'; -import { dataURLToImageData } from './dataURLToUint8ClampedArray'; +import { dataURLToImageData } from './dataURLToImageData'; +import { canvasToBlob } from './canvasToBlob'; const moduleLog = log.child({ namespace: 'getCanvasDataURLs' }); @@ -62,10 +63,13 @@ export const getCanvasData = async (state: RootState) => { }; const baseDataURL = canvasBaseLayer.toDataURL(offsetBoundingBox); + const baseBlob = await canvasToBlob( + canvasBaseLayer.toCanvas(offsetBoundingBox) + ); canvasBaseLayer.scale(tempScale); - const maskDataURL = generateMask( + const { maskDataURL, maskBlob } = await generateMask( isMaskEnabled ? objects.filter(isCanvasMaskLine) : [], boundingBox ); @@ -82,9 +86,6 @@ export const getCanvasData = async (state: RootState) => { boundingBox.height ); - console.log('baseImageData', baseImageData); - console.log('maskImageData', maskImageData); - const { isPartiallyTransparent: baseIsPartiallyTransparent, isFullyTransparent: baseIsFullyTransparent, @@ -117,7 +118,9 @@ export const getCanvasData = async (state: RootState) => { return { baseDataURL, + baseBlob, maskDataURL, + maskBlob, baseIsPartiallyTransparent, baseIsFullyTransparent, doesMaskHaveBlackPixels, diff --git a/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx index 4bb7abf982..b5783891ee 100644 --- a/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx @@ -1,16 +1,16 @@ import { HStack } from '@chakra-ui/react'; +import { userInvoked } from 'app/store/middleware/listenerMiddleware/listeners/userInvoked'; import { useAppDispatch } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import { memo, useCallback } from 'react'; import { Panel } from 'reactflow'; import { receivedOpenAPISchema } from 'services/thunks/schema'; -import { nodesGraphBuilt } from 'services/thunks/session'; const TopCenterPanel = () => { const dispatch = useAppDispatch(); const handleInvoke = useCallback(() => { - dispatch(nodesGraphBuilt()); + dispatch(userInvoked('nodes')); }, [dispatch]); const handleReloadSchema = useCallback(() => { diff --git a/invokeai/frontend/web/src/features/nodes/store/actions.ts b/invokeai/frontend/web/src/features/nodes/store/actions.ts new file mode 100644 index 0000000000..e7bc6d6000 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/store/actions.ts @@ -0,0 +1,12 @@ +import { createAction, isAnyOf } from '@reduxjs/toolkit'; +import { Graph } from 'services/api'; + +export const createGraphBuilt = createAction('nodes/createGraphBuilt'); +export const canvasGraphBuilt = createAction('nodes/canvasGraphBuilt'); +export const nodesGraphBuilt = createAction('nodes/nodesGraphBuilt'); + +export const isAnyGraphBuilt = isAnyOf( + createGraphBuilt, + canvasGraphBuilt, + nodesGraphBuilt +); diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts index d0202a5932..c27f2e83fc 100644 --- a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts +++ b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts @@ -13,11 +13,11 @@ import { } from 'reactflow'; import { Graph, ImageField } from 'services/api'; import { receivedOpenAPISchema } from 'services/thunks/schema'; -import { isFulfilledAnyGraphBuilt } from 'services/thunks/session'; import { InvocationTemplate, InvocationValue } from '../types/types'; import { parseSchema } from '../util/parseSchema'; import { log } from 'app/logging/useLogger'; import { size } from 'lodash-es'; +import { isAnyGraphBuilt } from './actions'; export type NodesState = { nodes: Node[]; @@ -25,7 +25,6 @@ export type NodesState = { schema: OpenAPIV3.Document | null; invocationTemplates: Record; connectionStartParams: OnConnectStartParams | null; - lastGraph: Graph | null; shouldShowGraphOverlay: boolean; }; @@ -35,7 +34,6 @@ export const initialNodesState: NodesState = { schema: null, invocationTemplates: {}, connectionStartParams: null, - lastGraph: null, shouldShowGraphOverlay: false, }; @@ -104,8 +102,9 @@ const nodesSlice = createSlice({ state.schema = action.payload; }); - builder.addMatcher(isFulfilledAnyGraphBuilt, (state, action) => { - state.lastGraph = action.payload; + builder.addMatcher(isAnyGraphBuilt, (state, action) => { + // TODO: Achtung! Side effect in a reducer! + log.info({ namespace: 'nodes', data: action.payload }, 'Graph built'); }); }, }); diff --git a/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts b/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts index 8182241843..a9cb058de3 100644 --- a/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts @@ -1,5 +1,15 @@ import { RootState } from 'app/store/store'; -import { DataURLToImageInvocation, Graph } from 'services/api'; +import { + DataURLToImageInvocation, + Edge, + Graph, + ImageToImageInvocation, + InpaintInvocation, + IterateInvocation, + RandomRangeInvocation, + RangeInvocation, + TextToImageInvocation, +} from 'services/api'; import { buildImg2ImgNode } from './linearGraphBuilder/buildImageToImageNode'; import { buildTxt2ImgNode } from './linearGraphBuilder/buildTextToImageNode'; import { buildRangeNode } from './linearGraphBuilder/buildRangeNode'; @@ -7,18 +17,54 @@ import { buildIterateNode } from './linearGraphBuilder/buildIterateNode'; import { buildEdges } from './linearGraphBuilder/buildEdges'; import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; import { getCanvasData } from 'features/canvas/util/getCanvasData'; -import { getNodeType } from './getNodeType'; +import { getGenerationMode } from './getGenerationMode'; import { v4 as uuidv4 } from 'uuid'; import { log } from 'app/logging/useLogger'; +import { buildInpaintNode } from './linearGraphBuilder/buildInpaintNode'; const moduleLog = log.child({ namespace: 'buildCanvasGraph' }); -/** - * Builds the Canvas workflow graph. - */ -export const buildCanvasGraph = async ( +const buildBaseNode = ( + nodeType: 'txt2img' | 'img2img' | 'inpaint' | 'outpaint', state: RootState -): Promise => { +): + | TextToImageInvocation + | ImageToImageInvocation + | InpaintInvocation + | undefined => { + if (nodeType === 'txt2img') { + return buildTxt2ImgNode(state, state.canvas.boundingBoxDimensions); + } + + if (nodeType === 'img2img') { + return buildImg2ImgNode(state, state.canvas.boundingBoxDimensions); + } + + if (nodeType === 'inpaint' || nodeType === 'outpaint') { + return buildInpaintNode(state, state.canvas.boundingBoxDimensions); + } +}; + +/** + * Builds the Canvas workflow graph and image blobs. + */ +export const buildCanvasGraphAndBlobs = async ( + state: RootState +): Promise< + | { + rangeNode: RangeInvocation | RandomRangeInvocation; + iterateNode: IterateInvocation; + baseNode: + | TextToImageInvocation + | ImageToImageInvocation + | InpaintInvocation; + edges: Edge[]; + baseBlob: Blob; + maskBlob: Blob; + generationMode: 'txt2img' | 'img2img' | 'inpaint' | 'outpaint'; + } + | undefined +> => { const c = await getCanvasData(state); if (!c) { @@ -26,35 +72,68 @@ export const buildCanvasGraph = async ( return; } - moduleLog.debug({ data: c }, 'Built canvas data'); - const { baseDataURL, + baseBlob, maskDataURL, + maskBlob, baseIsPartiallyTransparent, baseIsFullyTransparent, doesMaskHaveBlackPixels, } = c; - const nodeType = getNodeType( + moduleLog.debug( + { + data: { + // baseDataURL, + // maskDataURL, + baseIsPartiallyTransparent, + baseIsFullyTransparent, + doesMaskHaveBlackPixels, + }, + }, + 'Built canvas data' + ); + + const generationMode = getGenerationMode( baseIsPartiallyTransparent, baseIsFullyTransparent, doesMaskHaveBlackPixels ); - moduleLog.debug(`Node type ${nodeType}`); + moduleLog.debug(`Generation mode: ${generationMode}`); - // The base node is either a txt2img or img2img node - const baseNode = - nodeType === 'img2img' - ? buildImg2ImgNode(state, state.canvas.boundingBoxDimensions) - : buildTxt2ImgNode(state, state.canvas.boundingBoxDimensions); + // The base node is a txt2img, img2img or inpaint node + const baseNode = buildBaseNode(generationMode, state); - const dataURLNode: DataURLToImageInvocation = { - id: uuidv4(), - type: 'dataURL_image', - dataURL: baseDataURL, - }; + if (!baseNode) { + moduleLog.error('Problem building base node'); + return; + } + + if (baseNode.type === 'inpaint') { + const { + seamSize, + seamBlur, + seamSteps, + seamStrength, + tileSize, + infillMethod, + } = state.generation; + + // generationParameters.invert_mask = shouldPreserveMaskedArea; + // if (boundingBoxScale !== 'none') { + // generationParameters.inpaint_width = scaledBoundingBoxDimensions.width; + // generationParameters.inpaint_height = scaledBoundingBoxDimensions.height; + // } + baseNode.seam_size = seamSize; + baseNode.seam_blur = seamBlur; + baseNode.seam_strength = seamStrength; + baseNode.seam_steps = seamSteps; + baseNode.tile_size = tileSize; + // baseNode.infill_method = infillMethod; + // baseNode.force_outpaint = false; + } // We always range and iterate nodes, no matter the iteration count // This is required to provide the correct seeds to the backend engine @@ -64,31 +143,13 @@ export const buildCanvasGraph = async ( // Build the edges for the nodes selected. const edges = buildEdges(baseNode, rangeNode, iterateNode); - if (baseNode.type === 'img2img') { - edges.push({ - source: { - node_id: dataURLNode.id, - field: 'image', - }, - destination: { - node_id: baseNode.id, - field: 'image', - }, - }); - } - - // Assemble! - const graph = { - nodes: { - [dataURLNode.id]: dataURLNode, - [rangeNode.id]: rangeNode, - [iterateNode.id]: iterateNode, - [baseNode.id]: baseNode, - }, + return { + rangeNode, + iterateNode, + baseNode, edges, + baseBlob, + maskBlob, + generationMode, }; - - // TODO: hires fix requires latent space upscaling; we don't have nodes for this yet - - return graph; }; diff --git a/invokeai/frontend/web/src/features/nodes/util/getNodeType.ts b/invokeai/frontend/web/src/features/nodes/util/getGenerationMode.ts similarity index 91% rename from invokeai/frontend/web/src/features/nodes/util/getNodeType.ts rename to invokeai/frontend/web/src/features/nodes/util/getGenerationMode.ts index d26bff393f..28b316be40 100644 --- a/invokeai/frontend/web/src/features/nodes/util/getNodeType.ts +++ b/invokeai/frontend/web/src/features/nodes/util/getGenerationMode.ts @@ -1,4 +1,4 @@ -export const getNodeType = ( +export const getGenerationMode = ( baseIsPartiallyTransparent: boolean, baseIsFullyTransparent: boolean, doesMaskHaveBlackPixels: boolean diff --git a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildEdges.ts b/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildEdges.ts index 873dba3ac3..a1e9837647 100644 --- a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildEdges.ts +++ b/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildEdges.ts @@ -1,6 +1,7 @@ import { Edge, ImageToImageInvocation, + InpaintInvocation, IterateInvocation, RandomRangeInvocation, RangeInvocation, @@ -8,7 +9,7 @@ import { } from 'services/api'; export const buildEdges = ( - baseNode: TextToImageInvocation | ImageToImageInvocation, + baseNode: TextToImageInvocation | ImageToImageInvocation | InpaintInvocation, rangeNode: RangeInvocation | RandomRangeInvocation, iterateNode: IterateInvocation ): Edge[] => { diff --git a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildInpaintNode.ts b/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildInpaintNode.ts new file mode 100644 index 0000000000..fc8d485e6d --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildInpaintNode.ts @@ -0,0 +1,72 @@ +import { v4 as uuidv4 } from 'uuid'; +import { RootState } from 'app/store/store'; +import { + Edge, + ImageToImageInvocation, + InpaintInvocation, + TextToImageInvocation, +} from 'services/api'; +import { initialImageSelector } from 'features/parameters/store/generationSelectors'; +import { O } from 'ts-toolbelt'; + +export const buildInpaintNode = ( + state: RootState, + overrides: O.Partial = {} +): InpaintInvocation => { + const nodeId = uuidv4(); + const { generation, system, models } = state; + + const { selectedModelName } = models; + + const { + prompt, + negativePrompt, + seed, + steps, + width, + height, + cfgScale, + sampler, + seamless, + img2imgStrength: strength, + shouldFitToWidthHeight: fit, + shouldRandomizeSeed, + } = generation; + + const initialImage = initialImageSelector(state); + + if (!initialImage) { + // TODO: handle this + // throw 'no initial image'; + } + + const imageToImageNode: InpaintInvocation = { + id: nodeId, + type: 'inpaint', + prompt: `${prompt} [${negativePrompt}]`, + steps, + width, + height, + cfg_scale: cfgScale, + scheduler: sampler as InpaintInvocation['scheduler'], + seamless, + model: selectedModelName, + progress_images: true, + image: initialImage + ? { + image_name: initialImage.name, + image_type: initialImage.type, + } + : undefined, + strength, + fit, + }; + + if (!shouldRandomizeSeed) { + imageToImageNode.seed = seed; + } + + Object.assign(imageToImageNode, overrides); + + return imageToImageNode; +}; diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx index 60dd912b84..5532fab196 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx @@ -1,18 +1,17 @@ import { Box } from '@chakra-ui/react'; import { readinessSelector } from 'app/selectors/readinessSelector'; +import { userInvoked } from 'app/store/middleware/listenerMiddleware/listeners/userInvoked'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton, { IAIButtonProps } from 'common/components/IAIButton'; import IAIIconButton, { IAIIconButtonProps, } from 'common/components/IAIIconButton'; -import { usePrepareCanvasState } from 'features/canvas/hooks/usePrepareCanvasState'; import { clampSymmetrySteps } from 'features/parameters/store/generationSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { useCallback } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { FaPlay } from 'react-icons/fa'; -import { canvasGraphBuilt, generateGraphBuilt } from 'services/thunks/session'; interface InvokeButton extends Omit { @@ -24,15 +23,10 @@ export default function InvokeButton(props: InvokeButton) { const dispatch = useAppDispatch(); const { isReady } = useAppSelector(readinessSelector); const activeTabName = useAppSelector(activeTabNameSelector); - // const getGenerationParameters = usePrepareCanvasState(); const handleInvoke = useCallback(() => { dispatch(clampSymmetrySteps()); - if (activeTabName === 'unifiedCanvas') { - dispatch(canvasGraphBuilt()); - } else { - dispatch(generateGraphBuilt()); - } + dispatch(userInvoked(activeTabName)); }, [dispatch, activeTabName]); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx b/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx index b54106733c..868da4c8b1 100644 --- a/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx @@ -1,7 +1,7 @@ import { Box, FormControl, Textarea } from '@chakra-ui/react'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { ChangeEvent, KeyboardEvent, useRef } from 'react'; +import { ChangeEvent, KeyboardEvent, useCallback, useRef } from 'react'; import { createSelector } from '@reduxjs/toolkit'; import { readinessSelector } from 'app/selectors/readinessSelector'; @@ -15,7 +15,7 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { isEqual } from 'lodash-es'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; -import { generateGraphBuilt } from 'services/thunks/session'; +import { userInvoked } from 'app/store/middleware/listenerMiddleware/listeners/userInvoked'; const promptInputSelector = createSelector( [(state: RootState) => state.generation, activeTabNameSelector], @@ -37,7 +37,7 @@ const promptInputSelector = createSelector( */ const PromptInput = () => { const dispatch = useAppDispatch(); - const { prompt } = useAppSelector(promptInputSelector); + const { prompt, activeTabName } = useAppSelector(promptInputSelector); const { isReady } = useAppSelector(readinessSelector); const promptRef = useRef(null); @@ -56,13 +56,16 @@ const PromptInput = () => { [] ); - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Enter' && e.shiftKey === false && isReady) { - e.preventDefault(); - dispatch(clampSymmetrySteps()); - dispatch(generateGraphBuilt()); - } - }; + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Enter' && e.shiftKey === false && isReady) { + e.preventDefault(); + dispatch(clampSymmetrySteps()); + dispatch(userInvoked(activeTabName)); + } + }, + [dispatch, activeTabName, isReady] + ); return ( diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 75aa758198..16f118855f 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -120,7 +120,7 @@ const initialSystemState: SystemState = { shouldLogToConsole: true, statusTranslationKey: 'common.statusDisconnected', canceledSession: '', - infillMethods: ['tile'], + infillMethods: ['tile', 'patchmatch'], }; export const systemSlice = createSlice({ diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index f452de3ce9..5856832078 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -12,6 +12,7 @@ export type { Body_upload_image } from './models/Body_upload_image'; export type { CkptModelInfo } from './models/CkptModelInfo'; export type { CollectInvocation } from './models/CollectInvocation'; export type { CollectInvocationOutput } from './models/CollectInvocationOutput'; +export type { ColorField } from './models/ColorField'; export type { CreateModelRequest } from './models/CreateModelRequest'; export type { CropImageInvocation } from './models/CropImageInvocation'; export type { CvInpaintInvocation } from './models/CvInpaintInvocation'; @@ -76,6 +77,7 @@ export { $Body_upload_image } from './schemas/$Body_upload_image'; export { $CkptModelInfo } from './schemas/$CkptModelInfo'; export { $CollectInvocation } from './schemas/$CollectInvocation'; export { $CollectInvocationOutput } from './schemas/$CollectInvocationOutput'; +export { $ColorField } from './schemas/$ColorField'; export { $CreateModelRequest } from './schemas/$CreateModelRequest'; export { $CropImageInvocation } from './schemas/$CropImageInvocation'; export { $CvInpaintInvocation } from './schemas/$CvInpaintInvocation'; diff --git a/invokeai/frontend/web/src/services/api/models/ColorField.ts b/invokeai/frontend/web/src/services/api/models/ColorField.ts new file mode 100644 index 0000000000..01e5383e9c --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ColorField.ts @@ -0,0 +1,23 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type ColorField = { + /** + * The red component + */ + 'r': number; + /** + * The blue component + */ + 'b': number; + /** + * The green component + */ + 'g': number; + /** + * The alpha component + */ + 'a'?: number; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts index 7ea6a89f62..baa07bca66 100644 --- a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ +import type { ColorField } from './ColorField'; import type { ImageField } from './ImageField'; /** @@ -69,6 +70,42 @@ export type InpaintInvocation = { * The mask */ mask?: ImageField; + /** + * The seam inpaint size (px) + */ + seam_size?: number; + /** + * The seam inpaint blur radius (px) + */ + seam_blur?: number; + /** + * The seam inpaint strength + */ + seam_strength?: number; + /** + * The number of steps to use for seam inpaint + */ + seam_steps?: number; + /** + * The tile infill method size (px) + */ + tile_size?: number; + /** + * The method used to infill empty regions (px) + */ + infill_method?: 'patchmatch' | 'tile' | 'solid'; + /** + * The width of the inpaint region (px) + */ + inpaint_width?: number; + /** + * The height of the inpaint region (px) + */ + inpaint_height?: number; + /** + * The solid infill method color + */ + inpaint_fill?: ColorField; /** * The amount by which to replace masked areas with latent noise */ diff --git a/invokeai/frontend/web/src/services/api/schemas/$ColorField.ts b/invokeai/frontend/web/src/services/api/schemas/$ColorField.ts new file mode 100644 index 0000000000..8f49a13e71 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/schemas/$ColorField.ts @@ -0,0 +1,30 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ColorField = { + properties: { + 'r': { + type: 'number', + description: `The red component`, + isRequired: true, + maximum: 255, + }, + 'b': { + type: 'number', + description: `The blue component`, + isRequired: true, + maximum: 255, + }, + 'g': { + type: 'number', + description: `The green component`, + isRequired: true, + maximum: 255, + }, + 'a': { + type: 'number', + description: `The alpha component`, + maximum: 255, + }, + }, +} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$ImageToImageInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$ImageToImageInvocation.ts index 4b77f03ca3..61a7ec2967 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$ImageToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$ImageToImageInvocation.ts @@ -29,16 +29,17 @@ export const $ImageToImageInvocation = { width: { type: 'number', description: `The width of the resulting image`, - multipleOf: 64, + multipleOf: 8, }, height: { type: 'number', description: `The height of the resulting image`, - multipleOf: 64, + multipleOf: 8, }, cfg_scale: { type: 'number', description: `The Classifier-Free Guidance, higher values may result in a result closer to the prompt`, + exclusiveMinimum: 1, }, scheduler: { type: 'Enum', diff --git a/invokeai/frontend/web/src/services/api/schemas/$InpaintInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$InpaintInvocation.ts index ab022825b3..02b945b955 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$InpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$InpaintInvocation.ts @@ -29,16 +29,17 @@ export const $InpaintInvocation = { width: { type: 'number', description: `The width of the resulting image`, - multipleOf: 64, + multipleOf: 8, }, height: { type: 'number', description: `The height of the resulting image`, - multipleOf: 64, + multipleOf: 8, }, cfg_scale: { type: 'number', description: `The Classifier-Free Guidance, higher values may result in a result closer to the prompt`, + exclusiveMinimum: 1, }, scheduler: { type: 'Enum', @@ -78,6 +79,50 @@ export const $InpaintInvocation = { type: 'ImageField', }], }, + seam_size: { + type: 'number', + description: `The seam inpaint size (px)`, + minimum: 1, + }, + seam_blur: { + type: 'number', + description: `The seam inpaint blur radius (px)`, + }, + seam_strength: { + type: 'number', + description: `The seam inpaint strength`, + maximum: 1, + }, + seam_steps: { + type: 'number', + description: `The number of steps to use for seam inpaint`, + minimum: 1, + }, + tile_size: { + type: 'number', + description: `The tile infill method size (px)`, + minimum: 1, + }, + infill_method: { + type: 'Enum', + }, + inpaint_width: { + type: 'number', + description: `The width of the inpaint region (px)`, + multipleOf: 8, + }, + inpaint_height: { + type: 'number', + description: `The height of the inpaint region (px)`, + multipleOf: 8, + }, + inpaint_fill: { + type: 'all-of', + description: `The solid infill method color`, + contains: [{ + type: 'ColorField', + }], + }, inpaint_replace: { type: 'number', description: `The amount by which to replace masked areas with latent noise`, diff --git a/invokeai/frontend/web/src/services/api/schemas/$NoiseInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$NoiseInvocation.ts index 446e77e747..f6ae9fda0e 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$NoiseInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$NoiseInvocation.ts @@ -20,12 +20,12 @@ export const $NoiseInvocation = { width: { type: 'number', description: `The width of the resulting noise`, - multipleOf: 64, + multipleOf: 8, }, height: { type: 'number', description: `The height of the resulting noise`, - multipleOf: 64, + multipleOf: 8, }, }, } as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$TextToImageInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$TextToImageInvocation.ts index 70c5858012..a8b6cf41d0 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$TextToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$TextToImageInvocation.ts @@ -29,16 +29,17 @@ export const $TextToImageInvocation = { width: { type: 'number', description: `The width of the resulting image`, - multipleOf: 64, + multipleOf: 8, }, height: { type: 'number', description: `The height of the resulting image`, - multipleOf: 64, + multipleOf: 8, }, cfg_scale: { type: 'number', description: `The Classifier-Free Guidance, higher values may result in a result closer to the prompt`, + exclusiveMinimum: 1, }, scheduler: { type: 'Enum', diff --git a/invokeai/frontend/web/src/services/api/services/ImagesService.ts b/invokeai/frontend/web/src/services/api/services/ImagesService.ts index 504b75f6bf..9dc63688fc 100644 --- a/invokeai/frontend/web/src/services/api/services/ImagesService.ts +++ b/invokeai/frontend/web/src/services/api/services/ImagesService.ts @@ -114,13 +114,18 @@ export class ImagesService { * @throws ApiError */ public static uploadImage({ + imageType, formData, }: { + imageType: ImageType, formData: Body_upload_image, }): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/images/uploads/', + query: { + 'image_type': imageType, + }, formData: formData, mediaType: 'multipart/form-data', errors: { diff --git a/invokeai/frontend/web/src/services/thunks/session.ts b/invokeai/frontend/web/src/services/thunks/session.ts index 8e0b59b1d4..bc26ee7aba 100644 --- a/invokeai/frontend/web/src/services/thunks/session.ts +++ b/invokeai/frontend/web/src/services/thunks/session.ts @@ -1,7 +1,7 @@ import { createAppAsyncThunk } from 'app/store/storeUtils'; import { SessionsService } from 'services/api'; import { buildLinearGraph as buildGenerateGraph } from 'features/nodes/util/buildLinearGraph'; -import { buildCanvasGraph } from 'features/nodes/util/buildCanvasGraph'; +import { buildCanvasGraphAndBlobs } from 'features/nodes/util/buildCanvasGraph'; import { isAnyOf, isFulfilled } from '@reduxjs/toolkit'; import { buildNodesGraph } from 'features/nodes/util/buildNodesGraph'; import { log } from 'app/logging/useLogger'; @@ -9,62 +9,62 @@ import { serializeError } from 'serialize-error'; const sessionLog = log.child({ namespace: 'session' }); -export const generateGraphBuilt = createAppAsyncThunk( - 'api/generateGraphBuilt', - async (_, { dispatch, getState, rejectWithValue }) => { - try { - const graph = buildGenerateGraph(getState()); - dispatch(sessionCreated({ graph })); - return graph; - } catch (err: any) { - sessionLog.error( - { error: serializeError(err) }, - 'Problem building graph' - ); - return rejectWithValue(err.message); - } - } -); +// export const generateGraphBuilt = createAppAsyncThunk( +// 'api/generateGraphBuilt', +// async (_, { dispatch, getState, rejectWithValue }) => { +// try { +// const graph = buildGenerateGraph(getState()); +// dispatch(sessionCreated({ graph })); +// return graph; +// } catch (err: any) { +// sessionLog.error( +// { error: serializeError(err) }, +// 'Problem building graph' +// ); +// return rejectWithValue(err.message); +// } +// } +// ); -export const nodesGraphBuilt = createAppAsyncThunk( - 'api/nodesGraphBuilt', - async (_, { dispatch, getState, rejectWithValue }) => { - try { - const graph = buildNodesGraph(getState()); - dispatch(sessionCreated({ graph })); - return graph; - } catch (err: any) { - sessionLog.error( - { error: serializeError(err) }, - 'Problem building graph' - ); - return rejectWithValue(err.message); - } - } -); +// export const nodesGraphBuilt = createAppAsyncThunk( +// 'api/nodesGraphBuilt', +// async (_, { dispatch, getState, rejectWithValue }) => { +// try { +// const graph = buildNodesGraph(getState()); +// dispatch(sessionCreated({ graph })); +// return graph; +// } catch (err: any) { +// sessionLog.error( +// { error: serializeError(err) }, +// 'Problem building graph' +// ); +// return rejectWithValue(err.message); +// } +// } +// ); -export const canvasGraphBuilt = createAppAsyncThunk( - 'api/canvasGraphBuilt', - async (_, { dispatch, getState, rejectWithValue }) => { - try { - const graph = await buildCanvasGraph(getState()); - dispatch(sessionCreated({ graph })); - return graph; - } catch (err: any) { - sessionLog.error( - { error: serializeError(err) }, - 'Problem building graph' - ); - return rejectWithValue(err.message); - } - } -); +// export const canvasGraphBuilt = createAppAsyncThunk( +// 'api/canvasGraphBuilt', +// async (_, { dispatch, getState, rejectWithValue }) => { +// try { +// const graph = await buildCanvasGraph(getState()); +// dispatch(sessionCreated({ graph })); +// return graph; +// } catch (err: any) { +// sessionLog.error( +// { error: serializeError(err) }, +// 'Problem building graph' +// ); +// return rejectWithValue(err.message); +// } +// } +// ); -export const isFulfilledAnyGraphBuilt = isAnyOf( - generateGraphBuilt.fulfilled, - nodesGraphBuilt.fulfilled, - canvasGraphBuilt.fulfilled -); +// export const isFulfilledAnyGraphBuilt = isAnyOf( +// generateGraphBuilt.fulfilled, +// nodesGraphBuilt.fulfilled, +// canvasGraphBuilt.fulfilled +// ); type SessionCreatedArg = { graph: Parameters< From b0557aa16b73a2416ce067b9ede95a96ae617fcf Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 May 2023 10:56:08 +1000 Subject: [PATCH 15/66] fix(ui): fix currentimagepreview not working for uploads --- .../gallery/components/CurrentImagePreview.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index 61a6bd9279..bbc4cd1a58 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -2,22 +2,20 @@ import { Box, Flex, Image } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { useGetUrl } from 'common/util/getUrl'; -import { systemSelector } from 'features/system/store/systemSelectors'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { isEqual } from 'lodash-es'; -import { selectedImageSelector } from '../store/gallerySelectors'; -import CurrentImageFallback from './CurrentImageFallback'; +import { gallerySelector } from '../store/gallerySelectors'; import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer'; import NextPrevImageButtons from './NextPrevImageButtons'; import CurrentImageHidden from './CurrentImageHidden'; import { DragEvent, memo, useCallback } from 'react'; export const imagesSelector = createSelector( - [uiSelector, selectedImageSelector, systemSelector], - (ui, selectedImage, system) => { + [uiSelector, gallerySelector], + (ui, gallery) => { const { shouldShowImageDetails, shouldHidePreview } = ui; - + const { selectedImage } = gallery; return { shouldShowImageDetails, shouldHidePreview, From 350ffecc1f882b21a1505dd85a241c39f798f305 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 May 2023 10:56:27 +1000 Subject: [PATCH 16/66] feat(ui): endless gallery scroll --- .../features/gallery/components/ImageGalleryContent.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 1e1e73812f..b257e0b11f 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -170,6 +170,14 @@ const ImageGalleryContent = () => { } }, []); + const handleEndReached = useCallback(() => { + if (currentCategory === 'results') { + dispatch(receivedResultImagesPage()); + } else if (currentCategory === 'uploads') { + dispatch(receivedUploadImagesPage()); + } + }, [dispatch, currentCategory]); + return ( { Date: Fri, 5 May 2023 11:25:44 +1000 Subject: [PATCH 17/66] feat(ui): tweak gallery loading indicator --- .../web/src/features/gallery/components/ImageGalleryContent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index b257e0b11f..db5d23b62d 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -343,6 +343,7 @@ const ImageGalleryContent = () => { onClick={handleClickLoadMore} isDisabled={!areMoreImagesAvailable} isLoading={isLoading} + loadingText="Loading" flexShrink={0} > {areMoreImagesAvailable From 20f6a597ab2435d24384106b98f15be84f43e577 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 May 2023 11:38:01 +1000 Subject: [PATCH 18/66] fix(nodes): add MetadataColorField --- invokeai/app/services/metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/app/services/metadata.py b/invokeai/app/services/metadata.py index fd50ddad20..a7f2378ab1 100644 --- a/invokeai/app/services/metadata.py +++ b/invokeai/app/services/metadata.py @@ -31,7 +31,7 @@ class MetadataColorField(TypedDict): # TODO: This is a placeholder for `InvocationsUnion` pending resolution of circular imports NodeMetadata = Dict[ - str, None | str | int | float | bool | MetadataImageField | MetadataLatentsField + str, None | str | int | float | bool | MetadataImageField | MetadataLatentsField | MetadataColorField ] From 49db6f4facde0b1770f9978bc46cb4b38481f5b1 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 May 2023 11:38:17 +1000 Subject: [PATCH 19/66] fix(nodes): fix trivial typing issues --- invokeai/backend/generator/inpaint.py | 29 +++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/invokeai/backend/generator/inpaint.py b/invokeai/backend/generator/inpaint.py index 9cdc52fd29..8c471d025d 100644 --- a/invokeai/backend/generator/inpaint.py +++ b/invokeai/backend/generator/inpaint.py @@ -4,6 +4,7 @@ invokeai.backend.generator.inpaint descends from .generator from __future__ import annotations import math +from typing import Tuple, Union import cv2 import numpy as np @@ -59,7 +60,7 @@ class Inpaint(Img2Img): writeable=False, ) - def infill_patchmatch(self, im: Image.Image) -> Image: + def infill_patchmatch(self, im: Image.Image) -> Image.Image: if im.mode != "RGBA": return im @@ -75,18 +76,18 @@ class Inpaint(Img2Img): return im_patched def tile_fill_missing( - self, im: Image.Image, tile_size: int = 16, seed: int = None - ) -> Image: + self, im: Image.Image, tile_size: int = 16, seed: Union[int, None] = None + ) -> Image.Image: # Only fill if there's an alpha layer if im.mode != "RGBA": return im a = np.asarray(im, dtype=np.uint8) - tile_size = (tile_size, tile_size) + tile_size_tuple = (tile_size, tile_size) # Get the image as tiles of a specified size - tiles = self.get_tile_images(a, *tile_size).copy() + tiles = self.get_tile_images(a, *tile_size_tuple).copy() # Get the mask as tiles tiles_mask = tiles[:, :, :, :, 3] @@ -127,7 +128,9 @@ class Inpaint(Img2Img): return si - def mask_edge(self, mask: Image, edge_size: int, edge_blur: int) -> Image: + def mask_edge( + self, mask: Image.Image, edge_size: int, edge_blur: int + ) -> Image.Image: npimg = np.asarray(mask, dtype=np.uint8) # Detect any partially transparent regions @@ -206,8 +209,8 @@ class Inpaint(Img2Img): cfg_scale, ddim_eta, conditioning, - init_image: PIL.Image.Image | torch.FloatTensor, - mask_image: PIL.Image.Image | torch.FloatTensor, + init_image: Image.Image | torch.FloatTensor, + mask_image: Image.Image | torch.FloatTensor, strength: float, mask_blur_radius: int = 8, # Seam settings - when 0, doesn't fill seam @@ -222,7 +225,7 @@ class Inpaint(Img2Img): infill_method=None, inpaint_width=None, inpaint_height=None, - inpaint_fill: tuple(int) = (0x7F, 0x7F, 0x7F, 0xFF), + inpaint_fill: Tuple[int, int, int, int] = (0x7F, 0x7F, 0x7F, 0xFF), attention_maps_callback=None, **kwargs, ): @@ -239,7 +242,7 @@ class Inpaint(Img2Img): self.inpaint_width = inpaint_width self.inpaint_height = inpaint_height - if isinstance(init_image, PIL.Image.Image): + if isinstance(init_image, Image.Image): self.pil_image = init_image.copy() # Do infill @@ -250,8 +253,8 @@ class Inpaint(Img2Img): self.pil_image.copy(), seed=self.seed, tile_size=tile_size ) elif infill_method == "solid": - solid_bg = PIL.Image.new("RGBA", init_image.size, inpaint_fill) - init_filled = PIL.Image.alpha_composite(solid_bg, init_image) + solid_bg = Image.new("RGBA", init_image.size, inpaint_fill) + init_filled = Image.alpha_composite(solid_bg, init_image) else: raise ValueError( f"Non-supported infill type {infill_method}", infill_method @@ -269,7 +272,7 @@ class Inpaint(Img2Img): # Create init tensor init_image = image_resized_to_grid_as_tensor(init_filled.convert("RGB")) - if isinstance(mask_image, PIL.Image.Image): + if isinstance(mask_image, Image.Image): self.pil_mask = mask_image.copy() debug_image( mask_image, From ff3aa571177be54431ea3593ea4b1017c4ec38b6 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 May 2023 14:48:18 +1000 Subject: [PATCH 20/66] feat(ui): fix endless gallery scroll for single col layout --- .../web/src/features/gallery/components/ImageGalleryContent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index db5d23b62d..0ae6d29021 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -298,6 +298,7 @@ const ImageGalleryContent = () => { setScrollerRef(ref)} itemContent={(index, image) => { const { name } = image; From 6102e560ba7f17e91f6efbd1711d5c3d353a763d Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 May 2023 15:15:55 +1000 Subject: [PATCH 21/66] feat(nodes): add LatentsToImage node (VAE encode) --- invokeai/app/invocations/latent.py | 50 ++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 0d3ef4a8cd..01c7623e03 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -1,7 +1,8 @@ # Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654) import random -from typing import Literal, Optional +from typing import Literal, Optional, Union +import einops from pydantic import BaseModel, Field import torch @@ -13,7 +14,8 @@ from ...backend.model_management.model_manager import ModelManager from ...backend.util.devices import choose_torch_device, torch_dtype from ...backend.stable_diffusion.diffusion.shared_invokeai_diffusion import PostprocessingSettings from ...backend.image_util.seamless import configure_model_padding -from ...backend.stable_diffusion.diffusers_pipeline import ConditioningData, StableDiffusionGeneratorPipeline +from ...backend.prompting.conditioning import get_uc_and_c_and_ec +from ...backend.stable_diffusion.diffusers_pipeline import ConditioningData, StableDiffusionGeneratorPipeline, image_resized_to_grid_as_tensor from .baseinvocation import BaseInvocation, BaseInvocationOutput, InvocationContext, InvocationConfig import numpy as np from ..services.image_storage import ImageType @@ -433,3 +435,47 @@ class ScaleLatentsInvocation(BaseInvocation): name = f"{context.graph_execution_state_id}__{self.id}" context.services.latents.set(name, resized_latents) return LatentsOutput(latents=LatentsField(latents_name=name)) + + +class ImageToLatentsInvocation(BaseInvocation): + """Encodes an image into latents.""" + + type: Literal["i2l"] = "i2l" + + # Inputs + image: Union[ImageField, None] = Field(description="The image to encode") + model: str = Field(default="", description="The model to use") + + # Schema customisation + class Config(InvocationConfig): + schema_extra = { + "ui": { + "tags": ["latents", "image"], + "type_hints": {"model": "model"}, + }, + } + + @torch.no_grad() + def invoke(self, context: InvocationContext) -> LatentsOutput: + image = context.services.images.get( + self.image.image_type, self.image.image_name + ) + + # TODO: this only really needs the vae + model_info = choose_model(context.services.model_manager, self.model) + model: StableDiffusionGeneratorPipeline = model_info["model"] + + image_tensor = image_resized_to_grid_as_tensor(image.convert("RGB")) + + if image_tensor.dim() == 3: + image_tensor = einops.rearrange(image_tensor, "c h w -> 1 c h w") + + latents = model.non_noised_latents_from_image( + image_tensor, + device=model._model_group.device_for(model.unet), + dtype=model.unet.dtype, + ) + + name = f"{context.graph_execution_state_id}__{self.id}" + context.services.latents.set(name, latents) + return LatentsOutput(latents=LatentsField(latents_name=name)) From da4eacdffe11bacd50654d5fc471072cf670948d Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 May 2023 15:16:26 +1000 Subject: [PATCH 22/66] feat(nodes): add InfillInvocation --- invokeai/app/invocations/generate.py | 1 - invokeai/app/invocations/infill.py | 183 +++++++++++++++++++++++++++ invokeai/app/models/image.py | 7 +- 3 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 invokeai/app/invocations/infill.py diff --git a/invokeai/app/invocations/generate.py b/invokeai/app/invocations/generate.py index eeb0d86477..85400a834a 100644 --- a/invokeai/app/invocations/generate.py +++ b/invokeai/app/invocations/generate.py @@ -19,7 +19,6 @@ from ..util.step_callback import stable_diffusion_step_callback SAMPLER_NAME_VALUES = Literal[tuple(InvokeAIGenerator.schedulers())] INFILL_METHODS = Literal[tuple(infill_methods())] - DEFAULT_INFILL_METHOD = 'patchmatch' if 'patchmatch' in get_args(INFILL_METHODS) else 'tile' class SDImageInvocation(BaseModel): diff --git a/invokeai/app/invocations/infill.py b/invokeai/app/invocations/infill.py new file mode 100644 index 0000000000..05ef4eb9e7 --- /dev/null +++ b/invokeai/app/invocations/infill.py @@ -0,0 +1,183 @@ +# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) + +from typing import Literal, Optional, Union, get_args + +import numpy as np +import math +from PIL import Image, ImageOps +from pydantic import Field + +from invokeai.app.invocations.image import ImageOutput, build_image_output +from invokeai.backend.image_util.patchmatch import PatchMatch + +from ..models.image import ColorField, ImageField, ImageType +from .baseinvocation import ( + BaseInvocation, + InvocationContext, +) + + +def infill_methods() -> list[str]: + methods = [ + "tile", + "solid", + ] + if PatchMatch.patchmatch_available(): + methods.insert(0, "patchmatch") + return methods + + +INFILL_METHODS = Literal[tuple(infill_methods())] +DEFAULT_INFILL_METHOD = ( + "patchmatch" if "patchmatch" in get_args(INFILL_METHODS) else "tile" +) + + +def infill_patchmatch(im: Image.Image) -> Image.Image: + if im.mode != "RGBA": + return im + + # Skip patchmatch if patchmatch isn't available + if not PatchMatch.patchmatch_available(): + return im + + # Patchmatch (note, we may want to expose patch_size? Increasing it significantly impacts performance though) + im_patched_np = PatchMatch.inpaint( + im.convert("RGB"), ImageOps.invert(im.split()[-1]), patch_size=3 + ) + im_patched = Image.fromarray(im_patched_np, mode="RGB") + return im_patched + + +def get_tile_images(image: np.ndarray, width=8, height=8): + _nrows, _ncols, depth = image.shape + _strides = image.strides + + nrows, _m = divmod(_nrows, height) + ncols, _n = divmod(_ncols, width) + if _m != 0 or _n != 0: + return None + + return np.lib.stride_tricks.as_strided( + np.ravel(image), + shape=(nrows, ncols, height, width, depth), + strides=(height * _strides[0], width * _strides[1], *_strides), + writeable=False, + ) + + +def tile_fill_missing( + im: Image.Image, tile_size: int = 16, seed: Union[int, None] = None +) -> Image.Image: + # Only fill if there's an alpha layer + if im.mode != "RGBA": + return im + + a = np.asarray(im, dtype=np.uint8) + + tile_size_tuple = (tile_size, tile_size) + + # Get the image as tiles of a specified size + tiles = get_tile_images(a, *tile_size_tuple).copy() + + # Get the mask as tiles + tiles_mask = tiles[:, :, :, :, 3] + + # Find any mask tiles with any fully transparent pixels (we will be replacing these later) + tmask_shape = tiles_mask.shape + tiles_mask = tiles_mask.reshape(math.prod(tiles_mask.shape)) + n, ny = (math.prod(tmask_shape[0:2])), math.prod(tmask_shape[2:]) + tiles_mask = tiles_mask > 0 + tiles_mask = tiles_mask.reshape((n, ny)).all(axis=1) + + # Get RGB tiles in single array and filter by the mask + tshape = tiles.shape + tiles_all = tiles.reshape((math.prod(tiles.shape[0:2]), *tiles.shape[2:])) + filtered_tiles = tiles_all[tiles_mask] + + if len(filtered_tiles) == 0: + return im + + # Find all invalid tiles and replace with a random valid tile + replace_count = (tiles_mask == False).sum() + rng = np.random.default_rng(seed=seed) + tiles_all[np.logical_not(tiles_mask)] = filtered_tiles[ + rng.choice(filtered_tiles.shape[0], replace_count), :, :, : + ] + + # Convert back to an image + tiles_all = tiles_all.reshape(tshape) + tiles_all = tiles_all.swapaxes(1, 2) + st = tiles_all.reshape( + ( + math.prod(tiles_all.shape[0:2]), + math.prod(tiles_all.shape[2:4]), + tiles_all.shape[4], + ) + ) + si = Image.fromarray(st, mode="RGBA") + + return si + + +class InfillImageInvocation(BaseInvocation): + """Infills transparent areas of an image""" + + type: Literal["infill"] = "infill" + + image: ImageField = Field(default=None, description="The image to infill") + infill_method: INFILL_METHODS = Field( + default=DEFAULT_INFILL_METHOD, + description="The method used to infill empty regions (px)", + ) + inpaint_fill: Optional[ColorField] = Field( + default=ColorField(r=127, g=127, b=127, a=255), + description="The solid infill method color", + ) + tile_size: int = Field( + default=32, ge=1, description="The tile infill method size (px)" + ) + seed: int = Field( + default=-1, + ge=-1, + le=np.iinfo(np.uint32).max, + description="The seed to use (-1 for a random seed)", + ) + + def invoke(self, context: InvocationContext) -> ImageOutput: + image = context.services.images.get( + self.image.image_type, self.image.image_name + ) + + # Do infill + if self.infill_method == "patchmatch" and PatchMatch.patchmatch_available(): + infilled = infill_patchmatch(image.copy()) + elif self.infill_method == "tile": + infilled = tile_fill_missing( + image.copy(), seed=self.seed, tile_size=self.tile_size + ) + elif self.infill_method == "solid": + solid_bg = Image.new("RGBA", image.size, self.inpaint_fill.tuple()) + infilled = Image.alpha_composite(solid_bg, image) + else: + raise ValueError( + f"Non-supported infill type {self.infill_method}", self.infill_method + ) + + infilled.paste(image, (0, 0), image.split()[-1]) + + image_type = ImageType.RESULT + image_name = context.services.images.create_name( + context.graph_execution_state_id, self.id + ) + + metadata = context.services.metadata.build_metadata( + session_id=context.graph_execution_state_id, node=self + ) + + context.services.images.save(image_type, image_name, infilled, metadata) + return build_image_output( + image_type=image_type, + image_name=image_name, + image=image, + ) diff --git a/invokeai/app/models/image.py b/invokeai/app/models/image.py index e707343aa7..f6813c6d96 100644 --- a/invokeai/app/models/image.py +++ b/invokeai/app/models/image.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Optional +from typing import Optional, Tuple from pydantic import BaseModel, Field @@ -33,4 +33,7 @@ class ColorField(BaseModel): r: int = Field(ge=0, le=255, description="The red component") g: int = Field(ge=0, le=255, description="The green component") b: int = Field(ge=0, le=255, description="The blue component") - a: Optional[int] = Field(default=255, ge=0, le=255, description="The alpha component") + a: int = Field(ge=0, le=255, description="The alpha component") + + def tuple(self) -> Tuple[int, int, int, int]: + return (self.r, self.g, self.b, self.a) From bcc21531fb3488b3c6dc12c3057104cb89e74c8c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 May 2023 15:16:54 +1000 Subject: [PATCH 23/66] feat(ui): update for InfillInvocation --- .../nodes/components/InputFieldComponent.tsx | 11 +++++ .../fields/ColorInputFieldComponent.tsx | 31 +++++++++++++ .../src/features/nodes/store/nodesSlice.ts | 4 +- .../web/src/features/nodes/types/constants.ts | 7 +++ .../web/src/features/nodes/types/types.ts | 24 ++++++++-- .../features/nodes/util/buildNodesGraph.ts | 29 ++++++++++-- .../nodes/util/fieldTemplateBuilders.ts | 23 +++++++++- .../frontend/web/src/services/api/index.ts | 6 +++ .../web/src/services/api/models/ColorField.ts | 10 ++--- .../web/src/services/api/models/Graph.ts | 4 +- .../api/models/ImageToLatentsInvocation.ts | 25 +++++++++++ .../api/models/InfillImageInvocation.ts | 38 ++++++++++++++++ .../services/api/models/InvokeAIMetadata.ts | 3 +- .../services/api/models/MetadataColorField.ts | 11 +++++ .../src/services/api/schemas/$ColorField.ts | 13 +++--- .../web/src/services/api/schemas/$Graph.ts | 4 ++ .../api/schemas/$ImageToLatentsInvocation.ts | 27 ++++++++++++ .../api/schemas/$InfillImageInvocation.ts | 44 +++++++++++++++++++ .../services/api/schemas/$InvokeAIMetadata.ts | 2 + .../api/schemas/$MetadataColorField.ts | 23 ++++++++++ .../services/api/services/SessionsService.ts | 6 ++- 21 files changed, 320 insertions(+), 25 deletions(-) create mode 100644 invokeai/frontend/web/src/features/nodes/components/fields/ColorInputFieldComponent.tsx create mode 100644 invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/InfillImageInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/MetadataColorField.ts create mode 100644 invokeai/frontend/web/src/services/api/schemas/$ImageToLatentsInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/schemas/$InfillImageInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/schemas/$MetadataColorField.ts diff --git a/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx index b4502d1ff3..6d8d23a123 100644 --- a/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx @@ -11,6 +11,7 @@ import ModelInputFieldComponent from './fields/ModelInputFieldComponent'; import NumberInputFieldComponent from './fields/NumberInputFieldComponent'; import StringInputFieldComponent from './fields/StringInputFieldComponent'; import ItemInputFieldComponent from './fields/ItemInputFieldComponent'; +import ColorInputFieldComponent from './fields/ColorInputFieldComponent'; type InputFieldComponentProps = { nodeId: string; @@ -126,6 +127,16 @@ const InputFieldComponent = (props: InputFieldComponentProps) => { ); } + if (type === 'color' && template.type === 'color') { + return ( + + ); + } + return Unknown field type: {type}; }; diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ColorInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ColorInputFieldComponent.tsx new file mode 100644 index 0000000000..c4884dcffc --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ColorInputFieldComponent.tsx @@ -0,0 +1,31 @@ +import { + ColorInputFieldTemplate, + ColorInputFieldValue, +} from 'features/nodes/types/types'; +import { memo } from 'react'; +import { FieldComponentProps } from './types'; +import { RgbaColor, RgbaColorPicker } from 'react-colorful'; +import { fieldValueChanged } from 'features/nodes/store/nodesSlice'; +import { useAppDispatch } from 'app/store/storeHooks'; + +const ColorInputFieldComponent = ( + props: FieldComponentProps +) => { + const { nodeId, field } = props; + + const dispatch = useAppDispatch(); + + const handleValueChanged = (value: RgbaColor) => { + dispatch(fieldValueChanged({ nodeId, fieldName: field.name, value })); + }; + + return ( + + ); +}; + +export default memo(ColorInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts index c27f2e83fc..e39908f053 100644 --- a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts +++ b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts @@ -11,13 +11,14 @@ import { NodeChange, OnConnectStartParams, } from 'reactflow'; -import { Graph, ImageField } from 'services/api'; +import { ColorField, Graph, ImageField } from 'services/api'; import { receivedOpenAPISchema } from 'services/thunks/schema'; import { InvocationTemplate, InvocationValue } from '../types/types'; import { parseSchema } from '../util/parseSchema'; import { log } from 'app/logging/useLogger'; import { size } from 'lodash-es'; import { isAnyGraphBuilt } from './actions'; +import { RgbaColor } from 'react-colorful'; export type NodesState = { nodes: Node[]; @@ -69,6 +70,7 @@ const nodesSlice = createSlice({ | number | boolean | Pick + | RgbaColor | undefined; }> ) => { diff --git a/invokeai/frontend/web/src/features/nodes/types/constants.ts b/invokeai/frontend/web/src/features/nodes/types/constants.ts index e3b8d0563d..0e715cb8bb 100644 --- a/invokeai/frontend/web/src/features/nodes/types/constants.ts +++ b/invokeai/frontend/web/src/features/nodes/types/constants.ts @@ -15,6 +15,7 @@ export const FIELD_TYPE_MAP: Record = { model: 'model', array: 'array', item: 'item', + ColorField: 'color', }; const COLOR_TOKEN_VALUE = 500; @@ -89,4 +90,10 @@ export const FIELDS: Record = { title: 'Collection Item', description: 'TODO: Collection Item type description.', }, + color: { + color: 'gray', + colorCssVar: getColorTokenCssVariable('gray'), + title: 'Color', + description: 'A RGBA color.', + }, }; diff --git a/invokeai/frontend/web/src/features/nodes/types/types.ts b/invokeai/frontend/web/src/features/nodes/types/types.ts index f637fc965f..5cb67d1ef6 100644 --- a/invokeai/frontend/web/src/features/nodes/types/types.ts +++ b/invokeai/frontend/web/src/features/nodes/types/types.ts @@ -1,6 +1,9 @@ +import { Image } from 'app/types/invokeai'; import { OpenAPIV3 } from 'openapi-types'; +import { RgbaColor } from 'react-colorful'; import { ImageField } from 'services/api'; import { AnyInvocationType } from 'services/events/types'; +import { O } from 'ts-toolbelt'; export type InvocationValue = { id: string; @@ -59,7 +62,8 @@ export type FieldType = | 'conditioning' | 'model' | 'array' - | 'item'; + | 'item' + | 'color'; /** * An input field is persisted across reloads as part of the user's local state. @@ -80,7 +84,8 @@ export type InputFieldValue = | EnumInputFieldValue | ModelInputFieldValue | ArrayInputFieldValue - | ItemInputFieldValue; + | ItemInputFieldValue + | ColorInputFieldValue; /** * An input field template is generated on each page load from the OpenAPI schema. @@ -99,7 +104,8 @@ export type InputFieldTemplate = | EnumInputFieldTemplate | ModelInputFieldTemplate | ArrayInputFieldTemplate - | ItemInputFieldTemplate; + | ItemInputFieldTemplate + | ColorInputFieldTemplate; /** * An output field is persisted across as part of the user's local state. @@ -193,6 +199,11 @@ export type ItemInputFieldValue = FieldValueBase & { value?: undefined; }; +export type ColorInputFieldValue = FieldValueBase & { + type: 'color'; + value?: RgbaColor; +}; + export type InputFieldTemplateBase = { name: string; title: string; @@ -241,7 +252,7 @@ export type ImageInputFieldTemplate = InputFieldTemplateBase & { }; export type LatentsInputFieldTemplate = InputFieldTemplateBase & { - default: undefined; + default: string; type: 'latents'; }; @@ -272,6 +283,11 @@ export type ItemInputFieldTemplate = InputFieldTemplateBase & { type: 'item'; }; +export type ColorInputFieldTemplate = InputFieldTemplateBase & { + default: RgbaColor; + type: 'color'; +}; + /** * JANKY CUSTOMISATION OF OpenAPI SCHEMA TYPES */ diff --git a/invokeai/frontend/web/src/features/nodes/util/buildNodesGraph.ts b/invokeai/frontend/web/src/features/nodes/util/buildNodesGraph.ts index f12b141e09..7faa20bfd3 100644 --- a/invokeai/frontend/web/src/features/nodes/util/buildNodesGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/buildNodesGraph.ts @@ -1,8 +1,30 @@ import { Graph } from 'services/api'; import { v4 as uuidv4 } from 'uuid'; -import { reduce } from 'lodash-es'; +import { cloneDeep, reduce } from 'lodash-es'; import { RootState } from 'app/store/store'; -import { AnyInvocation } from 'services/events/types'; +import { InputFieldValue } from '../types/types'; + +/** + * We need to do special handling for some fields + */ +export const parseFieldValue = (field: InputFieldValue) => { + if (field.type === 'color') { + if (field.value) { + const clonedValue = cloneDeep(field.value); + + const { r, g, b, a } = field.value; + + // scale alpha value to PIL's desired range 0-255 + const scaledAlpha = Math.max(0, Math.min(a * 255, 255)); + const transformedColor = { r, g, b, a: scaledAlpha }; + + Object.assign(clonedValue, transformedColor); + return clonedValue; + } + } + + return field.value; +}; /** * Builds a graph from the node editor state. @@ -20,7 +42,8 @@ export const buildNodesGraph = (state: RootState): Graph => { const transformedInputs = reduce( inputs, (inputsAccumulator, input, name) => { - inputsAccumulator[name] = input.value; + const parsedValue = parseFieldValue(input); + inputsAccumulator[name] = parsedValue; return inputsAccumulator; }, diff --git a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts index 1912cb483e..77261209a9 100644 --- a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts +++ b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts @@ -12,12 +12,13 @@ import { ConditioningInputFieldTemplate, StringInputFieldTemplate, ModelInputFieldTemplate, + ArrayInputFieldTemplate, + ItemInputFieldTemplate, + ColorInputFieldTemplate, InputFieldTemplateBase, OutputFieldTemplate, TypeHints, FieldType, - ArrayInputFieldTemplate, - ItemInputFieldTemplate, } from '../types/types'; export type BaseFieldProperties = 'name' | 'title' | 'description'; @@ -262,6 +263,21 @@ const buildItemInputFieldTemplate = ({ return template; }; +const buildColorInputFieldTemplate = ({ + schemaObject, + baseField, +}: BuildInputFieldArg): ColorInputFieldTemplate => { + const template: ColorInputFieldTemplate = { + ...baseField, + type: 'color', + inputRequirement: 'always', + inputKind: 'direct', + default: schemaObject.default ?? { r: 127, g: 127, b: 127, a: 255 }, + }; + + return template; +}; + export const getFieldType = ( schemaObject: OpenAPIV3.SchemaObject, name: string, @@ -341,6 +357,9 @@ export const buildInputFieldTemplate = ( if (['item'].includes(fieldType)) { return buildItemInputFieldTemplate({ schemaObject, baseField }); } + if (['color'].includes(fieldType)) { + return buildColorInputFieldTemplate({ schemaObject, baseField }); + } return; }; diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index 5856832078..b3a63c8935 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -31,7 +31,9 @@ export type { ImageOutput } from './models/ImageOutput'; export type { ImageResponse } from './models/ImageResponse'; export type { ImageResponseMetadata } from './models/ImageResponseMetadata'; export type { ImageToImageInvocation } from './models/ImageToImageInvocation'; +export type { ImageToLatentsInvocation } from './models/ImageToLatentsInvocation'; export type { ImageType } from './models/ImageType'; +export type { InfillImageInvocation } from './models/InfillImageInvocation'; export type { InpaintInvocation } from './models/InpaintInvocation'; export type { IntCollectionOutput } from './models/IntCollectionOutput'; export type { IntOutput } from './models/IntOutput'; @@ -47,6 +49,7 @@ export type { LerpInvocation } from './models/LerpInvocation'; export type { LoadImageInvocation } from './models/LoadImageInvocation'; export type { MaskFromAlphaInvocation } from './models/MaskFromAlphaInvocation'; export type { MaskOutput } from './models/MaskOutput'; +export type { MetadataColorField } from './models/MetadataColorField'; export type { MetadataImageField } from './models/MetadataImageField'; export type { MetadataLatentsField } from './models/MetadataLatentsField'; export type { ModelsList } from './models/ModelsList'; @@ -96,7 +99,9 @@ export { $ImageOutput } from './schemas/$ImageOutput'; export { $ImageResponse } from './schemas/$ImageResponse'; export { $ImageResponseMetadata } from './schemas/$ImageResponseMetadata'; export { $ImageToImageInvocation } from './schemas/$ImageToImageInvocation'; +export { $ImageToLatentsInvocation } from './schemas/$ImageToLatentsInvocation'; export { $ImageType } from './schemas/$ImageType'; +export { $InfillImageInvocation } from './schemas/$InfillImageInvocation'; export { $InpaintInvocation } from './schemas/$InpaintInvocation'; export { $IntCollectionOutput } from './schemas/$IntCollectionOutput'; export { $IntOutput } from './schemas/$IntOutput'; @@ -112,6 +117,7 @@ export { $LerpInvocation } from './schemas/$LerpInvocation'; export { $LoadImageInvocation } from './schemas/$LoadImageInvocation'; export { $MaskFromAlphaInvocation } from './schemas/$MaskFromAlphaInvocation'; export { $MaskOutput } from './schemas/$MaskOutput'; +export { $MetadataColorField } from './schemas/$MetadataColorField'; export { $MetadataImageField } from './schemas/$MetadataImageField'; export { $MetadataLatentsField } from './schemas/$MetadataLatentsField'; export { $ModelsList } from './schemas/$ModelsList'; diff --git a/invokeai/frontend/web/src/services/api/models/ColorField.ts b/invokeai/frontend/web/src/services/api/models/ColorField.ts index 01e5383e9c..e0a609ec12 100644 --- a/invokeai/frontend/web/src/services/api/models/ColorField.ts +++ b/invokeai/frontend/web/src/services/api/models/ColorField.ts @@ -7,17 +7,17 @@ export type ColorField = { * The red component */ 'r': number; - /** - * The blue component - */ - 'b': number; /** * The green component */ 'g': number; + /** + * The blue component + */ + 'b': number; /** * The alpha component */ - 'a'?: number; + 'a': number; }; diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index f252f8d2f1..92e52c57a4 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -12,6 +12,8 @@ import type { DivideInvocation } from './DivideInvocation'; import type { Edge } from './Edge'; import type { GraphInvocation } from './GraphInvocation'; import type { ImageToImageInvocation } from './ImageToImageInvocation'; +import type { ImageToLatentsInvocation } from './ImageToLatentsInvocation'; +import type { InfillImageInvocation } from './InfillImageInvocation'; import type { InpaintInvocation } from './InpaintInvocation'; import type { InverseLerpInvocation } from './InverseLerpInvocation'; import type { IterateInvocation } from './IterateInvocation'; @@ -43,7 +45,7 @@ export type Graph = { /** * The nodes in this graph */ - nodes?: Record; + nodes?: Record; /** * The connections between nodes and their fields in this graph */ diff --git a/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts new file mode 100644 index 0000000000..f72d446615 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts @@ -0,0 +1,25 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ImageField } from './ImageField'; + +/** + * Encodes an image into latents. + */ +export type ImageToLatentsInvocation = { + /** + * The id of this node. Must be unique among all nodes. + */ + id: string; + type?: 'i2l'; + /** + * The image to encode + */ + image?: ImageField; + /** + * The model to use + */ + model?: string; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/InfillImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillImageInvocation.ts new file mode 100644 index 0000000000..31ba8cf79b --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/InfillImageInvocation.ts @@ -0,0 +1,38 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ColorField } from './ColorField'; +import type { ImageField } from './ImageField'; + +/** + * Infills transparent areas of an image + */ +export type InfillImageInvocation = { + /** + * The id of this node. Must be unique among all nodes. + */ + id: string; + type?: 'infill'; + /** + * The image to infill + */ + image?: ImageField; + /** + * The method used to infill empty regions (px) + */ + infill_method?: 'patchmatch' | 'tile' | 'solid'; + /** + * The solid infill method color + */ + inpaint_fill?: ColorField; + /** + * The tile infill method size (px) + */ + tile_size?: number; + /** + * The seed to use (-1 for a random seed) + */ + seed?: number; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/InvokeAIMetadata.ts b/invokeai/frontend/web/src/services/api/models/InvokeAIMetadata.ts index a6bc3f7744..ba80199f9a 100644 --- a/invokeai/frontend/web/src/services/api/models/InvokeAIMetadata.ts +++ b/invokeai/frontend/web/src/services/api/models/InvokeAIMetadata.ts @@ -2,11 +2,12 @@ /* tslint:disable */ /* eslint-disable */ +import type { MetadataColorField } from './MetadataColorField'; import type { MetadataImageField } from './MetadataImageField'; import type { MetadataLatentsField } from './MetadataLatentsField'; export type InvokeAIMetadata = { session_id?: string; - node?: Record; + node?: Record; }; diff --git a/invokeai/frontend/web/src/services/api/models/MetadataColorField.ts b/invokeai/frontend/web/src/services/api/models/MetadataColorField.ts new file mode 100644 index 0000000000..897a0123dd --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/MetadataColorField.ts @@ -0,0 +1,11 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type MetadataColorField = { + 'r': number; + 'g': number; + 'b': number; + 'a': number; +}; + diff --git a/invokeai/frontend/web/src/services/api/schemas/$ColorField.ts b/invokeai/frontend/web/src/services/api/schemas/$ColorField.ts index 8f49a13e71..e38788dae2 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$ColorField.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$ColorField.ts @@ -9,21 +9,22 @@ export const $ColorField = { isRequired: true, maximum: 255, }, - 'b': { - type: 'number', - description: `The blue component`, - isRequired: true, - maximum: 255, - }, 'g': { type: 'number', description: `The green component`, isRequired: true, maximum: 255, }, + 'b': { + type: 'number', + description: `The blue component`, + isRequired: true, + maximum: 255, + }, 'a': { type: 'number', description: `The alpha component`, + isRequired: true, maximum: 255, }, }, diff --git a/invokeai/frontend/web/src/services/api/schemas/$Graph.ts b/invokeai/frontend/web/src/services/api/schemas/$Graph.ts index a41ffd8cc5..882fc3f443 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$Graph.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$Graph.ts @@ -39,6 +39,8 @@ export const $Graph = { type: 'ResizeLatentsInvocation', }, { type: 'ScaleLatentsInvocation', + }, { + type: 'ImageToLatentsInvocation', }, { type: 'AddInvocation', }, { @@ -61,6 +63,8 @@ export const $Graph = { type: 'RestoreFaceInvocation', }, { type: 'TextToImageInvocation', + }, { + type: 'InfillImageInvocation', }, { type: 'GraphInvocation', }, { diff --git a/invokeai/frontend/web/src/services/api/schemas/$ImageToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$ImageToLatentsInvocation.ts new file mode 100644 index 0000000000..48e28f1315 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/schemas/$ImageToLatentsInvocation.ts @@ -0,0 +1,27 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ImageToLatentsInvocation = { + description: `Encodes an image into latents.`, + properties: { + id: { + type: 'string', + description: `The id of this node. Must be unique among all nodes.`, + isRequired: true, + }, + type: { + type: 'Enum', + }, + image: { + type: 'all-of', + description: `The image to encode`, + contains: [{ + type: 'ImageField', + }], + }, + model: { + type: 'string', + description: `The model to use`, + }, + }, +} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$InfillImageInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$InfillImageInvocation.ts new file mode 100644 index 0000000000..14bb346fb7 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/schemas/$InfillImageInvocation.ts @@ -0,0 +1,44 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $InfillImageInvocation = { + description: `Infills transparent areas of an image`, + properties: { + id: { + type: 'string', + description: `The id of this node. Must be unique among all nodes.`, + isRequired: true, + }, + type: { + type: 'Enum', + }, + image: { + type: 'all-of', + description: `The image to infill`, + contains: [{ + type: 'ImageField', + }], + }, + infill_method: { + type: 'Enum', + }, + inpaint_fill: { + type: 'all-of', + description: `The solid infill method color`, + contains: [{ + type: 'ColorField', + }], + }, + tile_size: { + type: 'number', + description: `The tile infill method size (px)`, + minimum: 1, + }, + seed: { + type: 'number', + description: `The seed to use (-1 for a random seed)`, + maximum: 4294967295, + minimum: -1, + }, + }, +} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$InvokeAIMetadata.ts b/invokeai/frontend/web/src/services/api/schemas/$InvokeAIMetadata.ts index 2d0b8e2db1..f2895f6646 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$InvokeAIMetadata.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$InvokeAIMetadata.ts @@ -22,6 +22,8 @@ export const $InvokeAIMetadata = { type: 'MetadataImageField', }, { type: 'MetadataLatentsField', + }, { + type: 'MetadataColorField', }], }, }, diff --git a/invokeai/frontend/web/src/services/api/schemas/$MetadataColorField.ts b/invokeai/frontend/web/src/services/api/schemas/$MetadataColorField.ts new file mode 100644 index 0000000000..234bd3e2f6 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/schemas/$MetadataColorField.ts @@ -0,0 +1,23 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $MetadataColorField = { + properties: { + 'r': { + type: 'number', + isRequired: true, + }, + 'g': { + type: 'number', + isRequired: true, + }, + 'b': { + type: 'number', + isRequired: true, + }, + 'a': { + type: 'number', + isRequired: true, + }, + }, +} as const; diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index f0eb38662d..6567ef2972 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -13,6 +13,8 @@ import type { Graph } from '../models/Graph'; import type { GraphExecutionState } from '../models/GraphExecutionState'; import type { GraphInvocation } from '../models/GraphInvocation'; import type { ImageToImageInvocation } from '../models/ImageToImageInvocation'; +import type { ImageToLatentsInvocation } from '../models/ImageToLatentsInvocation'; +import type { InfillImageInvocation } from '../models/InfillImageInvocation'; import type { InpaintInvocation } from '../models/InpaintInvocation'; import type { InverseLerpInvocation } from '../models/InverseLerpInvocation'; import type { IterateInvocation } from '../models/IterateInvocation'; @@ -145,7 +147,7 @@ export class SessionsService { * The id of the session */ sessionId: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | DataURLToImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | ParamIntInvocation | CvInpaintInvocation | RangeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | DataURLToImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | ParamIntInvocation | CvInpaintInvocation | RangeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillImageInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -182,7 +184,7 @@ export class SessionsService { * The path to the node in the graph */ nodePath: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | DataURLToImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | ParamIntInvocation | CvInpaintInvocation | RangeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | DataURLToImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | ParamIntInvocation | CvInpaintInvocation | RangeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillImageInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'PUT', From 09f166577e171963c17ad5ebaf08e02d68f08d59 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 00:28:00 +1000 Subject: [PATCH 24/66] feat(ui): migrate to redux-remember --- invokeai/frontend/web/package.json | 1 + .../web/src/app/components/InvokeAIUI.tsx | 14 +- .../frontend/web/src/app/store/actions.ts | 4 + .../frontend/web/src/app/store/constants.ts | 8 + .../enhancers/reduxRemember/serialize.ts | 36 ++++ .../enhancers/reduxRemember/unserialize.ts | 49 +++++ .../middleware/devtools/actionSanitizer.ts | 29 +++ .../middleware/devtools/actionsDenylist.ts | 11 ++ .../middleware/devtools/stateSanitizer.ts | 3 + .../middleware/listenerMiddleware/index.ts | 11 +- .../{userInvoked.ts => userInvokedCanvas.ts} | 45 +---- .../listeners/userInvokedCreate.ts | 24 +++ .../listeners/userInvokedNodes.ts | 24 +++ .../frontend/web/src/app/store/persistor.ts | 4 - invokeai/frontend/web/src/app/store/store.ts | 169 +++++------------- .../canvas/store/canvasPersistDenylist.ts | 6 + .../src/features/canvas/store/canvasSlice.ts | 2 +- .../gallery/store/galleryPersistDenylist.ts | 5 + .../gallery/store/gallerySelectors.ts | 7 +- .../features/gallery/store/gallerySlice.ts | 4 +- .../gallery/store/resultsPersistDenylist.ts | 2 + .../gallery/store/uploadsPersistDenylist.ts | 1 + .../features/gallery/store/uploadsSlice.ts | 2 +- .../lightbox/store/lightboxPersistDenylist.ts | 3 + .../features/lightbox/store/lightboxSlice.ts | 2 +- .../components/panels/TopCenterPanel.tsx | 2 +- .../nodes/store/nodesPersistDenylist.ts | 4 + .../ProcessButtons/InvokeButton.tsx | 2 +- .../components/PromptInput/PromptInput.tsx | 2 +- .../store/generationPersistDenylist.ts | 1 + .../parameters/store/generationSlice.ts | 8 +- .../store/postprocessingPersistDenylist.ts | 1 + .../parameters/store/postprocessingSlice.ts | 6 +- .../system/components/ModelSelect.tsx | 14 +- .../SettingsModal/SettingsModal.tsx | 19 +- .../src/features/system/store/configSlice.ts | 2 +- .../src/features/system/store/modelSlice.ts | 36 ++-- .../system/store/modelsPersistDenylist.ts | 1 + .../system/store/systemPersistDenylist.ts | 19 ++ .../src/features/system/store/systemSlice.ts | 8 +- .../web/src/features/ui/store/hotkeysSlice.ts | 6 +- .../features/ui/store/uiPersistDenylist.ts | 3 + .../web/src/features/ui/store/uiSlice.ts | 6 +- invokeai/frontend/web/src/i18n.ts | 9 +- .../web/src/services/events/middleware.ts | 6 +- .../web/src/services/thunks/session.ts | 66 ------- invokeai/frontend/web/yarn.lock | 5 + 47 files changed, 379 insertions(+), 313 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/constants.ts create mode 100644 invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts create mode 100644 invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/devtools/actionsDenylist.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/devtools/stateSanitizer.ts rename invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/{userInvoked.ts => userInvokedCanvas.ts} (71%) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts delete mode 100644 invokeai/frontend/web/src/app/store/persistor.ts diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index da5e58c9c9..5d704f67d2 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -99,6 +99,7 @@ "redux-deep-persist": "^1.0.7", "redux-dynamic-middlewares": "^2.2.0", "redux-persist": "^6.0.0", + "redux-remember": "^3.2.1", "roarr": "^7.15.0", "serialize-error": "^11.0.0", "socket.io-client": "^4.6.0", diff --git a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx index 97a8be6fc1..d9d99de6cf 100644 --- a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx +++ b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx @@ -1,8 +1,6 @@ import React, { lazy, memo, PropsWithChildren, useEffect } from 'react'; import { Provider } from 'react-redux'; -import { PersistGate } from 'redux-persist/integration/react'; import { store } from 'app/store/store'; -import { persistor } from '../store/persistor'; import { OpenAPI } from 'services/api'; import '@fontsource/inter/100.css'; import '@fontsource/inter/200.css'; @@ -57,13 +55,11 @@ const InvokeAIUI = ({ apiUrl, token, config, children }: Props) => { return ( - } persistor={persistor}> - }> - - {children} - - - + }> + + {children} + + ); diff --git a/invokeai/frontend/web/src/app/store/actions.ts b/invokeai/frontend/web/src/app/store/actions.ts index e69de29bb2..e07920ce0c 100644 --- a/invokeai/frontend/web/src/app/store/actions.ts +++ b/invokeai/frontend/web/src/app/store/actions.ts @@ -0,0 +1,4 @@ +import { createAction } from '@reduxjs/toolkit'; +import { InvokeTabName } from 'features/ui/store/tabMap'; + +export const userInvoked = createAction('app/userInvoked'); diff --git a/invokeai/frontend/web/src/app/store/constants.ts b/invokeai/frontend/web/src/app/store/constants.ts new file mode 100644 index 0000000000..6d48762bef --- /dev/null +++ b/invokeai/frontend/web/src/app/store/constants.ts @@ -0,0 +1,8 @@ +export const LOCALSTORAGE_KEYS = [ + 'chakra-ui-color-mode', + 'i18nextLng', + 'ROARR_FILTER', + 'ROARR_LOG', +]; + +export const LOCALSTORAGE_PREFIX = '@@invokeai-'; diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts new file mode 100644 index 0000000000..52995e0da3 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts @@ -0,0 +1,36 @@ +import { canvasPersistDenylist } from 'features/canvas/store/canvasPersistDenylist'; +import { galleryPersistDenylist } from 'features/gallery/store/galleryPersistDenylist'; +import { resultsPersistDenylist } from 'features/gallery/store/resultsPersistDenylist'; +import { uploadsPersistDenylist } from 'features/gallery/store/uploadsPersistDenylist'; +import { lightboxPersistDenylist } from 'features/lightbox/store/lightboxPersistDenylist'; +import { nodesPersistDenylist } from 'features/nodes/store/nodesPersistDenylist'; +import { generationPersistDenylist } from 'features/parameters/store/generationPersistDenylist'; +import { postprocessingPersistDenylist } from 'features/parameters/store/postprocessingPersistDenylist'; +import { modelsPersistDenylist } from 'features/system/store/modelsPersistDenylist'; +import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist'; +import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist'; +import { omit } from 'lodash-es'; +import { SerializeFunction } from 'redux-remember'; + +const serializationDenylist: { + [key: string]: string[]; +} = { + canvas: canvasPersistDenylist, + gallery: galleryPersistDenylist, + generation: generationPersistDenylist, + lightbox: lightboxPersistDenylist, + models: modelsPersistDenylist, + nodes: nodesPersistDenylist, + postprocessing: postprocessingPersistDenylist, + results: resultsPersistDenylist, + system: systemPersistDenylist, + // config: configPersistDenyList, + ui: uiPersistDenylist, + uploads: uploadsPersistDenylist, + // hotkeys: hotkeysPersistDenylist, +}; + +export const serialize: SerializeFunction = (data, key) => { + const result = omit(data, serializationDenylist[key]); + return JSON.stringify(result); +}; diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts new file mode 100644 index 0000000000..d775e06187 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts @@ -0,0 +1,49 @@ +import { canvasPersistDenylist } from 'features/canvas/store/canvasPersistDenylist'; +import { initialCanvasState } from 'features/canvas/store/canvasSlice'; +import { galleryPersistDenylist } from 'features/gallery/store/galleryPersistDenylist'; +import { initialGalleryState } from 'features/gallery/store/gallerySlice'; +import { resultsPersistDenylist } from 'features/gallery/store/resultsPersistDenylist'; +import { initialResultsState } from 'features/gallery/store/resultsSlice'; +import { uploadsPersistDenylist } from 'features/gallery/store/uploadsPersistDenylist'; +import { initialUploadsState } from 'features/gallery/store/uploadsSlice'; +import { lightboxPersistDenylist } from 'features/lightbox/store/lightboxPersistDenylist'; +import { initialLightboxState } from 'features/lightbox/store/lightboxSlice'; +import { nodesPersistDenylist } from 'features/nodes/store/nodesPersistDenylist'; +import { initialNodesState } from 'features/nodes/store/nodesSlice'; +import { generationPersistDenylist } from 'features/parameters/store/generationPersistDenylist'; +import { initialGenerationState } from 'features/parameters/store/generationSlice'; +import { postprocessingPersistDenylist } from 'features/parameters/store/postprocessingPersistDenylist'; +import { initialPostprocessingState } from 'features/parameters/store/postprocessingSlice'; +import { initialConfigState } from 'features/system/store/configSlice'; +import { initialModelsState } from 'features/system/store/modelSlice'; +import { modelsPersistDenylist } from 'features/system/store/modelsPersistDenylist'; +import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist'; +import { initialSystemState } from 'features/system/store/systemSlice'; +import { initialHotkeysState } from 'features/ui/store/hotkeysSlice'; +import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist'; +import { initialUIState } from 'features/ui/store/uiSlice'; +import { defaultsDeep, merge, omit } from 'lodash-es'; +import { UnserializeFunction } from 'redux-remember'; + +const initialStates: { + [key: string]: any; +} = { + canvas: initialCanvasState, + gallery: initialGalleryState, + generation: initialGenerationState, + lightbox: initialLightboxState, + models: initialModelsState, + nodes: initialNodesState, + postprocessing: initialPostprocessingState, + results: initialResultsState, + system: initialSystemState, + config: initialConfigState, + ui: initialUIState, + uploads: initialUploadsState, + hotkeys: initialHotkeysState, +}; + +export const unserialize: UnserializeFunction = (data, key) => { + const result = defaultsDeep(JSON.parse(data), initialStates[key]); + return result; +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts b/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts new file mode 100644 index 0000000000..c9d8e45886 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts @@ -0,0 +1,29 @@ +import { AnyAction } from '@reduxjs/toolkit'; +import { isAnyGraphBuilt } from 'features/nodes/store/actions'; +import { forEach } from 'lodash-es'; +import { Graph } from 'services/api'; + +export const actionSanitizer = (action: A): A => { + if (isAnyGraphBuilt(action)) { + if (action.payload.nodes) { + const sanitizedNodes: Graph['nodes'] = {}; + + // Sanitize nodes as needed + forEach(action.payload.nodes, (node, key) => { + if (node.type === 'dataURL_image') { + const { dataURL, ...rest } = node; + sanitizedNodes[key] = { ...rest, dataURL: '' }; + } else { + sanitizedNodes[key] = { ...node }; + } + }); + + return { + ...action, + payload: { ...action.payload, nodes: sanitizedNodes }, + }; + } + } + + return action; +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/devtools/actionsDenylist.ts b/invokeai/frontend/web/src/app/store/middleware/devtools/actionsDenylist.ts new file mode 100644 index 0000000000..743537d7ea --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/devtools/actionsDenylist.ts @@ -0,0 +1,11 @@ +export const actionsDenylist = [ + 'canvas/setCursorPosition', + 'canvas/setStageCoordinates', + 'canvas/setStageScale', + 'canvas/setIsDrawing', + 'canvas/setBoundingBoxCoordinates', + 'canvas/setBoundingBoxDimensions', + 'canvas/setIsDrawing', + 'canvas/addPointToCurrentLine', + 'socket/generatorProgress', +]; diff --git a/invokeai/frontend/web/src/app/store/middleware/devtools/stateSanitizer.ts b/invokeai/frontend/web/src/app/store/middleware/devtools/stateSanitizer.ts new file mode 100644 index 0000000000..312b4db189 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/devtools/stateSanitizer.ts @@ -0,0 +1,3 @@ +export const stateSanitizer = (state: S): S => { + return state; +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 5869038d6a..6e66e19780 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -11,12 +11,9 @@ import { addInitialImageSelectedListener } from './listeners/initialImageSelecte import { addImageResultReceivedListener } from './listeners/invocationComplete'; import { addImageUploadedListener } from './listeners/imageUploaded'; import { addRequestedImageDeletionListener } from './listeners/imageDeleted'; -import { - addUserInvokedCanvasListener, - addUserInvokedCreateListener, - addUserInvokedNodesListener, -} from './listeners/userInvoked'; -import { addCanvasGraphBuiltListener } from './listeners/canvasGraphBuilt'; +import { addUserInvokedCanvasListener } from './listeners/userInvokedCanvas'; +import { addUserInvokedCreateListener } from './listeners/userInvokedCreate'; +import { addUserInvokedNodesListener } from './listeners/userInvokedNodes'; export const listenerMiddleware = createListenerMiddleware(); @@ -40,7 +37,7 @@ addImageUploadedListener(); addInitialImageSelectedListener(); addImageResultReceivedListener(); addRequestedImageDeletionListener(); + addUserInvokedCanvasListener(); addUserInvokedCreateListener(); addUserInvokedNodesListener(); -// addCanvasGraphBuiltListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvoked.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts similarity index 71% rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvoked.ts rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts index 63da80440e..2490a358fe 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvoked.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts @@ -1,16 +1,8 @@ -import { createAction } from '@reduxjs/toolkit'; import { startAppListening } from '..'; -import { InvokeTabName } from 'features/ui/store/tabMap'; -import { buildLinearGraph } from 'features/nodes/util/buildLinearGraph'; import { sessionCreated, sessionInvoked } from 'services/thunks/session'; import { buildCanvasGraphAndBlobs } from 'features/nodes/util/buildCanvasGraph'; -import { buildNodesGraph } from 'features/nodes/util/buildNodesGraph'; import { log } from 'app/logging/useLogger'; -import { - canvasGraphBuilt, - createGraphBuilt, - nodesGraphBuilt, -} from 'features/nodes/store/actions'; +import { canvasGraphBuilt } from 'features/nodes/store/actions'; import { imageUploaded } from 'services/thunks/image'; import { v4 as uuidv4 } from 'uuid'; import { Graph } from 'services/api'; @@ -18,27 +10,10 @@ import { canvasSessionIdChanged, stagingAreaInitialized, } from 'features/canvas/store/canvasSlice'; +import { userInvoked } from 'app/store/actions'; const moduleLog = log.child({ namespace: 'invoke' }); -export const userInvoked = createAction('app/userInvoked'); - -export const addUserInvokedCreateListener = () => { - startAppListening({ - predicate: (action): action is ReturnType => - userInvoked.match(action) && action.payload === 'generate', - effect: (action, { getState, dispatch }) => { - const state = getState(); - - const graph = buildLinearGraph(state); - dispatch(createGraphBuilt(graph)); - moduleLog({ data: graph }, 'Create graph built'); - - dispatch(sessionCreated({ graph })); - }, - }); -}; - export const addUserInvokedCanvasListener = () => { startAppListening({ predicate: (action): action is ReturnType => @@ -149,19 +124,3 @@ export const addUserInvokedCanvasListener = () => { }, }); }; - -export const addUserInvokedNodesListener = () => { - startAppListening({ - predicate: (action): action is ReturnType => - userInvoked.match(action) && action.payload === 'nodes', - effect: (action, { getState, dispatch }) => { - const state = getState(); - - const graph = buildNodesGraph(state); - dispatch(nodesGraphBuilt(graph)); - moduleLog({ data: graph }, 'Nodes graph built'); - - dispatch(sessionCreated({ graph })); - }, - }); -}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts new file mode 100644 index 0000000000..d24385ea97 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts @@ -0,0 +1,24 @@ +import { startAppListening } from '..'; +import { buildLinearGraph } from 'features/nodes/util/buildLinearGraph'; +import { sessionCreated } from 'services/thunks/session'; +import { log } from 'app/logging/useLogger'; +import { createGraphBuilt } from 'features/nodes/store/actions'; +import { userInvoked } from 'app/store/actions'; + +const moduleLog = log.child({ namespace: 'invoke' }); + +export const addUserInvokedCreateListener = () => { + startAppListening({ + predicate: (action): action is ReturnType => + userInvoked.match(action) && action.payload === 'generate', + effect: (action, { getState, dispatch }) => { + const state = getState(); + + const graph = buildLinearGraph(state); + dispatch(createGraphBuilt(graph)); + moduleLog({ data: graph }, 'Create graph built'); + + dispatch(sessionCreated({ graph })); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts new file mode 100644 index 0000000000..baf7bc5baf --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts @@ -0,0 +1,24 @@ +import { startAppListening } from '..'; +import { sessionCreated } from 'services/thunks/session'; +import { buildNodesGraph } from 'features/nodes/util/buildNodesGraph'; +import { log } from 'app/logging/useLogger'; +import { nodesGraphBuilt } from 'features/nodes/store/actions'; +import { userInvoked } from 'app/store/actions'; + +const moduleLog = log.child({ namespace: 'invoke' }); + +export const addUserInvokedNodesListener = () => { + startAppListening({ + predicate: (action): action is ReturnType => + userInvoked.match(action) && action.payload === 'nodes', + effect: (action, { getState, dispatch }) => { + const state = getState(); + + const graph = buildNodesGraph(state); + dispatch(nodesGraphBuilt(graph)); + moduleLog({ data: graph }, 'Nodes graph built'); + + dispatch(sessionCreated({ graph })); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/persistor.ts b/invokeai/frontend/web/src/app/store/persistor.ts deleted file mode 100644 index 85dc934943..0000000000 --- a/invokeai/frontend/web/src/app/store/persistor.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { store } from 'app/store/store'; -import { persistStore } from 'redux-persist'; - -export const persistor = persistStore(store); diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 15eb045405..777f563cae 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -1,16 +1,13 @@ import { - Action, AnyAction, + Store, ThunkDispatch, combineReducers, configureStore, - isAnyOf, } from '@reduxjs/toolkit'; -import { persistReducer } from 'redux-persist'; -import storage from 'redux-persist/lib/storage'; // defaults to localStorage for web +import { rememberReducer, rememberEnhancer } from 'redux-remember'; import dynamicMiddlewares from 'redux-dynamic-middlewares'; -import { getPersistConfig } from 'redux-deep-persist'; import canvasReducer from 'features/canvas/store/canvasSlice'; import galleryReducer from 'features/gallery/store/gallerySlice'; @@ -26,35 +23,17 @@ import hotkeysReducer from 'features/ui/store/hotkeysSlice'; import modelsReducer from 'features/system/store/modelSlice'; import nodesReducer from 'features/nodes/store/nodesSlice'; -import { canvasDenylist } from 'features/canvas/store/canvasPersistDenylist'; -import { galleryDenylist } from 'features/gallery/store/galleryPersistDenylist'; -import { generationDenylist } from 'features/parameters/store/generationPersistDenylist'; -import { lightboxDenylist } from 'features/lightbox/store/lightboxPersistDenylist'; -import { modelsDenylist } from 'features/system/store/modelsPersistDenylist'; -import { nodesDenylist } from 'features/nodes/store/nodesPersistDenylist'; -import { postprocessingDenylist } from 'features/parameters/store/postprocessingPersistDenylist'; -import { systemDenylist } from 'features/system/store/systemPersistDenylist'; -import { uiDenylist } from 'features/ui/store/uiPersistDenylist'; import { listenerMiddleware } from './middleware/listenerMiddleware'; -import { isAnyGraphBuilt } from 'features/nodes/store/actions'; -import { forEach } from 'lodash-es'; -import { Graph } from 'services/api'; -/** - * redux-persist provides an easy and reliable way to persist state across reloads. - * - * While we definitely want generation parameters to be persisted, there are a number - * of things we do *not* want to be persisted across reloads: - * - Gallery/selected image (user may add/delete images from disk between page loads) - * - Connection/processing status - * - Availability of external libraries like ESRGAN/GFPGAN - * - * These can be denylisted in redux-persist. - * - * The necesssary nested persistors with denylists are configured below. - */ +import { actionSanitizer } from './middleware/devtools/actionSanitizer'; +import { stateSanitizer } from './middleware/devtools/stateSanitizer'; +import { actionsDenylist } from './middleware/devtools/actionsDenylist'; -const rootReducer = combineReducers({ +import { serialize } from './enhancers/reduxRemember/serialize'; +import { unserialize } from './enhancers/reduxRemember/unserialize'; +import { LOCALSTORAGE_PREFIX } from './constants'; + +const allReducers = { canvas: canvasReducer, gallery: galleryReducer, generation: generationReducer, @@ -68,65 +47,38 @@ const rootReducer = combineReducers({ ui: uiReducer, uploads: uploadsReducer, hotkeys: hotkeysReducer, -}); +}; -const rootPersistConfig = getPersistConfig({ - key: 'root', - storage, - rootReducer, - blacklist: [ - ...canvasDenylist, - ...galleryDenylist, - ...generationDenylist, - ...lightboxDenylist, - ...modelsDenylist, - ...nodesDenylist, - ...postprocessingDenylist, - // ...resultsDenylist, - 'results', - ...systemDenylist, - ...uiDenylist, - // ...uploadsDenylist, - 'uploads', - 'hotkeys', - 'config', +const rootReducer = combineReducers(allReducers); + +const rememberedRootReducer = rememberReducer(rootReducer); + +const rememberedKeys: (keyof typeof allReducers)[] = [ + 'canvas', + 'gallery', + 'generation', + 'lightbox', + // 'models', + 'nodes', + 'postprocessing', + 'system', + 'ui', + // 'hotkeys', + // 'results', + // 'uploads', + // 'config', +]; + +export const store: Store = configureStore({ + reducer: rememberedRootReducer, + enhancers: [ + rememberEnhancer(window.localStorage, rememberedKeys, { + persistDebounce: 300, + serialize, + unserialize, + prefix: LOCALSTORAGE_PREFIX, + }), ], -}); - -const persistedReducer = persistReducer(rootPersistConfig, rootReducer); - -// TODO: rip the old middleware out when nodes is complete -// export function buildMiddleware() { -// if (import.meta.env.MODE === 'nodes' || import.meta.env.MODE === 'package') { -// return socketMiddleware(); -// } else { -// return socketioMiddleware(); -// } -// } - -// const actionSanitizer = (action: AnyAction): AnyAction => { -// if (isAnyGraphBuilt(action)) { -// if (action.payload.nodes) { -// const sanitizedNodes: Graph['nodes'] = {}; -// forEach(action.payload.nodes, (node, key) => { -// if (node.type === 'dataURL_image') { -// const { dataURL, ...rest } = node; -// sanitizedNodes[key] = { ...rest, dataURL: '<>' }; -// } -// }); -// const sanitizedAction: AnyAction = { -// ...action, -// payload: { ...action.payload, nodes: sanitizedNodes }, -// }; -// return sanitizedAction; -// } -// } - -// return action; -// }; - -export const store = configureStore({ - reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ immutableCheck: false, @@ -135,43 +87,10 @@ export const store = configureStore({ .concat(dynamicMiddlewares) .prepend(listenerMiddleware.middleware), devTools: { - // Uncommenting these very rapidly called actions makes the redux dev tools output much more readable - actionsDenylist: [ - 'canvas/setCursorPosition', - 'canvas/setStageCoordinates', - 'canvas/setStageScale', - 'canvas/setIsDrawing', - 'canvas/setBoundingBoxCoordinates', - 'canvas/setBoundingBoxDimensions', - 'canvas/setIsDrawing', - 'canvas/addPointToCurrentLine', - 'socket/generatorProgress', - ], - actionSanitizer: (action) => { - if (isAnyGraphBuilt(action)) { - if (action.payload.nodes) { - const sanitizedNodes: Graph['nodes'] = {}; - - forEach(action.payload.nodes, (node, key) => { - if (node.type === 'dataURL_image') { - const { dataURL, ...rest } = node; - sanitizedNodes[key] = { ...rest, dataURL: '<>' }; - } else { - sanitizedNodes[key] = { ...node }; - } - }); - - return { - ...action, - payload: { ...action.payload, nodes: sanitizedNodes }, - }; - } - } - - return action; - }, - // stateSanitizer: (state) => - // state.data ? { ...state, data: '<>' } : state, + actionsDenylist, + actionSanitizer, + stateSanitizer, + trace: true, }, }); diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasPersistDenylist.ts b/invokeai/frontend/web/src/features/canvas/store/canvasPersistDenylist.ts index abaefab8b0..1f44b43021 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasPersistDenylist.ts @@ -9,6 +9,12 @@ const itemsToDenylist: (keyof CanvasState)[] = [ 'doesCanvasNeedScaling', ]; +export const canvasPersistDenylist: (keyof CanvasState)[] = [ + 'cursorPosition', + 'isCanvasInitialized', + 'doesCanvasNeedScaling', +]; + export const canvasDenylist = itemsToDenylist.map( (denylistItem) => `canvas.${denylistItem}` ); diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index 438f4df9e1..67adcc1769 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -40,7 +40,7 @@ export const initialLayerState: CanvasLayerState = { }, }; -const initialCanvasState: CanvasState = { +export const initialCanvasState: CanvasState = { boundingBoxCoordinates: { x: 0, y: 0 }, boundingBoxDimensions: { width: 512, height: 512 }, boundingBoxPreviewFill: { r: 0, g: 0, b: 0, a: 0.5 }, diff --git a/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts b/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts index 8c91a79dfb..dcec4fa373 100644 --- a/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts @@ -8,6 +8,11 @@ const itemsToDenylist: (keyof GalleryState)[] = [ 'shouldAutoSwitchToNewImages', ]; +export const galleryPersistDenylist: (keyof GalleryState)[] = [ + 'currentCategory', + 'shouldAutoSwitchToNewImages', +]; + export const galleryDenylist = itemsToDenylist.map( (denylistItem) => `gallery.${denylistItem}` ); diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts index 0fc8d300e9..082e644b43 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts @@ -44,6 +44,11 @@ export const imageGallerySelector = createSelector( const { isLightboxOpen } = lightbox; + const images = + currentCategory === 'results' + ? selectResultsEntities(state) + : selectUploadsAll(state); + return { shouldPinGallery, galleryImageMinimumWidth, @@ -53,7 +58,7 @@ export const imageGallerySelector = createSelector( : `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, auto))`, shouldAutoSwitchToNewImages, currentCategory, - images: state[currentCategory].entities, + images, galleryWidth, shouldEnableResize: isLightboxOpen || diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index 630bd0d6b3..2326295451 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -17,7 +17,7 @@ export interface GalleryState { currentCategory: 'results' | 'uploads'; } -const initialState: GalleryState = { +export const initialGalleryState: GalleryState = { galleryImageMinimumWidth: 64, galleryImageObjectFit: 'cover', shouldAutoSwitchToNewImages: true, @@ -28,7 +28,7 @@ const initialState: GalleryState = { export const gallerySlice = createSlice({ name: 'gallery', - initialState, + initialState: initialGalleryState, reducers: { imageSelected: (state, action: PayloadAction) => { state.selectedImage = action.payload; diff --git a/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts b/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts index ef21f4b7b2..4b8ccac6a7 100644 --- a/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts @@ -7,6 +7,8 @@ import { ResultsState } from './resultsSlice'; */ const itemsToDenylist: (keyof ResultsState)[] = []; +export const resultsPersistDenylist: (keyof ResultsState)[] = []; + export const resultsDenylist = itemsToDenylist.map( (denylistItem) => `results.${denylistItem}` ); diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts index ec4248e99c..97e23660a9 100644 --- a/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts @@ -6,6 +6,7 @@ import { UploadsState } from './uploadsSlice'; * Currently denylisting uploads slice entirely, see persist config in store.ts */ const itemsToDenylist: (keyof UploadsState)[] = []; +export const uploadsPersistDenylist: (keyof UploadsState)[] = []; export const uploadsDenylist = itemsToDenylist.map( (denylistItem) => `uploads.${denylistItem}` diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts index 6e39733f1b..fcad240058 100644 --- a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts @@ -21,7 +21,7 @@ type AdditionalUploadsState = { nextPage: number; }; -const initialUploadsState = +export const initialUploadsState = uploadsAdapter.getInitialState({ page: 0, pages: 0, diff --git a/invokeai/frontend/web/src/features/lightbox/store/lightboxPersistDenylist.ts b/invokeai/frontend/web/src/features/lightbox/store/lightboxPersistDenylist.ts index 96cf69f373..194fe50ca3 100644 --- a/invokeai/frontend/web/src/features/lightbox/store/lightboxPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/lightbox/store/lightboxPersistDenylist.ts @@ -4,6 +4,9 @@ import { LightboxState } from './lightboxSlice'; * Lightbox slice persist denylist */ const itemsToDenylist: (keyof LightboxState)[] = ['isLightboxOpen']; +export const lightboxPersistDenylist: (keyof LightboxState)[] = [ + 'isLightboxOpen', +]; export const lightboxDenylist = itemsToDenylist.map( (denylistItem) => `lightbox.${denylistItem}` diff --git a/invokeai/frontend/web/src/features/lightbox/store/lightboxSlice.ts b/invokeai/frontend/web/src/features/lightbox/store/lightboxSlice.ts index f7f6507d93..ea73e5bb13 100644 --- a/invokeai/frontend/web/src/features/lightbox/store/lightboxSlice.ts +++ b/invokeai/frontend/web/src/features/lightbox/store/lightboxSlice.ts @@ -5,7 +5,7 @@ export interface LightboxState { isLightboxOpen: boolean; } -const initialLightboxState: LightboxState = { +export const initialLightboxState: LightboxState = { isLightboxOpen: false, }; diff --git a/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx index b5783891ee..b97bf423e1 100644 --- a/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx @@ -1,5 +1,5 @@ import { HStack } from '@chakra-ui/react'; -import { userInvoked } from 'app/store/middleware/listenerMiddleware/listeners/userInvoked'; +import { userInvoked } from 'app/store/actions'; import { useAppDispatch } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import { memo, useCallback } from 'react'; diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesPersistDenylist.ts b/invokeai/frontend/web/src/features/nodes/store/nodesPersistDenylist.ts index 31d859ba8b..36da41f56c 100644 --- a/invokeai/frontend/web/src/features/nodes/store/nodesPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/nodes/store/nodesPersistDenylist.ts @@ -4,6 +4,10 @@ import { NodesState } from './nodesSlice'; * Nodes slice persist denylist */ const itemsToDenylist: (keyof NodesState)[] = ['schema', 'invocationTemplates']; +export const nodesPersistDenylist: (keyof NodesState)[] = [ + 'schema', + 'invocationTemplates', +]; export const nodesDenylist = itemsToDenylist.map( (denylistItem) => `nodes.${denylistItem}` diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx index 5532fab196..68d607c0fa 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx @@ -1,6 +1,6 @@ import { Box } from '@chakra-ui/react'; import { readinessSelector } from 'app/selectors/readinessSelector'; -import { userInvoked } from 'app/store/middleware/listenerMiddleware/listeners/userInvoked'; +import { userInvoked } from 'app/store/actions'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton, { IAIButtonProps } from 'common/components/IAIButton'; import IAIIconButton, { diff --git a/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx b/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx index 868da4c8b1..eb0340b0ee 100644 --- a/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx @@ -15,7 +15,7 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { isEqual } from 'lodash-es'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; -import { userInvoked } from 'app/store/middleware/listenerMiddleware/listeners/userInvoked'; +import { userInvoked } from 'app/store/actions'; const promptInputSelector = createSelector( [(state: RootState) => state.generation, activeTabNameSelector], diff --git a/invokeai/frontend/web/src/features/parameters/store/generationPersistDenylist.ts b/invokeai/frontend/web/src/features/parameters/store/generationPersistDenylist.ts index 70f35aa564..ab3e77801e 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationPersistDenylist.ts @@ -4,6 +4,7 @@ import { GenerationState } from './generationSlice'; * Generation slice persist denylist */ const itemsToDenylist: (keyof GenerationState)[] = []; +export const generationPersistDenylist: (keyof GenerationState)[] = []; export const generationDenylist = itemsToDenylist.map( (denylistItem) => `generation.${denylistItem}` diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index d889dd88e1..51627ce24c 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -38,9 +38,10 @@ export interface GenerationState { horizontalSymmetrySteps: number; verticalSymmetrySteps: number; isImageToImageEnabled: boolean; + model: string; } -const initialGenerationState: GenerationState = { +export const initialGenerationState: GenerationState = { cfgScale: 7.5, height: 512, img2imgStrength: 0.75, @@ -70,6 +71,7 @@ const initialGenerationState: GenerationState = { horizontalSymmetrySteps: 0, verticalSymmetrySteps: 0, isImageToImageEnabled: false, + model: '', }; const initialState: GenerationState = initialGenerationState; @@ -353,6 +355,9 @@ export const generationSlice = createSlice({ isImageToImageEnabledChanged: (state, action: PayloadAction) => { state.isImageToImageEnabled = action.payload; }, + modelSelected: (state, action: PayloadAction) => { + state.model = action.payload; + }, }, }); @@ -396,6 +401,7 @@ export const { setVerticalSymmetrySteps, initialImageChanged, isImageToImageEnabledChanged, + modelSelected, } = generationSlice.actions; export default generationSlice.reducer; diff --git a/invokeai/frontend/web/src/features/parameters/store/postprocessingPersistDenylist.ts b/invokeai/frontend/web/src/features/parameters/store/postprocessingPersistDenylist.ts index 947a136964..a6ba084c2e 100644 --- a/invokeai/frontend/web/src/features/parameters/store/postprocessingPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/parameters/store/postprocessingPersistDenylist.ts @@ -4,6 +4,7 @@ import { PostprocessingState } from './postprocessingSlice'; * Postprocessing slice persist denylist */ const itemsToDenylist: (keyof PostprocessingState)[] = []; +export const postprocessingPersistDenylist: (keyof PostprocessingState)[] = []; export const postprocessingDenylist = itemsToDenylist.map( (denylistItem) => `postprocessing.${denylistItem}` diff --git a/invokeai/frontend/web/src/features/parameters/store/postprocessingSlice.ts b/invokeai/frontend/web/src/features/parameters/store/postprocessingSlice.ts index 60991d3673..399a474008 100644 --- a/invokeai/frontend/web/src/features/parameters/store/postprocessingSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/postprocessingSlice.ts @@ -20,7 +20,7 @@ export interface PostprocessingState { upscalingStrength: number; } -const initialPostprocessingState: PostprocessingState = { +export const initialPostprocessingState: PostprocessingState = { codeformerFidelity: 0.75, facetoolStrength: 0.75, facetoolType: 'gfpgan', @@ -34,11 +34,9 @@ const initialPostprocessingState: PostprocessingState = { upscalingStrength: 0.75, }; -const initialState: PostprocessingState = initialPostprocessingState; - export const postprocessingSlice = createSlice({ name: 'postprocessing', - initialState, + initialState: initialPostprocessingState, reducers: { setFacetoolStrength: (state, action: PayloadAction) => { state.facetoolStrength = action.payload; diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index d0ad89ba36..d6c7c154e0 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -6,16 +6,22 @@ import { useTranslation } from 'react-i18next'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAISelect from 'common/components/IAISelect'; import { - modelSelected, + // modelSelected, selectedModelSelector, + selectModelsById, selectModelsIds, } from '../store/modelSlice'; import { RootState } from 'app/store/store'; +import generationSlice, { + modelSelected, +} from 'features/parameters/store/generationSlice'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; const selector = createSelector( - [(state: RootState) => state], - (state) => { - const selectedModel = selectedModelSelector(state); + [(state: RootState) => state, generationSelector], + (state, generation) => { + // const selectedModel = selectedModelSelector(state); + const selectedModel = selectModelsById(state, generation.model); const allModelNames = selectModelsIds(state); return { allModelNames, diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx index 0ca0b496fc..bf4004a79d 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -34,11 +34,11 @@ import { } from 'features/ui/store/uiSlice'; import { UIState } from 'features/ui/store/uiTypes'; import { isEqual } from 'lodash-es'; -import { persistor } from 'app/store/persistor'; import { ChangeEvent, cloneElement, ReactElement, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { VALID_LOG_LEVELS } from 'app/logging/useLogger'; import { LogLevelName } from 'roarr'; +import { LOCALSTORAGE_KEYS, LOCALSTORAGE_PREFIX } from 'app/store/constants'; const selector = createSelector( [systemSelector, uiSelector], @@ -119,15 +119,18 @@ const SettingsModal = ({ children }: SettingsModalProps) => { shouldLogToConsole, } = useAppSelector(selector); - /** - * Resets localstorage, then opens a secondary modal informing user to - * refresh their browser. - * */ const handleClickResetWebUI = useCallback(() => { - persistor.purge().then(() => { - onSettingsModalClose(); - onRefreshModalOpen(); + // Only remove our keys + Object.keys(window.localStorage).forEach((key) => { + if ( + LOCALSTORAGE_KEYS.includes(key) || + key.startsWith(LOCALSTORAGE_PREFIX) + ) { + localStorage.removeItem(key); + } }); + onSettingsModalClose(); + onRefreshModalOpen(); }, [onSettingsModalClose, onRefreshModalOpen]); const handleLogLevelChanged = useCallback( diff --git a/invokeai/frontend/web/src/features/system/store/configSlice.ts b/invokeai/frontend/web/src/features/system/store/configSlice.ts index d668a59574..b773692908 100644 --- a/invokeai/frontend/web/src/features/system/store/configSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/configSlice.ts @@ -3,7 +3,7 @@ import { createSlice } from '@reduxjs/toolkit'; import { AppConfig, PartialAppConfig } from 'app/types/invokeai'; import { merge } from 'lodash-es'; -const initialConfigState: AppConfig = { +export const initialConfigState: AppConfig = { shouldTransformUrls: false, shouldFetchImages: false, disabledTabs: [], diff --git a/invokeai/frontend/web/src/features/system/store/modelSlice.ts b/invokeai/frontend/web/src/features/system/store/modelSlice.ts index cb1cf05328..88ca71f2ff 100644 --- a/invokeai/frontend/web/src/features/system/store/modelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/modelSlice.ts @@ -27,12 +27,13 @@ export type ModelsState = typeof initialModelsState; export const modelsSlice = createSlice({ name: 'models', - initialState: initialModelsState, + initialState: modelsAdapter.getInitialState(), + // initialState: initialModelsState, reducers: { modelAdded: modelsAdapter.upsertOne, - modelSelected: (state, action: PayloadAction) => { - state.selectedModelName = action.payload; - }, + // modelSelected: (state, action: PayloadAction) => { + // state.selectedModelName = action.payload; + // }, }, extraReducers(builder) { /** @@ -44,18 +45,18 @@ export const modelsSlice = createSlice({ // If the current selected model is `''` or isn't actually in the list of models, // choose a random model - if ( - !state.selectedModelName || - !keys(models).includes(state.selectedModelName) - ) { - const randomModel = sample(models); + // if ( + // !state.selectedModelName || + // !keys(models).includes(state.selectedModelName) + // ) { + // const randomModel = sample(models); - if (randomModel) { - state.selectedModelName = randomModel.name; - } else { - state.selectedModelName = ''; - } - } + // if (randomModel) { + // state.selectedModelName = randomModel.name; + // } else { + // state.selectedModelName = ''; + // } + // } }); }, }); @@ -75,6 +76,9 @@ export const { selectTotal: selectModelsTotal, } = modelsAdapter.getSelectors((state) => state.models); -export const { modelAdded, modelSelected } = modelsSlice.actions; +export const { + modelAdded, + // modelSelected +} = modelsSlice.actions; export default modelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts index f374948e39..5b36a3f196 100644 --- a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts @@ -4,6 +4,7 @@ import { ModelsState } from './modelSlice'; * Models slice persist denylist */ const itemsToDenylist: (keyof ModelsState)[] = ['entities', 'ids']; +export const modelsPersistDenylist: (keyof ModelsState)[] = ['entities', 'ids']; export const modelsDenylist = itemsToDenylist.map( (denylistItem) => `models.${denylistItem}` diff --git a/invokeai/frontend/web/src/features/system/store/systemPersistDenylist.ts b/invokeai/frontend/web/src/features/system/store/systemPersistDenylist.ts index 8a4d381775..70284e831a 100644 --- a/invokeai/frontend/web/src/features/system/store/systemPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/system/store/systemPersistDenylist.ts @@ -21,6 +21,25 @@ const itemsToDenylist: (keyof SystemState)[] = [ 'wereModelsReceived', 'wasSchemaParsed', ]; +export const systemPersistDenylist: (keyof SystemState)[] = [ + 'currentIteration', + 'currentStatus', + 'currentStep', + 'isCancelable', + 'isConnected', + 'isESRGANAvailable', + 'isGFPGANAvailable', + 'isProcessing', + 'socketId', + 'totalIterations', + 'totalSteps', + 'openModel', + 'isCancelScheduled', + 'progressImage', + 'wereModelsReceived', + 'wasSchemaParsed', + 'isPersisted', +]; export const systemDenylist = itemsToDenylist.map( (denylistItem) => `system.${denylistItem}` diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 16f118855f..025cebf0a6 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -89,9 +89,10 @@ export interface SystemState { * TODO: get this from backend */ infillMethods: InfillMethod[]; + isPersisted: boolean; } -const initialSystemState: SystemState = { +export const initialSystemState: SystemState = { isConnected: false, isProcessing: false, shouldDisplayGuides: true, @@ -121,6 +122,7 @@ const initialSystemState: SystemState = { statusTranslationKey: 'common.statusDisconnected', canceledSession: '', infillMethods: ['tile', 'patchmatch'], + isPersisted: false, }; export const systemSlice = createSlice({ @@ -259,6 +261,9 @@ export const systemSlice = createSlice({ shouldLogToConsoleChanged: (state, action: PayloadAction) => { state.shouldLogToConsole = action.payload; }, + isPersistedChanged: (state, action: PayloadAction) => { + state.isPersisted = action.payload; + }, }, extraReducers(builder) { /** @@ -476,6 +481,7 @@ export const { subscribedNodeIdsSet, consoleLogLevelChanged, shouldLogToConsoleChanged, + isPersistedChanged, } = systemSlice.actions; export default systemSlice.reducer; diff --git a/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts b/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts index 4e72d1dce9..527e0b1740 100644 --- a/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts @@ -6,15 +6,13 @@ type HotkeysState = { shift: boolean; }; -const initialHotkeysState: HotkeysState = { +export const initialHotkeysState: HotkeysState = { shift: false, }; -const initialState: HotkeysState = initialHotkeysState; - export const hotkeysSlice = createSlice({ name: 'hotkeys', - initialState, + initialState: initialHotkeysState, reducers: { shiftKeyPressed: (state, action: PayloadAction) => { state.shift = action.payload; diff --git a/invokeai/frontend/web/src/features/ui/store/uiPersistDenylist.ts b/invokeai/frontend/web/src/features/ui/store/uiPersistDenylist.ts index ae357e7899..7f469e1f1a 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiPersistDenylist.ts @@ -4,6 +4,9 @@ import { UIState } from './uiTypes'; * UI slice persist denylist */ const itemsToDenylist: (keyof UIState)[] = ['floatingProgressImageRect']; +export const uiPersistDenylist: (keyof UIState)[] = [ + 'floatingProgressImageRect', +]; export const uiDenylist = itemsToDenylist.map( (denylistItem) => `ui.${denylistItem}` diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index 11abf6a20d..4a21a729d7 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -4,7 +4,7 @@ import { setActiveTabReducer } from './extraReducers'; import { InvokeTabName, tabMap } from './tabMap'; import { AddNewModelType, Coordinates, Rect, UIState } from './uiTypes'; -const initialUIState: UIState = { +export const initialUIState: UIState = { activeTab: 0, currentTheme: 'dark', parametersPanelScrollPosition: 0, @@ -26,11 +26,9 @@ const initialUIState: UIState = { shouldAutoShowProgressImages: false, }; -const initialState: UIState = initialUIState; - export const uiSlice = createSlice({ name: 'ui', - initialState, + initialState: initialUIState, reducers: { setActiveTab: (state, action: PayloadAction) => { setActiveTabReducer(state, action.payload); diff --git a/invokeai/frontend/web/src/i18n.ts b/invokeai/frontend/web/src/i18n.ts index ed71d583b3..f28365db20 100644 --- a/invokeai/frontend/web/src/i18n.ts +++ b/invokeai/frontend/web/src/i18n.ts @@ -3,7 +3,8 @@ import LanguageDetector from 'i18next-browser-languagedetector'; import Backend from 'i18next-http-backend'; import { initReactI18next } from 'react-i18next'; -import translationEN from '../public/locales/en.json'; +import translationEN from '../dist/locales/en.json'; +import { LOCALSTORAGE_PREFIX } from 'app/store/constants'; if (import.meta.env.MODE === 'package') { i18n.use(initReactI18next).init({ @@ -20,7 +21,11 @@ if (import.meta.env.MODE === 'package') { } else { i18n .use(Backend) - .use(LanguageDetector) + .use( + new LanguageDetector(null, { + lookupLocalStorage: `${LOCALSTORAGE_PREFIX}lng`, + }) + ) .use(initReactI18next) .init({ fallbackLng: 'en', diff --git a/invokeai/frontend/web/src/services/events/middleware.ts b/invokeai/frontend/web/src/services/events/middleware.ts index 9ece44665a..bd1d60099a 100644 --- a/invokeai/frontend/web/src/services/events/middleware.ts +++ b/invokeai/frontend/web/src/services/events/middleware.ts @@ -8,11 +8,7 @@ import { import { socketSubscribed, socketUnsubscribed } from './actions'; import { AppThunkDispatch, RootState } from 'app/store/store'; import { getTimestamp } from 'common/util/getTimestamp'; -import { - sessionInvoked, - isFulfilledSessionCreatedAction, - sessionCreated, -} from 'services/thunks/session'; +import { sessionInvoked, sessionCreated } from 'services/thunks/session'; import { OpenAPI } from 'services/api'; import { setEventListeners } from 'services/events/util/setEventListeners'; import { log } from 'app/logging/useLogger'; diff --git a/invokeai/frontend/web/src/services/thunks/session.ts b/invokeai/frontend/web/src/services/thunks/session.ts index bc26ee7aba..dca4134886 100644 --- a/invokeai/frontend/web/src/services/thunks/session.ts +++ b/invokeai/frontend/web/src/services/thunks/session.ts @@ -1,71 +1,10 @@ import { createAppAsyncThunk } from 'app/store/storeUtils'; import { SessionsService } from 'services/api'; -import { buildLinearGraph as buildGenerateGraph } from 'features/nodes/util/buildLinearGraph'; -import { buildCanvasGraphAndBlobs } from 'features/nodes/util/buildCanvasGraph'; -import { isAnyOf, isFulfilled } from '@reduxjs/toolkit'; -import { buildNodesGraph } from 'features/nodes/util/buildNodesGraph'; import { log } from 'app/logging/useLogger'; import { serializeError } from 'serialize-error'; const sessionLog = log.child({ namespace: 'session' }); -// export const generateGraphBuilt = createAppAsyncThunk( -// 'api/generateGraphBuilt', -// async (_, { dispatch, getState, rejectWithValue }) => { -// try { -// const graph = buildGenerateGraph(getState()); -// dispatch(sessionCreated({ graph })); -// return graph; -// } catch (err: any) { -// sessionLog.error( -// { error: serializeError(err) }, -// 'Problem building graph' -// ); -// return rejectWithValue(err.message); -// } -// } -// ); - -// export const nodesGraphBuilt = createAppAsyncThunk( -// 'api/nodesGraphBuilt', -// async (_, { dispatch, getState, rejectWithValue }) => { -// try { -// const graph = buildNodesGraph(getState()); -// dispatch(sessionCreated({ graph })); -// return graph; -// } catch (err: any) { -// sessionLog.error( -// { error: serializeError(err) }, -// 'Problem building graph' -// ); -// return rejectWithValue(err.message); -// } -// } -// ); - -// export const canvasGraphBuilt = createAppAsyncThunk( -// 'api/canvasGraphBuilt', -// async (_, { dispatch, getState, rejectWithValue }) => { -// try { -// const graph = await buildCanvasGraph(getState()); -// dispatch(sessionCreated({ graph })); -// return graph; -// } catch (err: any) { -// sessionLog.error( -// { error: serializeError(err) }, -// 'Problem building graph' -// ); -// return rejectWithValue(err.message); -// } -// } -// ); - -// export const isFulfilledAnyGraphBuilt = isAnyOf( -// generateGraphBuilt.fulfilled, -// nodesGraphBuilt.fulfilled, -// canvasGraphBuilt.fulfilled -// ); - type SessionCreatedArg = { graph: Parameters< (typeof SessionsService)['createSession'] @@ -96,11 +35,6 @@ export const sessionCreated = createAppAsyncThunk( } ); -/** - * Function to check if an action is a fulfilled `SessionsService.createSession()` thunk - */ -export const isFulfilledSessionCreatedAction = isFulfilled(sessionCreated); - type NodeAddedArg = Parameters<(typeof SessionsService)['addNode']>[0]; /** diff --git a/invokeai/frontend/web/yarn.lock b/invokeai/frontend/web/yarn.lock index 3901903bd4..18a08a01b5 100644 --- a/invokeai/frontend/web/yarn.lock +++ b/invokeai/frontend/web/yarn.lock @@ -5692,6 +5692,11 @@ redux-persist@^6.0.0: resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8" integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ== +redux-remember@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/redux-remember/-/redux-remember-3.2.1.tgz#e58600336ac7341c56dfe9d69d95db234a7c404e" + integrity sha512-ep2E5KOJDGmrvbAuHVfmVpnuftqhJ2um6VpHw/iWa7WvAIFcPq/B678n51NBd/g8BWnNdQ5131cDRKWrRd041Q== + redux-thunk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b" From cee8e85f761ad8f2889048a40723683226f083ee Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 09:34:35 +1000 Subject: [PATCH 25/66] chore(ui): bump redux-remember --- invokeai/frontend/web/package.json | 2 +- invokeai/frontend/web/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index 5d704f67d2..d508df2b5c 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -99,7 +99,7 @@ "redux-deep-persist": "^1.0.7", "redux-dynamic-middlewares": "^2.2.0", "redux-persist": "^6.0.0", - "redux-remember": "^3.2.1", + "redux-remember": "^3.3.1", "roarr": "^7.15.0", "serialize-error": "^11.0.0", "socket.io-client": "^4.6.0", diff --git a/invokeai/frontend/web/yarn.lock b/invokeai/frontend/web/yarn.lock index 18a08a01b5..1e4c30581a 100644 --- a/invokeai/frontend/web/yarn.lock +++ b/invokeai/frontend/web/yarn.lock @@ -5692,10 +5692,10 @@ redux-persist@^6.0.0: resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8" integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ== -redux-remember@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/redux-remember/-/redux-remember-3.2.1.tgz#e58600336ac7341c56dfe9d69d95db234a7c404e" - integrity sha512-ep2E5KOJDGmrvbAuHVfmVpnuftqhJ2um6VpHw/iWa7WvAIFcPq/B678n51NBd/g8BWnNdQ5131cDRKWrRd041Q== +redux-remember@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/redux-remember/-/redux-remember-3.3.1.tgz#fad0b3af81458d8e40a54cd30be148c17e40bda9" + integrity sha512-x30eZpdryapH8+hinYcyoTiGCSmtPUPdvL7OxjpMeRgTckJrVW57FgRAmiv41COqi/q4H+qn65Uftsasqj+F9A== redux-thunk@^2.4.2: version "2.4.2" From b8c1a3f96c040b20b397e637cd21d2b0bbbebdd9 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 09:46:10 +1000 Subject: [PATCH 26/66] chore(ui): remove unused babelrc & npm script --- invokeai/frontend/web/.babelrc | 13 ------------- invokeai/frontend/web/package.json | 1 - 2 files changed, 14 deletions(-) delete mode 100644 invokeai/frontend/web/.babelrc diff --git a/invokeai/frontend/web/.babelrc b/invokeai/frontend/web/.babelrc deleted file mode 100644 index 809872138a..0000000000 --- a/invokeai/frontend/web/.babelrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "plugins": [ - [ - "transform-imports", - { - "lodash": { - "transform": "lodash/${member}", - "preventFullImport": true - } - } - ] - ] -} diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index d508df2b5c..3cf8ba070a 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -21,7 +21,6 @@ "scripts": { "prepare": "cd ../../../ && husky install invokeai/frontend/web/.husky", "dev": "concurrently \"vite dev\" \"yarn run theme:watch\"", - "dev:nodes": "concurrently \"vite dev --mode nodes\" \"yarn run theme:watch\"", "dev:host": "concurrently \"vite dev --host\" \"yarn run theme:watch\"", "build": "yarn run lint && vite build", "api:web": "openapi -i http://localhost:9090/openapi.json -o src/services/api --client axios --useOptions --useUnionTypes --exportSchemas true --indent 2 --request src/services/fixtures/request.ts", From 5457c7f06997f8ef73efca41fa8578d168da6818 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 09:46:31 +1000 Subject: [PATCH 27/66] fix(ui): use `lodash-es` instead of lodash --- .../src/features/parameters/components/ProgressImagePreview.tsx | 2 +- invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/parameters/components/ProgressImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/ProgressImagePreview.tsx index c31215a13e..4458f873db 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProgressImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProgressImagePreview.tsx @@ -15,7 +15,7 @@ import { } from 'features/ui/store/uiSlice'; import { Rnd } from 'react-rnd'; import { Rect } from 'features/ui/store/uiTypes'; -import { isEqual } from 'lodash'; +import { isEqual } from 'lodash-es'; import ProgressImage from './ProgressImage'; const selector = createSelector( diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index 0c65a99293..17a84dd8f2 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -27,7 +27,7 @@ import GenerateWorkspace from './tabs/Generate/GenerateWorkspace'; import { createSelector } from '@reduxjs/toolkit'; import { BsLightningChargeFill } from 'react-icons/bs'; import { configSelector } from 'features/system/store/configSelectors'; -import { isEqual } from 'lodash'; +import { isEqual } from 'lodash-es'; export interface InvokeTabInfo { id: InvokeTabName; From a1079e455a76abfd76658968ed2cad597950dcfb Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 19:05:47 +1000 Subject: [PATCH 28/66] feat(nodes): cleanup unused params, seed generation --- invokeai/app/invocations/collections.py | 10 +++++----- invokeai/app/invocations/generate.py | 5 ++--- invokeai/app/invocations/image.py | 1 - invokeai/app/invocations/latent.py | 18 ++++++------------ invokeai/app/invocations/util/choose_model.py | 2 +- invokeai/app/util/misc.py | 8 ++++++++ 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/invokeai/app/invocations/collections.py b/invokeai/app/invocations/collections.py index 24a89c2cf4..62f6ad8037 100644 --- a/invokeai/app/invocations/collections.py +++ b/invokeai/app/invocations/collections.py @@ -3,12 +3,12 @@ from typing import Literal, Optional import numpy as np -import numpy.random from pydantic import Field +from invokeai.app.util.misc import SEED_MAX, get_random_seed + from .baseinvocation import ( BaseInvocation, - InvocationConfig, InvocationContext, BaseInvocationOutput, ) @@ -52,9 +52,9 @@ class RandomRangeInvocation(BaseInvocation): size: int = Field(default=1, description="The number of values to generate") seed: Optional[int] = Field( ge=0, - le=np.iinfo(np.int32).max, - description="The seed for the RNG", - default_factory=lambda: numpy.random.randint(0, np.iinfo(np.int32).max), + le=SEED_MAX, + description="The seed for the RNG (omit for random)", + default_factory=get_random_seed, ) def invoke(self, context: InvocationContext) -> IntCollectionOutput: diff --git a/invokeai/app/invocations/generate.py b/invokeai/app/invocations/generate.py index 85400a834a..2ca3e98dfa 100644 --- a/invokeai/app/invocations/generate.py +++ b/invokeai/app/invocations/generate.py @@ -10,6 +10,7 @@ from pydantic import BaseModel, Field from invokeai.app.models.image import ColorField, ImageField, ImageType from invokeai.app.invocations.util.choose_model import choose_model +from invokeai.app.util.misc import SEED_MAX, get_random_seed from invokeai.backend.generator.inpaint import infill_methods from .baseinvocation import BaseInvocation, InvocationContext, InvocationConfig from .image import ImageOutput, build_image_output @@ -46,15 +47,13 @@ class TextToImageInvocation(BaseInvocation, SDImageInvocation): # TODO: consider making prompt optional to enable providing prompt through a link # fmt: off prompt: Optional[str] = Field(description="The prompt to generate an image from") - seed: int = Field(default=-1,ge=-1, le=np.iinfo(np.uint32).max, description="The seed to use (-1 for a random seed)", ) + seed: Optional[int] = Field(ge=0, le=SEED_MAX, description="The seed to use (omit for random)", default_factory=get_random_seed) steps: int = Field(default=30, gt=0, description="The number of steps to use to generate the image") width: int = Field(default=512, multiple_of=8, gt=0, description="The width of the resulting image", ) height: int = Field(default=512, multiple_of=8, gt=0, description="The height of the resulting image", ) cfg_scale: float = Field(default=7.5, ge=1, description="The Classifier-Free Guidance, higher values may result in a result closer to the prompt", ) scheduler: SAMPLER_NAME_VALUES = Field(default="k_lms", description="The scheduler to use" ) - seamless: bool = Field(default=False, description="Whether or not to generate an image that can tile without seams", ) model: str = Field(default="", description="The model to use (currently ignored)") - progress_images: bool = Field(default=False, description="Whether or not to produce progress images during generation", ) # fmt: on # TODO: pass this an emitter method or something? or a session for dispatching? diff --git a/invokeai/app/invocations/image.py b/invokeai/app/invocations/image.py index 7c1465f4e3..0060dabfcf 100644 --- a/invokeai/app/invocations/image.py +++ b/invokeai/app/invocations/image.py @@ -54,7 +54,6 @@ def build_image_output( image=image_field, width=image.width, height=image.height, - mode=image.mode, ) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 01c7623e03..142a17d8f9 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -7,6 +7,7 @@ from pydantic import BaseModel, Field import torch from invokeai.app.invocations.util.choose_model import choose_model +from invokeai.app.util.misc import SEED_MAX, get_random_seed from invokeai.app.util.step_callback import stable_diffusion_step_callback @@ -104,17 +105,13 @@ def get_noise(width:int, height:int, device:torch.device, seed:int = 0, latent_c return x -def random_seed(): - return random.randint(0, np.iinfo(np.uint32).max) - - class NoiseInvocation(BaseInvocation): """Generates latent noise.""" type: Literal["noise"] = "noise" # Inputs - seed: int = Field(ge=0, le=np.iinfo(np.uint32).max, description="The seed to use", default_factory=random_seed) + seed: Optional[int] = Field(ge=0, le=SEED_MAX, description="The seed to use", default_factory=get_random_seed) width: int = Field(default=512, multiple_of=8, gt=0, description="The width of the resulting noise", ) height: int = Field(default=512, multiple_of=8, gt=0, description="The height of the resulting noise", ) @@ -152,10 +149,7 @@ class TextToLatentsInvocation(BaseInvocation): steps: int = Field(default=10, gt=0, description="The number of steps to use to generate the image") cfg_scale: float = Field(default=7.5, gt=0, description="The Classifier-Free Guidance, higher values may result in a result closer to the prompt", ) scheduler: SAMPLER_NAME_VALUES = Field(default="k_lms", description="The scheduler to use" ) - seamless: bool = Field(default=False, description="Whether or not to generate an image that can tile without seams", ) - seamless_axes: str = Field(default="", description="The axes to tile the image on, 'x' and/or 'y'") model: str = Field(default="", description="The model to use (currently ignored)") - progress_images: bool = Field(default=False, description="Whether or not to produce progress images during generation", ) # fmt: on # Schema customisation @@ -262,6 +256,10 @@ class LatentsToLatentsInvocation(TextToLatentsInvocation): type: Literal["l2l"] = "l2l" + # Inputs + latents: Optional[LatentsField] = Field(description="The latents to use as a base image") + strength: float = Field(default=0.5, description="The strength of the latents to use") + # Schema customisation class Config(InvocationConfig): schema_extra = { @@ -273,10 +271,6 @@ class LatentsToLatentsInvocation(TextToLatentsInvocation): }, } - # Inputs - latents: Optional[LatentsField] = Field(description="The latents to use as a base image") - strength: float = Field(default=0.5, description="The strength of the latents to use") - def invoke(self, context: InvocationContext) -> LatentsOutput: noise = context.services.latents.get(self.noise.latents_name) latent = context.services.latents.get(self.latents.latents_name) diff --git a/invokeai/app/invocations/util/choose_model.py b/invokeai/app/invocations/util/choose_model.py index cd03ce87a8..18d5357d78 100644 --- a/invokeai/app/invocations/util/choose_model.py +++ b/invokeai/app/invocations/util/choose_model.py @@ -8,6 +8,6 @@ def choose_model(model_manager: ModelManager, model_name: str): model = model_manager.get_model(model_name) else: model = model_manager.get_model() - logger.warning(f"{model_name}' is not a valid model name. Using default model \'{model['model_name']}\' instead.") + logger.warning(f"\'{model_name}\' is not a valid model name. Using default model \'{model['model_name']}\' instead.") return model diff --git a/invokeai/app/util/misc.py b/invokeai/app/util/misc.py index b2b57bd086..c3d091b653 100644 --- a/invokeai/app/util/misc.py +++ b/invokeai/app/util/misc.py @@ -1,5 +1,13 @@ import datetime +import numpy as np def get_timestamp(): return int(datetime.datetime.now(datetime.timezone.utc).timestamp()) + + +SEED_MAX = np.iinfo(np.int32).max + + +def get_random_seed(): + return np.random.randint(0, SEED_MAX) From 6c1de975d96324c741b923da82e9562d1699a646 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 19:06:39 +1000 Subject: [PATCH 29/66] feat(nodes): add infill nodes --- invokeai/app/invocations/infill.py | 116 +++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 33 deletions(-) diff --git a/invokeai/app/invocations/infill.py b/invokeai/app/invocations/infill.py index 05ef4eb9e7..eb1abb959e 100644 --- a/invokeai/app/invocations/infill.py +++ b/invokeai/app/invocations/infill.py @@ -8,6 +8,7 @@ from PIL import Image, ImageOps from pydantic import Field from invokeai.app.invocations.image import ImageOutput, build_image_output +from invokeai.app.util.misc import SEED_MAX, get_random_seed from invokeai.backend.image_util.patchmatch import PatchMatch from ..models.image import ColorField, ImageField, ImageType @@ -120,28 +121,14 @@ def tile_fill_missing( return si -class InfillImageInvocation(BaseInvocation): - """Infills transparent areas of an image""" - - type: Literal["infill"] = "infill" +class InfillColorInvocation(BaseInvocation): + """Infills transparent areas of an image with a color""" + type: Literal["infill_rgba"] = "infill_rgba" image: ImageField = Field(default=None, description="The image to infill") - infill_method: INFILL_METHODS = Field( - default=DEFAULT_INFILL_METHOD, - description="The method used to infill empty regions (px)", - ) - inpaint_fill: Optional[ColorField] = Field( + color: Optional[ColorField] = Field( default=ColorField(r=127, g=127, b=127, a=255), - description="The solid infill method color", - ) - tile_size: int = Field( - default=32, ge=1, description="The tile infill method size (px)" - ) - seed: int = Field( - default=-1, - ge=-1, - le=np.iinfo(np.uint32).max, - description="The seed to use (-1 for a random seed)", + description="The color to use to infill", ) def invoke(self, context: InvocationContext) -> ImageOutput: @@ -149,20 +136,8 @@ class InfillImageInvocation(BaseInvocation): self.image.image_type, self.image.image_name ) - # Do infill - if self.infill_method == "patchmatch" and PatchMatch.patchmatch_available(): - infilled = infill_patchmatch(image.copy()) - elif self.infill_method == "tile": - infilled = tile_fill_missing( - image.copy(), seed=self.seed, tile_size=self.tile_size - ) - elif self.infill_method == "solid": - solid_bg = Image.new("RGBA", image.size, self.inpaint_fill.tuple()) - infilled = Image.alpha_composite(solid_bg, image) - else: - raise ValueError( - f"Non-supported infill type {self.infill_method}", self.infill_method - ) + solid_bg = Image.new("RGBA", image.size, self.color.tuple()) + infilled = Image.alpha_composite(solid_bg, image) infilled.paste(image, (0, 0), image.split()[-1]) @@ -181,3 +156,78 @@ class InfillImageInvocation(BaseInvocation): image_name=image_name, image=image, ) + + +class InfillTileInvocation(BaseInvocation): + """Infills transparent areas of an image with tiles of the image""" + + type: Literal["infill_tile"] = "infill_tile" + + image: ImageField = Field(default=None, description="The image to infill") + tile_size: int = Field(default=32, ge=1, description="The tile size (px)") + seed: Optional[int] = Field( + ge=0, + le=SEED_MAX, + description="The seed to use for tile generation (omit for random)", + default_factory=get_random_seed, + ) + + def invoke(self, context: InvocationContext) -> ImageOutput: + image = context.services.images.get( + self.image.image_type, self.image.image_name + ) + + infilled = tile_fill_missing( + image.copy(), seed=self.seed, tile_size=self.tile_size + ) + infilled.paste(image, (0, 0), image.split()[-1]) + + image_type = ImageType.RESULT + image_name = context.services.images.create_name( + context.graph_execution_state_id, self.id + ) + + metadata = context.services.metadata.build_metadata( + session_id=context.graph_execution_state_id, node=self + ) + + context.services.images.save(image_type, image_name, infilled, metadata) + return build_image_output( + image_type=image_type, + image_name=image_name, + image=image, + ) + + +class InfillPatchMatchInvocation(BaseInvocation): + """Infills transparent areas of an image with tiles of the image""" + + type: Literal["infill_patchmatch"] = "infill_patchmatch" + + image: ImageField = Field(default=None, description="The image to infill") + + def invoke(self, context: InvocationContext) -> ImageOutput: + image = context.services.images.get( + self.image.image_type, self.image.image_name + ) + + if PatchMatch.patchmatch_available(): + infilled = infill_patchmatch(image.copy()) + else: + raise ValueError("PatchMatch is not available on this system") + + image_type = ImageType.RESULT + image_name = context.services.images.create_name( + context.graph_execution_state_id, self.id + ) + + metadata = context.services.metadata.build_metadata( + session_id=context.graph_execution_state_id, node=self + ) + + context.services.images.save(image_type, image_name, infilled, metadata) + return build_image_output( + image_type=image_type, + image_name=image_name, + image=image, + ) From a7786d5ff21db0a7f576c1ebc8a22dcc997ce6be Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 19:31:06 +1000 Subject: [PATCH 30/66] fix(nodes): restore seamless to TextToLatents --- invokeai/app/invocations/latent.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 142a17d8f9..861db7276f 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -150,6 +150,8 @@ class TextToLatentsInvocation(BaseInvocation): cfg_scale: float = Field(default=7.5, gt=0, description="The Classifier-Free Guidance, higher values may result in a result closer to the prompt", ) scheduler: SAMPLER_NAME_VALUES = Field(default="k_lms", description="The scheduler to use" ) model: str = Field(default="", description="The model to use (currently ignored)") + seamless: bool = Field(default=False, description="Whether or not to generate an image that can tile without seams", ) + seamless_axes: str = Field(default="", description="The axes to tile the image on, 'x' and/or 'y'") # fmt: on # Schema customisation From 4d6cef7ac8db8de5880f97da5cb433029ef1deb6 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 19:31:36 +1000 Subject: [PATCH 31/66] fix(ui): fix types bug --- invokeai/frontend/web/src/app/store/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 777f563cae..d5994be1a1 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -69,7 +69,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [ // 'config', ]; -export const store: Store = configureStore({ +export const store = configureStore({ reducer: rememberedRootReducer, enhancers: [ rememberEnhancer(window.localStorage, rememberedKeys, { From 6945d10297294d5539648a5dc93e03e63fbbfcf9 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 19:31:59 +1000 Subject: [PATCH 32/66] chore(ui): regen api client --- .../frontend/web/src/services/api/index.ts | 14 ++++++- .../services/api/models/CompelInvocation.ts | 23 +++++++++++ .../src/services/api/models/CompelOutput.ts | 17 +++++++++ .../services/api/models/ConditioningField.ts | 11 ++++++ .../web/src/services/api/models/Graph.ts | 7 +++- .../api/models/GraphExecutionState.ts | 3 +- .../api/models/ImageToImageInvocation.ts | 10 +---- .../api/models/InfillColorInvocation.ts | 26 +++++++++++++ .../api/models/InfillImageInvocation.ts | 38 ------------------- .../api/models/InfillPatchMatchInvocation.ts | 21 ++++++++++ .../api/models/InfillTileInvocation.ts | 29 ++++++++++++++ .../services/api/models/InpaintInvocation.ts | 10 +---- .../api/models/LatentsToLatentsInvocation.ts | 21 ++++------ .../api/models/RandomRangeInvocation.ts | 2 +- .../api/models/TextToImageInvocation.ts | 10 +---- .../api/models/TextToLatentsInvocation.ts | 23 ++++------- .../services/api/schemas/$CompelInvocation.ts | 24 ++++++++++++ .../src/services/api/schemas/$CompelOutput.ts | 18 +++++++++ .../api/schemas/$ConditioningField.ts | 12 ++++++ .../web/src/services/api/schemas/$Graph.ts | 8 +++- .../api/schemas/$GraphExecutionState.ts | 2 + .../api/schemas/$ImageToImageInvocation.ts | 15 ++------ .../api/schemas/$InfillColorInvocation.ts | 30 +++++++++++++++ .../schemas/$InfillPatchMatchInvocation.ts | 23 +++++++++++ ...Invocation.ts => $InfillTileInvocation.ts} | 21 +++------- .../api/schemas/$InpaintInvocation.ts | 15 ++------ .../schemas/$LatentsToLatentsInvocation.ts | 28 +++++++------- .../services/api/schemas/$NoiseInvocation.ts | 2 +- .../api/schemas/$RandomRangeInvocation.ts | 2 +- .../api/schemas/$TextToImageInvocation.ts | 15 ++------ .../api/schemas/$TextToLatentsInvocation.ts | 30 +++++++-------- .../services/api/services/SessionsService.ts | 9 +++-- 32 files changed, 330 insertions(+), 189 deletions(-) create mode 100644 invokeai/frontend/web/src/services/api/models/CompelInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/CompelOutput.ts create mode 100644 invokeai/frontend/web/src/services/api/models/ConditioningField.ts create mode 100644 invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts delete mode 100644 invokeai/frontend/web/src/services/api/models/InfillImageInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/schemas/$CompelInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/schemas/$CompelOutput.ts create mode 100644 invokeai/frontend/web/src/services/api/schemas/$ConditioningField.ts create mode 100644 invokeai/frontend/web/src/services/api/schemas/$InfillColorInvocation.ts create mode 100644 invokeai/frontend/web/src/services/api/schemas/$InfillPatchMatchInvocation.ts rename invokeai/frontend/web/src/services/api/schemas/{$InfillImageInvocation.ts => $InfillTileInvocation.ts} (53%) diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index b3a63c8935..24eeb458b6 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -13,6 +13,9 @@ export type { CkptModelInfo } from './models/CkptModelInfo'; export type { CollectInvocation } from './models/CollectInvocation'; export type { CollectInvocationOutput } from './models/CollectInvocationOutput'; export type { ColorField } from './models/ColorField'; +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'; @@ -33,7 +36,9 @@ export type { ImageResponseMetadata } from './models/ImageResponseMetadata'; export type { ImageToImageInvocation } from './models/ImageToImageInvocation'; export type { ImageToLatentsInvocation } from './models/ImageToLatentsInvocation'; export type { ImageType } from './models/ImageType'; -export type { InfillImageInvocation } from './models/InfillImageInvocation'; +export type { InfillColorInvocation } from './models/InfillColorInvocation'; +export type { InfillPatchMatchInvocation } from './models/InfillPatchMatchInvocation'; +export type { InfillTileInvocation } from './models/InfillTileInvocation'; export type { InpaintInvocation } from './models/InpaintInvocation'; export type { IntCollectionOutput } from './models/IntCollectionOutput'; export type { IntOutput } from './models/IntOutput'; @@ -81,6 +86,9 @@ export { $CkptModelInfo } from './schemas/$CkptModelInfo'; export { $CollectInvocation } from './schemas/$CollectInvocation'; export { $CollectInvocationOutput } from './schemas/$CollectInvocationOutput'; export { $ColorField } from './schemas/$ColorField'; +export { $CompelInvocation } from './schemas/$CompelInvocation'; +export { $CompelOutput } from './schemas/$CompelOutput'; +export { $ConditioningField } from './schemas/$ConditioningField'; export { $CreateModelRequest } from './schemas/$CreateModelRequest'; export { $CropImageInvocation } from './schemas/$CropImageInvocation'; export { $CvInpaintInvocation } from './schemas/$CvInpaintInvocation'; @@ -101,7 +109,9 @@ export { $ImageResponseMetadata } from './schemas/$ImageResponseMetadata'; export { $ImageToImageInvocation } from './schemas/$ImageToImageInvocation'; export { $ImageToLatentsInvocation } from './schemas/$ImageToLatentsInvocation'; export { $ImageType } from './schemas/$ImageType'; -export { $InfillImageInvocation } from './schemas/$InfillImageInvocation'; +export { $InfillColorInvocation } from './schemas/$InfillColorInvocation'; +export { $InfillPatchMatchInvocation } from './schemas/$InfillPatchMatchInvocation'; +export { $InfillTileInvocation } from './schemas/$InfillTileInvocation'; export { $InpaintInvocation } from './schemas/$InpaintInvocation'; export { $IntCollectionOutput } from './schemas/$IntCollectionOutput'; export { $IntOutput } from './schemas/$IntOutput'; diff --git a/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts b/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts new file mode 100644 index 0000000000..f03d53a841 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts @@ -0,0 +1,23 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Parse prompt using compel package to conditioning. + */ +export type CompelInvocation = { + /** + * The id of this node. Must be unique among all nodes. + */ + id: string; + type?: 'compel'; + /** + * Prompt + */ + prompt?: string; + /** + * Model to use + */ + model?: string; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/CompelOutput.ts b/invokeai/frontend/web/src/services/api/models/CompelOutput.ts new file mode 100644 index 0000000000..94f1fcb282 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/CompelOutput.ts @@ -0,0 +1,17 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ConditioningField } from './ConditioningField'; + +/** + * Compel parser output + */ +export type CompelOutput = { + type?: 'compel_output'; + /** + * Conditioning + */ + conditioning?: ConditioningField; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/ConditioningField.ts b/invokeai/frontend/web/src/services/api/models/ConditioningField.ts new file mode 100644 index 0000000000..7e53a63b42 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/ConditioningField.ts @@ -0,0 +1,11 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type ConditioningField = { + /** + * The name of conditioning data + */ + conditioning_name: string; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts index 92e52c57a4..9d56276e62 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -5,6 +5,7 @@ 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 { DataURLToImageInvocation } from './DataURLToImageInvocation'; @@ -13,7 +14,9 @@ import type { Edge } from './Edge'; import type { GraphInvocation } from './GraphInvocation'; import type { ImageToImageInvocation } from './ImageToImageInvocation'; import type { ImageToLatentsInvocation } from './ImageToLatentsInvocation'; -import type { InfillImageInvocation } from './InfillImageInvocation'; +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'; @@ -45,7 +48,7 @@ export type Graph = { /** * The nodes in this graph */ - nodes?: Record; + nodes?: Record; /** * The connections between nodes and their fields in this graph */ diff --git a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts index 2243542480..2e54601e7c 100644 --- a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts +++ b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts @@ -3,6 +3,7 @@ /* eslint-disable */ import type { CollectInvocationOutput } from './CollectInvocationOutput'; +import type { CompelOutput } from './CompelOutput'; import type { Graph } from './Graph'; import type { GraphInvocationOutput } from './GraphInvocationOutput'; import type { ImageOutput } from './ImageOutput'; @@ -41,7 +42,7 @@ export type GraphExecutionState = { /** * The results of node executions */ - results: Record; + results: Record; /** * Errors raised when executing nodes */ diff --git a/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts index d65ceeee3a..0dfb893213 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts @@ -18,7 +18,7 @@ export type ImageToImageInvocation = { */ prompt?: string; /** - * The seed to use (-1 for a random seed) + * The seed to use (omit for random) */ seed?: number; /** @@ -41,18 +41,10 @@ export type ImageToImageInvocation = { * The scheduler to use */ scheduler?: 'ddim' | 'dpmpp_2' | 'k_dpm_2' | 'k_dpm_2_a' | 'k_dpmpp_2' | 'k_euler' | 'k_euler_a' | 'k_heun' | 'k_lms' | 'plms'; - /** - * Whether or not to generate an image that can tile without seams - */ - seamless?: boolean; /** * The model to use (currently ignored) */ model?: string; - /** - * Whether or not to produce progress images during generation - */ - progress_images?: boolean; /** * The input image */ diff --git a/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts new file mode 100644 index 0000000000..a0335eab89 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts @@ -0,0 +1,26 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ColorField } from './ColorField'; +import type { ImageField } from './ImageField'; + +/** + * Infills transparent areas of an image with a color + */ +export type InfillColorInvocation = { + /** + * The id of this node. Must be unique among all nodes. + */ + id: string; + type?: 'infill_rgba'; + /** + * The image to infill + */ + image?: ImageField; + /** + * The color to use to infill + */ + color?: ColorField; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/InfillImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillImageInvocation.ts deleted file mode 100644 index 31ba8cf79b..0000000000 --- a/invokeai/frontend/web/src/services/api/models/InfillImageInvocation.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ColorField } from './ColorField'; -import type { ImageField } from './ImageField'; - -/** - * Infills transparent areas of an image - */ -export type InfillImageInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - type?: 'infill'; - /** - * The image to infill - */ - image?: ImageField; - /** - * The method used to infill empty regions (px) - */ - infill_method?: 'patchmatch' | 'tile' | 'solid'; - /** - * The solid infill method color - */ - inpaint_fill?: ColorField; - /** - * The tile infill method size (px) - */ - tile_size?: number; - /** - * The seed to use (-1 for a random seed) - */ - seed?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts new file mode 100644 index 0000000000..6d6c1074a5 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts @@ -0,0 +1,21 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ImageField } from './ImageField'; + +/** + * Infills transparent areas of an image with tiles of the image + */ +export type InfillPatchMatchInvocation = { + /** + * The id of this node. Must be unique among all nodes. + */ + id: string; + type?: 'infill_patchmatch'; + /** + * The image to infill + */ + image?: ImageField; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts new file mode 100644 index 0000000000..12113f57f5 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts @@ -0,0 +1,29 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ImageField } from './ImageField'; + +/** + * Infills transparent areas of an image with tiles of the image + */ +export type InfillTileInvocation = { + /** + * The id of this node. Must be unique among all nodes. + */ + id: string; + type?: 'infill_tile'; + /** + * The image to infill + */ + image?: ImageField; + /** + * The tile size (px) + */ + tile_size?: number; + /** + * The seed to use for tile generation (omit for random) + */ + seed?: number; +}; + diff --git a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts index baa07bca66..c4b125902a 100644 --- a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts @@ -19,7 +19,7 @@ export type InpaintInvocation = { */ prompt?: string; /** - * The seed to use (-1 for a random seed) + * The seed to use (omit for random) */ seed?: number; /** @@ -42,18 +42,10 @@ export type InpaintInvocation = { * The scheduler to use */ scheduler?: 'ddim' | 'dpmpp_2' | 'k_dpm_2' | 'k_dpm_2_a' | 'k_dpmpp_2' | 'k_euler' | 'k_euler_a' | 'k_heun' | 'k_lms' | 'plms'; - /** - * Whether or not to generate an image that can tile without seams - */ - seamless?: boolean; /** * The model to use (currently ignored) */ model?: string; - /** - * Whether or not to produce progress images during generation - */ - progress_images?: boolean; /** * The input image */ diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts index d04885bf85..7795ce2b21 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ +import type { ConditioningField } from './ConditioningField'; import type { LatentsField } from './LatentsField'; /** @@ -14,9 +15,13 @@ export type LatentsToLatentsInvocation = { id: string; type?: 'l2l'; /** - * The prompt to generate an image from + * Positive conditioning for generation */ - prompt?: string; + positive_conditioning?: ConditioningField; + /** + * Negative conditioning for generation + */ + negative_conditioning?: ConditioningField; /** * The noise to use */ @@ -33,22 +38,10 @@ export type LatentsToLatentsInvocation = { * The scheduler to use */ scheduler?: 'ddim' | 'dpmpp_2' | 'k_dpm_2' | 'k_dpm_2_a' | 'k_dpmpp_2' | 'k_euler' | 'k_euler_a' | 'k_heun' | 'k_lms' | 'plms'; - /** - * 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 model to use (currently ignored) */ model?: string; - /** - * Whether or not to produce progress images during generation - */ - progress_images?: boolean; /** * The latents to use as a base image */ diff --git a/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts index 55a94ec46d..c1f80042a6 100644 --- a/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts @@ -24,7 +24,7 @@ export type RandomRangeInvocation = { */ size?: number; /** - * The seed for the RNG + * The seed for the RNG (omit for random) */ seed?: number; }; diff --git a/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts index b1ff7a3525..d928515c76 100644 --- a/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts @@ -16,7 +16,7 @@ export type TextToImageInvocation = { */ prompt?: string; /** - * The seed to use (-1 for a random seed) + * The seed to use (omit for random) */ seed?: number; /** @@ -39,17 +39,9 @@ export type TextToImageInvocation = { * The scheduler to use */ scheduler?: 'ddim' | 'dpmpp_2' | 'k_dpm_2' | 'k_dpm_2_a' | 'k_dpmpp_2' | 'k_euler' | 'k_euler_a' | 'k_heun' | 'k_lms' | 'plms'; - /** - * Whether or not to generate an image that can tile without seams - */ - seamless?: boolean; /** * The model to use (currently ignored) */ model?: string; - /** - * Whether or not to produce progress images during generation - */ - progress_images?: boolean; }; diff --git a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts index 217b917f18..a67170d6c8 100644 --- a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts @@ -2,10 +2,11 @@ /* tslint:disable */ /* eslint-disable */ +import type { ConditioningField } from './ConditioningField'; import type { LatentsField } from './LatentsField'; /** - * Generates latents from a prompt. + * Generates latents from conditionings. */ export type TextToLatentsInvocation = { /** @@ -14,9 +15,13 @@ export type TextToLatentsInvocation = { id: string; type?: 't2l'; /** - * The prompt to generate an image from + * Positive conditioning for generation */ - prompt?: string; + positive_conditioning?: ConditioningField; + /** + * Negative conditioning for generation + */ + negative_conditioning?: ConditioningField; /** * The noise to use */ @@ -33,21 +38,9 @@ export type TextToLatentsInvocation = { * The scheduler to use */ scheduler?: 'ddim' | 'dpmpp_2' | 'k_dpm_2' | 'k_dpm_2_a' | 'k_dpmpp_2' | 'k_euler' | 'k_euler_a' | 'k_heun' | 'k_lms' | 'plms'; - /** - * 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 model to use (currently ignored) */ model?: string; - /** - * Whether or not to produce progress images during generation - */ - progress_images?: boolean; }; diff --git a/invokeai/frontend/web/src/services/api/schemas/$CompelInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$CompelInvocation.ts new file mode 100644 index 0000000000..61139412ad --- /dev/null +++ b/invokeai/frontend/web/src/services/api/schemas/$CompelInvocation.ts @@ -0,0 +1,24 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $CompelInvocation = { + description: `Parse prompt using compel package to conditioning.`, + properties: { + id: { + type: 'string', + description: `The id of this node. Must be unique among all nodes.`, + isRequired: true, + }, + type: { + type: 'Enum', + }, + prompt: { + type: 'string', + description: `Prompt`, + }, + model: { + type: 'string', + description: `Model to use`, + }, + }, +} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$CompelOutput.ts b/invokeai/frontend/web/src/services/api/schemas/$CompelOutput.ts new file mode 100644 index 0000000000..03a429040a --- /dev/null +++ b/invokeai/frontend/web/src/services/api/schemas/$CompelOutput.ts @@ -0,0 +1,18 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $CompelOutput = { + description: `Compel parser output`, + properties: { + type: { + type: 'Enum', + }, + conditioning: { + type: 'all-of', + description: `Conditioning`, + contains: [{ + type: 'ConditioningField', + }], + }, + }, +} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$ConditioningField.ts b/invokeai/frontend/web/src/services/api/schemas/$ConditioningField.ts new file mode 100644 index 0000000000..fcbd449af2 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/schemas/$ConditioningField.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ConditioningField = { + properties: { + conditioning_name: { + type: 'string', + description: `The name of conditioning data`, + isRequired: true, + }, + }, +} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$Graph.ts b/invokeai/frontend/web/src/services/api/schemas/$Graph.ts index 882fc3f443..f3d5fe0edd 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$Graph.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$Graph.ts @@ -29,6 +29,8 @@ export const $Graph = { type: 'LerpInvocation', }, { type: 'InverseLerpInvocation', + }, { + type: 'CompelInvocation', }, { type: 'NoiseInvocation', }, { @@ -64,7 +66,11 @@ export const $Graph = { }, { type: 'TextToImageInvocation', }, { - type: 'InfillImageInvocation', + type: 'InfillColorInvocation', + }, { + type: 'InfillTileInvocation', + }, { + type: 'InfillPatchMatchInvocation', }, { type: 'GraphInvocation', }, { diff --git a/invokeai/frontend/web/src/services/api/schemas/$GraphExecutionState.ts b/invokeai/frontend/web/src/services/api/schemas/$GraphExecutionState.ts index a21419a6a4..c0a2264877 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$GraphExecutionState.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$GraphExecutionState.ts @@ -47,6 +47,8 @@ export const $GraphExecutionState = { type: 'ImageOutput', }, { type: 'MaskOutput', + }, { + type: 'CompelOutput', }, { type: 'LatentsOutput', }, { diff --git a/invokeai/frontend/web/src/services/api/schemas/$ImageToImageInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$ImageToImageInvocation.ts index 61a7ec2967..098009d182 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$ImageToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$ImageToImageInvocation.ts @@ -18,9 +18,8 @@ export const $ImageToImageInvocation = { }, seed: { type: 'number', - description: `The seed to use (-1 for a random seed)`, - maximum: 4294967295, - minimum: -1, + description: `The seed to use (omit for random)`, + maximum: 2147483647, }, steps: { type: 'number', @@ -39,23 +38,15 @@ export const $ImageToImageInvocation = { cfg_scale: { type: 'number', description: `The Classifier-Free Guidance, higher values may result in a result closer to the prompt`, - exclusiveMinimum: 1, + minimum: 1, }, scheduler: { type: 'Enum', }, - seamless: { - type: 'boolean', - description: `Whether or not to generate an image that can tile without seams`, - }, model: { type: 'string', description: `The model to use (currently ignored)`, }, - progress_images: { - type: 'boolean', - description: `Whether or not to produce progress images during generation`, - }, image: { type: 'all-of', description: `The input image`, diff --git a/invokeai/frontend/web/src/services/api/schemas/$InfillColorInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$InfillColorInvocation.ts new file mode 100644 index 0000000000..42af32c9b2 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/schemas/$InfillColorInvocation.ts @@ -0,0 +1,30 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $InfillColorInvocation = { + description: `Infills transparent areas of an image with a color`, + properties: { + id: { + type: 'string', + description: `The id of this node. Must be unique among all nodes.`, + isRequired: true, + }, + type: { + type: 'Enum', + }, + image: { + type: 'all-of', + description: `The image to infill`, + contains: [{ + type: 'ImageField', + }], + }, + color: { + type: 'all-of', + description: `The color to use to infill`, + contains: [{ + type: 'ColorField', + }], + }, + }, +} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$InfillPatchMatchInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$InfillPatchMatchInvocation.ts new file mode 100644 index 0000000000..0278dafd35 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/schemas/$InfillPatchMatchInvocation.ts @@ -0,0 +1,23 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $InfillPatchMatchInvocation = { + description: `Infills transparent areas of an image with tiles of the image`, + properties: { + id: { + type: 'string', + description: `The id of this node. Must be unique among all nodes.`, + isRequired: true, + }, + type: { + type: 'Enum', + }, + image: { + type: 'all-of', + description: `The image to infill`, + contains: [{ + type: 'ImageField', + }], + }, + }, +} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$InfillImageInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$InfillTileInvocation.ts similarity index 53% rename from invokeai/frontend/web/src/services/api/schemas/$InfillImageInvocation.ts rename to invokeai/frontend/web/src/services/api/schemas/$InfillTileInvocation.ts index 14bb346fb7..7a14d94e5a 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$InfillImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$InfillTileInvocation.ts @@ -1,8 +1,8 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export const $InfillImageInvocation = { - description: `Infills transparent areas of an image`, +export const $InfillTileInvocation = { + description: `Infills transparent areas of an image with tiles of the image`, properties: { id: { type: 'string', @@ -19,26 +19,15 @@ export const $InfillImageInvocation = { type: 'ImageField', }], }, - infill_method: { - type: 'Enum', - }, - inpaint_fill: { - type: 'all-of', - description: `The solid infill method color`, - contains: [{ - type: 'ColorField', - }], - }, tile_size: { type: 'number', - description: `The tile infill method size (px)`, + description: `The tile size (px)`, minimum: 1, }, seed: { type: 'number', - description: `The seed to use (-1 for a random seed)`, - maximum: 4294967295, - minimum: -1, + description: `The seed to use for tile generation (omit for random)`, + maximum: 2147483647, }, }, } as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$InpaintInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$InpaintInvocation.ts index 02b945b955..1225cde1b6 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$InpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$InpaintInvocation.ts @@ -18,9 +18,8 @@ export const $InpaintInvocation = { }, seed: { type: 'number', - description: `The seed to use (-1 for a random seed)`, - maximum: 4294967295, - minimum: -1, + description: `The seed to use (omit for random)`, + maximum: 2147483647, }, steps: { type: 'number', @@ -39,23 +38,15 @@ export const $InpaintInvocation = { cfg_scale: { type: 'number', description: `The Classifier-Free Guidance, higher values may result in a result closer to the prompt`, - exclusiveMinimum: 1, + minimum: 1, }, scheduler: { type: 'Enum', }, - seamless: { - type: 'boolean', - description: `Whether or not to generate an image that can tile without seams`, - }, model: { type: 'string', description: `The model to use (currently ignored)`, }, - progress_images: { - type: 'boolean', - description: `Whether or not to produce progress images during generation`, - }, image: { type: 'all-of', description: `The input image`, diff --git a/invokeai/frontend/web/src/services/api/schemas/$LatentsToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$LatentsToLatentsInvocation.ts index b20ee88a52..38df3ad5cc 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$LatentsToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$LatentsToLatentsInvocation.ts @@ -12,9 +12,19 @@ export const $LatentsToLatentsInvocation = { type: { type: 'Enum', }, - prompt: { - type: 'string', - description: `The prompt to generate an image from`, + positive_conditioning: { + type: 'all-of', + description: `Positive conditioning for generation`, + contains: [{ + type: 'ConditioningField', + }], + }, + negative_conditioning: { + type: 'all-of', + description: `Negative conditioning for generation`, + contains: [{ + type: 'ConditioningField', + }], }, noise: { type: 'all-of', @@ -34,22 +44,10 @@ export const $LatentsToLatentsInvocation = { scheduler: { type: 'Enum', }, - seamless: { - type: 'boolean', - description: `Whether or not to generate an image that can tile without seams`, - }, - seamless_axes: { - type: 'string', - description: `The axes to tile the image on, 'x' and/or 'y'`, - }, model: { type: 'string', description: `The model to use (currently ignored)`, }, - progress_images: { - type: 'boolean', - description: `Whether or not to produce progress images during generation`, - }, latents: { type: 'all-of', description: `The latents to use as a base image`, diff --git a/invokeai/frontend/web/src/services/api/schemas/$NoiseInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$NoiseInvocation.ts index f6ae9fda0e..eade3611b7 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$NoiseInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$NoiseInvocation.ts @@ -15,7 +15,7 @@ export const $NoiseInvocation = { seed: { type: 'number', description: `The seed to use`, - maximum: 4294967295, + maximum: 2147483647, }, width: { type: 'number', diff --git a/invokeai/frontend/web/src/services/api/schemas/$RandomRangeInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$RandomRangeInvocation.ts index f13e1a8332..a71b223ba0 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$RandomRangeInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$RandomRangeInvocation.ts @@ -26,7 +26,7 @@ export const $RandomRangeInvocation = { }, seed: { type: 'number', - description: `The seed for the RNG`, + description: `The seed for the RNG (omit for random)`, maximum: 2147483647, }, }, diff --git a/invokeai/frontend/web/src/services/api/schemas/$TextToImageInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$TextToImageInvocation.ts index a8b6cf41d0..0f583dd2d0 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$TextToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$TextToImageInvocation.ts @@ -18,9 +18,8 @@ export const $TextToImageInvocation = { }, seed: { type: 'number', - description: `The seed to use (-1 for a random seed)`, - maximum: 4294967295, - minimum: -1, + description: `The seed to use (omit for random)`, + maximum: 2147483647, }, steps: { type: 'number', @@ -39,22 +38,14 @@ export const $TextToImageInvocation = { cfg_scale: { type: 'number', description: `The Classifier-Free Guidance, higher values may result in a result closer to the prompt`, - exclusiveMinimum: 1, + minimum: 1, }, scheduler: { type: 'Enum', }, - seamless: { - type: 'boolean', - description: `Whether or not to generate an image that can tile without seams`, - }, model: { type: 'string', description: `The model to use (currently ignored)`, }, - progress_images: { - type: 'boolean', - description: `Whether or not to produce progress images during generation`, - }, }, } as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$TextToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$TextToLatentsInvocation.ts index 06376824c6..1080890606 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$TextToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$TextToLatentsInvocation.ts @@ -2,7 +2,7 @@ /* tslint:disable */ /* eslint-disable */ export const $TextToLatentsInvocation = { - description: `Generates latents from a prompt.`, + description: `Generates latents from conditionings.`, properties: { id: { type: 'string', @@ -12,9 +12,19 @@ export const $TextToLatentsInvocation = { type: { type: 'Enum', }, - prompt: { - type: 'string', - description: `The prompt to generate an image from`, + positive_conditioning: { + type: 'all-of', + description: `Positive conditioning for generation`, + contains: [{ + type: 'ConditioningField', + }], + }, + negative_conditioning: { + type: 'all-of', + description: `Negative conditioning for generation`, + contains: [{ + type: 'ConditioningField', + }], }, noise: { type: 'all-of', @@ -34,21 +44,9 @@ export const $TextToLatentsInvocation = { scheduler: { type: 'Enum', }, - seamless: { - type: 'boolean', - description: `Whether or not to generate an image that can tile without seams`, - }, - seamless_axes: { - type: 'string', - description: `The axes to tile the image on, 'x' and/or 'y'`, - }, model: { type: 'string', description: `The model to use (currently ignored)`, }, - progress_images: { - type: 'boolean', - description: `Whether or not to produce progress images during generation`, - }, }, } as const; diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts index 6567ef2972..694f3822cb 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -4,6 +4,7 @@ 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 { DataURLToImageInvocation } from '../models/DataURLToImageInvocation'; @@ -14,7 +15,9 @@ import type { GraphExecutionState } from '../models/GraphExecutionState'; import type { GraphInvocation } from '../models/GraphInvocation'; import type { ImageToImageInvocation } from '../models/ImageToImageInvocation'; import type { ImageToLatentsInvocation } from '../models/ImageToLatentsInvocation'; -import type { InfillImageInvocation } from '../models/InfillImageInvocation'; +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'; @@ -147,7 +150,7 @@ export class SessionsService { * The id of the session */ sessionId: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | DataURLToImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | ParamIntInvocation | CvInpaintInvocation | RangeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillImageInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | DataURLToImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | CompelInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | ParamIntInvocation | CvInpaintInvocation | RangeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -184,7 +187,7 @@ export class SessionsService { * The path to the node in the graph */ nodePath: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | DataURLToImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | ParamIntInvocation | CvInpaintInvocation | RangeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillImageInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), + requestBody: (LoadImageInvocation | ShowImageInvocation | DataURLToImageInvocation | CropImageInvocation | PasteImageInvocation | MaskFromAlphaInvocation | BlurInvocation | LerpInvocation | InverseLerpInvocation | CompelInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | ParamIntInvocation | CvInpaintInvocation | RangeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), }): CancelablePromise { return __request(OpenAPI, { method: 'PUT', From 4ef0e43759e193c688248704a4d9432bb94fac35 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 19:36:05 +1000 Subject: [PATCH 33/66] fix(nodes): remove dataURL invocation --- invokeai/app/invocations/image.py | 42 +------------------------------ 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/invokeai/app/invocations/image.py b/invokeai/app/invocations/image.py index 0060dabfcf..27c337e92a 100644 --- a/invokeai/app/invocations/image.py +++ b/invokeai/app/invocations/image.py @@ -5,8 +5,7 @@ from typing import Literal, Optional import numpy from PIL import Image, ImageFilter, ImageOps -from pydantic import BaseModel, Field, validator -from w3lib.url import parse_data_uri +from pydantic import BaseModel, Field from ..models.image import ImageField, ImageType from .baseinvocation import ( @@ -118,45 +117,6 @@ class ShowImageInvocation(BaseInvocation): ) -class DataURLToImageInvocation(BaseInvocation, PILInvocationConfig): - """Outputs an image from a data URL.""" - - type: Literal["dataURL_image"] = "dataURL_image" - - # Inputs - dataURL: str = Field(description="The b64 data URL") - - @validator("dataURL") - def must_be_valid_image_dataURL(cls, v): - try: - result = parse_data_uri(v) - img = Image.open(io.BytesIO(result.data)) - img.verify() - except Exception: - raise ValueError("Invalid image dataURL") - return v - - def invoke(self, context: InvocationContext) -> ImageOutput: - # TODO: Figure out how to use pydantic validator to also transform into a different type, - # bc this is just the same logic as we use to validate the dataURL. - result = parse_data_uri(self.dataURL) - image = Image.open(io.BytesIO(result.data)) - - image_name = context.services.images.create_name( - context.graph_execution_state_id, self.id - ) - - metadata = context.services.metadata.build_metadata( - session_id=context.graph_execution_state_id, node=self - ) - - context.services.images.save(ImageType.RESULT, image_name, image, metadata) - - return build_image_output( - image_type=ImageType.RESULT, image_name=image_name, image=image - ) - - class CropImageInvocation(BaseInvocation, PILInvocationConfig): """Crops an image to a specified box. The box can be outside of the image.""" From 4e56c962f4474c5372b9442410308450f8bf070c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 19:36:51 +1000 Subject: [PATCH 34/66] fix(nodes): fix infill docstrings --- invokeai/app/invocations/infill.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/app/invocations/infill.py b/invokeai/app/invocations/infill.py index eb1abb959e..17a48cf47b 100644 --- a/invokeai/app/invocations/infill.py +++ b/invokeai/app/invocations/infill.py @@ -122,7 +122,7 @@ def tile_fill_missing( class InfillColorInvocation(BaseInvocation): - """Infills transparent areas of an image with a color""" + """Infills transparent areas of an image with a solid color""" type: Literal["infill_rgba"] = "infill_rgba" image: ImageField = Field(default=None, description="The image to infill") @@ -200,7 +200,7 @@ class InfillTileInvocation(BaseInvocation): class InfillPatchMatchInvocation(BaseInvocation): - """Infills transparent areas of an image with tiles of the image""" + """Infills transparent areas of an image using the PatchMatch algorithm""" type: Literal["infill_patchmatch"] = "infill_patchmatch" From d0bac1675e0c391b7f9cc763b94af5bd230a0562 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 19:47:58 +1000 Subject: [PATCH 35/66] fix(nodes): fix ImageOutput Config --- invokeai/app/invocations/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/app/invocations/image.py b/invokeai/app/invocations/image.py index 27c337e92a..d32f96857d 100644 --- a/invokeai/app/invocations/image.py +++ b/invokeai/app/invocations/image.py @@ -38,7 +38,7 @@ class ImageOutput(BaseInvocationOutput): # fmt: on class Config: - schema_extra = {"required": ["type", "image", "width", "height", "mode"]} + schema_extra = {"required": ["type", "image", "width", "height"]} def build_image_output( From 34f3a0f0e3dbe293f9e28a4bdc676addf5e04024 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 19:57:45 +1000 Subject: [PATCH 36/66] feat(nodes): improve default model choosing output --- invokeai/app/invocations/util/choose_model.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/invokeai/app/invocations/util/choose_model.py b/invokeai/app/invocations/util/choose_model.py index 18d5357d78..4c5ddca00d 100644 --- a/invokeai/app/invocations/util/choose_model.py +++ b/invokeai/app/invocations/util/choose_model.py @@ -4,10 +4,11 @@ from invokeai.backend.model_management.model_manager import ModelManager def choose_model(model_manager: ModelManager, model_name: str): """Returns the default model if the `model_name` not a valid model, else returns the selected model.""" logger = model_manager.logger - if model_manager.valid_model(model_name): - model = model_manager.get_model(model_name) - else: + if model_name and not model_manager.valid_model(model_name): + default_model_name = model_manager.default_model() + logger.warning(f"\'{model_name}\' is not a valid model name. Using default model \'{default_model_name}\' instead.") model = model_manager.get_model() - logger.warning(f"\'{model_name}\' is not a valid model name. Using default model \'{model['model_name']}\' instead.") + else: + model = model_manager.get_model(model_name) return model From 027a8562d74e65837322e123a4efea377aac4c3e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 May 2023 20:11:07 +1000 Subject: [PATCH 37/66] fix(ui): default node model selection --- .../nodes/components/fields/ModelInputFieldComponent.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx index 3ce790171a..ea84fca43c 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx @@ -48,7 +48,10 @@ const ModelInputFieldComponent = ( }; return ( - {allModelNames.map((option) => ( ))} From 3dc60254b9d6216c3386154178afa1b10cff6eff Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sun, 7 May 2023 15:18:01 +1000 Subject: [PATCH 38/66] feat(ui): support collect nodes --- .../features/nodes/components/InputFieldComponent.tsx | 11 +++++++++++ .../src/features/nodes/util/fieldTemplateBuilders.ts | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx index 6d8d23a123..4a1fa89eff 100644 --- a/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx @@ -12,6 +12,7 @@ import NumberInputFieldComponent from './fields/NumberInputFieldComponent'; import StringInputFieldComponent from './fields/StringInputFieldComponent'; import ItemInputFieldComponent from './fields/ItemInputFieldComponent'; import ColorInputFieldComponent from './fields/ColorInputFieldComponent'; +import ItemInputFieldComponent from './fields/ItemInputFieldComponent'; type InputFieldComponentProps = { nodeId: string; @@ -137,6 +138,16 @@ const InputFieldComponent = (props: InputFieldComponentProps) => { ); } + if (type === 'item' && template.type === 'item') { + return ( + + ); + } + return Unknown field type: {type}; }; diff --git a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts index 77261209a9..020873fe81 100644 --- a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts +++ b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts @@ -360,6 +360,12 @@ export const buildInputFieldTemplate = ( if (['color'].includes(fieldType)) { return buildColorInputFieldTemplate({ schemaObject, baseField }); } + if (['array'].includes(fieldType)) { + return buildArrayInputFieldTemplate({ schemaObject, baseField }); + } + if (['item'].includes(fieldType)) { + return buildItemInputFieldTemplate({ schemaObject, baseField }); + } return; }; From 5365f42a04b936273a5219bb9fa6f79c77f27a74 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 8 May 2023 00:55:31 +1000 Subject: [PATCH 39/66] feat(ui): wip layouting --- invokeai/frontend/web/package.json | 1 + .../frontend/web/src/app/components/App.tsx | 9 +- .../web/src/common/hooks/useGlobalHotkeys.ts | 22 +++ .../gallery/components/ImageGalleryPanel.tsx | 136 ++++++++++-------- .../nodes/components/InputFieldComponent.tsx | 1 - .../components/AnimatedImageToImagePanel.tsx | 2 +- .../ProcessButtons/CancelButton.tsx | 64 ++++----- .../ui/components/CreateParametersDrawer.tsx | 78 ++++++++++ .../ui/components/FloatingGalleryButton.tsx | 2 +- .../FloatingParametersPanelButtons.tsx | 10 +- .../src/features/ui/components/InvokeTabs.tsx | 62 ++++++-- .../components/common/OverlayScrollable.tsx | 24 ++++ .../ResizableDrawer/ResizableDrawer.tsx | 84 +++++------ .../components/common/ResizableDrawer/util.ts | 15 +- .../ui/components/tabs/Create/CreateTab.tsx | 96 +++++++++++++ .../{Generate => Create}/GenerateContent.tsx | 4 +- .../GenerateParameters.tsx | 44 ++++-- .../GenerateWorkspace.tsx | 6 +- .../ui/components/tabs/Nodes/NodesTab.tsx | 64 +++++++++ .../ui/components/tabs/ResizeHandle.tsx | 17 +++ .../UnifiedCanvas/UnifiedCanvasParameters.tsx | 23 ++- .../tabs/UnifiedCanvas/UnifiedCanvasTab.tsx | 89 ++++++++++++ .../UnifiedCanvas/UnifiedCanvasWorkarea.tsx | 10 +- .../web/src/features/ui/store/uiSlice.ts | 6 + invokeai/frontend/web/yarn.lock | 5 + 25 files changed, 687 insertions(+), 187 deletions(-) create mode 100644 invokeai/frontend/web/src/features/ui/components/CreateParametersDrawer.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateTab.tsx rename invokeai/frontend/web/src/features/ui/components/tabs/{Generate => Create}/GenerateContent.tsx (88%) rename invokeai/frontend/web/src/features/ui/components/tabs/{Generate => Create}/GenerateParameters.tsx (80%) rename invokeai/frontend/web/src/features/ui/components/tabs/{Generate => Create}/GenerateWorkspace.tsx (93%) create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index 3cf8ba070a..561efdd8e9 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -89,6 +89,7 @@ "react-konva": "^18.2.7", "react-konva-utils": "^1.0.4", "react-redux": "^8.0.5", + "react-resizable-panels": "^0.0.42", "react-rnd": "^10.4.1", "react-transition-group": "^4.4.5", "react-use": "^17.4.0", diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index 3aebfa4097..c5f2c6ff04 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -1,6 +1,6 @@ import ImageUploader from 'common/components/ImageUploader'; -import ProgressBar from 'features/system/components/ProgressBar'; import SiteHeader from 'features/system/components/SiteHeader'; +import ProgressBar from 'features/system/components/ProgressBar'; import InvokeTabs from 'features/ui/components/InvokeTabs'; import useToastWatcher from 'features/system/hooks/useToastWatcher'; @@ -28,6 +28,9 @@ import { configChanged } from 'features/system/store/configSlice'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useLogger } from 'app/logging/useLogger'; import ProgressImagePreview from 'features/parameters/components/ProgressImagePreview'; +import ResizableDrawer from 'features/ui/components/common/ResizableDrawer/ResizableDrawer'; +import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; +import CreateParametersDrawer from 'features/ui/components/CreateParametersDrawer'; const DEFAULT_CONFIG = {}; @@ -84,11 +87,13 @@ const App = ({ config = DEFAULT_CONFIG, children }: Props) => { flexDir={{ base: 'column', xl: 'row' }} > - + + + {!isApplicationReady && !loadingOverridden && ( { { keyup: true, keydown: true }, [shift] ); + + useHotkeys('o', () => { + dispatch(toggleParametersPanel()); + }); + + useHotkeys(['shift+o'], () => { + dispatch(togglePinParametersPanel()); + }); + + useHotkeys('g', () => { + dispatch(toggleGalleryPanel()); + }); + + useHotkeys(['shift+g'], () => { + dispatch(togglePinGalleryPanel()); + }); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx index 76442d340b..24ff1d72af 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx @@ -73,7 +73,7 @@ const galleryPanelSelector = createSelector( } ); -export const ImageGalleryPanel = () => { +const ImageGalleryPanel = () => { const dispatch = useAppDispatch(); const { shouldPinGallery, @@ -102,21 +102,21 @@ export const ImageGalleryPanel = () => { const resolution = useResolution(); - useHotkeys( - 'g', - () => { - handleToggleGallery(); - }, - [shouldPinGallery] - ); + // useHotkeys( + // 'g', + // () => { + // handleToggleGallery(); + // }, + // [shouldPinGallery] + // ); - useHotkeys( - 'shift+g', - () => { - handleSetShouldPinGallery(); - }, - [shouldPinGallery] - ); + // useHotkeys( + // 'shift+g', + // () => { + // handleSetShouldPinGallery(); + // }, + // [shouldPinGallery] + // ); useHotkeys( 'esc', @@ -162,55 +162,71 @@ export const ImageGalleryPanel = () => { [galleryImageMinimumWidth] ); - const calcGalleryMinHeight = () => { - if (resolution === 'desktop') return; - return 300; - }; + // const calcGalleryMinHeight = () => { + // if (resolution === 'desktop') return; + // return 300; + // }; - const imageGalleryContent = () => { - return ( - - - - ); - }; + // const imageGalleryContent = () => { + // return ( + // + // + // + // ); + // }; - const resizableImageGalleryContent = () => { - return ( - - - - ); - }; + // const resizableImageGalleryContent = () => { + // return ( + // + // + // + // ); + // }; - const renderImageGallery = () => { - if (['mobile', 'tablet'].includes(resolution)) return imageGalleryContent(); - return resizableImageGalleryContent(); - }; + // const renderImageGallery = () => { + // if (['mobile', 'tablet'].includes(resolution)) return imageGalleryContent(); + // return resizableImageGalleryContent(); + // }; - return renderImageGallery(); + if (shouldPinGallery) { + return null; + } + + return ( + + + + ); + + // return renderImageGallery(); }; export default memo(ImageGalleryPanel); diff --git a/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx index 4a1fa89eff..98698e2d81 100644 --- a/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx @@ -10,7 +10,6 @@ import ConditioningInputFieldComponent from './fields/ConditioningInputFieldComp import ModelInputFieldComponent from './fields/ModelInputFieldComponent'; import NumberInputFieldComponent from './fields/NumberInputFieldComponent'; import StringInputFieldComponent from './fields/StringInputFieldComponent'; -import ItemInputFieldComponent from './fields/ItemInputFieldComponent'; import ColorInputFieldComponent from './fields/ColorInputFieldComponent'; import ItemInputFieldComponent from './fields/ItemInputFieldComponent'; diff --git a/invokeai/frontend/web/src/features/parameters/components/AnimatedImageToImagePanel.tsx b/invokeai/frontend/web/src/features/parameters/components/AnimatedImageToImagePanel.tsx index da9262ac0f..8778e043e6 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AnimatedImageToImagePanel.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AnimatedImageToImagePanel.tsx @@ -20,7 +20,7 @@ const AnimatedImageToImagePanel = () => { exit={{ opacity: 0, scale: 0, width: 0 }} transition={{ type: 'spring', bounce: 0, duration: 0.35 }} > - + diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx index 4f6c2ecc1c..03e9643384 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx @@ -11,7 +11,7 @@ import { CancelStrategy, } from 'features/system/store/systemSlice'; import { isEqual } from 'lodash-es'; -import { useCallback, memo } from 'react'; +import { useCallback, memo, useMemo } from 'react'; import { ButtonSpinner, ButtonGroup, @@ -102,39 +102,39 @@ const CancelButton = ( [isConnected, isProcessing, isCancelable] ); + const cancelLabel = useMemo(() => { + if (isCancelScheduled) { + return t('parameters.cancel.isScheduled'); + } + if (cancelType === 'immediate') { + return t('parameters.cancel.immediate'); + } + + return t('parameters.cancel.schedule'); + }, [t, cancelType, isCancelScheduled]); + + const cancelIcon = useMemo(() => { + if (isCancelScheduled) { + return ; + } + if (cancelType === 'immediate') { + return ; + } + + return ; + }, [cancelType, isCancelScheduled]); + return ( - {cancelType === 'immediate' ? ( - } - tooltip={t('parameters.cancel.immediate')} - aria-label={t('parameters.cancel.immediate')} - isDisabled={!isConnected || !isProcessing || !isCancelable} - onClick={handleClickCancel} - colorScheme="error" - {...rest} - /> - ) : ( - : - } - tooltip={ - isCancelScheduled - ? t('parameters.cancel.isScheduled') - : t('parameters.cancel.schedule') - } - aria-label={ - isCancelScheduled - ? t('parameters.cancel.isScheduled') - : t('parameters.cancel.schedule') - } - isDisabled={!isConnected || !isProcessing || !isCancelable} - onClick={handleClickCancel} - colorScheme="error" - {...rest} - /> - )} + { + const { shouldPinParametersPanel, shouldShowParametersPanel } = ui; + const { isLightboxOpen } = lightbox; + + return { + shouldPinParametersPanel, + shouldShowParametersPanel, + }; + }, + { + memoizeOptions: { + resultEqualityCheck: isEqual, + }, + } +); + +const CreateParametersPanel = () => { + const dispatch = useAppDispatch(); + const { shouldPinParametersPanel, shouldShowParametersPanel } = + useAppSelector(selector); + + const handleClosePanel = () => { + dispatch(setShouldShowParametersPanel(false)); + }; + + if (shouldPinParametersPanel) { + return null; + } + + return ( + + + + + + + + + + ); +}; + +export default memo(CreateParametersPanel); diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx index 83a699aca0..bcbcc1cefc 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx @@ -16,7 +16,7 @@ const floatingGalleryButtonSelector = createSelector( return { shouldPinGallery, - shouldShowGalleryButton: !shouldPinGallery || !shouldShowGallery, + shouldShowGalleryButton: !shouldShowGallery, }; }, { memoizeOptions: { resultEqualityCheck: isEqual } } diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx index 3055216b66..90d199076d 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx @@ -39,7 +39,7 @@ export const floatingParametersPanelButtonSelector = createSelector( const shouldShowParametersPanelButton = !canvasBetaLayoutCheck && - (!shouldPinParametersPanel || !shouldShowParametersPanel) && + !shouldShowParametersPanel && ['generate', 'unifiedCanvas'].includes(activeTabName); return { @@ -65,7 +65,11 @@ const FloatingParametersPanelButtons = () => { shouldPinParametersPanel && dispatch(requestCanvasRescale()); }; - return shouldShowParametersPanelButton ? ( + if (!shouldShowParametersPanelButton) { + return null; + } + + return ( { )} - ) : null; + ); }; export default memo(FloatingParametersPanelButtons); diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index 17a84dd8f2..f548744c44 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -1,5 +1,7 @@ import { + Box, ChakraProps, + Flex, Icon, Tab, TabList, @@ -14,7 +16,14 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice'; import { InvokeTabName } from 'features/ui/store/tabMap'; import { setActiveTab, togglePanels } from 'features/ui/store/uiSlice'; -import { memo, ReactNode, useMemo } from 'react'; +import { + memo, + ReactNode, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { MdDeviceHub, MdGridOn } from 'react-icons/md'; import { activeTabIndexSelector } from '../store/uiSelectors'; @@ -23,33 +32,47 @@ import { useTranslation } from 'react-i18next'; import { ResourceKey } from 'i18next'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import NodeEditor from 'features/nodes/components/NodeEditor'; -import GenerateWorkspace from './tabs/Generate/GenerateWorkspace'; +import GenerateWorkspace from './tabs/Create/GenerateWorkspace'; import { createSelector } from '@reduxjs/toolkit'; import { BsLightningChargeFill } from 'react-icons/bs'; import { configSelector } from 'features/system/store/configSelectors'; import { isEqual } from 'lodash-es'; +import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel'; +import Scrollable from './common/Scrollable'; +import GenerateParameters from './tabs/Create/GenerateParameters'; +import PinParametersPanelButton from './PinParametersPanelButton'; +import ParametersSlide from './common/ParametersSlide'; +import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel'; +import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; +import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; +import CreateTabContent from './tabs/Create/GenerateContent'; +import ParametersPanel from './ParametersPanel'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import CreateTab from './tabs/Create/CreateTab'; +import UnifiedCanvasTab from './tabs/UnifiedCanvas/UnifiedCanvasTab'; +import NodesTab from './tabs/Nodes/NodesTab'; export interface InvokeTabInfo { id: InvokeTabName; icon: ReactNode; - workarea: ReactNode; + content: ReactNode; } const tabs: InvokeTabInfo[] = [ { id: 'generate', icon: , - workarea: , + content: , }, { id: 'unifiedCanvas', icon: , - workarea: , + content: , }, { id: 'nodes', icon: , - workarea: , + content: , }, ]; @@ -72,9 +95,12 @@ const InvokeTabs = () => { (state: RootState) => state.lightbox.isLightboxOpen ); - const { shouldPinGallery, shouldPinParametersPanel } = useAppSelector( - (state: RootState) => state.ui - ); + const { + shouldPinGallery, + shouldPinParametersPanel, + shouldShowGallery, + shouldShowParametersPanel, + } = useAppSelector((state: RootState) => state.ui); const { t } = useTranslation(); @@ -133,9 +159,7 @@ const InvokeTabs = () => { const tabPanels = useMemo( () => - enabledTabs.map((tab) => ( - {tab.workarea} - )), + enabledTabs.map((tab) => {tab.content}), [enabledTabs] ); @@ -165,3 +189,17 @@ const InvokeTabs = () => { }; export default memo(InvokeTabs); + +// +// +// +// +// +// +// +// +// +// +// +// +// ; diff --git a/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx b/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx new file mode 100644 index 0000000000..af4f65f011 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx @@ -0,0 +1,24 @@ +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import { PropsWithChildren, memo } from 'react'; + +const OverlayScrollable = (props: PropsWithChildren) => { + return ( + + {props.children} + + ); +}; + +export default memo(OverlayScrollable); diff --git a/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx index cabe58ccf2..d9fd765656 100644 --- a/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx +++ b/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx @@ -26,7 +26,6 @@ import { type ResizableDrawerProps = ResizableProps & { children: ReactNode; isResizable: boolean; - isPinned: boolean; isOpen: boolean; onClose: () => void; direction?: SlideDirection; @@ -51,7 +50,6 @@ const ChakraResizeable = chakra(Resizable, { const ResizableDrawer = ({ direction = 'left', isResizable, - isPinned, isOpen, onClose, children, @@ -95,7 +93,7 @@ const ResizableDrawer = ({ handler: () => { onClose(); }, - enabled: isOpen && !isPinned, + enabled: isOpen, }); const handleEnables = useMemo( @@ -107,30 +105,33 @@ const ResizableDrawer = ({ () => getMinMaxDimensions({ direction, - minWidth: isResizable - ? parseAndPadSize(minWidth, 18) - : parseAndPadSize(minWidth), - maxWidth: isResizable - ? parseAndPadSize(maxWidth, 18) - : parseAndPadSize(maxWidth), - minHeight: isResizable - ? parseAndPadSize(minHeight, 18) - : parseAndPadSize(minHeight), - maxHeight: isResizable - ? parseAndPadSize(maxHeight, 18) - : parseAndPadSize(maxHeight), + // minWidth: isResizable + // ? parseAndPadSize(minWidth, 18) + // : parseAndPadSize(minWidth), + // maxWidth: isResizable + // ? parseAndPadSize(maxWidth, 18) + // : parseAndPadSize(maxWidth), + // minHeight: isResizable + // ? parseAndPadSize(minHeight, 18) + // : parseAndPadSize(minHeight), + // maxHeight: isResizable + // ? parseAndPadSize(maxHeight, 18) + // : parseAndPadSize(maxHeight), + minWidth, + maxWidth, + minHeight, + maxHeight, }), - [minWidth, maxWidth, minHeight, maxHeight, direction, isResizable] + [minWidth, maxWidth, minHeight, maxHeight, direction] ); const { containerStyles, handleStyles } = useMemo( () => getStyles({ - isPinned, isResizable, direction, }), - [isPinned, isResizable, direction] + [isResizable, direction] ); const slideDirection = useMemo( @@ -140,34 +141,37 @@ const ResizableDrawer = ({ useEffect(() => { if (['left', 'right'].includes(direction)) { - setHeight(isPinned ? '100%' : '100vh'); + setHeight('100vh'); + // setHeight(isPinned ? '100%' : '100vh'); } if (['top', 'bottom'].includes(direction)) { - setWidth(isPinned ? '100%' : '100vw'); + setWidth('100vw'); + // setWidth(isPinned ? '100%' : '100vw'); } - }, [isPinned, direction]); + }, [direction]); return ( { + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + } = ui; + + return { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + }; +}); + +const CreateTab = () => { + const dispatch = useAppDispatch(); + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + } = useAppSelector(selector); + + return ( + + {shouldPinParametersPanel && shouldShowParametersPanel && ( + <> + + + + + + + )} + {shouldPinParametersPanel && shouldShowParametersPanel && ( + <> + + + + + + )} + { + dispatch(requestCanvasRescale()); + }} + > + + + {shouldPinGallery && shouldShowGallery && ( + <> + + + + + + )} + + ); +}; + +export default memo(CreateTab); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateContent.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateContent.tsx similarity index 88% rename from invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateContent.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateContent.tsx index de7b738956..cd0c6a6142 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateContent.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateContent.tsx @@ -2,7 +2,7 @@ import { Box, Flex } from '@chakra-ui/react'; import CurrentImageDisplay from 'features/gallery/components/CurrentImageDisplay'; import ProgressImagePreview from 'features/parameters/components/ProgressImagePreview'; -const GenerateContent = () => { +const CreateTabContent = () => { return ( { ); }; -export default GenerateContent; +export default CreateTabContent; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateParameters.tsx similarity index 80% rename from invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateParameters.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateParameters.tsx index 5b56fa5b0c..b03594eec6 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateParameters.tsx @@ -30,9 +30,15 @@ import ProcessButtons from 'features/parameters/components/ProcessButtons/Proces import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput'; import PromptInput from 'features/parameters/components/PromptInput/PromptInput'; import { findIndex } from 'lodash-es'; -import { memo, useMemo, useState } from 'react'; +import { + OverlayScrollbarsComponent, + useOverlayScrollbars, +} from 'overlayscrollbars-react'; +import { memo, useMemo, useState, useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; +import OverlayScrollable from '../../common/OverlayScrollable'; +import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel'; const GenerateParameters = () => { const { t } = useTranslation(); @@ -83,25 +89,35 @@ const GenerateParameters = () => { ); return ( - - - - + - + + + + + + + + - - - + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateWorkspace.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx similarity index 93% rename from invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateWorkspace.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx index df201af6ac..ebbf5cb5be 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateWorkspace.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx @@ -1,7 +1,7 @@ import { Box, Flex } from '@chakra-ui/react'; import { useAppSelector } from 'app/store/storeHooks'; import { memo } from 'react'; -import GenerateContent from './GenerateContent'; +import CreateTabContent from './GenerateContent'; import GenerateParameters from './GenerateParameters'; import PinParametersPanelButton from '../../PinParametersPanelButton'; import { RootState } from 'app/store/store'; @@ -14,6 +14,8 @@ const GenerateWorkspace = () => { (state: RootState) => state.ui.shouldPinParametersPanel ); + return ; + return ( { )} - + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx new file mode 100644 index 0000000000..d754cca45e --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx @@ -0,0 +1,64 @@ +import { TabPanel } from '@chakra-ui/react'; +import { memo } from 'react'; +import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; +import PinParametersPanelButton from '../../PinParametersPanelButton'; +import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; +import { createSelector } from '@reduxjs/toolkit'; +import { uiSelector } from 'features/ui/store/uiSelectors'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; +import ResizeHandle from '../ResizeHandle'; +import NodeEditor from 'features/nodes/components/NodeEditor'; + +const selector = createSelector(uiSelector, (ui) => { + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + } = ui; + + return { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + }; +}); + +const NodesTab = () => { + const dispatch = useAppDispatch(); + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + } = useAppSelector(selector); + + return ( + + { + dispatch(requestCanvasRescale()); + }} + > + + + {shouldPinGallery && shouldShowGallery && ( + <> + + + + + + )} + + ); +}; + +export default memo(NodesTab); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx new file mode 100644 index 0000000000..02f2486fbe --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx @@ -0,0 +1,17 @@ +import { Box, Flex } from '@chakra-ui/react'; +import { memo } from 'react'; +import { PanelResizeHandle } from 'react-resizable-panels'; + +const ResizeHandle = () => { + return ( + + + + + + ); +}; + +export default memo(ResizeHandle); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx index d0b009a6f5..5e64f72cc4 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx @@ -17,6 +17,7 @@ import ProcessButtons from 'features/parameters/components/ProcessButtons/Proces import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput'; import PromptInput from 'features/parameters/components/PromptInput/PromptInput'; import { useTranslation } from 'react-i18next'; +import OverlayScrollable from '../../common/OverlayScrollable'; export default function UnifiedCanvasParameters() { const { t } = useTranslation(); @@ -74,11 +75,21 @@ export default function UnifiedCanvasParameters() { }; return ( - - - - - - + + + + + + + + ); } diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx new file mode 100644 index 0000000000..382bfd8105 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx @@ -0,0 +1,89 @@ +import { TabPanel } from '@chakra-ui/react'; +import { memo } from 'react'; +import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; +import PinParametersPanelButton from '../../PinParametersPanelButton'; +import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; +import { createSelector } from '@reduxjs/toolkit'; +import { uiSelector } from 'features/ui/store/uiSelectors'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; +import UnifiedCanvasContent from './UnifiedCanvasContent'; +import ResizeHandle from '../ResizeHandle'; +import UnifiedCanvasParameters from './UnifiedCanvasParameters'; +import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; + +const selector = createSelector(uiSelector, (ui) => { + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldUseCanvasBetaLayout, + } = ui; + + return { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldUseCanvasBetaLayout, + }; +}); + +const UnifiedCanvasTab = () => { + const dispatch = useAppDispatch(); + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldUseCanvasBetaLayout, + } = useAppSelector(selector); + + return ( + + {shouldPinParametersPanel && shouldShowParametersPanel && ( + <> + + + + + + + )} + { + dispatch(requestCanvasRescale()); + }} + > + {shouldUseCanvasBetaLayout ? ( + + ) : ( + + )} + + {shouldPinGallery && shouldShowGallery && ( + <> + + + + + + )} + + ); +}; + +export default memo(UnifiedCanvasTab); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx index ee7f4d242d..53a7ca0d76 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx @@ -18,6 +18,12 @@ const CanvasWorkspace = () => { (state: RootState) => state.ui.shouldUseCanvasBetaLayout ); + return shouldUseCanvasBetaLayout ? ( + + ) : ( + + ); + return ( { h="full" gap={4} > - {shouldPinParametersPanel ? ( + {/* {shouldPinParametersPanel ? ( @@ -38,7 +44,7 @@ const CanvasWorkspace = () => { - )} + )} */} {shouldUseCanvasBetaLayout ? ( ) : ( diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index 4a21a729d7..c5d3442f38 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -78,9 +78,15 @@ export const uiSlice = createSlice({ }, togglePinGalleryPanel: (state) => { state.shouldPinGallery = !state.shouldPinGallery; + if (!state.shouldPinGallery) { + state.shouldShowGallery = true; + } }, togglePinParametersPanel: (state) => { state.shouldPinParametersPanel = !state.shouldPinParametersPanel; + if (!state.shouldPinParametersPanel) { + state.shouldShowParametersPanel = true; + } }, toggleParametersPanel: (state) => { state.shouldShowParametersPanel = !state.shouldShowParametersPanel; diff --git a/invokeai/frontend/web/yarn.lock b/invokeai/frontend/web/yarn.lock index 1e4c30581a..90bc4055ac 100644 --- a/invokeai/frontend/web/yarn.lock +++ b/invokeai/frontend/web/yarn.lock @@ -5579,6 +5579,11 @@ react-remove-scroll@^2.5.5: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" +react-resizable-panels@^0.0.42: + version "0.0.42" + resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-0.0.42.tgz#e1a5d7fde7be4d18f32d0e021a0b4edb28b9edfe" + integrity sha512-nOaN9DeUTsmKjN3MFGaLd35kngGyO29SHRLdBRafYR1SV2F/LbWbpVUKVPwL2fBBTnQe2/rqOQwT4k+3cKeK+w== + react-rnd@^10.4.1: version "10.4.1" resolved "https://registry.yarnpkg.com/react-rnd/-/react-rnd-10.4.1.tgz#9e1c3f244895d7862ef03be98b2a620848c3fba1" From 864f4bb4af181bb4818e727ae64580283609ce48 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 8 May 2023 12:51:40 +1000 Subject: [PATCH 40/66] feat(ui): wip img2img layouting --- invokeai/frontend/web/public/locales/en.json | 3 +- .../app/store/util/defaultMemoizeOptions.ts | 7 ++ .../common/components/ImageToImageOverlay.tsx | 1 + .../components/ImageToImageSettingsHeader.tsx | 6 +- .../components/CurrentImageButtons.tsx | 26 +++---- .../components/ImageGalleryContent.tsx | 12 +++- .../ImageToImage/ImageToImageSettings.tsx | 4 +- .../ImageToImage/ImageToImageToggle.tsx | 34 ++++++--- .../ImageToImage/InitialImagePreview.tsx | 72 ++++++++++--------- .../ProcessButtons/CancelButton.tsx | 5 +- .../ProcessButtons/ProcessButtons.tsx | 2 + .../components/ProgressImagePreview.tsx | 1 - .../ui/components/CreateParametersDrawer.tsx | 61 ++++++++++++---- .../src/features/ui/components/InvokeTabs.tsx | 4 +- ...eParameters.tsx => CreateBaseSettings.tsx} | 7 +- ...{GenerateContent.tsx => CreateContent.tsx} | 10 ++- .../tabs/Create/CreateImageSettings.tsx | 53 ++++++++++++++ .../tabs/Create/CreateSidePanelPinned.tsx | 69 ++++++++++++++++++ .../ui/components/tabs/Create/CreateTab.tsx | 50 +++++++------ .../tabs/Create/GenerateWorkspace.tsx | 8 +-- .../ui/components/tabs/Nodes/NodesTab.tsx | 1 + .../ui/components/tabs/ResizeHandle.tsx | 2 +- .../tabs/UnifiedCanvas/UnifiedCanvasTab.tsx | 1 + .../web/src/features/ui/store/uiSlice.ts | 8 +++ .../web/src/features/ui/store/uiTypes.ts | 1 + 25 files changed, 338 insertions(+), 110 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/util/defaultMemoizeOptions.ts rename invokeai/frontend/web/src/features/ui/components/tabs/Create/{GenerateParameters.tsx => CreateBaseSettings.tsx} (97%) rename invokeai/frontend/web/src/features/ui/components/tabs/Create/{GenerateContent.tsx => CreateContent.tsx} (78%) create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateImageSettings.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateSidePanelPinned.tsx diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 18f506d5bf..751d56372f 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -102,7 +102,8 @@ "generate": "Generate", "openInNewTab": "Open in New Tab", "dontAskMeAgain": "Don't ask me again", - "areYouSure": "Are you sure?" + "areYouSure": "Are you sure?", + "imagePrompt": "Image Prompt" }, "gallery": { "generations": "Generations", diff --git a/invokeai/frontend/web/src/app/store/util/defaultMemoizeOptions.ts b/invokeai/frontend/web/src/app/store/util/defaultMemoizeOptions.ts new file mode 100644 index 0000000000..fd2abd228d --- /dev/null +++ b/invokeai/frontend/web/src/app/store/util/defaultMemoizeOptions.ts @@ -0,0 +1,7 @@ +import { isEqual } from 'lodash-es'; + +export const defaultSelectorOptions = { + memoizeOptions: { + resultEqualityCheck: isEqual, + }, +}; diff --git a/invokeai/frontend/web/src/common/components/ImageToImageOverlay.tsx b/invokeai/frontend/web/src/common/components/ImageToImageOverlay.tsx index 45a45e37d3..9d864f5c9c 100644 --- a/invokeai/frontend/web/src/common/components/ImageToImageOverlay.tsx +++ b/invokeai/frontend/web/src/common/components/ImageToImageOverlay.tsx @@ -14,6 +14,7 @@ const ImageToImageOverlay = ({ image }: ImageToImageOverlayProps) => { w: 'full', h: 'full', position: 'absolute', + pointerEvents: 'none', }} > { +const ImagePromptHeading = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -18,7 +18,7 @@ const ImageToImageSettingsHeader = () => { return ( - Image to Image + {t('parameters.initialImage')} @@ -38,4 +38,4 @@ const ImageToImageSettingsHeader = () => { ); }; -export default ImageToImageSettingsHeader; +export default ImagePromptHeading; diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index 31f36a3761..824860e219 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -410,7 +410,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { }} {...props} > - + { )} - + } tooltip={`${t('parameters.usePrompt')} (P)`} @@ -528,7 +528,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { {(isUpscalingEnabled || isFaceRestoreEnabled) && ( - + {isFaceRestoreEnabled && ( { )} - + } tooltip={`${t('parameters.info')} (I)`} @@ -603,14 +603,16 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { /> - } - tooltip={`${t('gallery.deleteImage')} (Del)`} - aria-label={`${t('gallery.deleteImage')} (Del)`} - isDisabled={!image || !isConnected} - colorScheme="error" - /> + + } + tooltip={`${t('gallery.deleteImage')} (Del)`} + aria-label={`${t('gallery.deleteImage')} (Del)`} + isDisabled={!image || !isConnected} + colorScheme="error" + /> + {image && ( { }, [dispatch, currentCategory]); return ( - + - + diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageToggle.tsx index 89da0ae8b0..eeb0804b5d 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageToggle.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageToggle.tsx @@ -1,34 +1,50 @@ import { Flex } from '@chakra-ui/react'; +import { createSelector } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISwitch from 'common/components/IAISwitch'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; import { isImageToImageEnabledChanged } from 'features/parameters/store/generationSlice'; +import { uiSelector } from 'features/ui/store/uiSelectors'; +import { shouldShowImageParametersChanged } from 'features/ui/store/uiSlice'; import { ChangeEvent } from 'react'; import { useTranslation } from 'react-i18next'; +const selector = createSelector( + [uiSelector, generationSelector], + (ui, generation) => { + const { isImageToImageEnabled } = generation; + const { shouldShowImageParameters } = ui; + return { + isImageToImageEnabled, + shouldShowImageParameters, + }; + }, + defaultSelectorOptions +); + export default function ImageToImageToggle() { - const isImageToImageEnabled = useAppSelector( - (state: RootState) => state.generation.isImageToImageEnabled - ); + const { isImageToImageEnabled, shouldShowImageParameters } = + useAppSelector(selector); const { t } = useTranslation(); const dispatch = useAppDispatch(); const handleChange = (e: ChangeEvent) => - dispatch(isImageToImageEnabledChanged(e.target.checked)); + dispatch(shouldShowImageParametersChanged(e.target.checked)); return ( - + diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx index ddfdb622ef..7d5a6f3422 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx @@ -70,7 +70,6 @@ const InitialImagePreview = () => { return ( { }} onDrop={handleDrop} > - {initialImage?.url && ( - - { - setIsLoaded(true); - }} - fallback={ - - - - } - /> - {isLoaded && } - - )} - {!initialImage?.url && } + + {initialImage?.url && ( + <> + { + setIsLoaded(true); + }} + fallback={ + + + + } + /> + {isLoaded && } + + )} + {!initialImage?.url && } + {!isImageToImageEnabled && ( - } + icon={} paddingX={0} paddingY={0} colorScheme="error" diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/ProcessButtons.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/ProcessButtons.tsx index ba8522f0bf..2b4399866e 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/ProcessButtons.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/ProcessButtons.tsx @@ -4,6 +4,8 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import CancelButton from './CancelButton'; import InvokeButton from './InvokeButton'; import LoopbackButton from './Loopback'; +import IAICheckbox from 'common/components/IAICheckbox'; +import IAISwitch from 'common/components/IAISwitch'; /** * Buttons to start and cancel image generation. diff --git a/invokeai/frontend/web/src/features/parameters/components/ProgressImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/ProgressImagePreview.tsx index 4458f873db..09fad6acda 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProgressImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProgressImagePreview.tsx @@ -55,7 +55,6 @@ const ProgressImagePreview = () => { return ( <> - {' '} dispatch(setShouldShowProgressImages(!showProgressWindow)) diff --git a/invokeai/frontend/web/src/features/ui/components/CreateParametersDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/CreateParametersDrawer.tsx index 507c349471..a033ae3a05 100644 --- a/invokeai/frontend/web/src/features/ui/components/CreateParametersDrawer.tsx +++ b/invokeai/frontend/web/src/features/ui/components/CreateParametersDrawer.tsx @@ -1,6 +1,6 @@ import { isEqual } from 'lodash-es'; import ResizableDrawer from './common/ResizableDrawer/ResizableDrawer'; -import GenerateParameters from './tabs/Create/GenerateParameters'; +import CreateBaseSettings from './tabs/Create/CreateBaseSettings'; import { createSelector } from '@reduxjs/toolkit'; import { activeTabNameSelector, uiSelector } from '../store/uiSelectors'; import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; @@ -13,16 +13,26 @@ import { memo } from 'react'; import { Flex } from '@chakra-ui/react'; import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent'; import PinParametersPanelButton from './PinParametersPanelButton'; +import { Panel, PanelGroup } from 'react-resizable-panels'; +import CreateSidePanelPinned from './tabs/Create/CreateSidePanelPinned'; +import CreateTextParameters from './tabs/Create/CreateBaseSettings'; +import ResizeHandle from './tabs/ResizeHandle'; +import CreateImageSettings from './tabs/Create/CreateImageSettings'; const selector = createSelector( [uiSelector, activeTabNameSelector, lightboxSelector], (ui, activeTabName, lightbox) => { - const { shouldPinParametersPanel, shouldShowParametersPanel } = ui; + const { + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldShowImageParameters, + } = ui; const { isLightboxOpen } = lightbox; return { shouldPinParametersPanel, shouldShowParametersPanel, + shouldShowImageParameters, }; }, { @@ -34,8 +44,11 @@ const selector = createSelector( const CreateParametersPanel = () => { const dispatch = useAppDispatch(); - const { shouldPinParametersPanel, shouldShowParametersPanel } = - useAppSelector(selector); + const { + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldShowImageParameters, + } = useAppSelector(selector); const handleClosePanel = () => { dispatch(setShouldShowParametersPanel(false)); @@ -53,13 +66,7 @@ const CreateParametersPanel = () => { onClose={handleClosePanel} minWidth={500} > - + { - + + <> + + + + {shouldShowImageParameters && ( + <> + + + + + + )} + + ); diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index f548744c44..509272a2e4 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -39,13 +39,13 @@ import { configSelector } from 'features/system/store/configSelectors'; import { isEqual } from 'lodash-es'; import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel'; import Scrollable from './common/Scrollable'; -import GenerateParameters from './tabs/Create/GenerateParameters'; +import CreateBaseSettings from './tabs/Create/CreateBaseSettings'; import PinParametersPanelButton from './PinParametersPanelButton'; import ParametersSlide from './common/ParametersSlide'; import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; -import CreateTabContent from './tabs/Create/GenerateContent'; +import CreateTabContent from './tabs/Create/CreateContent'; import ParametersPanel from './ParametersPanel'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import CreateTab from './tabs/Create/CreateTab'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateBaseSettings.tsx similarity index 97% rename from invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateParameters.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateBaseSettings.tsx index b03594eec6..1208f867b2 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateBaseSettings.tsx @@ -40,7 +40,7 @@ import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; import OverlayScrollable from '../../common/OverlayScrollable'; import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel'; -const GenerateParameters = () => { +const CreateBaseSettings = () => { const { t } = useTranslation(); const generateAccordionItems: ParametersAccordionItems = useMemo( @@ -102,23 +102,22 @@ const GenerateParameters = () => { + - ); }; -export default memo(GenerateParameters); +export default memo(CreateBaseSettings); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateContent.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateContent.tsx similarity index 78% rename from invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateContent.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateContent.tsx index cd0c6a6142..bad7475c2e 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateContent.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateContent.tsx @@ -10,10 +10,16 @@ const CreateTabContent = () => { width: '100%', height: '100%', borderRadius: 'base', - bg: 'base.850', + // bg: 'base.850', }} > - + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateImageSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateImageSettings.tsx new file mode 100644 index 0000000000..ac80ae77f3 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateImageSettings.tsx @@ -0,0 +1,53 @@ +import { memo } from 'react'; +import OverlayScrollable from '../../common/OverlayScrollable'; +import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings'; +import { + Box, + ButtonGroup, + Collapse, + Flex, + Heading, + HStack, + Image, + Spacer, + useDisclosure, + VStack, +} from '@chakra-ui/react'; +import { motion } from 'framer-motion'; + +import IAIButton from 'common/components/IAIButton'; +import ImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageFit'; +import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength'; +import IAIIconButton from 'common/components/IAIIconButton'; + +import { useTranslation } from 'react-i18next'; +import { useState } from 'react'; +import { FaUndo, FaUpload } from 'react-icons/fa'; +import ImagePromptHeading from 'common/components/ImageToImageSettingsHeader'; +import InitialImagePreview from 'features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview'; + +const CreateImageSettings = () => { + return ( + + + + + + + + + ); +}; + +export default memo(CreateImageSettings); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateSidePanelPinned.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateSidePanelPinned.tsx new file mode 100644 index 0000000000..b17237d46e --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateSidePanelPinned.tsx @@ -0,0 +1,69 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { uiSelector } from 'features/ui/store/uiSelectors'; +import { memo } from 'react'; +import { Panel } from 'react-resizable-panels'; +import CreateTextParameters from './CreateBaseSettings'; +import PinParametersPanelButton from '../../PinParametersPanelButton'; +import ResizeHandle from '../ResizeHandle'; +import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import CreateImageSettings from './CreateImageSettings'; + +const selector = createSelector( + uiSelector, + (ui) => { + const { + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldShowImageParameters, + } = ui; + + return { + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldShowImageParameters, + }; + }, + defaultSelectorOptions +); + +const CreateSidePanelPinned = () => { + const dispatch = useAppDispatch(); + const { + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldShowImageParameters, + } = useAppSelector(selector); + return ( + <> + + + + + {shouldShowImageParameters && ( + <> + + + + + + )} + + + ); +}; + +export default memo(CreateSidePanelPinned); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateTab.tsx index b6236386d6..9bd11d828b 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateTab.tsx @@ -1,17 +1,20 @@ import { Portal, TabPanel } from '@chakra-ui/react'; import { memo } from 'react'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; -import GenerateParameters from './GenerateParameters'; +import CreateBaseSettings from './CreateBaseSettings'; import PinParametersPanelButton from '../../PinParametersPanelButton'; import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; import { createSelector } from '@reduxjs/toolkit'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; -import CreateTabContent from './GenerateContent'; +import CreateTabContent from './CreateContent'; import ResizeHandle from '../ResizeHandle'; import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel'; import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings'; +import CreateSidePanelPinned from './CreateSidePanelPinned'; +import CreateTextParameters from './CreateBaseSettings'; +import CreateImageSettings from './CreateImageSettings'; const selector = createSelector(uiSelector, (ui) => { const { @@ -19,6 +22,7 @@ const selector = createSelector(uiSelector, (ui) => { shouldShowGallery, shouldPinParametersPanel, shouldShowParametersPanel, + shouldShowImageParameters, } = ui; return { @@ -26,6 +30,7 @@ const selector = createSelector(uiSelector, (ui) => { shouldShowGallery, shouldPinParametersPanel, shouldShowParametersPanel, + shouldShowImageParameters, }; }); @@ -36,44 +41,49 @@ const CreateTab = () => { shouldShowGallery, shouldPinParametersPanel, shouldShowParametersPanel, + shouldShowImageParameters, } = useAppSelector(selector); return ( {shouldPinParametersPanel && shouldShowParametersPanel && ( <> - + - - - )} - {shouldPinParametersPanel && shouldShowParametersPanel && ( - <> - - - + {shouldShowImageParameters && ( + <> + + + + + + )} )} { dispatch(requestCanvasRescale()); @@ -84,7 +94,7 @@ const CreateTab = () => { {shouldPinGallery && shouldShowGallery && ( <> - + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx index ebbf5cb5be..a49836e027 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx @@ -1,8 +1,8 @@ import { Box, Flex } from '@chakra-ui/react'; import { useAppSelector } from 'app/store/storeHooks'; import { memo } from 'react'; -import CreateTabContent from './GenerateContent'; -import GenerateParameters from './GenerateParameters'; +import CreateTabContent from './CreateContent'; +import CreateBaseSettings from './CreateBaseSettings'; import PinParametersPanelButton from '../../PinParametersPanelButton'; import { RootState } from 'app/store/store'; import Scrollable from '../../common/Scrollable'; @@ -35,7 +35,7 @@ const GenerateWorkspace = () => { }} > - + { ) : ( - + )} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx index d754cca45e..c3f82a6b7c 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx @@ -37,6 +37,7 @@ const NodesTab = () => { return ( diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx index 02f2486fbe..e75a4fa88a 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx @@ -8,7 +8,7 @@ const ResizeHandle = () => { - + ); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx index 382bfd8105..65122af536 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx @@ -42,6 +42,7 @@ const UnifiedCanvasTab = () => { return ( diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index c5d3442f38..c4e2ad367c 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -24,6 +24,7 @@ export const initialUIState: UIState = { floatingProgressImageRect: { x: 0, y: 0, width: 0, height: 0 }, shouldShowProgressImages: false, shouldAutoShowProgressImages: false, + shouldShowImageParameters: false, }; export const uiSlice = createSlice({ @@ -136,6 +137,12 @@ export const uiSlice = createSlice({ ) => { state.shouldAutoShowProgressImages = action.payload; }, + shouldShowImageParametersChanged: ( + state, + action: PayloadAction + ) => { + state.shouldShowImageParameters = action.payload; + }, }, }); @@ -163,6 +170,7 @@ export const { floatingProgressImageResized, setShouldShowProgressImages, setShouldAutoShowProgressImages, + shouldShowImageParametersChanged, } = uiSlice.actions; export default uiSlice.reducer; diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts index bdcf0a3c30..3e2a3a1616 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts @@ -32,4 +32,5 @@ export interface UIState { floatingProgressImageRect: Rect; shouldShowProgressImages: boolean; shouldAutoShowProgressImages: boolean; + shouldShowImageParameters: boolean; } From 33c69359c29ef5107ad566bfe099628e87397824 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 8 May 2023 19:05:43 +1000 Subject: [PATCH 41/66] feat(ui): add IAICollapse for parameters --- invokeai/frontend/web/public/locales/en.json | 5 +- .../listeners/userInvokedCreate.ts | 2 +- .../web/src/common/components/IAICollapse.tsx | 61 +++++++++ .../{SeamBlur.tsx => ParamSeamBlur.tsx} | 2 +- .../{SeamSize.tsx => ParamSeamSize.tsx} | 2 +- .../{SeamSteps.tsx => ParamSeamSteps.tsx} | 2 +- ...SeamStrength.tsx => ParamSeamStrength.tsx} | 2 +- .../SeamCorrection/SeamCorrectionSettings.tsx | 16 +-- .../Output/ImageToImageOutputSettings.tsx | 12 -- .../Output/OutputSettings.tsx | 15 --- .../Output/SymmetrySettings.tsx | 54 -------- .../AdvancedParameters/Seed/SeedSettings.tsx | 20 --- .../Variations/GenerateVariations.tsx | 25 ---- .../Variations/VariationsSettings.tsx | 17 --- .../MainParameters/MainSettings.tsx | 39 +++--- .../Parameters/Hires/ParamHiresCollapse.tsx | 37 ++++++ .../Parameters/Hires/ParamHiresHeight.tsx | 3 + .../Parameters/Hires/ParamHiresSteps.tsx | 3 + .../Hires/ParamHiresStrength.tsx} | 35 +---- .../Parameters/Hires/ParamHiresToggle.tsx | 31 +++++ .../Parameters/Hires/ParamHiresWidth.tsx | 3 + .../Parameters/Noise/ParamNoiseCollapse.tsx | 37 ++++++ .../Noise/ParamNoiseThreshold.tsx} | 2 +- .../Noise/ParamPerlinNoise.tsx} | 2 +- .../components/Parameters/OtherSettings.tsx | 18 +++ .../ParamCFGScale.tsx} | 4 +- .../ParamHeight.tsx} | 4 +- .../ParamIterations.tsx} | 4 +- .../ParamNegativeConditioning.tsx} | 4 +- .../ParamPositiveConditioning.tsx} | 4 +- .../ParamScheduler.tsx} | 4 +- .../ParamSteps.tsx} | 4 +- .../ParamWidth.tsx} | 4 +- .../Seamless/ParamSeamlessCollapse.tsx | 51 ++++++++ .../Seamless/ParamSeamlessToggle.tsx} | 4 +- .../Seamless/ParamSeamlessXAxis.tsx | 43 ++++++ .../Seamless/ParamSeamlessYAxis.tsx | 43 ++++++ .../Seed/ParamSeed.tsx} | 44 +++---- .../Parameters/Seed/ParamSeedCollapse.tsx | 48 +++++++ .../Seed/ParamSeedRandomize.tsx} | 36 +++-- .../Seed/ParamSeedShuffle.tsx} | 19 +-- .../Symmetry/ParamSymmetryCollapse.tsx | 37 ++++++ .../Symmetry/ParamSymmetryHorizontal.tsx | 32 +++++ .../Symmetry/ParamSymmetryToggle.tsx} | 2 +- .../Symmetry/ParamSymmetryVertical.tsx | 32 +++++ .../Variations/ParamVariationAmount.tsx} | 2 +- .../Variations/ParamVariationCollapse.tsx | 37 ++++++ .../Variations/ParamVariationWeights.tsx} | 2 +- .../parameters/store/generationSlice.ts | 26 +++- .../features/parameters/store/hiresSlice.ts | 98 ++++++++++++++ .../ui/components/CreateParametersDrawer.tsx | 8 +- .../FloatingParametersPanelButtons.tsx | 2 +- .../src/features/ui/components/InvokeTabs.tsx | 64 +++++---- .../tabs/Create/CreateBaseSettings.tsx | 123 ------------------ .../tabs/Create/GenerateWorkspace.tsx | 55 -------- .../ui/components/tabs/ResizeHandle.tsx | 33 ++++- .../UnifiedCanvas/UnifiedCanvasParameters.tsx | 62 +++++---- .../CreateTab.tsx => image/ImageTab.tsx} | 46 ++----- .../ImageTabSettings.tsx} | 0 .../ui/components/tabs/text/TextTab.tsx | 78 +++++++++++ .../TextTabMain.tsx} | 4 +- .../tabs/text/TextTabParameters.tsx | 108 +++++++++++++++ .../TextTabSettingsPinned.tsx} | 4 +- .../web/src/features/ui/store/tabMap.ts | 6 +- .../web/src/features/ui/store/uiSlice.ts | 16 ++- .../web/src/features/ui/store/uiTypes.ts | 6 +- 66 files changed, 1076 insertions(+), 572 deletions(-) create mode 100644 invokeai/frontend/web/src/common/components/IAICollapse.tsx rename invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/{SeamBlur.tsx => ParamSeamBlur.tsx} (95%) rename invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/{SeamSize.tsx => ParamSeamSize.tsx} (95%) rename invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/{SeamSteps.tsx => ParamSeamSteps.tsx} (95%) rename invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/{SeamStrength.tsx => ParamSeamStrength.tsx} (94%) delete mode 100644 invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/ImageToImageOutputSettings.tsx delete mode 100644 invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/OutputSettings.tsx delete mode 100644 invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/SymmetrySettings.tsx delete mode 100644 invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/SeedSettings.tsx delete mode 100644 invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/GenerateVariations.tsx delete mode 100644 invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/VariationsSettings.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresCollapse.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresHeight.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresSteps.tsx rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters/Output/HiresSettings.tsx => Parameters/Hires/ParamHiresStrength.tsx} (61%) create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresToggle.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresWidth.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseCollapse.tsx rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters/Seed/Threshold.tsx => Parameters/Noise/ParamNoiseThreshold.tsx} (94%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters/Seed/Perlin.tsx => Parameters/Noise/ParamPerlinNoise.tsx} (94%) create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/OtherSettings.tsx rename invokeai/frontend/web/src/features/parameters/components/{MainParameters/MainCFGScale.tsx => Parameters/ParamCFGScale.tsx} (97%) rename invokeai/frontend/web/src/features/parameters/components/{MainParameters/HeightSlider.tsx => Parameters/ParamHeight.tsx} (96%) rename invokeai/frontend/web/src/features/parameters/components/{MainParameters/MainIterations.tsx => Parameters/ParamIterations.tsx} (96%) rename invokeai/frontend/web/src/features/parameters/components/{PromptInput/NegativePromptInput.tsx => Parameters/ParamNegativeConditioning.tsx} (91%) rename invokeai/frontend/web/src/features/parameters/components/{PromptInput/PromptInput.tsx => Parameters/ParamPositiveConditioning.tsx} (96%) rename invokeai/frontend/web/src/features/parameters/components/{MainParameters/MainSampler.tsx => Parameters/ParamScheduler.tsx} (92%) rename invokeai/frontend/web/src/features/parameters/components/{MainParameters/MainSteps.tsx => Parameters/ParamSteps.tsx} (97%) rename invokeai/frontend/web/src/features/parameters/components/{MainParameters/WidthSlider.tsx => Parameters/ParamWidth.tsx} (96%) create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse.tsx rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters/Output/SeamlessSettings.tsx => Parameters/Seamless/ParamSeamlessToggle.tsx} (91%) create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessXAxis.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessYAxis.tsx rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters/Seed/Seed.tsx => Parameters/Seed/ParamSeed.tsx} (54%) create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedCollapse.tsx rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters/Seed/RandomizeSeed.tsx => Parameters/Seed/ParamSeedRandomize.tsx} (64%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters/Seed/ShuffleSeed.tsx => Parameters/Seed/ParamSeedShuffle.tsx} (75%) create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryHorizontal.tsx rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters/Output/SymmetryToggle.tsx => Parameters/Symmetry/ParamSymmetryToggle.tsx} (91%) create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryVertical.tsx rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters/Variations/VariationAmount.tsx => Parameters/Variations/ParamVariationAmount.tsx} (95%) create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationCollapse.tsx rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters/Variations/SeedWeights.tsx => Parameters/Variations/ParamVariationWeights.tsx} (95%) create mode 100644 invokeai/frontend/web/src/features/parameters/store/hiresSlice.ts delete mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateBaseSettings.tsx delete mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx rename invokeai/frontend/web/src/features/ui/components/tabs/{Create/CreateTab.tsx => image/ImageTab.tsx} (53%) rename invokeai/frontend/web/src/features/ui/components/tabs/{Create/CreateImageSettings.tsx => image/ImageTabSettings.tsx} (100%) create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx rename invokeai/frontend/web/src/features/ui/components/tabs/{Create/CreateContent.tsx => text/TextTabMain.tsx} (89%) create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParameters.tsx rename invokeai/frontend/web/src/features/ui/components/tabs/{Create/CreateSidePanelPinned.tsx => text/TextTabSettingsPinned.tsx} (93%) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 751d56372f..fe104a762b 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -454,9 +454,10 @@ "seed": "Seed", "imageToImage": "Image to Image", "randomizeSeed": "Randomize Seed", - "shuffle": "Shuffle", + "shuffle": "Shuffle Seed", "noiseThreshold": "Noise Threshold", "perlinNoise": "Perlin Noise", + "noiseSettings": "Noise", "variations": "Variations", "variationAmount": "Variation Amount", "seedWeights": "Seed Weights", @@ -471,6 +472,8 @@ "scale": "Scale", "otherOptions": "Other Options", "seamlessTiling": "Seamless Tiling", + "seamlessXAxis": "X Axis", + "seamlessYAxis": "Y Axis", "hiresOptim": "High Res Optimization", "hiresStrength": "High Res Strength", "imageFit": "Fit Initial Image To Output Size", diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts index d24385ea97..f07aa4530e 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts @@ -10,7 +10,7 @@ const moduleLog = log.child({ namespace: 'invoke' }); export const addUserInvokedCreateListener = () => { startAppListening({ predicate: (action): action is ReturnType => - userInvoked.match(action) && action.payload === 'generate', + userInvoked.match(action) && action.payload === 'text', effect: (action, { getState, dispatch }) => { const state = getState(); diff --git a/invokeai/frontend/web/src/common/components/IAICollapse.tsx b/invokeai/frontend/web/src/common/components/IAICollapse.tsx new file mode 100644 index 0000000000..25d2dee56c --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAICollapse.tsx @@ -0,0 +1,61 @@ +import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; +import { Box, Collapse, Flex, Spacer, Switch } from '@chakra-ui/react'; +import { PropsWithChildren, memo } from 'react'; + +export type IAIToggleCollapseProps = PropsWithChildren & { + label: string; + isOpen: boolean; + onToggle: () => void; + withSwitch?: boolean; +}; + +const IAICollapse = (props: IAIToggleCollapseProps) => { + const { label, isOpen, onToggle, children, withSwitch = false } = props; + return ( + + + {label} + + {withSwitch && } + {!withSwitch && ( + + )} + + + + {children} + + + + ); +}; + +export default memo(IAICollapse); diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamBlur.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamBlur.tsx similarity index 95% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamBlur.tsx rename to invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamBlur.tsx index 693313e606..5c20ba7a13 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamBlur.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamBlur.tsx @@ -4,7 +4,7 @@ import IAISlider from 'common/components/IAISlider'; import { setSeamBlur } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; -export default function SeamBlur() { +export default function ParamSeamBlur() { const dispatch = useAppDispatch(); const seamBlur = useAppSelector( (state: RootState) => state.generation.seamBlur diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamSize.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamSize.tsx similarity index 95% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamSize.tsx rename to invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamSize.tsx index 02403ac5ec..8e56cded7b 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamSize.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamSize.tsx @@ -4,7 +4,7 @@ import IAISlider from 'common/components/IAISlider'; import { setSeamSize } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; -export default function SeamSize() { +export default function ParamSeamSize() { const dispatch = useAppDispatch(); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamSteps.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamSteps.tsx similarity index 95% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamSteps.tsx rename to invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamSteps.tsx index 0319b26820..8ca5226621 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamSteps.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamSteps.tsx @@ -4,7 +4,7 @@ import IAISlider from 'common/components/IAISlider'; import { setSeamSteps } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; -export default function SeamSteps() { +export default function ParamSeamSteps() { const { t } = useTranslation(); const seamSteps = useAppSelector( (state: RootState) => state.generation.seamSteps diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamStrength.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamStrength.tsx similarity index 94% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamStrength.tsx rename to invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamStrength.tsx index 7d447cfda1..de74156cd3 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamStrength.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamStrength.tsx @@ -4,7 +4,7 @@ import IAISlider from 'common/components/IAISlider'; import { setSeamStrength } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; -export default function SeamStrength() { +export default function ParamSeamStrength() { const dispatch = useAppDispatch(); const { t } = useTranslation(); const seamStrength = useAppSelector( diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamCorrectionSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamCorrectionSettings.tsx index 176dfe1590..a49eac26a1 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamCorrectionSettings.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamCorrectionSettings.tsx @@ -1,16 +1,16 @@ import { VStack } from '@chakra-ui/react'; -import SeamBlur from './SeamBlur'; -import SeamSize from './SeamSize'; -import SeamSteps from './SeamSteps'; -import SeamStrength from './SeamStrength'; +import ParamSeamBlur from './ParamSeamBlur'; +import ParamSeamSize from './ParamSeamSize'; +import ParamSeamSteps from './ParamSeamSteps'; +import ParamSeamStrength from './ParamSeamStrength'; const SeamCorrectionSettings = () => { return ( - - - - + + + + ); }; diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/ImageToImageOutputSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/ImageToImageOutputSettings.tsx deleted file mode 100644 index c2dea1cbf8..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/ImageToImageOutputSettings.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { VStack } from '@chakra-ui/react'; -import SeamlessSettings from './SeamlessSettings'; - -const ImageToImageOutputSettings = () => { - return ( - - - - ); -}; - -export default ImageToImageOutputSettings; diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/OutputSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/OutputSettings.tsx deleted file mode 100644 index 93ba63d065..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/OutputSettings.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { VStack } from '@chakra-ui/react'; -import { HiresStrength, HiresToggle } from './HiresSettings'; -import SeamlessSettings from './SeamlessSettings'; - -const OutputSettings = () => { - return ( - - - - - - ); -}; - -export default OutputSettings; diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/SymmetrySettings.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/SymmetrySettings.tsx deleted file mode 100644 index 21e014b715..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/SymmetrySettings.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { VStack } from '@chakra-ui/react'; -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISlider from 'common/components/IAISlider'; -import { - setHorizontalSymmetrySteps, - setVerticalSymmetrySteps, -} from 'features/parameters/store/generationSlice'; -import { useTranslation } from 'react-i18next'; - -export default function SymmetrySettings() { - const horizontalSymmetrySteps = useAppSelector( - (state: RootState) => state.generation.horizontalSymmetrySteps - ); - - const verticalSymmetrySteps = useAppSelector( - (state: RootState) => state.generation.verticalSymmetrySteps - ); - - const steps = useAppSelector((state: RootState) => state.generation.steps); - - const dispatch = useAppDispatch(); - - const { t } = useTranslation(); - - return ( - - dispatch(setHorizontalSymmetrySteps(v))} - min={0} - max={steps} - step={1} - withInput - withSliderMarks - withReset - handleReset={() => dispatch(setHorizontalSymmetrySteps(0))} - /> - dispatch(setVerticalSymmetrySteps(v))} - min={0} - max={steps} - step={1} - withInput - withSliderMarks - withReset - handleReset={() => dispatch(setVerticalSymmetrySteps(0))} - /> - - ); -} diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/SeedSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/SeedSettings.tsx deleted file mode 100644 index 576358d2e1..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/SeedSettings.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { VStack } from '@chakra-ui/react'; -import Perlin from './Perlin'; -import RandomizeSeed from './RandomizeSeed'; -import Seed from './Seed'; -import Threshold from './Threshold'; - -/** - * Seed & variation options. Includes iteration, seed, seed randomization, variation options. - */ -const SeedSettings = () => { - return ( - - - - - - ); -}; - -export default SeedSettings; diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/GenerateVariations.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/GenerateVariations.tsx deleted file mode 100644 index ec9a8ae276..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/GenerateVariations.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISwitch from 'common/components/IAISwitch'; -import { setShouldGenerateVariations } from 'features/parameters/store/generationSlice'; -import { ChangeEvent } from 'react'; - -export default function GenerateVariationsToggle() { - const shouldGenerateVariations = useAppSelector( - (state: RootState) => state.generation.shouldGenerateVariations - ); - - const dispatch = useAppDispatch(); - - const handleChangeShouldGenerateVariations = ( - e: ChangeEvent - ) => dispatch(setShouldGenerateVariations(e.target.checked)); - - return ( - - ); -} diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/VariationsSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/VariationsSettings.tsx deleted file mode 100644 index d3bc43f7ae..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/VariationsSettings.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { VStack } from '@chakra-ui/react'; -import SeedWeights from './SeedWeights'; -import VariationAmount from './VariationAmount'; - -/** - * Seed & variation options. Includes iteration, seed, seed randomization, variation options. - */ -const VariationsSettings = () => { - return ( - - - - - ); -}; - -export default VariationsSettings; diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSettings.tsx index db2701e0c9..d7f918e1f0 100644 --- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSettings.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSettings.tsx @@ -1,14 +1,15 @@ +import { memo } from 'react'; import { Box, Flex, VStack } from '@chakra-ui/react'; import { RootState } from 'app/store/store'; import { useAppSelector } from 'app/store/storeHooks'; + import ModelSelect from 'features/system/components/ModelSelect'; -import { memo } from 'react'; -import HeightSlider from './HeightSlider'; -import MainCFGScale from './MainCFGScale'; -import MainIterations from './MainIterations'; -import MainSampler from './MainSampler'; -import MainSteps from './MainSteps'; -import WidthSlider from './WidthSlider'; +import ParamHeight from 'features/parameters/components/Parameters/ParamHeight'; +import ParamCFGScale from 'features/parameters/components/Parameters/ParamCFGScale'; +import ParamIterations from 'features/parameters/components/Parameters/ParamIterations'; +import ParamScheduler from 'features/parameters/components/Parameters/ParamScheduler'; +import ParamSteps from 'features/parameters/components/Parameters/ParamSteps'; +import ParamWidth from 'features/parameters/components/Parameters/ParamWidth'; const MainSettings = () => { const shouldUseSliders = useAppSelector( @@ -17,14 +18,14 @@ const MainSettings = () => { return shouldUseSliders ? ( - - - - - + + + + + - + @@ -34,15 +35,15 @@ const MainSettings = () => { ) : ( - - - + + + - - + + - + diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresCollapse.tsx new file mode 100644 index 0000000000..06ecd6762b --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresCollapse.tsx @@ -0,0 +1,37 @@ +import { Flex } from '@chakra-ui/react'; +import { useTranslation } from 'react-i18next'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { RootState } from 'app/store/store'; +import IAICollapse from 'common/components/IAICollapse'; +import { memo } from 'react'; +import { ParamHiresStrength } from './ParamHiresStrength'; +import { setHiresFix } from 'features/parameters/store/postprocessingSlice'; + +/** + * Seed & variation options. Includes iteration, seed, seed randomization, variation options. + */ +const ParamHiresCollapse = () => { + const { t } = useTranslation(); + const hiresFix = useAppSelector( + (state: RootState) => state.postprocessing.hiresFix + ); + + const dispatch = useAppDispatch(); + + const handleToggle = () => dispatch(setHiresFix(!hiresFix)); + + return ( + + + + + + ); +}; + +export default memo(ParamHiresCollapse); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresHeight.tsx new file mode 100644 index 0000000000..80a15f591b --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresHeight.tsx @@ -0,0 +1,3 @@ +// TODO + +export default {}; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresSteps.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresSteps.tsx new file mode 100644 index 0000000000..80a15f591b --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresSteps.tsx @@ -0,0 +1,3 @@ +// TODO + +export default {}; diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/HiresSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresStrength.tsx similarity index 61% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/HiresSettings.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresStrength.tsx index 7f20a1d6c3..2655841590 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/HiresSettings.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresStrength.tsx @@ -1,15 +1,9 @@ import { createSelector } from '@reduxjs/toolkit'; -import type { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAISlider from 'common/components/IAISlider'; -import IAISwitch from 'common/components/IAISwitch'; import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors'; -import { - setHiresFix, - setHiresStrength, -} from 'features/parameters/store/postprocessingSlice'; +import { setHiresStrength } from 'features/parameters/store/postprocessingSlice'; import { isEqual } from 'lodash-es'; -import { ChangeEvent } from 'react'; import { useTranslation } from 'react-i18next'; const hiresStrengthSelector = createSelector( @@ -22,7 +16,7 @@ const hiresStrengthSelector = createSelector( } ); -export const HiresStrength = () => { +export const ParamHiresStrength = () => { const { hiresFix, hiresStrength } = useAppSelector(hiresStrengthSelector); const dispatch = useAppDispatch(); @@ -55,28 +49,3 @@ export const HiresStrength = () => { /> ); }; - -/** - * Hires Fix Toggle - */ -export const HiresToggle = () => { - const dispatch = useAppDispatch(); - - const hiresFix = useAppSelector( - (state: RootState) => state.postprocessing.hiresFix - ); - - const { t } = useTranslation(); - - const handleChangeHiresFix = (e: ChangeEvent) => - dispatch(setHiresFix(e.target.checked)); - - return ( - - ); -}; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresToggle.tsx new file mode 100644 index 0000000000..0fc600e9e8 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresToggle.tsx @@ -0,0 +1,31 @@ +import type { RootState } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAISwitch from 'common/components/IAISwitch'; +import { setHiresFix } from 'features/parameters/store/postprocessingSlice'; +import { ChangeEvent } from 'react'; +import { useTranslation } from 'react-i18next'; + +/** + * Hires Fix Toggle + */ +export const ParamHiresToggle = () => { + const dispatch = useAppDispatch(); + + const hiresFix = useAppSelector( + (state: RootState) => state.postprocessing.hiresFix + ); + + const { t } = useTranslation(); + + const handleChangeHiresFix = (e: ChangeEvent) => + dispatch(setHiresFix(e.target.checked)); + + return ( + + ); +}; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresWidth.tsx new file mode 100644 index 0000000000..80a15f591b --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresWidth.tsx @@ -0,0 +1,3 @@ +// TODO + +export default {}; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseCollapse.tsx new file mode 100644 index 0000000000..30947e9709 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseCollapse.tsx @@ -0,0 +1,37 @@ +import { useTranslation } from 'react-i18next'; +import { Flex } from '@chakra-ui/react'; +import IAICollapse from 'common/components/IAICollapse'; +import ParamPerlinNoise from './ParamPerlinNoise'; +import ParamNoiseThreshold from './ParamNoiseThreshold'; +import { RootState } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { setShouldUseNoiseSettings } from 'features/parameters/store/generationSlice'; +import { memo } from 'react'; + +const ParamNoiseCollapse = () => { + const { t } = useTranslation(); + const shouldUseNoiseSettings = useAppSelector( + (state: RootState) => state.generation.shouldUseNoiseSettings + ); + + const dispatch = useAppDispatch(); + + const handleToggle = () => + dispatch(setShouldUseNoiseSettings(!shouldUseNoiseSettings)); + + return ( + + + + + + + ); +}; + +export default memo(ParamNoiseCollapse); diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/Threshold.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseThreshold.tsx similarity index 94% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/Threshold.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseThreshold.tsx index 14ca46b53c..e339734992 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/Threshold.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseThreshold.tsx @@ -4,7 +4,7 @@ import IAISlider from 'common/components/IAISlider'; import { setThreshold } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; -export default function Threshold() { +export default function ParamNoiseThreshold() { const dispatch = useAppDispatch(); const threshold = useAppSelector( (state: RootState) => state.generation.threshold diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/Perlin.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamPerlinNoise.tsx similarity index 94% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/Perlin.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamPerlinNoise.tsx index d2f4ea4249..ad710eae54 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/Perlin.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamPerlinNoise.tsx @@ -4,7 +4,7 @@ import IAISlider from 'common/components/IAISlider'; import { setPerlin } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; -export default function Perlin() { +export default function ParamPerlinNoise() { const dispatch = useAppDispatch(); const perlin = useAppSelector((state: RootState) => state.generation.perlin); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/OtherSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/OtherSettings.tsx new file mode 100644 index 0000000000..b41b0690e1 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/OtherSettings.tsx @@ -0,0 +1,18 @@ +import { VStack } from '@chakra-ui/react'; +import ParamSeamlessToggle from './Seamless/ParamSeamlessToggle'; +// import ParamSeamlessAxes from '../../Parameters/Seamless/ParamSeamlessAxes'; +import { ParamHiresToggle } from './Hires/ParamHiresToggle'; +import { ParamHiresStrength } from './Hires/ParamHiresStrength'; + +const OtherSettings = () => { + return ( + + + {/* */} + + + + ); +}; + +export default OtherSettings; diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainCFGScale.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamCFGScale.tsx similarity index 97% rename from invokeai/frontend/web/src/features/parameters/components/MainParameters/MainCFGScale.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ParamCFGScale.tsx index 928cccafd1..111e3d3ae8 100644 --- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainCFGScale.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamCFGScale.tsx @@ -30,7 +30,7 @@ const selector = createSelector( } ); -const GuidanceScale = () => { +const ParamCFGScale = () => { const { cfgScale, initial, @@ -82,4 +82,4 @@ const GuidanceScale = () => { ); }; -export default memo(GuidanceScale); +export default memo(ParamCFGScale); diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/HeightSlider.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamHeight.tsx similarity index 96% rename from invokeai/frontend/web/src/features/parameters/components/MainParameters/HeightSlider.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ParamHeight.tsx index 35e97fb266..9ba1aeaaf8 100644 --- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/HeightSlider.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamHeight.tsx @@ -31,7 +31,7 @@ const selector = createSelector( } ); -const HeightSlider = () => { +const ParamHeight = () => { const { height, initial, @@ -74,4 +74,4 @@ const HeightSlider = () => { ); }; -export default memo(HeightSlider); +export default memo(ParamHeight); diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainIterations.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamIterations.tsx similarity index 96% rename from invokeai/frontend/web/src/features/parameters/components/MainParameters/MainIterations.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ParamIterations.tsx index d1d142d7ff..5a5b782c04 100644 --- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainIterations.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamIterations.tsx @@ -32,7 +32,7 @@ const selector = createSelector( } ); -const MainIterations = () => { +const ParamIterations = () => { const { iterations, initial, @@ -83,4 +83,4 @@ const MainIterations = () => { ); }; -export default memo(MainIterations); +export default memo(ParamIterations); diff --git a/invokeai/frontend/web/src/features/parameters/components/PromptInput/NegativePromptInput.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamNegativeConditioning.tsx similarity index 91% rename from invokeai/frontend/web/src/features/parameters/components/PromptInput/NegativePromptInput.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ParamNegativeConditioning.tsx index ea3f12db42..d3790d4c24 100644 --- a/invokeai/frontend/web/src/features/parameters/components/PromptInput/NegativePromptInput.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamNegativeConditioning.tsx @@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setNegativePrompt } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; -const NegativePromptInput = () => { +const ParamNegativeConditioning = () => { const negativePrompt = useAppSelector( (state: RootState) => state.generation.negativePrompt ); @@ -29,4 +29,4 @@ const NegativePromptInput = () => { ); }; -export default NegativePromptInput; +export default ParamNegativeConditioning; diff --git a/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamPositiveConditioning.tsx similarity index 96% rename from invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ParamPositiveConditioning.tsx index eb0340b0ee..70e9b81957 100644 --- a/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamPositiveConditioning.tsx @@ -35,7 +35,7 @@ const promptInputSelector = createSelector( /** * Prompt input text area. */ -const PromptInput = () => { +const ParamPositiveConditioning = () => { const dispatch = useAppDispatch(); const { prompt, activeTabName } = useAppSelector(promptInputSelector); const { isReady } = useAppSelector(readinessSelector); @@ -88,4 +88,4 @@ const PromptInput = () => { ); }; -export default PromptInput; +export default ParamPositiveConditioning; diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSampler.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamScheduler.tsx similarity index 92% rename from invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSampler.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ParamScheduler.tsx index b71ff20e01..ef1574b64c 100644 --- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSampler.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamScheduler.tsx @@ -6,7 +6,7 @@ import { setSampler } from 'features/parameters/store/generationSlice'; import { ChangeEvent, memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -const Scheduler = () => { +const ParamScheduler = () => { const sampler = useAppSelector( (state: RootState) => state.generation.sampler ); @@ -29,4 +29,4 @@ const Scheduler = () => { ); }; -export default memo(Scheduler); +export default memo(ParamScheduler); diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSteps.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamSteps.tsx similarity index 97% rename from invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSteps.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ParamSteps.tsx index 43e399848e..f43cdd425b 100644 --- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSteps.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamSteps.tsx @@ -36,7 +36,7 @@ const selector = createSelector( } ); -const MainSteps = () => { +const ParamSteps = () => { const { steps, initial, min, sliderMax, inputMax, step, shouldUseSliders } = useAppSelector(selector); const dispatch = useAppDispatch(); @@ -84,4 +84,4 @@ const MainSteps = () => { ); }; -export default memo(MainSteps); +export default memo(ParamSteps); diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/WidthSlider.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamWidth.tsx similarity index 96% rename from invokeai/frontend/web/src/features/parameters/components/MainParameters/WidthSlider.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ParamWidth.tsx index 0b871245c7..e8deb2ba70 100644 --- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/WidthSlider.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamWidth.tsx @@ -30,7 +30,7 @@ const selector = createSelector( } ); -const WidthSlider = () => { +const ParamWidth = () => { const { width, initial, @@ -73,4 +73,4 @@ const WidthSlider = () => { ); }; -export default memo(WidthSlider); +export default memo(ParamWidth); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse.tsx new file mode 100644 index 0000000000..c8645d3607 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse.tsx @@ -0,0 +1,51 @@ +import { useTranslation } from 'react-i18next'; +import { Box, Flex } from '@chakra-ui/react'; +import IAICollapse from 'common/components/IAICollapse'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { setSeamless } from 'features/parameters/store/generationSlice'; +import { memo } from 'react'; +import { createSelector } from '@reduxjs/toolkit'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import ParamSeamlessXAxis from './ParamSeamlessXAxis'; +import ParamSeamlessYAxis from './ParamSeamlessYAxis'; + +const selector = createSelector( + generationSelector, + (generation) => { + const { shouldUseSeamless, seamlessXAxis, seamlessYAxis } = generation; + + return { shouldUseSeamless, seamlessXAxis, seamlessYAxis }; + }, + defaultSelectorOptions +); + +const ParamSeamlessCollapse = () => { + const { t } = useTranslation(); + const { shouldUseSeamless, seamlessXAxis, seamlessYAxis } = + useAppSelector(selector); + + const dispatch = useAppDispatch(); + + const handleToggle = () => dispatch(setSeamless(!shouldUseSeamless)); + + return ( + + + + + + + + + + + ); +}; + +export default memo(ParamSeamlessCollapse); diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/SeamlessSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessToggle.tsx similarity index 91% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/SeamlessSettings.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessToggle.tsx index fb333c6f00..1a3b046bcf 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/SeamlessSettings.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessToggle.tsx @@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next'; /** * Seamless tiling toggle */ -const SeamlessSettings = () => { +const ParamSeamlessToggle = () => { const dispatch = useAppDispatch(); const seamless = useAppSelector( @@ -30,4 +30,4 @@ const SeamlessSettings = () => { ); }; -export default SeamlessSettings; +export default ParamSeamlessToggle; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessXAxis.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessXAxis.tsx new file mode 100644 index 0000000000..31e2aced9c --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessXAxis.tsx @@ -0,0 +1,43 @@ +import { useTranslation } from 'react-i18next'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { ChangeEvent, memo, useCallback } from 'react'; +import { createSelector } from '@reduxjs/toolkit'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAISwitch from 'common/components/IAISwitch'; +import { setSeamlessXAxis } from 'features/parameters/store/generationSlice'; + +const selector = createSelector( + generationSelector, + (generation) => { + const { seamlessXAxis } = generation; + + return { seamlessXAxis }; + }, + defaultSelectorOptions +); + +const ParamSeamlessXAxis = () => { + const { t } = useTranslation(); + const { seamlessXAxis } = useAppSelector(selector); + + const dispatch = useAppDispatch(); + + const handleChange = useCallback( + (e: ChangeEvent) => { + dispatch(setSeamlessXAxis(e.target.checked)); + }, + [dispatch] + ); + + return ( + + ); +}; + +export default memo(ParamSeamlessXAxis); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessYAxis.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessYAxis.tsx new file mode 100644 index 0000000000..edd78443c7 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessYAxis.tsx @@ -0,0 +1,43 @@ +import { useTranslation } from 'react-i18next'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { ChangeEvent, memo, useCallback } from 'react'; +import { createSelector } from '@reduxjs/toolkit'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAISwitch from 'common/components/IAISwitch'; +import { setSeamlessYAxis } from 'features/parameters/store/generationSlice'; + +const selector = createSelector( + generationSelector, + (generation) => { + const { seamlessYAxis } = generation; + + return { seamlessYAxis }; + }, + defaultSelectorOptions +); + +const ParamSeamlessYAxis = () => { + const { t } = useTranslation(); + const { seamlessYAxis } = useAppSelector(selector); + + const dispatch = useAppDispatch(); + + const handleChange = useCallback( + (e: ChangeEvent) => { + dispatch(setSeamlessYAxis(e.target.checked)); + }, + [dispatch] + ); + + return ( + + ); +}; + +export default memo(ParamSeamlessYAxis); diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/Seed.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeed.tsx similarity index 54% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/Seed.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeed.tsx index 96c929a462..c5a00d32c8 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/Seed.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeed.tsx @@ -1,13 +1,14 @@ -import { HStack } from '@chakra-ui/react'; +import { Flex, HStack } from '@chakra-ui/react'; import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAINumberInput from 'common/components/IAINumberInput'; import { setSeed } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; -import ShuffleSeed from './ShuffleSeed'; +import ParamSeedShuffle from './ParamSeedShuffle'; +import ParamSeedRandomize from './ParamSeedRandomize'; -export default function Seed() { +export default function ParamSeed() { const seed = useAppSelector((state: RootState) => state.generation.seed); const shouldRandomizeSeed = useAppSelector( (state: RootState) => state.generation.shouldRandomizeSeed @@ -23,25 +24,22 @@ export default function Seed() { const handleChangeSeed = (v: number) => dispatch(setSeed(v)); return ( - - - - + ); } diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedCollapse.tsx new file mode 100644 index 0000000000..2867029f7e --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedCollapse.tsx @@ -0,0 +1,48 @@ +import { Flex } from '@chakra-ui/react'; +import ParamSeed from './ParamSeed'; +import { memo, useCallback } from 'react'; +import ParamSeedShuffle from './ParamSeedShuffle'; +import { useTranslation } from 'react-i18next'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { createSelector } from '@reduxjs/toolkit'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { setShouldRandomizeSeed } from 'features/parameters/store/generationSlice'; +import IAICollapse from 'common/components/IAICollapse'; + +const selector = createSelector( + generationSelector, + (generation) => { + const { shouldRandomizeSeed } = generation; + + return { shouldRandomizeSeed }; + }, + defaultSelectorOptions +); + +const ParamSeedSettings = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const { shouldRandomizeSeed } = useAppSelector(selector); + + const handleToggle = useCallback( + () => dispatch(setShouldRandomizeSeed(!shouldRandomizeSeed)), + [dispatch, shouldRandomizeSeed] + ); + + return ( + + + + + + + ); +}; + +export default memo(ParamSeedSettings); diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/RandomizeSeed.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedRandomize.tsx similarity index 64% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/RandomizeSeed.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedRandomize.tsx index ea60124f74..13380f3660 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/RandomizeSeed.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedRandomize.tsx @@ -5,7 +5,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAISwitch from 'common/components/IAISwitch'; import { setShouldRandomizeSeed } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; -import { Switch } from '@chakra-ui/react'; +import { FormControl, FormLabel, Switch } from '@chakra-ui/react'; // export default function RandomizeSeed() { // const dispatch = useAppDispatch(); @@ -27,7 +27,7 @@ import { Switch } from '@chakra-ui/react'; // ); // } -const SeedToggle = () => { +const ParamSeedRandomize = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -36,15 +36,33 @@ const SeedToggle = () => { ); const handleChangeShouldRandomizeSeed = (e: ChangeEvent) => - dispatch(setShouldRandomizeSeed(!e.target.checked)); + dispatch(setShouldRandomizeSeed(e.target.checked)); return ( - + + + {t('parameters.randomizeSeed')} + + + ); }; -export default memo(SeedToggle); +export default memo(ParamSeedRandomize); diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/ShuffleSeed.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedShuffle.tsx similarity index 75% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/ShuffleSeed.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedShuffle.tsx index f2d222de7c..9dba81104c 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/ShuffleSeed.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedShuffle.tsx @@ -1,14 +1,15 @@ -import { Button } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIButton from 'common/components/IAIButton'; import IAIIconButton from 'common/components/IAIIconButton'; import randomInt from 'common/util/randomInt'; import { setSeed } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; import { FaRandom } from 'react-icons/fa'; -export default function ShuffleSeed() { +export default function ParamSeedShuffle() { const dispatch = useAppDispatch(); const shouldRandomizeSeed = useAppSelector( (state: RootState) => state.generation.shouldRandomizeSeed @@ -19,20 +20,14 @@ export default function ShuffleSeed() { dispatch(setSeed(randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX))); return ( - } onClick={handleClickRandomizeSeed} - /> - // + > + {t('parameters.shuffle')} + ); } diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse.tsx new file mode 100644 index 0000000000..97c51d4461 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse.tsx @@ -0,0 +1,37 @@ +import { memo } from 'react'; +import { Flex } from '@chakra-ui/react'; +import ParamSymmetryHorizontal from './ParamSymmetryHorizontal'; +import ParamSymmetryVertical from './ParamSymmetryVertical'; + +import { useTranslation } from 'react-i18next'; +import IAICollapse from 'common/components/IAICollapse'; +import { RootState } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { setShouldUseSymmetry } from 'features/parameters/store/generationSlice'; + +const ParamSymmetryCollapse = () => { + const { t } = useTranslation(); + const shouldUseSymmetry = useAppSelector( + (state: RootState) => state.generation.shouldUseSymmetry + ); + + const dispatch = useAppDispatch(); + + const handleToggle = () => dispatch(setShouldUseSymmetry(!shouldUseSymmetry)); + + return ( + + + + + + + ); +}; + +export default memo(ParamSymmetryCollapse); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryHorizontal.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryHorizontal.tsx new file mode 100644 index 0000000000..99af147f2c --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryHorizontal.tsx @@ -0,0 +1,32 @@ +import { RootState } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAISlider from 'common/components/IAISlider'; +import { setHorizontalSymmetrySteps } from 'features/parameters/store/generationSlice'; +import { useTranslation } from 'react-i18next'; + +export default function ParamSymmetryHorizontal() { + const horizontalSymmetrySteps = useAppSelector( + (state: RootState) => state.generation.horizontalSymmetrySteps + ); + + const steps = useAppSelector((state: RootState) => state.generation.steps); + + const dispatch = useAppDispatch(); + + const { t } = useTranslation(); + + return ( + dispatch(setHorizontalSymmetrySteps(v))} + min={0} + max={steps} + step={1} + withInput + withSliderMarks + withReset + handleReset={() => dispatch(setHorizontalSymmetrySteps(0))} + /> + ); +} diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/SymmetryToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryToggle.tsx similarity index 91% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/SymmetryToggle.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryToggle.tsx index c155336c1e..7cc17c045e 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Output/SymmetryToggle.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryToggle.tsx @@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAISwitch from 'common/components/IAISwitch'; import { setShouldUseSymmetry } from 'features/parameters/store/generationSlice'; -export default function SymmetryToggle() { +export default function ParamSymmetryToggle() { const shouldUseSymmetry = useAppSelector( (state: RootState) => state.generation.shouldUseSymmetry ); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryVertical.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryVertical.tsx new file mode 100644 index 0000000000..c8ddb46a3a --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryVertical.tsx @@ -0,0 +1,32 @@ +import { RootState } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAISlider from 'common/components/IAISlider'; +import { setVerticalSymmetrySteps } from 'features/parameters/store/generationSlice'; +import { useTranslation } from 'react-i18next'; + +export default function ParamSymmetryVertical() { + const verticalSymmetrySteps = useAppSelector( + (state: RootState) => state.generation.verticalSymmetrySteps + ); + + const steps = useAppSelector((state: RootState) => state.generation.steps); + + const dispatch = useAppDispatch(); + + const { t } = useTranslation(); + + return ( + dispatch(setVerticalSymmetrySteps(v))} + min={0} + max={steps} + step={1} + withInput + withSliderMarks + withReset + handleReset={() => dispatch(setVerticalSymmetrySteps(0))} + /> + ); +} diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/VariationAmount.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationAmount.tsx similarity index 95% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/VariationAmount.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationAmount.tsx index 21b5001d6a..9bc84a0bf4 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/VariationAmount.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationAmount.tsx @@ -4,7 +4,7 @@ import IAISlider from 'common/components/IAISlider'; import { setVariationAmount } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; -export default function VariationAmount() { +export default function ParamVariationAmount() { const variationAmount = useAppSelector( (state: RootState) => state.generation.variationAmount ); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationCollapse.tsx new file mode 100644 index 0000000000..0e1134e9f0 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationCollapse.tsx @@ -0,0 +1,37 @@ +import ParamVariationWeights from './ParamVariationWeights'; +import ParamVariationAmount from './ParamVariationAmount'; +import { useTranslation } from 'react-i18next'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { RootState } from 'app/store/store'; +import { setShouldGenerateVariations } from 'features/parameters/store/generationSlice'; +import { Flex } from '@chakra-ui/react'; +import IAICollapse from 'common/components/IAICollapse'; +import { memo } from 'react'; + +const ParamVariationCollapse = () => { + const { t } = useTranslation(); + const shouldGenerateVariations = useAppSelector( + (state: RootState) => state.generation.shouldGenerateVariations + ); + + const dispatch = useAppDispatch(); + + const handleToggle = () => + dispatch(setShouldGenerateVariations(!shouldGenerateVariations)); + + return ( + + + + + + + ); +}; + +export default memo(ParamVariationCollapse); diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/SeedWeights.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationWeights.tsx similarity index 95% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/SeedWeights.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationWeights.tsx index 7f8b096757..30876597a8 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Variations/SeedWeights.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationWeights.tsx @@ -6,7 +6,7 @@ import { setSeedWeights } from 'features/parameters/store/generationSlice'; import { ChangeEvent } from 'react'; import { useTranslation } from 'react-i18next'; -export default function SeedWeights() { +export default function ParamVariationWeights() { const seedWeights = useAppSelector( (state: RootState) => state.generation.seedWeights ); diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 51627ce24c..89891e96aa 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -20,7 +20,6 @@ export interface GenerationState { negativePrompt: string; sampler: string; seamBlur: number; - seamless: boolean; seamSize: number; seamSteps: number; seamStrength: number; @@ -29,6 +28,7 @@ export interface GenerationState { shouldFitToWidthHeight: boolean; shouldGenerateVariations: boolean; shouldRandomizeSeed: boolean; + shouldUseNoiseSettings: boolean; steps: number; threshold: number; tileSize: number; @@ -39,6 +39,9 @@ export interface GenerationState { verticalSymmetrySteps: number; isImageToImageEnabled: boolean; model: string; + shouldUseSeamless: boolean; + seamlessXAxis: boolean; + seamlessYAxis: boolean; } export const initialGenerationState: GenerationState = { @@ -53,7 +56,6 @@ export const initialGenerationState: GenerationState = { negativePrompt: '', sampler: 'k_lms', seamBlur: 16, - seamless: false, seamSize: 96, seamSteps: 30, seamStrength: 0.7, @@ -62,6 +64,7 @@ export const initialGenerationState: GenerationState = { shouldFitToWidthHeight: true, shouldGenerateVariations: false, shouldRandomizeSeed: true, + shouldUseNoiseSettings: false, steps: 50, threshold: 0, tileSize: 32, @@ -72,6 +75,9 @@ export const initialGenerationState: GenerationState = { verticalSymmetrySteps: 0, isImageToImageEnabled: false, model: '', + shouldUseSeamless: false, + seamlessXAxis: true, + seamlessYAxis: true, }; const initialState: GenerationState = initialGenerationState; @@ -146,7 +152,13 @@ export const generationSlice = createSlice({ state.maskPath = action.payload; }, setSeamless: (state, action: PayloadAction) => { - state.seamless = action.payload; + state.shouldUseSeamless = action.payload; + }, + setSeamlessXAxis: (state, action: PayloadAction) => { + state.seamlessXAxis = action.payload; + }, + setSeamlessYAxis: (state, action: PayloadAction) => { + state.seamlessYAxis = action.payload; }, setShouldFitToWidthHeight: (state, action: PayloadAction) => { state.shouldFitToWidthHeight = action.payload; @@ -348,6 +360,9 @@ export const generationSlice = createSlice({ setVerticalSymmetrySteps: (state, action: PayloadAction) => { state.verticalSymmetrySteps = action.payload; }, + setShouldUseNoiseSettings: (state, action: PayloadAction) => { + state.shouldUseNoiseSettings = action.payload; + }, initialImageChanged: (state, action: PayloadAction) => { state.initialImage = action.payload; state.isImageToImageEnabled = true; @@ -382,7 +397,6 @@ export const { setNegativePrompt, setSampler, setSeamBlur, - setSeamless, setSeamSize, setSeamSteps, setSeamStrength, @@ -402,6 +416,10 @@ export const { initialImageChanged, isImageToImageEnabledChanged, modelSelected, + setShouldUseNoiseSettings, + setSeamless, + setSeamlessXAxis, + setSeamlessYAxis, } = generationSlice.actions; export default generationSlice.reducer; diff --git a/invokeai/frontend/web/src/features/parameters/store/hiresSlice.ts b/invokeai/frontend/web/src/features/parameters/store/hiresSlice.ts new file mode 100644 index 0000000000..15098afed5 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/store/hiresSlice.ts @@ -0,0 +1,98 @@ +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; +import { FACETOOL_TYPES } from 'app/constants'; + +export interface HiresState { + codeformerFidelity: number; + facetoolStrength: number; + facetoolType: FacetoolType; + hiresFix: boolean; + hiresStrength: number; + shouldLoopback: boolean; + shouldRunESRGAN: boolean; + shouldRunFacetool: boolean; + upscalingLevel: UpscalingLevel; + upscalingDenoising: number; + upscalingStrength: number; +} + +export const initialHiresState: HiresState = { + codeformerFidelity: 0.75, + facetoolStrength: 0.75, + facetoolType: 'gfpgan', + hiresFix: false, + hiresStrength: 0.75, + hiresSteps: 30, + hiresWidth: 512, + hiresHeight: 512, + hiresModel: '', + shouldLoopback: false, + shouldRunESRGAN: false, + shouldRunFacetool: false, + upscalingLevel: 4, + upscalingDenoising: 0.75, + upscalingStrength: 0.75, +}; + +export const postprocessingSlice = createSlice({ + name: 'postprocessing', + initialState: initialPostprocessingState, + reducers: { + setFacetoolStrength: (state, action: PayloadAction) => { + state.facetoolStrength = action.payload; + }, + setCodeformerFidelity: (state, action: PayloadAction) => { + state.codeformerFidelity = action.payload; + }, + setUpscalingLevel: (state, action: PayloadAction) => { + state.upscalingLevel = action.payload; + }, + setUpscalingDenoising: (state, action: PayloadAction) => { + state.upscalingDenoising = action.payload; + }, + setUpscalingStrength: (state, action: PayloadAction) => { + state.upscalingStrength = action.payload; + }, + setHiresFix: (state, action: PayloadAction) => { + state.hiresFix = action.payload; + }, + setHiresStrength: (state, action: PayloadAction) => { + state.hiresStrength = action.payload; + }, + resetPostprocessingState: (state) => { + return { + ...state, + ...initialPostprocessingState, + }; + }, + setShouldRunFacetool: (state, action: PayloadAction) => { + state.shouldRunFacetool = action.payload; + }, + setFacetoolType: (state, action: PayloadAction) => { + state.facetoolType = action.payload; + }, + setShouldRunESRGAN: (state, action: PayloadAction) => { + state.shouldRunESRGAN = action.payload; + }, + setShouldLoopback: (state, action: PayloadAction) => { + state.shouldLoopback = action.payload; + }, + }, +}); + +export const { + resetPostprocessingState, + setCodeformerFidelity, + setFacetoolStrength, + setFacetoolType, + setHiresFix, + setHiresStrength, + setShouldLoopback, + setShouldRunESRGAN, + setShouldRunFacetool, + setUpscalingLevel, + setUpscalingDenoising, + setUpscalingStrength, +} = postprocessingSlice.actions; + +export default postprocessingSlice.reducer; diff --git a/invokeai/frontend/web/src/features/ui/components/CreateParametersDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/CreateParametersDrawer.tsx index a033ae3a05..0518d792cb 100644 --- a/invokeai/frontend/web/src/features/ui/components/CreateParametersDrawer.tsx +++ b/invokeai/frontend/web/src/features/ui/components/CreateParametersDrawer.tsx @@ -1,6 +1,6 @@ import { isEqual } from 'lodash-es'; import ResizableDrawer from './common/ResizableDrawer/ResizableDrawer'; -import CreateBaseSettings from './tabs/Create/CreateBaseSettings'; +import TextTabParameters from './tabs/text/TextTabParameters'; import { createSelector } from '@reduxjs/toolkit'; import { activeTabNameSelector, uiSelector } from '../store/uiSelectors'; import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; @@ -14,10 +14,10 @@ import { Flex } from '@chakra-ui/react'; import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent'; import PinParametersPanelButton from './PinParametersPanelButton'; import { Panel, PanelGroup } from 'react-resizable-panels'; -import CreateSidePanelPinned from './tabs/Create/CreateSidePanelPinned'; -import CreateTextParameters from './tabs/Create/CreateBaseSettings'; +import CreateSidePanelPinned from './tabs/text/TextTabSettingsPinned'; +import CreateTextParameters from './tabs/text/TextTabParameters'; import ResizeHandle from './tabs/ResizeHandle'; -import CreateImageSettings from './tabs/Create/CreateImageSettings'; +import CreateImageSettings from './tabs/image/ImageTabSettings'; const selector = createSelector( [uiSelector, activeTabNameSelector, lightboxSelector], diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx index 90d199076d..8ceb8ee20a 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx @@ -40,7 +40,7 @@ export const floatingParametersPanelButtonSelector = createSelector( const shouldShowParametersPanelButton = !canvasBetaLayoutCheck && !shouldShowParametersPanel && - ['generate', 'unifiedCanvas'].includes(activeTabName); + ['text', 'image', 'unifiedCanvas'].includes(activeTabName); return { shouldPinParametersPanel, diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index 509272a2e4..3cda3111b7 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -26,31 +26,34 @@ import { } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { MdDeviceHub, MdGridOn } from 'react-icons/md'; +import { GoTextSize } from 'react-icons/go'; import { activeTabIndexSelector } from '../store/uiSelectors'; import UnifiedCanvasWorkarea from 'features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea'; import { useTranslation } from 'react-i18next'; import { ResourceKey } from 'i18next'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import NodeEditor from 'features/nodes/components/NodeEditor'; -import GenerateWorkspace from './tabs/Create/GenerateWorkspace'; +import GenerateWorkspace from './tabs/text/GenerateWorkspace'; import { createSelector } from '@reduxjs/toolkit'; import { BsLightningChargeFill } from 'react-icons/bs'; import { configSelector } from 'features/system/store/configSelectors'; import { isEqual } from 'lodash-es'; import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel'; import Scrollable from './common/Scrollable'; -import CreateBaseSettings from './tabs/Create/CreateBaseSettings'; +import TextTabParameters from './tabs/text/TextTabParameters'; import PinParametersPanelButton from './PinParametersPanelButton'; import ParametersSlide from './common/ParametersSlide'; import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; -import CreateTabContent from './tabs/Create/CreateContent'; +import TextTabMain from './tabs/text/TextTabMain'; import ParametersPanel from './ParametersPanel'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; -import CreateTab from './tabs/Create/CreateTab'; +import TextTab from './tabs/text/TextTab'; import UnifiedCanvasTab from './tabs/UnifiedCanvas/UnifiedCanvasTab'; import NodesTab from './tabs/Nodes/NodesTab'; +import { FaImage } from 'react-icons/fa'; +import ResizeHandle from './tabs/ResizeHandle'; export interface InvokeTabInfo { id: InvokeTabName; @@ -60,9 +63,14 @@ export interface InvokeTabInfo { const tabs: InvokeTabInfo[] = [ { - id: 'generate', - icon: , - content: , + id: 'text', + icon: , + content: , + }, + { + id: 'image', + icon: , + content: , }, { id: 'unifiedCanvas', @@ -107,14 +115,18 @@ const InvokeTabs = () => { const dispatch = useAppDispatch(); useHotkeys('1', () => { - dispatch(setActiveTab('generate')); + dispatch(setActiveTab('text')); }); useHotkeys('2', () => { - dispatch(setActiveTab('unifiedCanvas')); + dispatch(setActiveTab('image')); }); useHotkeys('3', () => { + dispatch(setActiveTab('unifiedCanvas')); + }); + + useHotkeys('4', () => { dispatch(setActiveTab('nodes')); }); @@ -183,23 +195,27 @@ const InvokeTabs = () => { > {tabs} - {tabPanels} + + + + {tabPanels} + + + {shouldPinGallery && shouldShowGallery && ( + <> + + + + + + )} + ); }; export default memo(InvokeTabs); - -// -// -// -// -// -// -// -// -// -// -// -// -// ; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateBaseSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateBaseSettings.tsx deleted file mode 100644 index 1208f867b2..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateBaseSettings.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { - AspectRatio, - Box, - Flex, - Select, - Slider, - SliderFilledTrack, - SliderThumb, - SliderTrack, - Text, -} from '@chakra-ui/react'; -import { Feature } from 'app/features'; -import IAISlider from 'common/components/IAISlider'; -import IAISwitch from 'common/components/IAISwitch'; -import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings'; -import ImageToImageToggle from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageToggle'; -import OutputSettings from 'features/parameters/components/AdvancedParameters/Output/OutputSettings'; -import SymmetrySettings from 'features/parameters/components/AdvancedParameters/Output/SymmetrySettings'; -import SymmetryToggle from 'features/parameters/components/AdvancedParameters/Output/SymmetryToggle'; -import RandomizeSeed from 'features/parameters/components/AdvancedParameters/Seed/RandomizeSeed'; -import SeedSettings from 'features/parameters/components/AdvancedParameters/Seed/SeedSettings'; -import GenerateVariationsToggle from 'features/parameters/components/AdvancedParameters/Variations/GenerateVariations'; -import VariationsSettings from 'features/parameters/components/AdvancedParameters/Variations/VariationsSettings'; -import DimensionsSettings from 'features/parameters/components/ImageDimensions/DimensionsSettings'; -import MainSettings from 'features/parameters/components/MainParameters/MainSettings'; -import ParametersAccordion, { - ParametersAccordionItems, -} from 'features/parameters/components/ParametersAccordion'; -import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; -import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput'; -import PromptInput from 'features/parameters/components/PromptInput/PromptInput'; -import { findIndex } from 'lodash-es'; -import { - OverlayScrollbarsComponent, - useOverlayScrollbars, -} from 'overlayscrollbars-react'; -import { memo, useMemo, useState, useRef, useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; -import OverlayScrollable from '../../common/OverlayScrollable'; -import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel'; - -const CreateBaseSettings = () => { - const { t } = useTranslation(); - - const generateAccordionItems: ParametersAccordionItems = useMemo( - () => ({ - // general: { - // name: 'general', - // header: `${t('parameters.general')}`, - // feature: undefined, - // content: , - // }, - seed: { - name: 'seed', - header: `${t('parameters.seed')}`, - feature: Feature.SEED, - content: , - additionalHeaderComponents: , - }, - // imageToImage: { - // name: 'imageToImage', - // header: `${t('parameters.imageToImage')}`, - // feature: undefined, - // content: , - // additionalHeaderComponents: , - // }, - variations: { - name: 'variations', - header: `${t('parameters.variations')}`, - feature: Feature.VARIATIONS, - content: , - additionalHeaderComponents: , - }, - symmetry: { - name: 'symmetry', - header: `${t('parameters.symmetry')}`, - content: , - additionalHeaderComponents: , - }, - other: { - name: 'other', - header: `${t('parameters.otherOptions')}`, - feature: Feature.OTHER, - content: , - }, - }), - [t] - ); - - return ( - - - - - - - - - - - - - ); -}; - -export default memo(CreateBaseSettings); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx deleted file mode 100644 index a49836e027..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Box, Flex } from '@chakra-ui/react'; -import { useAppSelector } from 'app/store/storeHooks'; -import { memo } from 'react'; -import CreateTabContent from './CreateContent'; -import CreateBaseSettings from './CreateBaseSettings'; -import PinParametersPanelButton from '../../PinParametersPanelButton'; -import { RootState } from 'app/store/store'; -import Scrollable from '../../common/Scrollable'; -import ParametersSlide from '../../common/ParametersSlide'; -import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel'; - -const GenerateWorkspace = () => { - const shouldPinParametersPanel = useAppSelector( - (state: RootState) => state.ui.shouldPinParametersPanel - ); - - return ; - - return ( - - {shouldPinParametersPanel ? ( - - - - - - - - - - ) : ( - - - - )} - - - ); -}; - -export default memo(GenerateWorkspace); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx index e75a4fa88a..c62f82600b 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx @@ -2,13 +2,40 @@ import { Box, Flex } from '@chakra-ui/react'; import { memo } from 'react'; import { PanelResizeHandle } from 'react-resizable-panels'; -const ResizeHandle = () => { +type ResizeHandleProps = { + direction?: 'horizontal' | 'vertical'; +}; + +const ResizeHandle = (props: ResizeHandleProps) => { + const { direction = 'horizontal' } = props; + + if (direction === 'horizontal') { + return ( + + + + + + ); + } return ( - + ); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx index 5e64f72cc4..3c4ccb10b6 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx @@ -4,20 +4,23 @@ import BoundingBoxSettings from 'features/parameters/components/AdvancedParamete import InfillAndScalingSettings from 'features/parameters/components/AdvancedParameters/Canvas/InfillAndScalingSettings'; import SeamCorrectionSettings from 'features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamCorrectionSettings'; import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength'; -import SymmetrySettings from 'features/parameters/components/AdvancedParameters/Output/SymmetrySettings'; -import SymmetryToggle from 'features/parameters/components/AdvancedParameters/Output/SymmetryToggle'; -import SeedSettings from 'features/parameters/components/AdvancedParameters/Seed/SeedSettings'; -import GenerateVariationsToggle from 'features/parameters/components/AdvancedParameters/Variations/GenerateVariations'; -import VariationsSettings from 'features/parameters/components/AdvancedParameters/Variations/VariationsSettings'; import MainSettings from 'features/parameters/components/MainParameters/MainSettings'; import ParametersAccordion, { ParametersAccordionItems, } from 'features/parameters/components/ParametersAccordion'; import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; -import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput'; -import PromptInput from 'features/parameters/components/PromptInput/PromptInput'; import { useTranslation } from 'react-i18next'; import OverlayScrollable from '../../common/OverlayScrollable'; +// import ParamSeedSettings from 'features/parameters/components/Parameters/Seed/ParamSeedSettings'; +// import ParamVariationSettings from 'features/parameters/components/Parameters/Variations/ParamVariationSettings'; +// import ParamSymmetrySettings from 'features/parameters/components/Parameters/Symmetry/ParamSymmetrySettings'; +// import ParamVariationToggle from 'features/parameters/components/Parameters/Variations/ParamVariationToggle'; +// import ParamSymmetryToggle from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryToggle'; +import ParamPositiveConditioning from 'features/parameters/components/Parameters/ParamPositiveConditioning'; +import ParamNegativeConditioning from 'features/parameters/components/Parameters/ParamNegativeConditioning'; +import ParamSeedCollapse from 'features/parameters/components/Parameters/Seed/ParamSeedCollapse'; +import ParamVariationCollapse from 'features/parameters/components/Parameters/Variations/ParamVariationCollapse'; +import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; export default function UnifiedCanvasParameters() { const { t } = useTranslation(); @@ -35,12 +38,12 @@ export default function UnifiedCanvasParameters() { feature: undefined, content: , }, - seed: { - name: 'seed', - header: `${t('parameters.seed')}`, - feature: Feature.SEED, - content: , - }, + // seed: { + // name: 'seed', + // header: `${t('parameters.seed')}`, + // feature: Feature.SEED, + // content: , + // }, boundingBox: { name: 'boundingBox', header: `${t('parameters.boundingBoxHeader')}`, @@ -59,19 +62,19 @@ export default function UnifiedCanvasParameters() { feature: Feature.INFILL_AND_SCALING, content: , }, - variations: { - name: 'variations', - header: `${t('parameters.variations')}`, - feature: Feature.VARIATIONS, - content: , - additionalHeaderComponents: , - }, - symmetry: { - name: 'symmetry', - header: `${t('parameters.symmetry')}`, - content: , - additionalHeaderComponents: , - }, + // variations: { + // name: 'variations', + // header: `${t('parameters.variations')}`, + // feature: Feature.VARIATIONS, + // content: , + // additionalHeaderComponents: , + // }, + // symmetry: { + // name: 'symmetry', + // header: `${t('parameters.symmetry')}`, + // content: , + // additionalHeaderComponents: , + // }, }; return ( @@ -85,9 +88,12 @@ export default function UnifiedCanvasParameters() { position: 'absolute', }} > - - + + + + + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx similarity index 53% rename from invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateTab.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx index 9bd11d828b..b069ff31db 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx @@ -1,20 +1,12 @@ import { Portal, TabPanel } from '@chakra-ui/react'; import { memo } from 'react'; -import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; -import CreateBaseSettings from './CreateBaseSettings'; +import { Panel, PanelGroup } from 'react-resizable-panels'; import PinParametersPanelButton from '../../PinParametersPanelButton'; -import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; import { createSelector } from '@reduxjs/toolkit'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; -import CreateTabContent from './CreateContent'; import ResizeHandle from '../ResizeHandle'; -import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel'; -import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings'; -import CreateSidePanelPinned from './CreateSidePanelPinned'; -import CreateTextParameters from './CreateBaseSettings'; -import CreateImageSettings from './CreateImageSettings'; const selector = createSelector(uiSelector, (ui) => { const { @@ -34,7 +26,7 @@ const selector = createSelector(uiSelector, (ui) => { }; }); -const CreateTab = () => { +const TextTab = () => { const dispatch = useAppDispatch(); const { shouldPinGallery, @@ -46,61 +38,39 @@ const CreateTab = () => { return ( {shouldPinParametersPanel && shouldShowParametersPanel && ( <> - + {/* */} - {shouldShowImageParameters && ( - <> - - - - - - )} )} { dispatch(requestCanvasRescale()); }} > - + {/* */} - {shouldPinGallery && shouldShowGallery && ( - <> - - - - - - )} ); }; -export default memo(CreateTab); +export default memo(TextTab); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateImageSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabSettings.tsx similarity index 100% rename from invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateImageSettings.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabSettings.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx new file mode 100644 index 0000000000..c26700a39d --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx @@ -0,0 +1,78 @@ +import { Portal, TabPanel } from '@chakra-ui/react'; +import { memo } from 'react'; +import { Panel, PanelGroup } from 'react-resizable-panels'; +import PinParametersPanelButton from '../../PinParametersPanelButton'; +import { createSelector } from '@reduxjs/toolkit'; +import { uiSelector } from 'features/ui/store/uiSelectors'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; +import TextTabMain from './TextTabMain'; +import ResizeHandle from '../ResizeHandle'; +import TextTabSettings from './TextTabParameters'; + +const selector = createSelector(uiSelector, (ui) => { + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldShowImageParameters, + } = ui; + + return { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldShowImageParameters, + }; +}); + +const TextTab = () => { + const dispatch = useAppDispatch(); + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldShowImageParameters, + } = useAppSelector(selector); + + return ( + + {shouldPinParametersPanel && shouldShowParametersPanel && ( + <> + + + + + + + )} + { + dispatch(requestCanvasRescale()); + }} + > + + + + ); +}; + +export default memo(TextTab); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateContent.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx similarity index 89% rename from invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateContent.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx index bad7475c2e..36c5f9bd94 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateContent.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx @@ -2,7 +2,7 @@ import { Box, Flex } from '@chakra-ui/react'; import CurrentImageDisplay from 'features/gallery/components/CurrentImageDisplay'; import ProgressImagePreview from 'features/parameters/components/ProgressImagePreview'; -const CreateTabContent = () => { +const TextTabMain = () => { return ( { ); }; -export default CreateTabContent; +export default TextTabMain; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParameters.tsx new file mode 100644 index 0000000000..eaf3362539 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParameters.tsx @@ -0,0 +1,108 @@ +import { Box, Flex } from '@chakra-ui/react'; +import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; +import { memo } from 'react'; +import OverlayScrollable from '../../common/OverlayScrollable'; +import ParamPositiveConditioning from 'features/parameters/components/Parameters/ParamPositiveConditioning'; +import ParamNegativeConditioning from 'features/parameters/components/Parameters/ParamNegativeConditioning'; +import { createSelector } from '@reduxjs/toolkit'; +import { uiSelector } from 'features/ui/store/uiSelectors'; +import { useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import ParamIterations from 'features/parameters/components/Parameters/ParamIterations'; +import ParamSteps from 'features/parameters/components/Parameters/ParamSteps'; +import ParamCFGScale from 'features/parameters/components/Parameters/ParamCFGScale'; +import ParamWidth from 'features/parameters/components/Parameters/ParamWidth'; +import ParamHeight from 'features/parameters/components/Parameters/ParamHeight'; +import ParamScheduler from 'features/parameters/components/Parameters/ParamScheduler'; +import ModelSelect from 'features/system/components/ModelSelect'; +import ParamSeedCollapse from 'features/parameters/components/Parameters/Seed/ParamSeedCollapse'; +import ParamVariationCollapse from 'features/parameters/components/Parameters/Variations/ParamVariationCollapse'; +import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/ParamNoiseCollapse'; +import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; +import ParamHiresCollapse from 'features/parameters/components/Parameters/Hires/ParamHiresCollapse'; +import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse'; + +const selector = createSelector( + uiSelector, + (ui) => { + const { shouldUseSliders } = ui; + + return { shouldUseSliders }; + }, + defaultSelectorOptions +); + +const TextTabParameters = () => { + const { shouldUseSliders } = useAppSelector(selector); + + return ( + + + + + + + {shouldUseSliders ? ( + + + + + + + + + + + + + + + + ) : ( + + + + + + + + + + + + + + + + + + )} + + + + + + + + + + ); +}; + +export default memo(TextTabParameters); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateSidePanelPinned.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabSettingsPinned.tsx similarity index 93% rename from invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateSidePanelPinned.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabSettingsPinned.tsx index b17237d46e..9d58eed899 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Create/CreateSidePanelPinned.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabSettingsPinned.tsx @@ -3,12 +3,12 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo } from 'react'; import { Panel } from 'react-resizable-panels'; -import CreateTextParameters from './CreateBaseSettings'; +import CreateTextParameters from './TextTabParameters'; import PinParametersPanelButton from '../../PinParametersPanelButton'; import ResizeHandle from '../ResizeHandle'; import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import CreateImageSettings from './CreateImageSettings'; +import CreateImageSettings from '../image/ImageTabSettings'; const selector = createSelector( uiSelector, diff --git a/invokeai/frontend/web/src/features/ui/store/tabMap.ts b/invokeai/frontend/web/src/features/ui/store/tabMap.ts index fe6e2d033a..30bfb459a0 100644 --- a/invokeai/frontend/web/src/features/ui/store/tabMap.ts +++ b/invokeai/frontend/web/src/features/ui/store/tabMap.ts @@ -1,7 +1,7 @@ export const tabMap = [ - // 'txt2img', - // 'img2img', - 'generate', + 'text', + 'image', + // 'generate', 'unifiedCanvas', 'nodes', // 'postprocessing', diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index c4e2ad367c..b585c5300b 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -18,9 +18,9 @@ export const initialUIState: UIState = { shouldPinGallery: true, shouldShowGallery: true, shouldHidePreview: false, - openLinearAccordionItems: [], - openGenerateAccordionItems: [], - openUnifiedCanvasAccordionItems: [], + textTabAccordionState: [], + imageTabAccordionState: [], + canvasTabAccordionState: [], floatingProgressImageRect: { x: 0, y: 0, width: 0, height: 0 }, shouldShowProgressImages: false, shouldAutoShowProgressImages: false, @@ -105,12 +105,16 @@ export const uiSlice = createSlice({ } }, openAccordionItemsChanged: (state, action: PayloadAction) => { - if (tabMap[state.activeTab] === 'generate') { - state.openGenerateAccordionItems = action.payload; + if (tabMap[state.activeTab] === 'text') { + state.textTabAccordionState = action.payload; + } + + if (tabMap[state.activeTab] === 'image') { + state.imageTabAccordionState = action.payload; } if (tabMap[state.activeTab] === 'unifiedCanvas') { - state.openUnifiedCanvasAccordionItems = action.payload; + state.canvasTabAccordionState = action.payload; } }, floatingProgressImageMoved: (state, action: PayloadAction) => { diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts index 3e2a3a1616..59140cdfde 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts @@ -26,9 +26,9 @@ export interface UIState { shouldHidePreview: boolean; shouldPinGallery: boolean; shouldShowGallery: boolean; - openLinearAccordionItems: number[]; - openGenerateAccordionItems: number[]; - openUnifiedCanvasAccordionItems: number[]; + textTabAccordionState: number[]; + imageTabAccordionState: number[]; + canvasTabAccordionState: number[]; floatingProgressImageRect: Rect; shouldShowProgressImages: boolean; shouldAutoShowProgressImages: boolean; From c4b3a24ed701680b89289a07c7ceb7b54ae03da9 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 8 May 2023 22:57:05 +1000 Subject: [PATCH 42/66] feat(ui): revert tabs to txt2img/img2img --- .../frontend/web/src/app/components/App.tsx | 10 +- .../middleware/listenerMiddleware/index.ts | 6 +- .../listeners/userInvokedCanvas.ts | 2 +- .../listeners/userInvokedImageToImage.ts | 24 ++++ .../listeners/userInvokedNodes.ts | 2 +- ...kedCreate.ts => userInvokedTextToImage.ts} | 12 +- ...ingsHeader.tsx => ImageToImageButtons.tsx} | 4 +- .../components/SelectImagePlaceholder.tsx | 2 +- .../web/src/common/hooks/useGlobalHotkeys.ts | 17 +++ .../components/CurrentImageButtons.tsx | 4 +- .../gallery/components/ImageGalleryPanel.tsx | 54 ++++---- .../nodes/components/NodeGraphOverlay.tsx | 2 +- .../web/src/features/nodes/store/actions.ts | 10 +- .../buildEdges.ts | 0 .../{ => graphBuilders}/buildCanvasGraph.ts | 14 +-- .../buildImageToImageGraph.ts} | 16 +-- .../{ => graphBuilders}/buildNodesGraph.ts | 2 +- .../graphBuilders/buildTextToImageGraph.ts | 35 ++++++ .../buildImageToImageNode.ts | 11 +- .../buildInpaintNode.ts | 0 .../buildIterateNode.ts | 0 .../buildRangeNode.ts | 0 .../buildTextToImageNode.ts | 8 +- .../{ImageFit.tsx => ImageToImageFit.tsx} | 2 +- .../ImageToImage/ImageToImageSettings.tsx | 8 +- .../ImageToImage/InitialImageDisplay.tsx | 36 ++++++ .../ImageToImage/InitialImagePreview.tsx | 5 +- .../Parameters/Hires/ParamHiresCollapse.tsx | 3 - .../src/features/system/store/modelSlice.ts | 10 +- .../ui/components/CreateParametersDrawer.tsx | 115 ------------------ .../src/features/ui/components/InvokeTabs.tsx | 32 ++--- .../ui/components/ParametersDrawer.tsx | 32 +++++ .../ui/components/tabs/image/ImageTab.tsx | 48 ++++++-- .../tabs/image/ImageTabImageParameters.tsx | 61 ++++++++++ .../tabs/image/ImageTabParameters.tsx | 113 +++++++++++++++++ .../tabs/image/ImageTabSettings.tsx | 53 -------- .../ui/components/tabs/text/TextTab.tsx | 15 +-- .../ui/components/tabs/text/TextTabMain.tsx | 1 - .../tabs/text/TextTabParametersDrawer.tsx | 73 +++++++++++ .../tabs/text/TextTabSettingsPinned.tsx | 69 ----------- invokeai/frontend/web/tsconfig.json | 3 +- 41 files changed, 535 insertions(+), 379 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts rename invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/{userInvokedCreate.ts => userInvokedTextToImage.ts} (58%) rename invokeai/frontend/web/src/common/components/{ImageToImageSettingsHeader.tsx => ImageToImageButtons.tsx} (93%) rename invokeai/frontend/web/src/features/nodes/util/{linearGraphBuilder => edgeBuilders}/buildEdges.ts (100%) rename invokeai/frontend/web/src/features/nodes/util/{ => graphBuilders}/buildCanvasGraph.ts (88%) rename invokeai/frontend/web/src/features/nodes/util/{buildLinearGraph.ts => graphBuilders/buildImageToImageGraph.ts} (55%) rename invokeai/frontend/web/src/features/nodes/util/{ => graphBuilders}/buildNodesGraph.ts (97%) create mode 100644 invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts rename invokeai/frontend/web/src/features/nodes/util/{linearGraphBuilder => nodeBuilders}/buildImageToImageNode.ts (94%) rename invokeai/frontend/web/src/features/nodes/util/{linearGraphBuilder => nodeBuilders}/buildInpaintNode.ts (100%) rename invokeai/frontend/web/src/features/nodes/util/{linearGraphBuilder => nodeBuilders}/buildIterateNode.ts (100%) rename invokeai/frontend/web/src/features/nodes/util/{linearGraphBuilder => nodeBuilders}/buildRangeNode.ts (100%) rename invokeai/frontend/web/src/features/nodes/util/{linearGraphBuilder => nodeBuilders}/buildTextToImageNode.ts (88%) rename invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/{ImageFit.tsx => ImageToImageFit.tsx} (94%) create mode 100644 invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImageDisplay.tsx delete mode 100644 invokeai/frontend/web/src/features/ui/components/CreateParametersDrawer.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabImageParameters.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabParameters.tsx delete mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabSettings.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParametersDrawer.tsx delete mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabSettingsPinned.tsx diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index c5f2c6ff04..f65947a1fa 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -9,7 +9,7 @@ import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton' import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons'; import { Box, Flex, Grid, Portal, useColorMode } from '@chakra-ui/react'; import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants'; -import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel'; +import GalleryDrawer from 'features/gallery/components/ImageGalleryPanel'; import Lightbox from 'features/lightbox/components/Lightbox'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { @@ -28,9 +28,7 @@ import { configChanged } from 'features/system/store/configSlice'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useLogger } from 'app/logging/useLogger'; import ProgressImagePreview from 'features/parameters/components/ProgressImagePreview'; -import ResizableDrawer from 'features/ui/components/common/ResizableDrawer/ResizableDrawer'; -import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; -import CreateParametersDrawer from 'features/ui/components/CreateParametersDrawer'; +import ParametersDrawer from 'features/ui/components/ParametersDrawer'; const DEFAULT_CONFIG = {}; @@ -91,8 +89,8 @@ const App = ({ config = DEFAULT_CONFIG, children }: Props) => { - - + + {!isApplicationReady && !loadingOverridden && ( diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 6e66e19780..36bf6adfe7 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -12,8 +12,9 @@ import { addImageResultReceivedListener } from './listeners/invocationComplete'; import { addImageUploadedListener } from './listeners/imageUploaded'; import { addRequestedImageDeletionListener } from './listeners/imageDeleted'; import { addUserInvokedCanvasListener } from './listeners/userInvokedCanvas'; -import { addUserInvokedCreateListener } from './listeners/userInvokedCreate'; import { addUserInvokedNodesListener } from './listeners/userInvokedNodes'; +import { addUserInvokedTextToImageListener } from './listeners/userInvokedTextToImage'; +import { addUserInvokedImageToImageListener } from './listeners/userInvokedImageToImage'; export const listenerMiddleware = createListenerMiddleware(); @@ -39,5 +40,6 @@ addImageResultReceivedListener(); addRequestedImageDeletionListener(); addUserInvokedCanvasListener(); -addUserInvokedCreateListener(); addUserInvokedNodesListener(); +addUserInvokedTextToImageListener(); +addUserInvokedImageToImageListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts index 2490a358fe..cdb2c83e12 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts @@ -1,6 +1,6 @@ import { startAppListening } from '..'; import { sessionCreated, sessionInvoked } from 'services/thunks/session'; -import { buildCanvasGraphAndBlobs } from 'features/nodes/util/buildCanvasGraph'; +import { buildCanvasGraphAndBlobs } 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'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts new file mode 100644 index 0000000000..d326e2a1d9 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts @@ -0,0 +1,24 @@ +import { startAppListening } from '..'; +import { buildImageToImageGraph } from 'features/nodes/util/graphBuilders/buildImageToImageGraph'; +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'; + +const moduleLog = log.child({ namespace: 'invoke' }); + +export const addUserInvokedImageToImageListener = () => { + startAppListening({ + predicate: (action): action is ReturnType => + userInvoked.match(action) && action.payload === 'image', + effect: (action, { getState, dispatch }) => { + const state = getState(); + + const graph = buildImageToImageGraph(state); + dispatch(imageToImageGraphBuilt(graph)); + moduleLog({ data: graph }, 'Image to Image graph built'); + + dispatch(sessionCreated({ graph })); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts index baf7bc5baf..01e532d5ff 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts @@ -1,6 +1,6 @@ import { startAppListening } from '..'; import { sessionCreated } from 'services/thunks/session'; -import { buildNodesGraph } from 'features/nodes/util/buildNodesGraph'; +import { buildNodesGraph } from 'features/nodes/util/graphBuilders/buildNodesGraph'; import { log } from 'app/logging/useLogger'; import { nodesGraphBuilt } from 'features/nodes/store/actions'; import { userInvoked } from 'app/store/actions'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts similarity index 58% rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts index f07aa4530e..f4cced4ade 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts @@ -1,22 +1,22 @@ import { startAppListening } from '..'; -import { buildLinearGraph } from 'features/nodes/util/buildLinearGraph'; +import { buildTextToImageGraph } from 'features/nodes/util/graphBuilders/buildTextToImageGraph'; import { sessionCreated } from 'services/thunks/session'; import { log } from 'app/logging/useLogger'; -import { createGraphBuilt } from 'features/nodes/store/actions'; +import { textToImageGraphBuilt } from 'features/nodes/store/actions'; import { userInvoked } from 'app/store/actions'; const moduleLog = log.child({ namespace: 'invoke' }); -export const addUserInvokedCreateListener = () => { +export const addUserInvokedTextToImageListener = () => { startAppListening({ predicate: (action): action is ReturnType => userInvoked.match(action) && action.payload === 'text', effect: (action, { getState, dispatch }) => { const state = getState(); - const graph = buildLinearGraph(state); - dispatch(createGraphBuilt(graph)); - moduleLog({ data: graph }, 'Create graph built'); + const graph = buildTextToImageGraph(state); + dispatch(textToImageGraphBuilt(graph)); + moduleLog({ data: graph }, 'Text to Image graph built'); dispatch(sessionCreated({ graph })); }, diff --git a/invokeai/frontend/web/src/common/components/ImageToImageSettingsHeader.tsx b/invokeai/frontend/web/src/common/components/ImageToImageButtons.tsx similarity index 93% rename from invokeai/frontend/web/src/common/components/ImageToImageSettingsHeader.tsx rename to invokeai/frontend/web/src/common/components/ImageToImageButtons.tsx index 042cef3818..00a6e70c1d 100644 --- a/invokeai/frontend/web/src/common/components/ImageToImageSettingsHeader.tsx +++ b/invokeai/frontend/web/src/common/components/ImageToImageButtons.tsx @@ -7,7 +7,7 @@ import { useAppDispatch } from 'app/store/storeHooks'; import { useCallback } from 'react'; import { clearInitialImage } from 'features/parameters/store/generationSlice'; -const ImagePromptHeading = () => { +const InitialImageButtons = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -38,4 +38,4 @@ const ImagePromptHeading = () => { ); }; -export default ImagePromptHeading; +export default InitialImageButtons; diff --git a/invokeai/frontend/web/src/common/components/SelectImagePlaceholder.tsx b/invokeai/frontend/web/src/common/components/SelectImagePlaceholder.tsx index 2c0d71ca69..a19d447755 100644 --- a/invokeai/frontend/web/src/common/components/SelectImagePlaceholder.tsx +++ b/invokeai/frontend/web/src/common/components/SelectImagePlaceholder.tsx @@ -7,7 +7,7 @@ const SelectImagePlaceholder = () => { sx={{ w: 'full', h: 'full', - bg: 'base.800', + // bg: 'base.800', borderRadius: 'base', alignItems: 'center', justifyContent: 'center', diff --git a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts index 9f781bbc93..98aa824790 100644 --- a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts +++ b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts @@ -3,6 +3,7 @@ import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice'; import { + setActiveTab, toggleGalleryPanel, toggleParametersPanel, togglePinGalleryPanel, @@ -58,4 +59,20 @@ export const useGlobalHotkeys = () => { useHotkeys(['shift+g'], () => { dispatch(togglePinGalleryPanel()); }); + + useHotkeys('1', () => { + dispatch(setActiveTab('text')); + }); + + useHotkeys('2', () => { + dispatch(setActiveTab('image')); + }); + + useHotkeys('3', () => { + dispatch(setActiveTab('unifiedCanvas')); + }); + + useHotkeys('4', () => { + dispatch(setActiveTab('nodes')); + }); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index 824860e219..c9c472a9b8 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -441,13 +441,13 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { {t('parameters.sendToUnifiedCanvas')} - } > {t('parameters.copyImage')} - + */} = { - // txt2img: { galleryMinWidth: 200, galleryMaxWidth: 500 }, - // img2img: { galleryMinWidth: 200, galleryMaxWidth: 500 }, - generate: { galleryMinWidth: 200, galleryMaxWidth: 500 }, - unifiedCanvas: { galleryMinWidth: 200, galleryMaxWidth: 200 }, - nodes: { galleryMinWidth: 200, galleryMaxWidth: 500 }, - // postprocessing: { galleryMinWidth: 200, galleryMaxWidth: 500 }, - // training: { galleryMinWidth: 200, galleryMaxWidth: 500 }, -}; +// const GALLERY_TAB_WIDTHS: Record< +// InvokeTabName, +// { galleryMinWidth: number; galleryMaxWidth: number } +// > = { +// txt2img: { galleryMinWidth: 200, galleryMaxWidth: 500 }, +// img2img: { galleryMinWidth: 200, galleryMaxWidth: 500 }, +// generate: { galleryMinWidth: 200, galleryMaxWidth: 500 }, +// unifiedCanvas: { galleryMinWidth: 200, galleryMaxWidth: 200 }, +// nodes: { galleryMinWidth: 200, galleryMaxWidth: 500 }, +// postprocessing: { galleryMinWidth: 200, galleryMaxWidth: 500 }, +// training: { galleryMinWidth: 200, galleryMaxWidth: 500 }, +// }; const galleryPanelSelector = createSelector( [ @@ -73,34 +73,34 @@ const galleryPanelSelector = createSelector( } ); -const ImageGalleryPanel = () => { +const GalleryDrawer = () => { const dispatch = useAppDispatch(); const { shouldPinGallery, shouldShowGallery, galleryImageMinimumWidth, - activeTabName, - isStaging, - isResizable, - isLightboxOpen, + // activeTabName, + // isStaging, + // isResizable, + // isLightboxOpen, } = useAppSelector(galleryPanelSelector); - const handleSetShouldPinGallery = () => { - dispatch(togglePinGalleryPanel()); - dispatch(requestCanvasRescale()); - }; + // const handleSetShouldPinGallery = () => { + // dispatch(togglePinGalleryPanel()); + // dispatch(requestCanvasRescale()); + // }; - const handleToggleGallery = () => { - dispatch(toggleGalleryPanel()); - shouldPinGallery && dispatch(requestCanvasRescale()); - }; + // const handleToggleGallery = () => { + // dispatch(toggleGalleryPanel()); + // shouldPinGallery && dispatch(requestCanvasRescale()); + // }; const handleCloseGallery = () => { dispatch(setShouldShowGallery(false)); shouldPinGallery && dispatch(requestCanvasRescale()); }; - const resolution = useResolution(); + // const resolution = useResolution(); // useHotkeys( // 'g', @@ -229,4 +229,4 @@ const ImageGalleryPanel = () => { // return renderImageGallery(); }; -export default memo(ImageGalleryPanel); +export default memo(GalleryDrawer); diff --git a/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx b/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx index bcf02eabf5..e66f75792b 100644 --- a/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx @@ -2,7 +2,7 @@ import { Box } from '@chakra-ui/react'; import { RootState } from 'app/store/store'; import { useAppSelector } from 'app/store/storeHooks'; import { memo } from 'react'; -import { buildNodesGraph } from '../util/buildNodesGraph'; +import { buildNodesGraph } from '../util/graphBuilders/buildNodesGraph'; const NodeGraphOverlay = () => { const state = useAppSelector((state: RootState) => state); diff --git a/invokeai/frontend/web/src/features/nodes/store/actions.ts b/invokeai/frontend/web/src/features/nodes/store/actions.ts index e7bc6d6000..eda753b9dc 100644 --- a/invokeai/frontend/web/src/features/nodes/store/actions.ts +++ b/invokeai/frontend/web/src/features/nodes/store/actions.ts @@ -1,12 +1,18 @@ import { createAction, isAnyOf } from '@reduxjs/toolkit'; import { Graph } from 'services/api'; -export const createGraphBuilt = createAction('nodes/createGraphBuilt'); +export const textToImageGraphBuilt = createAction( + 'nodes/textToImageGraphBuilt' +); +export const imageToImageGraphBuilt = createAction( + 'nodes/imageToImageGraphBuilt' +); export const canvasGraphBuilt = createAction('nodes/canvasGraphBuilt'); export const nodesGraphBuilt = createAction('nodes/nodesGraphBuilt'); export const isAnyGraphBuilt = isAnyOf( - createGraphBuilt, + textToImageGraphBuilt, + imageToImageGraphBuilt, canvasGraphBuilt, nodesGraphBuilt ); diff --git a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildEdges.ts b/invokeai/frontend/web/src/features/nodes/util/edgeBuilders/buildEdges.ts similarity index 100% rename from invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildEdges.ts rename to invokeai/frontend/web/src/features/nodes/util/edgeBuilders/buildEdges.ts diff --git a/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts similarity index 88% rename from invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts rename to invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts index a9cb058de3..2ae31a261c 100644 --- a/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts @@ -10,17 +10,17 @@ import { RangeInvocation, TextToImageInvocation, } from 'services/api'; -import { buildImg2ImgNode } from './linearGraphBuilder/buildImageToImageNode'; -import { buildTxt2ImgNode } from './linearGraphBuilder/buildTextToImageNode'; -import { buildRangeNode } from './linearGraphBuilder/buildRangeNode'; -import { buildIterateNode } from './linearGraphBuilder/buildIterateNode'; -import { buildEdges } from './linearGraphBuilder/buildEdges'; +import { buildImg2ImgNode } from '../nodeBuilders/buildImageToImageNode'; +import { buildTxt2ImgNode } from '../nodeBuilders/buildTextToImageNode'; +import { buildRangeNode } from '../nodeBuilders/buildRangeNode'; +import { buildIterateNode } from '../nodeBuilders/buildIterateNode'; +import { buildEdges } from '../edgeBuilders/buildEdges'; import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; import { getCanvasData } from 'features/canvas/util/getCanvasData'; -import { getGenerationMode } from './getGenerationMode'; +import { getGenerationMode } from '../getGenerationMode'; import { v4 as uuidv4 } from 'uuid'; import { log } from 'app/logging/useLogger'; -import { buildInpaintNode } from './linearGraphBuilder/buildInpaintNode'; +import { buildInpaintNode } from '../nodeBuilders/buildInpaintNode'; const moduleLog = log.child({ namespace: 'buildCanvasGraph' }); diff --git a/invokeai/frontend/web/src/features/nodes/util/buildLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts similarity index 55% rename from invokeai/frontend/web/src/features/nodes/util/buildLinearGraph.ts rename to invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts index f247964c72..d7a0fc66d3 100644 --- a/invokeai/frontend/web/src/features/nodes/util/buildLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts @@ -1,19 +1,15 @@ import { RootState } from 'app/store/store'; import { Graph } from 'services/api'; -import { buildImg2ImgNode } from './linearGraphBuilder/buildImageToImageNode'; -import { buildTxt2ImgNode } from './linearGraphBuilder/buildTextToImageNode'; -import { buildRangeNode } from './linearGraphBuilder/buildRangeNode'; -import { buildIterateNode } from './linearGraphBuilder/buildIterateNode'; -import { buildEdges } from './linearGraphBuilder/buildEdges'; +import { buildImg2ImgNode } from '../nodeBuilders/buildImageToImageNode'; +import { buildRangeNode } from '../nodeBuilders/buildRangeNode'; +import { buildIterateNode } from '../nodeBuilders/buildIterateNode'; +import { buildEdges } from '../edgeBuilders/buildEdges'; /** * Builds the Linear workflow graph. */ -export const buildLinearGraph = (state: RootState): Graph => { - // The base node is either a txt2img or img2img node - const baseNode = state.generation.isImageToImageEnabled - ? buildImg2ImgNode(state) - : buildTxt2ImgNode(state); +export const buildImageToImageGraph = (state: RootState): Graph => { + const baseNode = buildImg2ImgNode(state); // We always range and iterate nodes, no matter the iteration count // This is required to provide the correct seeds to the backend engine diff --git a/invokeai/frontend/web/src/features/nodes/util/buildNodesGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts similarity index 97% rename from invokeai/frontend/web/src/features/nodes/util/buildNodesGraph.ts rename to invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts index 7faa20bfd3..eef7379624 100644 --- a/invokeai/frontend/web/src/features/nodes/util/buildNodesGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts @@ -2,7 +2,7 @@ import { Graph } from 'services/api'; import { v4 as uuidv4 } from 'uuid'; import { cloneDeep, reduce } from 'lodash-es'; import { RootState } from 'app/store/store'; -import { InputFieldValue } from '../types/types'; +import { InputFieldValue } from 'features/nodes/types/types'; /** * We need to do special handling for some fields diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts new file mode 100644 index 0000000000..8b1d8edcc9 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts @@ -0,0 +1,35 @@ +import { RootState } from 'app/store/store'; +import { Graph } from 'services/api'; +import { buildTxt2ImgNode } from '../nodeBuilders/buildTextToImageNode'; +import { buildRangeNode } from '../nodeBuilders/buildRangeNode'; +import { buildIterateNode } from '../nodeBuilders/buildIterateNode'; +import { buildEdges } from '../edgeBuilders/buildEdges'; + +/** + * Builds the Linear workflow graph. + */ +export const buildTextToImageGraph = (state: RootState): Graph => { + const baseNode = buildTxt2ImgNode(state); + + // We always range and iterate nodes, no matter the iteration count + // This is required to provide the correct seeds to the backend engine + const rangeNode = buildRangeNode(state); + const iterateNode = buildIterateNode(); + + // Build the edges for the nodes selected. + const edges = buildEdges(baseNode, rangeNode, iterateNode); + + // Assemble! + const graph = { + nodes: { + [rangeNode.id]: rangeNode, + [iterateNode.id]: iterateNode, + [baseNode.id]: baseNode, + }, + edges, + }; + + // TODO: hires fix requires latent space upscaling; we don't have nodes for this yet + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildImageToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts similarity index 94% rename from invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildImageToImageNode.ts rename to invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts index 53736cbe42..0e1937ced8 100644 --- a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildImageToImageNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts @@ -15,8 +15,6 @@ export const buildImg2ImgNode = ( const nodeId = uuidv4(); const { generation, system, models } = state; - const { selectedModelName } = models; - const { prompt, negativePrompt, @@ -26,10 +24,13 @@ export const buildImg2ImgNode = ( height, cfgScale, sampler, - seamless, + model, img2imgStrength: strength, shouldFitToWidthHeight: fit, shouldRandomizeSeed, + shouldUseSeamless, + seamlessXAxis, + seamlessYAxis, } = generation; const initialImage = initialImageSelector(state); @@ -48,9 +49,7 @@ export const buildImg2ImgNode = ( height, cfg_scale: cfgScale, scheduler: sampler as ImageToImageInvocation['scheduler'], - seamless, - model: selectedModelName, - progress_images: true, + model, image: initialImage ? { image_name: initialImage.name, diff --git a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildInpaintNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildInpaintNode.ts similarity index 100% rename from invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildInpaintNode.ts rename to invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildInpaintNode.ts diff --git a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildIterateNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildIterateNode.ts similarity index 100% rename from invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildIterateNode.ts rename to invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildIterateNode.ts diff --git a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildRangeNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildRangeNode.ts similarity index 100% rename from invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildRangeNode.ts rename to invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildRangeNode.ts diff --git a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildTextToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildTextToImageNode.ts similarity index 88% rename from invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildTextToImageNode.ts rename to invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildTextToImageNode.ts index 42c0c12c1a..711c64ed67 100644 --- a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildTextToImageNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildTextToImageNode.ts @@ -10,8 +10,6 @@ export const buildTxt2ImgNode = ( const nodeId = uuidv4(); const { generation, models } = state; - const { selectedModelName } = models; - const { prompt, negativePrompt, @@ -21,8 +19,8 @@ export const buildTxt2ImgNode = ( height, cfgScale: cfg_scale, sampler, - seamless, shouldRandomizeSeed, + model, } = generation; const textToImageNode: NonNullable = { @@ -34,9 +32,7 @@ export const buildTxt2ImgNode = ( height, cfg_scale, scheduler: sampler as TextToImageInvocation['scheduler'], - seamless, - model: selectedModelName, - progress_images: true, + model, }; if (!shouldRandomizeSeed) { diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageFit.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageFit.tsx similarity index 94% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageFit.tsx rename to invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageFit.tsx index f479def1ab..03f502846c 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageFit.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageFit.tsx @@ -5,7 +5,7 @@ import { setShouldFitToWidthHeight } from 'features/parameters/store/generationS import { ChangeEvent } from 'react'; import { useTranslation } from 'react-i18next'; -export default function ImageFit() { +export default function ImageToImageFit() { const dispatch = useAppDispatch(); const shouldFitToWidthHeight = useAppSelector( diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings.tsx index f35d5ae9b8..ad6d31dd17 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings.tsx @@ -13,7 +13,7 @@ import { import { motion } from 'framer-motion'; import IAIButton from 'common/components/IAIButton'; -import ImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageFit'; +import ImageToImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageFit'; import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength'; import IAIIconButton from 'common/components/IAIIconButton'; @@ -21,16 +21,16 @@ import { useTranslation } from 'react-i18next'; import InitialImagePreview from './InitialImagePreview'; import { useState } from 'react'; import { FaUndo, FaUpload } from 'react-icons/fa'; -import ImagePromptHeading from 'common/components/ImageToImageSettingsHeader'; +import InitialImageButtons from 'common/components/ImageToImageSettingsHeader'; export default function ImageToImageSettings() { const { t } = useTranslation(); return ( - + - + ); } diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImageDisplay.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImageDisplay.tsx new file mode 100644 index 0000000000..236146099f --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImageDisplay.tsx @@ -0,0 +1,36 @@ +import { Flex } from '@chakra-ui/react'; +import InitialImagePreview from './InitialImagePreview'; +import InitialImageButtons from 'common/components/ImageToImageButtons'; + +const InitialImageDisplay = () => { + return ( + + + + + + + ); +}; + +export default InitialImageDisplay; diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx index 7d5a6f3422..184e88a505 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx @@ -71,6 +71,7 @@ const InitialImagePreview = () => { { )} {!initialImage?.url && } - {!isImageToImageEnabled && ( + {/* {!isImageToImageEnabled && ( { Image to Image is Disabled - )} + )} */} ); }; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresCollapse.tsx index 06ecd6762b..9c1f3a3f14 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresCollapse.tsx @@ -7,9 +7,6 @@ import { memo } from 'react'; import { ParamHiresStrength } from './ParamHiresStrength'; import { setHiresFix } from 'features/parameters/store/postprocessingSlice'; -/** - * Seed & variation options. Includes iteration, seed, seed randomization, variation options. - */ const ParamHiresCollapse = () => { const { t } = useTranslation(); const hiresFix = useAppSelector( diff --git a/invokeai/frontend/web/src/features/system/store/modelSlice.ts b/invokeai/frontend/web/src/features/system/store/modelSlice.ts index 88ca71f2ff..6bd676762c 100644 --- a/invokeai/frontend/web/src/features/system/store/modelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/modelSlice.ts @@ -61,12 +61,12 @@ export const modelsSlice = createSlice({ }, }); -export const selectedModelSelector = (state: RootState) => { - const { selectedModelName } = state.models; - const selectedModel = selectModelsById(state, selectedModelName); +// export const selectedModelSelector = (state: RootState) => { +// const { selectedModelName } = state.models; +// const selectedModel = selectModelsById(state, selectedModelName); - return selectedModel ?? null; -}; +// return selectedModel ?? null; +// }; export const { selectAll: selectModelsAll, diff --git a/invokeai/frontend/web/src/features/ui/components/CreateParametersDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/CreateParametersDrawer.tsx deleted file mode 100644 index 0518d792cb..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/CreateParametersDrawer.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { isEqual } from 'lodash-es'; -import ResizableDrawer from './common/ResizableDrawer/ResizableDrawer'; -import TextTabParameters from './tabs/text/TextTabParameters'; -import { createSelector } from '@reduxjs/toolkit'; -import { activeTabNameSelector, uiSelector } from '../store/uiSelectors'; -import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { - setShouldShowParametersPanel, - toggleParametersPanel, -} from '../store/uiSlice'; -import { memo } from 'react'; -import { Flex } from '@chakra-ui/react'; -import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent'; -import PinParametersPanelButton from './PinParametersPanelButton'; -import { Panel, PanelGroup } from 'react-resizable-panels'; -import CreateSidePanelPinned from './tabs/text/TextTabSettingsPinned'; -import CreateTextParameters from './tabs/text/TextTabParameters'; -import ResizeHandle from './tabs/ResizeHandle'; -import CreateImageSettings from './tabs/image/ImageTabSettings'; - -const selector = createSelector( - [uiSelector, activeTabNameSelector, lightboxSelector], - (ui, activeTabName, lightbox) => { - const { - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - } = ui; - const { isLightboxOpen } = lightbox; - - return { - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -const CreateParametersPanel = () => { - const dispatch = useAppDispatch(); - const { - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - } = useAppSelector(selector); - - const handleClosePanel = () => { - dispatch(setShouldShowParametersPanel(false)); - }; - - if (shouldPinParametersPanel) { - return null; - } - - return ( - - - - - - - - <> - - - - {shouldShowImageParameters && ( - <> - - - - - - )} - - - - - ); -}; - -export default memo(CreateParametersPanel); diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index 3cda3111b7..9ba1f7f58e 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -33,7 +33,6 @@ import { useTranslation } from 'react-i18next'; import { ResourceKey } from 'i18next'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import NodeEditor from 'features/nodes/components/NodeEditor'; -import GenerateWorkspace from './tabs/text/GenerateWorkspace'; import { createSelector } from '@reduxjs/toolkit'; import { BsLightningChargeFill } from 'react-icons/bs'; import { configSelector } from 'features/system/store/configSelectors'; @@ -43,7 +42,7 @@ import Scrollable from './common/Scrollable'; import TextTabParameters from './tabs/text/TextTabParameters'; import PinParametersPanelButton from './PinParametersPanelButton'; import ParametersSlide from './common/ParametersSlide'; -import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel'; +import GalleryDrawer from 'features/gallery/components/ImageGalleryPanel'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; import TextTabMain from './tabs/text/TextTabMain'; @@ -54,6 +53,7 @@ import UnifiedCanvasTab from './tabs/UnifiedCanvas/UnifiedCanvasTab'; import NodesTab from './tabs/Nodes/NodesTab'; import { FaImage } from 'react-icons/fa'; import ResizeHandle from './tabs/ResizeHandle'; +import ImageTab from './tabs/image/ImageTab'; export interface InvokeTabInfo { id: InvokeTabName; @@ -70,7 +70,7 @@ const tabs: InvokeTabInfo[] = [ { id: 'image', icon: , - content: , + content: , }, { id: 'unifiedCanvas', @@ -114,22 +114,6 @@ const InvokeTabs = () => { const dispatch = useAppDispatch(); - useHotkeys('1', () => { - dispatch(setActiveTab('text')); - }); - - useHotkeys('2', () => { - dispatch(setActiveTab('image')); - }); - - useHotkeys('3', () => { - dispatch(setActiveTab('unifiedCanvas')); - }); - - useHotkeys('4', () => { - dispatch(setActiveTab('nodes')); - }); - // Lightbox Hotkey useHotkeys( 'z', @@ -200,7 +184,7 @@ const InvokeTabs = () => { direction="horizontal" style={{ height: '100%', width: '100%' }} > - + {tabPanels} @@ -208,7 +192,13 @@ const InvokeTabs = () => { {shouldPinGallery && shouldShowGallery && ( <> - + diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx new file mode 100644 index 0000000000..72af8e6b14 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx @@ -0,0 +1,32 @@ +import { useAppSelector } from 'app/store/storeHooks'; +import { memo } from 'react'; +import { activeTabNameSelector } from '../store/uiSelectors'; +import TextTabParametersDrawer from './tabs/text/TextTabParametersDrawer'; +import { RootState } from 'app/store/store'; + +const ParametersDrawer = () => { + const activeTabName = useAppSelector(activeTabNameSelector); + const shouldPinParametersPanel = useAppSelector( + (state: RootState) => state.ui.shouldPinParametersPanel + ); + + if (shouldPinParametersPanel) { + return null; + } + + if (activeTabName === 'text') { + return ; + } + + if (activeTabName === 'image') { + return null; + } + + if (activeTabName === 'unifiedCanvas') { + return null; + } + + return null; +}; + +export default memo(ParametersDrawer); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx index b069ff31db..c2d1b3ddea 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx @@ -7,6 +7,11 @@ import { uiSelector } from 'features/ui/store/uiSelectors'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import ResizeHandle from '../ResizeHandle'; +import ImageTabParameters from './ImageTabParameters'; +import ImageTabImageParameters from './ImageTabImageParameters'; +import TextTabMain from '../text/TextTabMain'; +import InitialImagePreview from 'features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview'; +import InitialImageDisplay from 'features/parameters/components/AdvancedParameters/ImageToImage/InitialImageDisplay'; const selector = createSelector(uiSelector, (ui) => { const { @@ -38,20 +43,20 @@ const TextTab = () => { return ( {shouldPinParametersPanel && shouldShowParametersPanel && ( <> - {/* */} + @@ -59,15 +64,34 @@ const TextTab = () => { )} - { - dispatch(requestCanvasRescale()); - }} - > - {/* */} + + + + + + + { + dispatch(requestCanvasRescale()); + }} + > + + + ); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabImageParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabImageParameters.tsx new file mode 100644 index 0000000000..3d1fd3e950 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabImageParameters.tsx @@ -0,0 +1,61 @@ +import { Box, Flex } from '@chakra-ui/react'; +import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; +import { memo } from 'react'; +import OverlayScrollable from '../../common/OverlayScrollable'; +import ParamPositiveConditioning from 'features/parameters/components/Parameters/ParamPositiveConditioning'; +import ParamNegativeConditioning from 'features/parameters/components/Parameters/ParamNegativeConditioning'; +import { createSelector } from '@reduxjs/toolkit'; +import { uiSelector } from 'features/ui/store/uiSelectors'; +import { useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import ParamIterations from 'features/parameters/components/Parameters/ParamIterations'; +import ParamSteps from 'features/parameters/components/Parameters/ParamSteps'; +import ParamCFGScale from 'features/parameters/components/Parameters/ParamCFGScale'; +import ParamWidth from 'features/parameters/components/Parameters/ParamWidth'; +import ParamHeight from 'features/parameters/components/Parameters/ParamHeight'; +import ParamScheduler from 'features/parameters/components/Parameters/ParamScheduler'; +import ModelSelect from 'features/system/components/ModelSelect'; +import ParamSeedCollapse from 'features/parameters/components/Parameters/Seed/ParamSeedCollapse'; +import ParamVariationCollapse from 'features/parameters/components/Parameters/Variations/ParamVariationCollapse'; +import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/ParamNoiseCollapse'; +import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; +import ParamHiresCollapse from 'features/parameters/components/Parameters/Hires/ParamHiresCollapse'; +import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse'; +import InitialImagePreview from 'features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview'; +import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength'; +import ImageToImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageFit'; +import InitialImageButtons from 'common/components/ImageToImageButtons'; + +const selector = createSelector( + uiSelector, + (ui) => { + const { shouldUseSliders } = ui; + + return { shouldUseSliders }; + }, + defaultSelectorOptions +); + +const ImageTabParameters = () => { + const { shouldUseSliders } = useAppSelector(selector); + + return ( + + + + + + + + ); +}; + +export default memo(ImageTabParameters); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabParameters.tsx new file mode 100644 index 0000000000..09181b6669 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabParameters.tsx @@ -0,0 +1,113 @@ +import { Box, Flex } from '@chakra-ui/react'; +import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; +import { memo } from 'react'; +import OverlayScrollable from '../../common/OverlayScrollable'; +import ParamPositiveConditioning from 'features/parameters/components/Parameters/ParamPositiveConditioning'; +import ParamNegativeConditioning from 'features/parameters/components/Parameters/ParamNegativeConditioning'; +import { createSelector } from '@reduxjs/toolkit'; +import { uiSelector } from 'features/ui/store/uiSelectors'; +import { useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import ParamIterations from 'features/parameters/components/Parameters/ParamIterations'; +import ParamSteps from 'features/parameters/components/Parameters/ParamSteps'; +import ParamCFGScale from 'features/parameters/components/Parameters/ParamCFGScale'; +import ParamWidth from 'features/parameters/components/Parameters/ParamWidth'; +import ParamHeight from 'features/parameters/components/Parameters/ParamHeight'; +import ParamScheduler from 'features/parameters/components/Parameters/ParamScheduler'; +import ModelSelect from 'features/system/components/ModelSelect'; +import ParamSeedCollapse from 'features/parameters/components/Parameters/Seed/ParamSeedCollapse'; +import ParamVariationCollapse from 'features/parameters/components/Parameters/Variations/ParamVariationCollapse'; +import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/ParamNoiseCollapse'; +import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; +import ParamHiresCollapse from 'features/parameters/components/Parameters/Hires/ParamHiresCollapse'; +import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse'; +import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength'; +import ImageToImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageFit'; + +const selector = createSelector( + uiSelector, + (ui) => { + const { shouldUseSliders } = ui; + + return { shouldUseSliders }; + }, + defaultSelectorOptions +); + +const ImageTabParameters = () => { + const { shouldUseSliders } = useAppSelector(selector); + + return ( + + + + + + + {shouldUseSliders ? ( + + + + + + + + + + + + + + + + + + ) : ( + + + + + + + + + + + + + + + + + + + + )} + + + + + + + + + ); +}; + +export default memo(ImageTabParameters); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabSettings.tsx deleted file mode 100644 index ac80ae77f3..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabSettings.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { memo } from 'react'; -import OverlayScrollable from '../../common/OverlayScrollable'; -import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings'; -import { - Box, - ButtonGroup, - Collapse, - Flex, - Heading, - HStack, - Image, - Spacer, - useDisclosure, - VStack, -} from '@chakra-ui/react'; -import { motion } from 'framer-motion'; - -import IAIButton from 'common/components/IAIButton'; -import ImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageFit'; -import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength'; -import IAIIconButton from 'common/components/IAIIconButton'; - -import { useTranslation } from 'react-i18next'; -import { useState } from 'react'; -import { FaUndo, FaUpload } from 'react-icons/fa'; -import ImagePromptHeading from 'common/components/ImageToImageSettingsHeader'; -import InitialImagePreview from 'features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview'; - -const CreateImageSettings = () => { - return ( - - - - - - - - - ); -}; - -export default memo(CreateImageSettings); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx index c26700a39d..b28cac2581 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx @@ -8,7 +8,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import TextTabMain from './TextTabMain'; import ResizeHandle from '../ResizeHandle'; -import TextTabSettings from './TextTabParameters'; +import TextTabParameters from './TextTabParameters'; const selector = createSelector(uiSelector, (ui) => { const { @@ -47,13 +47,13 @@ const TextTab = () => { {shouldPinParametersPanel && shouldShowParametersPanel && ( <> - + @@ -61,14 +61,7 @@ const TextTab = () => { )} - { - dispatch(requestCanvasRescale()); - }} - > + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx index 36c5f9bd94..226d145e34 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx @@ -1,6 +1,5 @@ import { Box, Flex } from '@chakra-ui/react'; import CurrentImageDisplay from 'features/gallery/components/CurrentImageDisplay'; -import ProgressImagePreview from 'features/parameters/components/ProgressImagePreview'; const TextTabMain = () => { return ( diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParametersDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParametersDrawer.tsx new file mode 100644 index 0000000000..d1de868ec7 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParametersDrawer.tsx @@ -0,0 +1,73 @@ +import { isEqual } from 'lodash-es'; +import ResizableDrawer from '../../common/ResizableDrawer/ResizableDrawer'; +import TextTabParameters from './TextTabParameters'; +import { createSelector } from '@reduxjs/toolkit'; +import { activeTabNameSelector, uiSelector } from '../../../store/uiSelectors'; +import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { setShouldShowParametersPanel } from '../../../store/uiSlice'; +import { memo } from 'react'; +import { Flex } from '@chakra-ui/react'; +import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent'; +import PinParametersPanelButton from '../../PinParametersPanelButton'; +import { Panel, PanelGroup } from 'react-resizable-panels'; + +const selector = createSelector( + [uiSelector, activeTabNameSelector, lightboxSelector], + (ui, activeTabName, lightbox) => { + const { + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldShowImageParameters, + } = ui; + + const { isLightboxOpen } = lightbox; + + return { + activeTabName, + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldShowImageParameters, + }; + }, + { + memoizeOptions: { + resultEqualityCheck: isEqual, + }, + } +); + +const TextTabParametersDrawer = () => { + const dispatch = useAppDispatch(); + const { shouldPinParametersPanel, shouldShowParametersPanel } = + useAppSelector(selector); + + const handleClosePanel = () => { + dispatch(setShouldShowParametersPanel(false)); + }; + + return ( + + + + + + + + + + ); +}; + +export default memo(TextTabParametersDrawer); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabSettingsPinned.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabSettingsPinned.tsx deleted file mode 100644 index 9d58eed899..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabSettingsPinned.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { uiSelector } from 'features/ui/store/uiSelectors'; -import { memo } from 'react'; -import { Panel } from 'react-resizable-panels'; -import CreateTextParameters from './TextTabParameters'; -import PinParametersPanelButton from '../../PinParametersPanelButton'; -import ResizeHandle from '../ResizeHandle'; -import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import CreateImageSettings from '../image/ImageTabSettings'; - -const selector = createSelector( - uiSelector, - (ui) => { - const { - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - } = ui; - - return { - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - }; - }, - defaultSelectorOptions -); - -const CreateSidePanelPinned = () => { - const dispatch = useAppDispatch(); - const { - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - } = useAppSelector(selector); - return ( - <> - - - - - {shouldShowImageParameters && ( - <> - - - - - - )} - - - ); -}; - -export default memo(CreateSidePanelPinned); diff --git a/invokeai/frontend/web/tsconfig.json b/invokeai/frontend/web/tsconfig.json index fa8f1fd262..3c777b9318 100644 --- a/invokeai/frontend/web/tsconfig.json +++ b/invokeai/frontend/web/tsconfig.json @@ -24,7 +24,8 @@ "src/**/*.ts", "src/**/*.tsx", "*.d.ts", - "src/app/store/middleware/listenerMiddleware" + "src/app/store/middleware/listenerMiddleware", + "src/features/nodes/util/edgeBuilders" ], "exclude": ["src/services/fixtures/*", "node_modules", "dist"], "references": [{ "path": "./tsconfig.node.json" }] From 77f26907115191e450d74b750727041021144246 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 May 2023 00:12:54 +1000 Subject: [PATCH 43/66] fix(ui): remove duplicate gallery --- .../components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx index 65122af536..d0f509b651 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx @@ -49,6 +49,7 @@ const UnifiedCanvasTab = () => { {shouldPinParametersPanel && shouldShowParametersPanel && ( <> { )} - {shouldPinGallery && shouldShowGallery && ( - <> - - - - - - )} ); }; From ec6c8e2a38bf48b3d5a1e7631414b2fa817d0c45 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 May 2023 16:03:56 +1000 Subject: [PATCH 44/66] feat(ui): wip layout --- .../common/components/ImageToImageButtons.tsx | 7 +- .../components/CurrentImageButtons.tsx | 10 +- .../ImageToImage/InitialImageDisplay.tsx | 2 + .../src/features/ui/components/InvokeTabs.tsx | 26 +-- .../features/ui/components/InvokeWorkarea.tsx | 84 ---------- .../ui/components/ParametersDrawer.tsx | 111 ++++++++++--- .../ui/components/ParametersPanel.tsx | 155 ------------------ .../ui/components/common/ParametersSlide.tsx | 143 ---------------- .../ResizableDrawer/ResizableDrawer.tsx | 33 +--- .../components/common/ResizableDrawer/util.ts | 86 +++++----- .../ui/components/tabs/ResizeHandle.tsx | 9 +- .../UnifiedCanvas/UnifiedCanvasWorkarea.tsx | 2 +- .../ui/components/tabs/image/ImageTab.tsx | 71 ++++---- .../ui/components/tabs/text/TextTab.tsx | 43 ++--- .../ui/components/tabs/text/TextTabMain.tsx | 4 +- .../tabs/text/TextTabParameters.tsx | 2 +- .../tabs/text/TextTabParametersDrawer.tsx | 73 --------- .../frontend/web/src/theme/util/constants.ts | 4 +- 18 files changed, 209 insertions(+), 656 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/ui/components/InvokeWorkarea.tsx delete mode 100644 invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx delete mode 100644 invokeai/frontend/web/src/features/ui/components/common/ParametersSlide.tsx delete mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParametersDrawer.tsx diff --git a/invokeai/frontend/web/src/common/components/ImageToImageButtons.tsx b/invokeai/frontend/web/src/common/components/ImageToImageButtons.tsx index 00a6e70c1d..315571b6e7 100644 --- a/invokeai/frontend/web/src/common/components/ImageToImageButtons.tsx +++ b/invokeai/frontend/web/src/common/components/ImageToImageButtons.tsx @@ -23,16 +23,11 @@ const InitialImageButtons = () => { } aria-label={t('accessibility.reset')} onClick={handleResetInitialImage} /> - } - aria-label={t('common.upload')} - /> + } aria-label={t('common.upload')} /> ); diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index c9c472a9b8..b3452f5587 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -410,7 +410,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { }} {...props} > - + { )} - + } tooltip={`${t('parameters.usePrompt')} (P)`} @@ -528,7 +528,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { {(isUpscalingEnabled || isFaceRestoreEnabled) && ( - + {isFaceRestoreEnabled && ( { )} - + } tooltip={`${t('parameters.info')} (I)`} @@ -603,7 +603,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { /> - + } diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImageDisplay.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImageDisplay.tsx index 236146099f..c9c6e525b4 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImageDisplay.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImageDisplay.tsx @@ -14,6 +14,8 @@ const InitialImageDisplay = () => { borderRadius: 'base', alignItems: 'center', justifyContent: 'center', + bg: 'base.850', + p: 4, }} > { - const { shouldPinParametersPanel } = ui; - return { - shouldPinParametersPanel, - activeTabName, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -type InvokeWorkareaProps = BoxProps & { - parametersPanelContent: ReactNode; - children: ReactNode; -}; - -const InvokeWorkarea = (props: InvokeWorkareaProps) => { - const { parametersPanelContent, children, ...rest } = props; - const dispatch = useAppDispatch(); - const { activeTabName } = useAppSelector(workareaSelector); - - const getImageByUuid = useGetImageByUuid(); - - const handleDrop = (e: DragEvent) => { - const uuid = e.dataTransfer.getData('invokeai/imageUuid'); - const image = getImageByUuid(uuid); - if (!image) return; - if (activeTabName === 'unifiedCanvas') { - dispatch(setInitialCanvasImage(image)); - } - }; - - return ( - - {parametersPanelContent} - - - {children} - - - - ); -}; - -export default InvokeWorkarea; diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx index 72af8e6b14..512d43bcd6 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx @@ -1,32 +1,93 @@ -import { useAppSelector } from 'app/store/storeHooks'; -import { memo } from 'react'; -import { activeTabNameSelector } from '../store/uiSelectors'; +import { isEqual } from 'lodash-es'; +import { createSelector } from '@reduxjs/toolkit'; +import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { PropsWithChildren, memo, useMemo } from 'react'; +import { Box, Flex } from '@chakra-ui/react'; +import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent'; +import OverlayScrollable from './common/OverlayScrollable'; +import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; +import { + activeTabNameSelector, + uiSelector, +} from 'features/ui/store/uiSelectors'; +import { setShouldShowParametersPanel } from 'features/ui/store/uiSlice'; +import ResizableDrawer from './common/ResizableDrawer/ResizableDrawer'; +import PinParametersPanelButton from './PinParametersPanelButton'; import TextTabParametersDrawer from './tabs/text/TextTabParametersDrawer'; -import { RootState } from 'app/store/store'; +import TextTabParameters from './tabs/text/TextTabParameters'; +import ImageTabParameters from './tabs/image/ImageTabParameters'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; + +const selector = createSelector( + [uiSelector, activeTabNameSelector, lightboxSelector], + (ui, activeTabName, lightbox) => { + const { + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldShowImageParameters, + } = ui; + + const { isLightboxOpen } = lightbox; + + return { + activeTabName, + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldShowImageParameters, + }; + }, + defaultSelectorOptions +); const ParametersDrawer = () => { - const activeTabName = useAppSelector(activeTabNameSelector); - const shouldPinParametersPanel = useAppSelector( - (state: RootState) => state.ui.shouldPinParametersPanel + const dispatch = useAppDispatch(); + const { shouldPinParametersPanel, shouldShowParametersPanel, activeTabName } = + useAppSelector(selector); + + const handleClosePanel = () => { + dispatch(setShouldShowParametersPanel(false)); + }; + + const drawerContent = useMemo(() => { + if (activeTabName === 'text') { + return ; + } + + if (activeTabName === 'image') { + return ; + } + + if (activeTabName === 'unifiedCanvas') { + return null; + } + }, [activeTabName]); + + return ( + + + + + + + + + {drawerContent} + + + + ); - - if (shouldPinParametersPanel) { - return null; - } - - if (activeTabName === 'text') { - return ; - } - - if (activeTabName === 'image') { - return null; - } - - if (activeTabName === 'unifiedCanvas') { - return null; - } - - return null; }; export default memo(ParametersDrawer); diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx deleted file mode 100644 index b36199e263..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; - -import { memo, ReactNode } from 'react'; - -import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; -import ResizableDrawer from 'features/ui/components/common/ResizableDrawer/ResizableDrawer'; -import { - setShouldShowParametersPanel, - toggleParametersPanel, - togglePinParametersPanel, -} from 'features/ui/store/uiSlice'; -import { useHotkeys } from 'react-hotkeys-hook'; -import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent'; -import Scrollable from './common/Scrollable'; -import PinParametersPanelButton from './PinParametersPanelButton'; -import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; -import { createSelector } from '@reduxjs/toolkit'; -import { activeTabNameSelector, uiSelector } from '../store/uiSelectors'; -import { isEqual } from 'lodash-es'; -import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; -import useResolution from 'common/hooks/useResolution'; - -const parametersPanelSelector = createSelector( - [uiSelector, activeTabNameSelector, lightboxSelector], - (ui, activeTabName, lightbox) => { - const { shouldPinParametersPanel, shouldShowParametersPanel } = ui; - const { isLightboxOpen } = lightbox; - - return { - shouldPinParametersPanel, - shouldShowParametersPanel, - isResizable: activeTabName !== 'unifiedCanvas', - isLightboxOpen, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -type ParametersPanelProps = { - children: ReactNode; -}; - -const ParametersPanel = ({ children }: ParametersPanelProps) => { - const dispatch = useAppDispatch(); - - const { - shouldPinParametersPanel, - shouldShowParametersPanel, - isResizable, - isLightboxOpen, - } = useAppSelector(parametersPanelSelector); - - const closeParametersPanel = () => { - dispatch(setShouldShowParametersPanel(false)); - }; - - const resolution = useResolution(); - - useHotkeys( - 'o', - () => { - dispatch(toggleParametersPanel()); - shouldPinParametersPanel && dispatch(requestCanvasRescale()); - }, - { enabled: () => !isLightboxOpen }, - [shouldPinParametersPanel, isLightboxOpen] - ); - - useHotkeys( - 'esc', - () => { - dispatch(setShouldShowParametersPanel(false)); - }, - { - enabled: () => !shouldPinParametersPanel, - preventDefault: true, - }, - [shouldPinParametersPanel] - ); - - useHotkeys( - 'shift+o', - () => { - dispatch(togglePinParametersPanel()); - dispatch(requestCanvasRescale()); - }, - [] - ); - - const parametersPanelContent = () => { - return ( - - {!shouldPinParametersPanel && ( - - - {resolution == 'desktop' && } - - )} - {children} - {shouldPinParametersPanel && resolution == 'desktop' && ( - - )} - - ); - }; - - const resizableParametersPanelContent = () => { - return ( - - {parametersPanelContent()} - - ); - }; - - const renderParametersPanel = () => { - if (['mobile', 'tablet'].includes(resolution)) - return parametersPanelContent(); - return resizableParametersPanelContent(); - }; - - return renderParametersPanel(); -}; - -export default memo(ParametersPanel); diff --git a/invokeai/frontend/web/src/features/ui/components/common/ParametersSlide.tsx b/invokeai/frontend/web/src/features/ui/components/common/ParametersSlide.tsx deleted file mode 100644 index 3342a9338b..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/common/ParametersSlide.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { Box, Flex, useOutsideClick } from '@chakra-ui/react'; -import { Slide } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { uiSelector } from 'features/ui/store/uiSelectors'; -import { isEqual } from 'lodash-es'; -import { memo, PropsWithChildren, useRef } from 'react'; -import PinParametersPanelButton from 'features/ui/components/PinParametersPanelButton'; -import { - setShouldShowParametersPanel, - toggleParametersPanel, - togglePinParametersPanel, -} from 'features/ui/store/uiSlice'; -import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent'; -import Scrollable from 'features/ui/components/common/Scrollable'; -import { useLangDirection } from 'features/ui/hooks/useDirection'; -import { useHotkeys } from 'react-hotkeys-hook'; -import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; -import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel'; - -const parametersSlideSelector = createSelector( - [uiSelector, generationSelector], - (ui, generation) => { - const { shouldPinParametersPanel, shouldShowParametersPanel } = ui; - const { isImageToImageEnabled } = generation; - - return { - shouldPinParametersPanel, - shouldShowParametersPanel, - isImageToImageEnabled, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -type ParametersSlideProps = PropsWithChildren; - -const ParametersSlide = (props: ParametersSlideProps) => { - const dispatch = useAppDispatch(); - - const { - shouldShowParametersPanel, - isImageToImageEnabled, - shouldPinParametersPanel, - } = useAppSelector(parametersSlideSelector); - - const langDirection = useLangDirection(); - - const outsideClickRef = useRef(null); - - const closeParametersPanel = () => { - dispatch(setShouldShowParametersPanel(false)); - }; - - useOutsideClick({ - ref: outsideClickRef, - handler: () => { - closeParametersPanel(); - }, - enabled: shouldShowParametersPanel && !shouldPinParametersPanel, - }); - - useHotkeys( - 'o', - () => { - dispatch(toggleParametersPanel()); - shouldPinParametersPanel && dispatch(requestCanvasRescale()); - }, - [shouldPinParametersPanel] - ); - - useHotkeys( - 'esc', - () => { - dispatch(setShouldShowParametersPanel(false)); - }, - { - enabled: () => !shouldPinParametersPanel, - preventDefault: true, - }, - [shouldPinParametersPanel] - ); - - useHotkeys( - 'shift+o', - () => { - dispatch(togglePinParametersPanel()); - dispatch(requestCanvasRescale()); - }, - [] - ); - - return ( - - - - - - - - - {props.children} - - - - - - ); -}; - -export default memo(ParametersSlide); diff --git a/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx index d9fd765656..993dd45adb 100644 --- a/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx +++ b/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx @@ -20,7 +20,6 @@ import { getMinMaxDimensions, getSlideDirection, getStyles, - parseAndPadSize, } from './util'; type ResizableDrawerProps = ResizableProps & { @@ -72,7 +71,7 @@ const ResizableDrawer = ({ () => initialWidth ?? minWidth ?? - (['left', 'right'].includes(direction) ? 500 : '100%'), + (['left', 'right'].includes(direction) ? 'auto' : '100%'), [initialWidth, minWidth, direction] ); @@ -80,7 +79,7 @@ const ResizableDrawer = ({ () => initialHeight ?? minHeight ?? - (['top', 'bottom'].includes(direction) ? 500 : '100%'), + (['top', 'bottom'].includes(direction) ? 'auto' : '100%'), [initialHeight, minHeight, direction] ); @@ -105,18 +104,6 @@ const ResizableDrawer = ({ () => getMinMaxDimensions({ direction, - // minWidth: isResizable - // ? parseAndPadSize(minWidth, 18) - // : parseAndPadSize(minWidth), - // maxWidth: isResizable - // ? parseAndPadSize(maxWidth, 18) - // : parseAndPadSize(maxWidth), - // minHeight: isResizable - // ? parseAndPadSize(minHeight, 18) - // : parseAndPadSize(minHeight), - // maxHeight: isResizable - // ? parseAndPadSize(maxHeight, 18) - // : parseAndPadSize(maxHeight), minWidth, maxWidth, minHeight, @@ -154,24 +141,8 @@ const ResizableDrawer = ({ { - if (!isResizable) { - return { containerStyles: {}, handleStyles: {} }; - } + // if (!isResizable) { + // return { containerStyles: {}, handleStyles: {} }; + // } // Calculate the positioning offset of the handle hitbox so it is centered over the handle const handleOffset = `calc((2 * ${HANDLE_INTERACT_PADDING} + ${HANDLE_WIDTH}) / -2)`; @@ -190,13 +187,15 @@ export const getStyles = ({ borderBottomWidth: HANDLE_WIDTH, paddingBottom: HANDLE_PADDING, }, - handleStyles: { - top: { - paddingTop: HANDLE_INTERACT_PADDING, - paddingBottom: HANDLE_INTERACT_PADDING, - bottom: handleOffset, - }, - }, + handleStyles: isResizable + ? { + top: { + paddingTop: HANDLE_INTERACT_PADDING, + paddingBottom: HANDLE_INTERACT_PADDING, + bottom: handleOffset, + }, + } + : {}, }; } @@ -206,13 +205,15 @@ export const getStyles = ({ borderInlineEndWidth: HANDLE_WIDTH, paddingInlineEnd: HANDLE_PADDING, }, - handleStyles: { - right: { - paddingInlineStart: HANDLE_INTERACT_PADDING, - paddingInlineEnd: HANDLE_INTERACT_PADDING, - insetInlineEnd: handleOffset, - }, - }, + handleStyles: isResizable + ? { + right: { + paddingInlineStart: HANDLE_INTERACT_PADDING, + paddingInlineEnd: HANDLE_INTERACT_PADDING, + insetInlineEnd: handleOffset, + }, + } + : {}, }; } @@ -222,13 +223,15 @@ export const getStyles = ({ borderTopWidth: HANDLE_WIDTH, paddingTop: HANDLE_PADDING, }, - handleStyles: { - bottom: { - paddingTop: HANDLE_INTERACT_PADDING, - paddingBottom: HANDLE_INTERACT_PADDING, - top: handleOffset, - }, - }, + handleStyles: isResizable + ? { + bottom: { + paddingTop: HANDLE_INTERACT_PADDING, + paddingBottom: HANDLE_INTERACT_PADDING, + top: handleOffset, + }, + } + : {}, }; } @@ -238,13 +241,15 @@ export const getStyles = ({ borderInlineStartWidth: HANDLE_WIDTH, paddingInlineStart: HANDLE_PADDING, }, - handleStyles: { - left: { - paddingInlineStart: HANDLE_INTERACT_PADDING, - paddingInlineEnd: HANDLE_INTERACT_PADDING, - insetInlineStart: handleOffset, - }, - }, + handleStyles: isResizable + ? { + left: { + paddingInlineStart: HANDLE_INTERACT_PADDING, + paddingInlineEnd: HANDLE_INTERACT_PADDING, + insetInlineStart: handleOffset, + }, + } + : {}, }; } @@ -276,16 +281,3 @@ export const getSlideDirection = ( return 'left'; }; - -// Hack to correct the width of panels while pinned and unpinned, due to different padding in pinned vs unpinned -export const parseAndPadSize = (size?: number, padding?: number) => { - if (!size) { - return undefined; - } - - if (!padding) { - return size; - } - - return size + padding; -}; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx index c62f82600b..d53a4d4fef 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx @@ -1,13 +1,13 @@ -import { Box, Flex } from '@chakra-ui/react'; +import { Box, Flex, FlexProps } from '@chakra-ui/react'; import { memo } from 'react'; import { PanelResizeHandle } from 'react-resizable-panels'; -type ResizeHandleProps = { +type ResizeHandleProps = FlexProps & { direction?: 'horizontal' | 'vertical'; }; const ResizeHandle = (props: ResizeHandleProps) => { - const { direction = 'horizontal' } = props; + const { direction = 'horizontal', ...rest } = props; if (direction === 'horizontal') { return ( @@ -19,12 +19,14 @@ const ResizeHandle = (props: ResizeHandleProps) => { justifyContent: 'center', alignItems: 'center', }} + {...rest} > ); } + return ( { justifyContent: 'center', alignItems: 'center', }} + {...rest} > diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx index 53a7ca0d76..455ff4a32b 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx @@ -4,7 +4,7 @@ import { memo } from 'react'; import PinParametersPanelButton from '../../PinParametersPanelButton'; import { RootState } from 'app/store/store'; import Scrollable from '../../common/Scrollable'; -import ParametersSlide from '../../common/ParametersSlide'; +import ParametersSlide from '../../ParametersDrawer'; import UnifiedCanvasParameters from './UnifiedCanvasParameters'; import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; import UnifiedCanvasContent from './UnifiedCanvasContent'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx index c2d1b3ddea..b906ec4d97 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx @@ -1,6 +1,10 @@ -import { Portal, TabPanel } from '@chakra-ui/react'; -import { memo } from 'react'; -import { Panel, PanelGroup } from 'react-resizable-panels'; +import { Box, Flex, Portal, TabPanel } from '@chakra-ui/react'; +import { memo, useCallback, useRef } from 'react'; +import { + ImperativePanelHandle, + Panel, + PanelGroup, +} from 'react-resizable-panels'; import PinParametersPanelButton from '../../PinParametersPanelButton'; import { createSelector } from '@reduxjs/toolkit'; import { uiSelector } from 'features/ui/store/uiSelectors'; @@ -12,6 +16,8 @@ import ImageTabImageParameters from './ImageTabImageParameters'; import TextTabMain from '../text/TextTabMain'; import InitialImagePreview from 'features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview'; import InitialImageDisplay from 'features/parameters/components/AdvancedParameters/ImageToImage/InitialImageDisplay'; +import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; +import { ImperativePanelGroupHandle } from 'react-resizable-panels'; const selector = createSelector(uiSelector, (ui) => { const { @@ -33,6 +39,16 @@ const selector = createSelector(uiSelector, (ui) => { const TextTab = () => { const dispatch = useAppDispatch(); + const panelGroupRef = useRef(null); + + const handleDoubleClickHandle = useCallback(() => { + if (!panelGroupRef.current) { + return; + } + + panelGroupRef.current.setLayout([50, 50]); + }, []); + const { shouldPinGallery, shouldShowGallery, @@ -42,36 +58,31 @@ const TextTab = () => { } = useAppSelector(selector); return ( - + {shouldPinParametersPanel && shouldShowParametersPanel && ( - <> - - - - - - + + + + )} - + { > - + { - - + + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx index b28cac2581..4582a8a242 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx @@ -1,4 +1,4 @@ -import { Portal, TabPanel } from '@chakra-ui/react'; +import { Box, Flex, Portal, TabPanel } from '@chakra-ui/react'; import { memo } from 'react'; import { Panel, PanelGroup } from 'react-resizable-panels'; import PinParametersPanelButton from '../../PinParametersPanelButton'; @@ -9,6 +9,7 @@ import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvas import TextTabMain from './TextTabMain'; import ResizeHandle from '../ResizeHandle'; import TextTabParameters from './TextTabParameters'; +import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; const selector = createSelector(uiSelector, (ui) => { const { @@ -39,32 +40,24 @@ const TextTab = () => { } = useAppSelector(selector); return ( - + {shouldPinParametersPanel && shouldShowParametersPanel && ( - <> - - - - - - + + + + )} - - - - + + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx index 226d145e34..7970bb13f4 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx @@ -9,12 +9,12 @@ const TextTabMain = () => { width: '100%', height: '100%', borderRadius: 'base', - // bg: 'base.850', + bg: 'base.850', + p: 4, }} > { flexDirection: 'column', h: 'full', w: 'full', - position: 'absolute', }} > diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParametersDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParametersDrawer.tsx deleted file mode 100644 index d1de868ec7..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParametersDrawer.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { isEqual } from 'lodash-es'; -import ResizableDrawer from '../../common/ResizableDrawer/ResizableDrawer'; -import TextTabParameters from './TextTabParameters'; -import { createSelector } from '@reduxjs/toolkit'; -import { activeTabNameSelector, uiSelector } from '../../../store/uiSelectors'; -import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { setShouldShowParametersPanel } from '../../../store/uiSlice'; -import { memo } from 'react'; -import { Flex } from '@chakra-ui/react'; -import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent'; -import PinParametersPanelButton from '../../PinParametersPanelButton'; -import { Panel, PanelGroup } from 'react-resizable-panels'; - -const selector = createSelector( - [uiSelector, activeTabNameSelector, lightboxSelector], - (ui, activeTabName, lightbox) => { - const { - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - } = ui; - - const { isLightboxOpen } = lightbox; - - return { - activeTabName, - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -const TextTabParametersDrawer = () => { - const dispatch = useAppDispatch(); - const { shouldPinParametersPanel, shouldShowParametersPanel } = - useAppSelector(selector); - - const handleClosePanel = () => { - dispatch(setShouldShowParametersPanel(false)); - }; - - return ( - - - - - - - - - - ); -}; - -export default memo(TextTabParametersDrawer); diff --git a/invokeai/frontend/web/src/theme/util/constants.ts b/invokeai/frontend/web/src/theme/util/constants.ts index 38b859913c..c73209ee75 100644 --- a/invokeai/frontend/web/src/theme/util/constants.ts +++ b/invokeai/frontend/web/src/theme/util/constants.ts @@ -11,7 +11,7 @@ export const APP_GALLERY_POPOVER_HEIGHT = `calc(100vh - (${APP_CONTENT_HEIGHT_CU export const APP_METADATA_HEIGHT = `calc(100vh - (${APP_CONTENT_HEIGHT_CUTOFF} + 4.4rem))`; // this is in pixels -export const PARAMETERS_PANEL_WIDTH = 384; +// export const PARAMETERS_PANEL_WIDTH = 384; // do not touch ffs export const APP_TEXT_TO_IMAGE_HEIGHT = @@ -19,3 +19,5 @@ export const APP_TEXT_TO_IMAGE_HEIGHT = // option bar export const OPTIONS_BAR_MAX_WIDTH = '22.5rem'; + +export const PARAMETERS_PANEL_WIDTH = '30rem'; From c565812723c47b39a71c603fcd8267aec1a6d1e3 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 May 2023 18:12:34 +1000 Subject: [PATCH 45/66] feat(ui): organize parameters panels --- .../components/CurrentImageButtons.tsx | 4 +- .../AccordionItems/InvokeAccordionItem.tsx | 37 ---- .../BoundingBox/BoundingBoxSettings.tsx | 113 ------------ .../Canvas/InfillAndScalingSettings.tsx | 174 ------------------ .../SeamCorrection/SeamCorrectionSettings.tsx | 18 -- .../components/AnimatedImageToImagePanel.tsx | 32 ---- .../components/MainParameters/MainHeight.tsx | 45 ----- .../MainParameters/MainSettings.tsx | 56 ------ .../components/MainParameters/MainWidth.tsx | 46 ----- .../BoundingBox/ParamBoundingBoxCollapse.tsx | 26 +++ .../BoundingBox/ParamBoundingBoxHeight.tsx | 64 +++++++ .../BoundingBox/ParamBoundingBoxWidth.tsx | 64 +++++++ .../ParamInfillAndScalingCollapse.tsx | 33 ++++ .../InfillAndScaling/ParamInfillMethod.tsx | 49 +++++ .../InfillAndScaling/ParamInfillTilesize.tsx | 58 ++++++ .../ParamScaleBeforeProcessing.tsx | 49 +++++ .../InfillAndScaling/ParamScaledHeight.tsx | 68 +++++++ .../InfillAndScaling/ParamScaledWidth.tsx | 66 +++++++ .../Canvas/SeamCorrection/ParamSeamBlur.tsx | 0 .../ParamSeamCorrectionCollapse.tsx | 28 +++ .../Canvas/SeamCorrection/ParamSeamSize.tsx | 0 .../Canvas/SeamCorrection/ParamSeamSteps.tsx | 0 .../SeamCorrection/ParamSeamStrength.tsx | 0 .../Parameters/{ => Core}/ParamCFGScale.tsx | 0 .../Parameters/{ => Core}/ParamHeight.tsx | 0 .../Parameters/{ => Core}/ParamIterations.tsx | 0 .../{ => Core}/ParamNegativeConditioning.tsx | 0 .../{ => Core}/ParamPositiveConditioning.tsx | 0 .../ParamSampler.tsx} | 4 +- .../Parameters/{ => Core}/ParamSteps.tsx | 0 .../Parameters/{ => Core}/ParamWidth.tsx | 0 .../FaceRestore/CodeformerFidelity.tsx | 0 .../FaceRestore/FaceRestoreSettings.tsx | 0 .../FaceRestore/FaceRestoreStrength.tsx | 0 .../FaceRestore/FaceRestoreToggle.tsx | 0 .../FaceRestore/FaceRestoreType.tsx | 0 .../ImageToImage/ImageToImageFit.tsx | 0 .../ImageToImage/ImageToImageSettings.tsx | 0 .../ImageToImage/ImageToImageStrength.tsx | 0 .../ImageToImage/ImageToImageToggle.tsx | 0 .../ImageToImage/InitialImageDisplay.tsx | 0 .../ImageToImage/InitialImagePreview.tsx | 0 .../components/Parameters/OtherSettings.tsx | 18 -- .../Upscale/UpscaleDenoisingStrength.tsx | 0 .../Upscale/UpscaleScale.tsx | 0 .../Upscale/UpscaleSettings.tsx | 0 .../Upscale/UpscaleStrength.tsx | 0 .../Upscale/UpscaleToggle.tsx | 0 .../_ImageDimensions}/AspectRatioPreview.tsx | 0 .../_ImageDimensions}/DimensionsSettings.tsx | 0 .../components/ParametersAccordion.tsx | 94 ---------- .../ui/components/ParametersDrawer.tsx | 20 +- .../ui/components/ParametersPinnedWrapper.tsx | 39 ++++ .../components/common/OverlayScrollable.tsx | 2 +- .../UnifiedCanvas/UnifiedCanvasContent.tsx | 5 +- .../UnifiedCanvasCoreParameters.tsx | 83 +++++++++ .../UnifiedCanvas/UnifiedCanvasParameters.tsx | 117 +++--------- .../tabs/UnifiedCanvas/UnifiedCanvasTab.tsx | 48 ++--- .../ui/components/tabs/image/ImageTab.tsx | 18 +- .../tabs/image/ImageTabCoreParameters.tsx | 83 +++++++++ .../tabs/image/ImageTabParameters.tsx | 115 ++---------- .../ui/components/tabs/text/TextTab.tsx | 15 +- .../tabs/text/TextTabCoreParameters.tsx | 77 ++++++++ .../tabs/text/TextTabParameters.tsx | 108 ++--------- 64 files changed, 880 insertions(+), 996 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/parameters/components/AccordionItems/InvokeAccordionItem.tsx delete mode 100644 invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/BoundingBox/BoundingBoxSettings.tsx delete mode 100644 invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/InfillAndScalingSettings.tsx delete mode 100644 invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamCorrectionSettings.tsx delete mode 100644 invokeai/frontend/web/src/features/parameters/components/AnimatedImageToImagePanel.tsx delete mode 100644 invokeai/frontend/web/src/features/parameters/components/MainParameters/MainHeight.tsx delete mode 100644 invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSettings.tsx delete mode 100644 invokeai/frontend/web/src/features/parameters/components/MainParameters/MainWidth.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxCollapse.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillMethod.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillTilesize.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledHeight.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledWidth.tsx rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/Canvas/SeamCorrection/ParamSeamBlur.tsx (100%) create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamCorrectionCollapse.tsx rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/Canvas/SeamCorrection/ParamSeamSize.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/Canvas/SeamCorrection/ParamSeamSteps.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/Canvas/SeamCorrection/ParamSeamStrength.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/Parameters/{ => Core}/ParamCFGScale.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/Parameters/{ => Core}/ParamHeight.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/Parameters/{ => Core}/ParamIterations.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/Parameters/{ => Core}/ParamNegativeConditioning.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/Parameters/{ => Core}/ParamPositiveConditioning.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/Parameters/{ParamScheduler.tsx => Core/ParamSampler.tsx} (92%) rename invokeai/frontend/web/src/features/parameters/components/Parameters/{ => Core}/ParamSteps.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/Parameters/{ => Core}/ParamWidth.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/FaceRestore/CodeformerFidelity.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/FaceRestore/FaceRestoreSettings.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/FaceRestore/FaceRestoreStrength.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/FaceRestore/FaceRestoreToggle.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/FaceRestore/FaceRestoreType.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/ImageToImage/ImageToImageFit.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/ImageToImage/ImageToImageSettings.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/ImageToImage/ImageToImageStrength.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/ImageToImage/ImageToImageToggle.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/ImageToImage/InitialImageDisplay.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/ImageToImage/InitialImagePreview.tsx (100%) delete mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/OtherSettings.tsx rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/Upscale/UpscaleDenoisingStrength.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/Upscale/UpscaleScale.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/Upscale/UpscaleSettings.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/Upscale/UpscaleStrength.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{AdvancedParameters => Parameters}/Upscale/UpscaleToggle.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{ImageDimensions => Parameters/_ImageDimensions}/AspectRatioPreview.tsx (100%) rename invokeai/frontend/web/src/features/parameters/components/{ImageDimensions => Parameters/_ImageDimensions}/DimensionsSettings.tsx (100%) delete mode 100644 invokeai/frontend/web/src/features/parameters/components/ParametersAccordion.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/ParametersPinnedWrapper.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabCoreParameters.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabCoreParameters.tsx diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index b3452f5587..456d8622f0 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -19,8 +19,6 @@ import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { GalleryState } from 'features/gallery/store/gallerySlice'; import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice'; -import FaceRestoreSettings from 'features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreSettings'; -import UpscaleSettings from 'features/parameters/components/AdvancedParameters/Upscale/UpscaleSettings'; import { initialImageChanged, setAllParameters, @@ -69,6 +67,8 @@ import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useParameters } from 'features/parameters/hooks/useParameters'; import { initialImageSelected } from 'features/parameters/store/actions'; import { requestedImageDeletion } from '../store/actions'; +import FaceRestoreSettings from 'features/parameters/components/Parameters/FaceRestore/FaceRestoreSettings'; +import UpscaleSettings from 'features/parameters/components/Parameters/Upscale/UpscaleSettings'; const currentImageButtonsSelector = createSelector( [ diff --git a/invokeai/frontend/web/src/features/parameters/components/AccordionItems/InvokeAccordionItem.tsx b/invokeai/frontend/web/src/features/parameters/components/AccordionItems/InvokeAccordionItem.tsx deleted file mode 100644 index ccf0a8ed26..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/AccordionItems/InvokeAccordionItem.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { - AccordionButton, - AccordionIcon, - AccordionItem, - AccordionPanel, - Box, - Flex, -} from '@chakra-ui/react'; -import GuideIcon from 'common/components/GuideIcon'; -import { ParametersAccordionItem } from '../ParametersAccordion'; - -type InvokeAccordionItemProps = { - accordionItem: ParametersAccordionItem; -}; - -export default function InvokeAccordionItem({ - accordionItem, -}: InvokeAccordionItemProps) { - const { header, feature, content, additionalHeaderComponents } = - accordionItem; - - return ( - - - - - {header} - - {additionalHeaderComponents} - {/* {feature && } */} - - - - {content} - - ); -} diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/BoundingBox/BoundingBoxSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/BoundingBox/BoundingBoxSettings.tsx deleted file mode 100644 index 35a325e74e..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/BoundingBox/BoundingBoxSettings.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { Box, VStack } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISlider from 'common/components/IAISlider'; -import { canvasSelector } from 'features/canvas/store/canvasSelectors'; -import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; -import { isEqual } from 'lodash-es'; - -import { useTranslation } from 'react-i18next'; - -const selector = createSelector( - canvasSelector, - (canvas) => { - const { boundingBoxDimensions, boundingBoxScaleMethod: boundingBoxScale } = - canvas; - return { - boundingBoxDimensions, - boundingBoxScale, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -const BoundingBoxSettings = () => { - const dispatch = useAppDispatch(); - const { boundingBoxDimensions } = useAppSelector(selector); - - const { t } = useTranslation(); - - const handleChangeWidth = (v: number) => { - dispatch( - setBoundingBoxDimensions({ - ...boundingBoxDimensions, - width: Math.floor(v), - }) - ); - }; - - const handleChangeHeight = (v: number) => { - dispatch( - setBoundingBoxDimensions({ - ...boundingBoxDimensions, - height: Math.floor(v), - }) - ); - }; - - const handleResetWidth = () => { - dispatch( - setBoundingBoxDimensions({ - ...boundingBoxDimensions, - width: Math.floor(512), - }) - ); - }; - - const handleResetHeight = () => { - dispatch( - setBoundingBoxDimensions({ - ...boundingBoxDimensions, - height: Math.floor(512), - }) - ); - }; - - return ( - - - - - ); -}; - -export default BoundingBoxSettings; - -export const BoundingBoxSettingsHeader = () => { - const { t } = useTranslation(); - return ( - - {t('parameters.boundingBoxHeader')} - - ); -}; diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/InfillAndScalingSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/InfillAndScalingSettings.tsx deleted file mode 100644 index d578361624..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/InfillAndScalingSettings.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import { VStack } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISelect from 'common/components/IAISelect'; -import IAISlider from 'common/components/IAISlider'; -import { canvasSelector } from 'features/canvas/store/canvasSelectors'; -import { - setBoundingBoxScaleMethod, - setScaledBoundingBoxDimensions, -} from 'features/canvas/store/canvasSlice'; -import { - BoundingBoxScale, - BOUNDING_BOX_SCALES_DICT, -} from 'features/canvas/store/canvasTypes'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { - setInfillMethod, - setTileSize, -} from 'features/parameters/store/generationSlice'; -import { systemSelector } from 'features/system/store/systemSelectors'; -import { isEqual } from 'lodash-es'; - -import { ChangeEvent } from 'react'; -import { useTranslation } from 'react-i18next'; - -const selector = createSelector( - [generationSelector, systemSelector, canvasSelector], - (parameters, system, canvas) => { - const { tileSize, infillMethod } = parameters; - - const { infillMethods } = system; - - const { - boundingBoxScaleMethod: boundingBoxScale, - scaledBoundingBoxDimensions, - } = canvas; - - return { - boundingBoxScale, - scaledBoundingBoxDimensions, - tileSize, - infillMethod, - infillMethods, - isManual: boundingBoxScale === 'manual', - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -const InfillAndScalingSettings = () => { - const dispatch = useAppDispatch(); - const { - tileSize, - infillMethod, - infillMethods, - boundingBoxScale, - isManual, - scaledBoundingBoxDimensions, - } = useAppSelector(selector); - - const { t } = useTranslation(); - - const handleChangeScaledWidth = (v: number) => { - dispatch( - setScaledBoundingBoxDimensions({ - ...scaledBoundingBoxDimensions, - width: Math.floor(v), - }) - ); - }; - - const handleChangeScaledHeight = (v: number) => { - dispatch( - setScaledBoundingBoxDimensions({ - ...scaledBoundingBoxDimensions, - height: Math.floor(v), - }) - ); - }; - - const handleResetScaledWidth = () => { - dispatch( - setScaledBoundingBoxDimensions({ - ...scaledBoundingBoxDimensions, - width: Math.floor(512), - }) - ); - }; - - const handleResetScaledHeight = () => { - dispatch( - setScaledBoundingBoxDimensions({ - ...scaledBoundingBoxDimensions, - height: Math.floor(512), - }) - ); - }; - - const handleChangeBoundingBoxScaleMethod = ( - e: ChangeEvent - ) => { - dispatch(setBoundingBoxScaleMethod(e.target.value as BoundingBoxScale)); - }; - - return ( - - - - - dispatch(setInfillMethod(e.target.value))} - /> - { - dispatch(setTileSize(v)); - }} - withInput - withSliderMarks - withReset - handleReset={() => { - dispatch(setTileSize(32)); - }} - /> - - ); -}; - -export default InfillAndScalingSettings; diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamCorrectionSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamCorrectionSettings.tsx deleted file mode 100644 index a49eac26a1..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamCorrectionSettings.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { VStack } from '@chakra-ui/react'; -import ParamSeamBlur from './ParamSeamBlur'; -import ParamSeamSize from './ParamSeamSize'; -import ParamSeamSteps from './ParamSeamSteps'; -import ParamSeamStrength from './ParamSeamStrength'; - -const SeamCorrectionSettings = () => { - return ( - - - - - - - ); -}; - -export default SeamCorrectionSettings; diff --git a/invokeai/frontend/web/src/features/parameters/components/AnimatedImageToImagePanel.tsx b/invokeai/frontend/web/src/features/parameters/components/AnimatedImageToImagePanel.tsx deleted file mode 100644 index 8778e043e6..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/AnimatedImageToImagePanel.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { memo, useState } from 'react'; -import { AnimatePresence, motion } from 'framer-motion'; - -import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings'; -import { useAppSelector } from 'app/store/storeHooks'; -import { RootState } from 'app/store/store'; -import { Box } from '@chakra-ui/react'; - -const AnimatedImageToImagePanel = () => { - const isImageToImageEnabled = useAppSelector( - (state: RootState) => state.generation.isImageToImageEnabled - ); - - return ( - - {isImageToImageEnabled && ( - - - - - - )} - - ); -}; - -export default memo(AnimatedImageToImagePanel); diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainHeight.tsx deleted file mode 100644 index e3a312f706..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainHeight.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { HEIGHTS } from 'app/constants'; -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISelect from 'common/components/IAISelect'; -import IAISlider from 'common/components/IAISlider'; -import { setHeight } from 'features/parameters/store/generationSlice'; -import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; - -import { useTranslation } from 'react-i18next'; - -export default function MainHeight() { - const height = useAppSelector((state: RootState) => state.generation.height); - const shouldUseSliders = useAppSelector( - (state: RootState) => state.ui.shouldUseSliders - ); - const activeTabName = useAppSelector(activeTabNameSelector); - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - return shouldUseSliders ? ( - dispatch(setHeight(v))} - handleReset={() => dispatch(setHeight(512))} - withInput - withReset - withSliderMarks - sliderNumberInputProps={{ max: 15360 }} - /> - ) : ( - dispatch(setHeight(Number(e.target.value)))} - validValues={HEIGHTS} - /> - ); -} diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSettings.tsx deleted file mode 100644 index d7f918e1f0..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSettings.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { memo } from 'react'; -import { Box, Flex, VStack } from '@chakra-ui/react'; -import { RootState } from 'app/store/store'; -import { useAppSelector } from 'app/store/storeHooks'; - -import ModelSelect from 'features/system/components/ModelSelect'; -import ParamHeight from 'features/parameters/components/Parameters/ParamHeight'; -import ParamCFGScale from 'features/parameters/components/Parameters/ParamCFGScale'; -import ParamIterations from 'features/parameters/components/Parameters/ParamIterations'; -import ParamScheduler from 'features/parameters/components/Parameters/ParamScheduler'; -import ParamSteps from 'features/parameters/components/Parameters/ParamSteps'; -import ParamWidth from 'features/parameters/components/Parameters/ParamWidth'; - -const MainSettings = () => { - const shouldUseSliders = useAppSelector( - (state: RootState) => state.ui.shouldUseSliders - ); - - return shouldUseSliders ? ( - - - - - - - - - - - - - - - - ) : ( - - - - - - - - - - - - - - - - - - ); -}; - -export default memo(MainSettings); diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainWidth.tsx deleted file mode 100644 index 7a8534147c..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainWidth.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { WIDTHS } from 'app/constants'; -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISelect from 'common/components/IAISelect'; -import IAISlider from 'common/components/IAISlider'; -import { setWidth } from 'features/parameters/store/generationSlice'; -import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; -import { useTranslation } from 'react-i18next'; - -export default function MainWidth() { - const width = useAppSelector((state: RootState) => state.generation.width); - const shouldUseSliders = useAppSelector( - (state: RootState) => state.ui.shouldUseSliders - ); - const activeTabName = useAppSelector(activeTabNameSelector); - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - return shouldUseSliders ? ( - dispatch(setWidth(v))} - handleReset={() => dispatch(setWidth(512))} - withInput - withReset - withSliderMarks - inputReadOnly - sliderNumberInputProps={{ max: 15360 }} - /> - ) : ( - dispatch(setWidth(Number(e.target.value)))} - validValues={WIDTHS} - /> - ); -} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxCollapse.tsx new file mode 100644 index 0000000000..fea0d8330a --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxCollapse.tsx @@ -0,0 +1,26 @@ +import { Flex, useDisclosure } from '@chakra-ui/react'; +import { useTranslation } from 'react-i18next'; +import IAICollapse from 'common/components/IAICollapse'; +import { memo } from 'react'; +import ParamBoundingBoxWidth from './ParamBoundingBoxWidth'; +import ParamBoundingBoxHeight from './ParamBoundingBoxHeight'; + +const ParamBoundingBoxCollapse = () => { + const { t } = useTranslation(); + const { isOpen, onToggle } = useDisclosure(); + + return ( + + + + + + + ); +}; + +export default memo(ParamBoundingBoxCollapse); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx new file mode 100644 index 0000000000..75ec70f257 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx @@ -0,0 +1,64 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAISlider from 'common/components/IAISlider'; +import { canvasSelector } from 'features/canvas/store/canvasSelectors'; +import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; +import { memo } from 'react'; + +import { useTranslation } from 'react-i18next'; + +const selector = createSelector( + canvasSelector, + (canvas) => { + const { boundingBoxDimensions } = canvas; + return { + boundingBoxDimensions, + }; + }, + defaultSelectorOptions +); + +const ParamBoundingBoxWidth = () => { + const dispatch = useAppDispatch(); + const { boundingBoxDimensions } = useAppSelector(selector); + + const { t } = useTranslation(); + + const handleChangeHeight = (v: number) => { + dispatch( + setBoundingBoxDimensions({ + ...boundingBoxDimensions, + height: Math.floor(v), + }) + ); + }; + + const handleResetHeight = () => { + dispatch( + setBoundingBoxDimensions({ + ...boundingBoxDimensions, + height: Math.floor(512), + }) + ); + }; + + return ( + + ); +}; + +export default memo(ParamBoundingBoxWidth); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth.tsx new file mode 100644 index 0000000000..cf6ccff852 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth.tsx @@ -0,0 +1,64 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAISlider from 'common/components/IAISlider'; +import { canvasSelector } from 'features/canvas/store/canvasSelectors'; +import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; +import { memo } from 'react'; + +import { useTranslation } from 'react-i18next'; + +const selector = createSelector( + canvasSelector, + (canvas) => { + const { boundingBoxDimensions } = canvas; + return { + boundingBoxDimensions, + }; + }, + defaultSelectorOptions +); + +const ParamBoundingBoxWidth = () => { + const dispatch = useAppDispatch(); + const { boundingBoxDimensions } = useAppSelector(selector); + + const { t } = useTranslation(); + + const handleChangeWidth = (v: number) => { + dispatch( + setBoundingBoxDimensions({ + ...boundingBoxDimensions, + width: Math.floor(v), + }) + ); + }; + + const handleResetWidth = () => { + dispatch( + setBoundingBoxDimensions({ + ...boundingBoxDimensions, + width: Math.floor(512), + }) + ); + }; + + return ( + + ); +}; + +export default memo(ParamBoundingBoxWidth); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse.tsx new file mode 100644 index 0000000000..78a8995bee --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse.tsx @@ -0,0 +1,33 @@ +import { Flex, useDisclosure } from '@chakra-ui/react'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import IAICollapse from 'common/components/IAICollapse'; +import ParamInfillMethod from './ParamInfillMethod'; +import ParamInfillTilesize from './ParamInfillTilesize'; +import ParamScaleBeforeProcessing from './ParamScaleBeforeProcessing'; +import ParamScaledWidth from './ParamScaledWidth'; +import ParamScaledHeight from './ParamScaledHeight'; + +const ParamInfillCollapse = () => { + const { t } = useTranslation(); + const { isOpen, onToggle } = useDisclosure(); + + return ( + + + + + + + + + + ); +}; + +export default memo(ParamInfillCollapse); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillMethod.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillMethod.tsx new file mode 100644 index 0000000000..00812f458a --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillMethod.tsx @@ -0,0 +1,49 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAISelect from 'common/components/IAISelect'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { setInfillMethod } from 'features/parameters/store/generationSlice'; +import { systemSelector } from 'features/system/store/systemSelectors'; + +import { ChangeEvent, memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; + +const selector = createSelector( + [generationSelector, systemSelector], + (parameters, system) => { + const { infillMethod } = parameters; + const { infillMethods } = system; + + return { + infillMethod, + infillMethods, + }; + }, + defaultSelectorOptions +); + +const ParamInfillMethod = () => { + const dispatch = useAppDispatch(); + const { infillMethod, infillMethods } = useAppSelector(selector); + + const { t } = useTranslation(); + + const handleChange = useCallback( + (e: ChangeEvent) => { + dispatch(setInfillMethod(e.target.value)); + }, + [dispatch] + ); + + return ( + + ); +}; + +export default memo(ParamInfillMethod); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillTilesize.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillTilesize.tsx new file mode 100644 index 0000000000..fc6f02184c --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillTilesize.tsx @@ -0,0 +1,58 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAISlider from 'common/components/IAISlider'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { setTileSize } from 'features/parameters/store/generationSlice'; +import { memo, useCallback } from 'react'; + +import { useTranslation } from 'react-i18next'; + +const selector = createSelector( + [generationSelector], + (parameters) => { + const { tileSize, infillMethod } = parameters; + + return { + tileSize, + infillMethod, + }; + }, + defaultSelectorOptions +); + +const ParamInfillTileSize = () => { + const dispatch = useAppDispatch(); + const { tileSize, infillMethod } = useAppSelector(selector); + + const { t } = useTranslation(); + + const handleChange = useCallback( + (v: number) => { + dispatch(setTileSize(v)); + }, + [dispatch] + ); + + const handleReset = useCallback(() => { + dispatch(setTileSize(32)); + }, [dispatch]); + + return ( + + ); +}; + +export default memo(ParamInfillTileSize); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx new file mode 100644 index 0000000000..8164371b56 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx @@ -0,0 +1,49 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAISelect from 'common/components/IAISelect'; +import { canvasSelector } from 'features/canvas/store/canvasSelectors'; +import { setBoundingBoxScaleMethod } from 'features/canvas/store/canvasSlice'; +import { + BoundingBoxScale, + BOUNDING_BOX_SCALES_DICT, +} from 'features/canvas/store/canvasTypes'; + +import { ChangeEvent, memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +const selector = createSelector( + [canvasSelector], + (canvas) => { + const { boundingBoxScaleMethod: boundingBoxScale } = canvas; + + return { + boundingBoxScale, + }; + }, + defaultSelectorOptions +); + +const ParamScaleBeforeProcessing = () => { + const dispatch = useAppDispatch(); + const { boundingBoxScale } = useAppSelector(selector); + + const { t } = useTranslation(); + + const handleChangeBoundingBoxScaleMethod = ( + e: ChangeEvent + ) => { + dispatch(setBoundingBoxScaleMethod(e.target.value as BoundingBoxScale)); + }; + + return ( + + ); +}; + +export default memo(ParamScaleBeforeProcessing); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledHeight.tsx new file mode 100644 index 0000000000..a7e4a926b8 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledHeight.tsx @@ -0,0 +1,68 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAISlider from 'common/components/IAISlider'; +import { canvasSelector } from 'features/canvas/store/canvasSelectors'; +import { setScaledBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { systemSelector } from 'features/system/store/systemSelectors'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +const selector = createSelector( + [generationSelector, systemSelector, canvasSelector], + (parameters, system, canvas) => { + const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = canvas; + + return { + scaledBoundingBoxDimensions, + isManual: boundingBoxScaleMethod === 'manual', + }; + }, + defaultSelectorOptions +); + +const ParamScaledHeight = () => { + const dispatch = useAppDispatch(); + const { isManual, scaledBoundingBoxDimensions } = useAppSelector(selector); + + const { t } = useTranslation(); + + const handleChangeScaledHeight = (v: number) => { + dispatch( + setScaledBoundingBoxDimensions({ + ...scaledBoundingBoxDimensions, + height: Math.floor(v), + }) + ); + }; + + const handleResetScaledHeight = () => { + dispatch( + setScaledBoundingBoxDimensions({ + ...scaledBoundingBoxDimensions, + height: Math.floor(512), + }) + ); + }; + + return ( + + ); +}; + +export default memo(ParamScaledHeight); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledWidth.tsx new file mode 100644 index 0000000000..8104140808 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledWidth.tsx @@ -0,0 +1,66 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAISlider from 'common/components/IAISlider'; +import { canvasSelector } from 'features/canvas/store/canvasSelectors'; +import { setScaledBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +const selector = createSelector( + [canvasSelector], + (canvas) => { + const { boundingBoxScaleMethod, scaledBoundingBoxDimensions } = canvas; + + return { + scaledBoundingBoxDimensions, + isManual: boundingBoxScaleMethod === 'manual', + }; + }, + defaultSelectorOptions +); + +const ParamScaledWidth = () => { + const dispatch = useAppDispatch(); + const { isManual, scaledBoundingBoxDimensions } = useAppSelector(selector); + + const { t } = useTranslation(); + + const handleChangeScaledWidth = (v: number) => { + dispatch( + setScaledBoundingBoxDimensions({ + ...scaledBoundingBoxDimensions, + width: Math.floor(v), + }) + ); + }; + + const handleResetScaledWidth = () => { + dispatch( + setScaledBoundingBoxDimensions({ + ...scaledBoundingBoxDimensions, + width: Math.floor(512), + }) + ); + }; + + return ( + + ); +}; + +export default memo(ParamScaledWidth); diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamBlur.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamBlur.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamBlur.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamBlur.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamCorrectionCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamCorrectionCollapse.tsx new file mode 100644 index 0000000000..992e8b6d02 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamCorrectionCollapse.tsx @@ -0,0 +1,28 @@ +import ParamSeamBlur from './ParamSeamBlur'; +import ParamSeamSize from './ParamSeamSize'; +import ParamSeamSteps from './ParamSeamSteps'; +import ParamSeamStrength from './ParamSeamStrength'; +import { useDisclosure } from '@chakra-ui/react'; +import { useTranslation } from 'react-i18next'; +import IAICollapse from 'common/components/IAICollapse'; +import { memo } from 'react'; + +const ParamSeamCorrectionCollapse = () => { + const { t } = useTranslation(); + const { isOpen, onToggle } = useDisclosure(); + + return ( + + + + + + + ); +}; + +export default memo(ParamSeamCorrectionCollapse); diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamSize.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamSize.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamSize.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamSize.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamSteps.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamSteps.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamSteps.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamSteps.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamStrength.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamStrength.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/ParamSeamStrength.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamStrength.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamCFGScale.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamCFGScale.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/Parameters/ParamCFGScale.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamCFGScale.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/Parameters/ParamHeight.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamIterations.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamIterations.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/Parameters/ParamIterations.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamIterations.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamNegativeConditioning.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamNegativeConditioning.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/Parameters/ParamNegativeConditioning.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamNegativeConditioning.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamPositiveConditioning.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamPositiveConditioning.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/Parameters/ParamPositiveConditioning.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamPositiveConditioning.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamScheduler.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSampler.tsx similarity index 92% rename from invokeai/frontend/web/src/features/parameters/components/Parameters/ParamScheduler.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSampler.tsx index ef1574b64c..5a20f54438 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamScheduler.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSampler.tsx @@ -6,7 +6,7 @@ import { setSampler } from 'features/parameters/store/generationSlice'; import { ChangeEvent, memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -const ParamScheduler = () => { +const ParamSampler = () => { const sampler = useAppSelector( (state: RootState) => state.generation.sampler ); @@ -29,4 +29,4 @@ const ParamScheduler = () => { ); }; -export default memo(ParamScheduler); +export default memo(ParamSampler); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamSteps.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSteps.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/Parameters/ParamSteps.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSteps.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ParamWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/Parameters/ParamWidth.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/FaceRestore/CodeformerFidelity.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/CodeformerFidelity.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/FaceRestore/CodeformerFidelity.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/CodeformerFidelity.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/FaceRestoreSettings.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreSettings.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/FaceRestoreSettings.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreStrength.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/FaceRestoreStrength.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreStrength.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/FaceRestoreStrength.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/FaceRestoreToggle.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreToggle.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/FaceRestoreToggle.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreType.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/FaceRestoreType.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreType.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/FaceRestoreType.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageFit.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageFit.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageFit.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageFit.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageSettings.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageSettings.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageToggle.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageToggle.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageToggle.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImageDisplay.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImageDisplay.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/OtherSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/OtherSettings.tsx deleted file mode 100644 index b41b0690e1..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/OtherSettings.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { VStack } from '@chakra-ui/react'; -import ParamSeamlessToggle from './Seamless/ParamSeamlessToggle'; -// import ParamSeamlessAxes from '../../Parameters/Seamless/ParamSeamlessAxes'; -import { ParamHiresToggle } from './Hires/ParamHiresToggle'; -import { ParamHiresStrength } from './Hires/ParamHiresStrength'; - -const OtherSettings = () => { - return ( - - - {/* */} - - - - ); -}; - -export default OtherSettings; diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Upscale/UpscaleDenoisingStrength.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/UpscaleDenoisingStrength.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Upscale/UpscaleDenoisingStrength.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/UpscaleDenoisingStrength.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Upscale/UpscaleScale.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/UpscaleScale.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Upscale/UpscaleScale.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/UpscaleScale.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Upscale/UpscaleSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/UpscaleSettings.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Upscale/UpscaleSettings.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/UpscaleSettings.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Upscale/UpscaleStrength.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/UpscaleStrength.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Upscale/UpscaleStrength.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/UpscaleStrength.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Upscale/UpscaleToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/UpscaleToggle.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Upscale/UpscaleToggle.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/UpscaleToggle.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/ImageDimensions/AspectRatioPreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/_ImageDimensions/AspectRatioPreview.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/ImageDimensions/AspectRatioPreview.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/_ImageDimensions/AspectRatioPreview.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/ImageDimensions/DimensionsSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/_ImageDimensions/DimensionsSettings.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/ImageDimensions/DimensionsSettings.tsx rename to invokeai/frontend/web/src/features/parameters/components/Parameters/_ImageDimensions/DimensionsSettings.tsx diff --git a/invokeai/frontend/web/src/features/parameters/components/ParametersAccordion.tsx b/invokeai/frontend/web/src/features/parameters/components/ParametersAccordion.tsx deleted file mode 100644 index 22d7a6228e..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/ParametersAccordion.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { Accordion } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { Feature } from 'app/features'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { tabMap } from 'features/ui/store/tabMap'; -import { uiSelector } from 'features/ui/store/uiSelectors'; -import { openAccordionItemsChanged } from 'features/ui/store/uiSlice'; -import { map } from 'lodash-es'; -import { ReactNode, useCallback } from 'react'; -import InvokeAccordionItem from './AccordionItems/InvokeAccordionItem'; - -const parametersAccordionSelector = createSelector([uiSelector], (uiSlice) => { - const { - activeTab, - openLinearAccordionItems, - openUnifiedCanvasAccordionItems, - } = uiSlice; - - let openAccordions: number[] = []; - - if (tabMap[activeTab] === 'generate') { - openAccordions = openLinearAccordionItems; - } - - if (tabMap[activeTab] === 'unifiedCanvas') { - openAccordions = openUnifiedCanvasAccordionItems; - } - - return { - openAccordions, - }; -}); - -export type ParametersAccordionItem = { - name: string; - header: string; - content: ReactNode; - feature?: Feature; - additionalHeaderComponents?: ReactNode; -}; - -export type ParametersAccordionItems = { - [parametersAccordionKey: string]: ParametersAccordionItem; -}; - -type ParametersAccordionProps = { - accordionItems: ParametersAccordionItems; -}; - -/** - * Main container for generation and processing parameters. - */ -const ParametersAccordion = ({ accordionItems }: ParametersAccordionProps) => { - const { openAccordions } = useAppSelector(parametersAccordionSelector); - - const dispatch = useAppDispatch(); - - const handleChangeAccordionState = (openAccordions: number | number[]) => { - dispatch( - openAccordionItemsChanged( - Array.isArray(openAccordions) ? openAccordions : [openAccordions] - ) - ); - }; - - // Render function for accordion items - const renderAccordionItems = useCallback( - () => - map(accordionItems, (accordionItem) => ( - - )), - [accordionItems] - ); - - return ( - - {renderAccordionItems()} - - ); -}; - -export default ParametersAccordion; diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx index 512d43bcd6..5219d5b1e8 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx @@ -14,10 +14,10 @@ import { import { setShouldShowParametersPanel } from 'features/ui/store/uiSlice'; import ResizableDrawer from './common/ResizableDrawer/ResizableDrawer'; import PinParametersPanelButton from './PinParametersPanelButton'; -import TextTabParametersDrawer from './tabs/text/TextTabParametersDrawer'; import TextTabParameters from './tabs/text/TextTabParameters'; import ImageTabParameters from './tabs/image/ImageTabParameters'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import UnifiedCanvasParameters from './tabs/UnifiedCanvas/UnifiedCanvasParameters'; const selector = createSelector( [uiSelector, activeTabNameSelector, lightboxSelector], @@ -59,18 +59,26 @@ const ParametersDrawer = () => { } if (activeTabName === 'unifiedCanvas') { - return null; + return ; } + + return null; }, [activeTabName]); + if (shouldPinParametersPanel) { + return null; + } + return ( - + { - - {drawerContent} - + {drawerContent} diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPinnedWrapper.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPinnedWrapper.tsx new file mode 100644 index 0000000000..82bf38f26c --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPinnedWrapper.tsx @@ -0,0 +1,39 @@ +import { Box, Flex } from '@chakra-ui/react'; +import { PropsWithChildren, memo } from 'react'; +import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; +import OverlayScrollable from './common/OverlayScrollable'; +import PinParametersPanelButton from './PinParametersPanelButton'; + +type ParametersPinnedWrapperProps = PropsWithChildren; + +const ParametersPinnedWrapper = (props: ParametersPinnedWrapperProps) => { + return ( + + + + {props.children} + + + + + ); +}; + +export default memo(ParametersPinnedWrapper); diff --git a/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx b/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx index af4f65f011..71413fd01a 100644 --- a/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx +++ b/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx @@ -5,7 +5,7 @@ const OverlayScrollable = (props: PropsWithChildren) => { return ( { ); }; -export default UnifiedCanvasContent; +export default memo(UnifiedCanvasContent); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx new file mode 100644 index 0000000000..cc03ef560d --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx @@ -0,0 +1,83 @@ +import { memo } from 'react'; +import { Box, Flex } from '@chakra-ui/react'; +import { createSelector } from '@reduxjs/toolkit'; +import { uiSelector } from 'features/ui/store/uiSelectors'; +import { useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import ModelSelect from 'features/system/components/ModelSelect'; +import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations'; +import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps'; +import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale'; +import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth'; +import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight'; +import ImageToImageStrength from 'features/parameters/components/Parameters/ImageToImage/ImageToImageStrength'; +import ImageToImageFit from 'features/parameters/components/Parameters/ImageToImage/ImageToImageFit'; +import ParamSampler from 'features/parameters/components/Parameters/Core/ParamSampler'; + +const selector = createSelector( + uiSelector, + (ui) => { + const { shouldUseSliders } = ui; + + return { shouldUseSliders }; + }, + defaultSelectorOptions +); + +const UnifiedCanvasCoreParameters = () => { + const { shouldUseSliders } = useAppSelector(selector); + + return ( + + {shouldUseSliders ? ( + + + + + + + + + + + + + + + + + + ) : ( + + + + + + + + + + + + + + + + + + + + )} + + ); +}; + +export default memo(UnifiedCanvasCoreParameters); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx index 3c4ccb10b6..4aa68ad56a 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx @@ -1,101 +1,30 @@ -import { Flex } from '@chakra-ui/react'; -import { Feature } from 'app/features'; -import BoundingBoxSettings from 'features/parameters/components/AdvancedParameters/Canvas/BoundingBox/BoundingBoxSettings'; -import InfillAndScalingSettings from 'features/parameters/components/AdvancedParameters/Canvas/InfillAndScalingSettings'; -import SeamCorrectionSettings from 'features/parameters/components/AdvancedParameters/Canvas/SeamCorrection/SeamCorrectionSettings'; -import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength'; -import MainSettings from 'features/parameters/components/MainParameters/MainSettings'; -import ParametersAccordion, { - ParametersAccordionItems, -} from 'features/parameters/components/ParametersAccordion'; import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; -import { useTranslation } from 'react-i18next'; -import OverlayScrollable from '../../common/OverlayScrollable'; -// import ParamSeedSettings from 'features/parameters/components/Parameters/Seed/ParamSeedSettings'; -// import ParamVariationSettings from 'features/parameters/components/Parameters/Variations/ParamVariationSettings'; -// import ParamSymmetrySettings from 'features/parameters/components/Parameters/Symmetry/ParamSymmetrySettings'; -// import ParamVariationToggle from 'features/parameters/components/Parameters/Variations/ParamVariationToggle'; -// import ParamSymmetryToggle from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryToggle'; -import ParamPositiveConditioning from 'features/parameters/components/Parameters/ParamPositiveConditioning'; -import ParamNegativeConditioning from 'features/parameters/components/Parameters/ParamNegativeConditioning'; import ParamSeedCollapse from 'features/parameters/components/Parameters/Seed/ParamSeedCollapse'; import ParamVariationCollapse from 'features/parameters/components/Parameters/Variations/ParamVariationCollapse'; import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; +import ParamBoundingBoxCollapse from 'features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxCollapse'; +import ParamInfillAndScalingCollapse from 'features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse'; +import ParamSeamCorrectionCollapse from 'features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamCorrectionCollapse'; +import UnifiedCanvasCoreParameters from './UnifiedCanvasCoreParameters'; +import { memo } from 'react'; +import ParamPositiveConditioning from 'features/parameters/components/Parameters/Core/ParamPositiveConditioning'; +import ParamNegativeConditioning from 'features/parameters/components/Parameters/Core/ParamNegativeConditioning'; -export default function UnifiedCanvasParameters() { - const { t } = useTranslation(); - - const unifiedCanvasAccordions: ParametersAccordionItems = { - general: { - name: 'general', - header: `${t('parameters.general')}`, - feature: undefined, - content: , - }, - unifiedCanvasImg2Img: { - name: 'unifiedCanvasImg2Img', - header: `${t('parameters.imageToImage')}`, - feature: undefined, - content: , - }, - // seed: { - // name: 'seed', - // header: `${t('parameters.seed')}`, - // feature: Feature.SEED, - // content: , - // }, - boundingBox: { - name: 'boundingBox', - header: `${t('parameters.boundingBoxHeader')}`, - feature: Feature.BOUNDING_BOX, - content: , - }, - seamCorrection: { - name: 'seamCorrection', - header: `${t('parameters.seamCorrectionHeader')}`, - feature: Feature.SEAM_CORRECTION, - content: , - }, - infillAndScaling: { - name: 'infillAndScaling', - header: `${t('parameters.infillScalingHeader')}`, - feature: Feature.INFILL_AND_SCALING, - content: , - }, - // variations: { - // name: 'variations', - // header: `${t('parameters.variations')}`, - // feature: Feature.VARIATIONS, - // content: , - // additionalHeaderComponents: , - // }, - // symmetry: { - // name: 'symmetry', - // header: `${t('parameters.symmetry')}`, - // content: , - // additionalHeaderComponents: , - // }, - }; - +const UnifiedCanvasParameters = () => { return ( - - - - - - - - - - - + <> + + + + + + + + + + + ); -} +}; + +export default memo(UnifiedCanvasParameters); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx index d0f509b651..999adb1d91 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx @@ -1,4 +1,4 @@ -import { TabPanel } from '@chakra-ui/react'; +import { Box, Flex, TabPanel } from '@chakra-ui/react'; import { memo } from 'react'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import PinParametersPanelButton from '../../PinParametersPanelButton'; @@ -11,6 +11,8 @@ import UnifiedCanvasContent from './UnifiedCanvasContent'; import ResizeHandle from '../ResizeHandle'; import UnifiedCanvasParameters from './UnifiedCanvasParameters'; import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; +import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; +import ParametersPinnedWrapper from '../../ParametersPinnedWrapper'; const selector = createSelector(uiSelector, (ui) => { const { @@ -41,42 +43,18 @@ const UnifiedCanvasTab = () => { } = useAppSelector(selector); return ( - + {shouldPinParametersPanel && shouldShowParametersPanel && ( - <> - - - - - - + + + )} - { - dispatch(requestCanvasRescale()); - }} - > - {shouldUseCanvasBetaLayout ? ( - - ) : ( - - )} - - + {shouldUseCanvasBetaLayout ? ( + + ) : ( + + )} + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx index b906ec4d97..c9dfb47720 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTab.tsx @@ -14,10 +14,10 @@ import ResizeHandle from '../ResizeHandle'; import ImageTabParameters from './ImageTabParameters'; import ImageTabImageParameters from './ImageTabImageParameters'; import TextTabMain from '../text/TextTabMain'; -import InitialImagePreview from 'features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview'; -import InitialImageDisplay from 'features/parameters/components/AdvancedParameters/ImageToImage/InitialImageDisplay'; import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; import { ImperativePanelGroupHandle } from 'react-resizable-panels'; +import ParametersPinnedWrapper from '../../ParametersPinnedWrapper'; +import InitialImageDisplay from 'features/parameters/components/Parameters/ImageToImage/InitialImageDisplay'; const selector = createSelector(uiSelector, (ui) => { const { @@ -60,19 +60,9 @@ const TextTab = () => { return ( {shouldPinParametersPanel && shouldShowParametersPanel && ( - + - - + )} { + const { shouldUseSliders } = ui; + + return { shouldUseSliders }; + }, + defaultSelectorOptions +); + +const ImageTabCoreParameters = () => { + const { shouldUseSliders } = useAppSelector(selector); + + return ( + + {shouldUseSliders ? ( + + + + + + + + + + + + + + + + + + ) : ( + + + + + + + + + + + + + + + + + + + + )} + + ); +}; + +export default memo(ImageTabCoreParameters); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabParameters.tsx index 09181b6669..60b9d11822 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabParameters.tsx @@ -1,112 +1,27 @@ -import { Box, Flex } from '@chakra-ui/react'; -import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; import { memo } from 'react'; -import OverlayScrollable from '../../common/OverlayScrollable'; -import ParamPositiveConditioning from 'features/parameters/components/Parameters/ParamPositiveConditioning'; -import ParamNegativeConditioning from 'features/parameters/components/Parameters/ParamNegativeConditioning'; -import { createSelector } from '@reduxjs/toolkit'; -import { uiSelector } from 'features/ui/store/uiSelectors'; -import { useAppSelector } from 'app/store/storeHooks'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import ParamIterations from 'features/parameters/components/Parameters/ParamIterations'; -import ParamSteps from 'features/parameters/components/Parameters/ParamSteps'; -import ParamCFGScale from 'features/parameters/components/Parameters/ParamCFGScale'; -import ParamWidth from 'features/parameters/components/Parameters/ParamWidth'; -import ParamHeight from 'features/parameters/components/Parameters/ParamHeight'; -import ParamScheduler from 'features/parameters/components/Parameters/ParamScheduler'; -import ModelSelect from 'features/system/components/ModelSelect'; +import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; +import ParamPositiveConditioning from 'features/parameters/components/Parameters/Core/ParamPositiveConditioning'; +import ParamNegativeConditioning from 'features/parameters/components/Parameters/Core/ParamNegativeConditioning'; import ParamSeedCollapse from 'features/parameters/components/Parameters/Seed/ParamSeedCollapse'; import ParamVariationCollapse from 'features/parameters/components/Parameters/Variations/ParamVariationCollapse'; import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/ParamNoiseCollapse'; import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; -import ParamHiresCollapse from 'features/parameters/components/Parameters/Hires/ParamHiresCollapse'; import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse'; -import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength'; -import ImageToImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageFit'; - -const selector = createSelector( - uiSelector, - (ui) => { - const { shouldUseSliders } = ui; - - return { shouldUseSliders }; - }, - defaultSelectorOptions -); +import ImageTabCoreParameters from './ImageTabCoreParameters'; const ImageTabParameters = () => { - const { shouldUseSliders } = useAppSelector(selector); - return ( - - - - - - - {shouldUseSliders ? ( - - - - - - - - - - - - - - - - - - ) : ( - - - - - - - - - - - - - - - - - - - - )} - - - - - - - - + <> + + + + + + + + + + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx index 4582a8a242..e05f1fd11e 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx @@ -10,6 +10,7 @@ import TextTabMain from './TextTabMain'; import ResizeHandle from '../ResizeHandle'; import TextTabParameters from './TextTabParameters'; import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; +import ParametersPinnedWrapper from '../../ParametersPinnedWrapper'; const selector = createSelector(uiSelector, (ui) => { const { @@ -42,19 +43,9 @@ const TextTab = () => { return ( {shouldPinParametersPanel && shouldShowParametersPanel && ( - + - - + )} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabCoreParameters.tsx new file mode 100644 index 0000000000..1b463a777b --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabCoreParameters.tsx @@ -0,0 +1,77 @@ +import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations'; +import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps'; +import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale'; +import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth'; +import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight'; +import ParamSampler from 'features/parameters/components/Parameters/Core/ParamSampler'; +import ModelSelect from 'features/system/components/ModelSelect'; +import { Box, Flex } from '@chakra-ui/react'; +import { useAppSelector } from 'app/store/storeHooks'; +import { createSelector } from '@reduxjs/toolkit'; +import { uiSelector } from 'features/ui/store/uiSelectors'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { memo } from 'react'; + +const selector = createSelector( + uiSelector, + (ui) => { + const { shouldUseSliders } = ui; + + return { shouldUseSliders }; + }, + defaultSelectorOptions +); + +const TextTabCoreParameters = () => { + const { shouldUseSliders } = useAppSelector(selector); + + return ( + + {shouldUseSliders ? ( + + + + + + + + + + + + + + + + ) : ( + + + + + + + + + + + + + + + + + + )} + + ); +}; + +export default memo(TextTabCoreParameters); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParameters.tsx index 84f7295bd3..1977fd9fa1 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParameters.tsx @@ -1,107 +1,29 @@ -import { Box, Flex } from '@chakra-ui/react'; import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; import { memo } from 'react'; -import OverlayScrollable from '../../common/OverlayScrollable'; -import ParamPositiveConditioning from 'features/parameters/components/Parameters/ParamPositiveConditioning'; -import ParamNegativeConditioning from 'features/parameters/components/Parameters/ParamNegativeConditioning'; -import { createSelector } from '@reduxjs/toolkit'; -import { uiSelector } from 'features/ui/store/uiSelectors'; -import { useAppSelector } from 'app/store/storeHooks'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import ParamIterations from 'features/parameters/components/Parameters/ParamIterations'; -import ParamSteps from 'features/parameters/components/Parameters/ParamSteps'; -import ParamCFGScale from 'features/parameters/components/Parameters/ParamCFGScale'; -import ParamWidth from 'features/parameters/components/Parameters/ParamWidth'; -import ParamHeight from 'features/parameters/components/Parameters/ParamHeight'; -import ParamScheduler from 'features/parameters/components/Parameters/ParamScheduler'; -import ModelSelect from 'features/system/components/ModelSelect'; +import ParamPositiveConditioning from 'features/parameters/components/Parameters/Core/ParamPositiveConditioning'; +import ParamNegativeConditioning from 'features/parameters/components/Parameters/Core/ParamNegativeConditioning'; import ParamSeedCollapse from 'features/parameters/components/Parameters/Seed/ParamSeedCollapse'; import ParamVariationCollapse from 'features/parameters/components/Parameters/Variations/ParamVariationCollapse'; import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/ParamNoiseCollapse'; import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; import ParamHiresCollapse from 'features/parameters/components/Parameters/Hires/ParamHiresCollapse'; import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse'; -import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; - -const selector = createSelector( - uiSelector, - (ui) => { - const { shouldUseSliders } = ui; - - return { shouldUseSliders }; - }, - defaultSelectorOptions -); +import TextTabCoreParameters from './TextTabCoreParameters'; const TextTabParameters = () => { - const { shouldUseSliders } = useAppSelector(selector); - return ( - - - - - - - {shouldUseSliders ? ( - - - - - - - - - - - - - - - - ) : ( - - - - - - - - - - - - - - - - - - )} - - - - - - - - - + <> + + + + + + + + + + + ); }; From 279468c0e87226bef38a02b78dd305e7dd161bbc Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 May 2023 18:25:53 +1000 Subject: [PATCH 46/66] feat(ui): restore tab names --- invokeai/frontend/web/public/locales/en.json | 2 +- .../listeners/userInvokedImageToImage.ts | 2 +- .../listeners/userInvokedTextToImage.ts | 2 +- .../web/src/common/hooks/useGlobalHotkeys.ts | 4 +- .../FloatingParametersPanelButtons.tsx | 2 +- .../src/features/ui/components/InvokeTabs.tsx | 10 +-- .../ui/components/ParametersDrawer.tsx | 12 ++-- .../ui/components/ParametersPinnedWrapper.tsx | 19 ++++++ .../ImageToImageTab.tsx} | 59 ++++-------------- .../ImageToImageTabCoreParameters.tsx} | 4 +- .../ImageToImageTabParameters.tsx} | 8 +-- .../tabs/TextToImage/TextToImageTab.tsx | 18 ++++++ .../TextToImageTabCoreParameters.tsx} | 4 +- .../TextToImageTabMain.tsx} | 4 +- .../TextToImageTabParameters.tsx} | 8 +-- .../tabs/UnifiedCanvas/UnifiedCanvasTab.tsx | 39 +++--------- .../tabs/image/ImageTabImageParameters.tsx | 61 ------------------- .../ui/components/tabs/text/TextTab.tsx | 55 ----------------- .../web/src/features/ui/store/tabMap.ts | 4 +- .../web/src/features/ui/store/uiSlice.ts | 4 +- 20 files changed, 90 insertions(+), 231 deletions(-) rename invokeai/frontend/web/src/features/ui/components/tabs/{image/ImageTab.tsx => ImageToImage/ImageToImageTab.tsx} (52%) rename invokeai/frontend/web/src/features/ui/components/tabs/{image/ImageTabCoreParameters.tsx => ImageToImage/ImageToImageTabCoreParameters.tsx} (96%) rename invokeai/frontend/web/src/features/ui/components/tabs/{image/ImageTabParameters.tsx => ImageToImage/ImageToImageTabParameters.tsx} (84%) create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTab.tsx rename invokeai/frontend/web/src/features/ui/components/tabs/{text/TextTabCoreParameters.tsx => TextToImage/TextToImageTabCoreParameters.tsx} (95%) rename invokeai/frontend/web/src/features/ui/components/tabs/{text/TextTabMain.tsx => TextToImage/TextToImageTabMain.tsx} (87%) rename invokeai/frontend/web/src/features/ui/components/tabs/{text/TextTabParameters.tsx => TextToImage/TextToImageTabParameters.tsx} (86%) delete mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabImageParameters.tsx delete mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index fe104a762b..13c99c22f2 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -54,7 +54,7 @@ "img2img": "Image To Image", "unifiedCanvas": "Unified Canvas", "linear": "Linear", - "nodes": "Nodes", + "nodes": "Node Editor", "postprocessing": "Post Processing", "nodesDesc": "A node based system for the generation of images is under development currently. Stay tuned for updates about this amazing feature.", "postProcessing": "Post Processing", diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts index d326e2a1d9..e747aefa08 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts @@ -10,7 +10,7 @@ const moduleLog = log.child({ namespace: 'invoke' }); export const addUserInvokedImageToImageListener = () => { startAppListening({ predicate: (action): action is ReturnType => - userInvoked.match(action) && action.payload === 'image', + userInvoked.match(action) && action.payload === 'img2img', effect: (action, { getState, dispatch }) => { const state = getState(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts index f4cced4ade..e3eb5d0b38 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts @@ -10,7 +10,7 @@ const moduleLog = log.child({ namespace: 'invoke' }); export const addUserInvokedTextToImageListener = () => { startAppListening({ predicate: (action): action is ReturnType => - userInvoked.match(action) && action.payload === 'text', + userInvoked.match(action) && action.payload === 'txt2img', effect: (action, { getState, dispatch }) => { const state = getState(); diff --git a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts index 98aa824790..3935a390fb 100644 --- a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts +++ b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts @@ -61,11 +61,11 @@ export const useGlobalHotkeys = () => { }); useHotkeys('1', () => { - dispatch(setActiveTab('text')); + dispatch(setActiveTab('txt2img')); }); useHotkeys('2', () => { - dispatch(setActiveTab('image')); + dispatch(setActiveTab('img2img')); }); useHotkeys('3', () => { diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx index 8ceb8ee20a..95ac1257c0 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx @@ -40,7 +40,7 @@ export const floatingParametersPanelButtonSelector = createSelector( const shouldShowParametersPanelButton = !canvasBetaLayoutCheck && !shouldShowParametersPanel && - ['text', 'image', 'unifiedCanvas'].includes(activeTabName); + ['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName); return { shouldPinParametersPanel, diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index f199ab05dc..47a250b933 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -26,12 +26,12 @@ import { configSelector } from 'features/system/store/configSelectors'; import { isEqual } from 'lodash-es'; import { Panel, PanelGroup } from 'react-resizable-panels'; import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; -import TextTab from './tabs/text/TextTab'; +import TextToImageTab from './tabs/TextToImage/TextToImageTab'; import UnifiedCanvasTab from './tabs/UnifiedCanvas/UnifiedCanvasTab'; import NodesTab from './tabs/Nodes/NodesTab'; import { FaImage } from 'react-icons/fa'; import ResizeHandle from './tabs/ResizeHandle'; -import ImageTab from './tabs/image/ImageTab'; +import ImageTab from './tabs/ImageToImage/ImageToImageTab'; export interface InvokeTabInfo { id: InvokeTabName; @@ -41,12 +41,12 @@ export interface InvokeTabInfo { const tabs: InvokeTabInfo[] = [ { - id: 'text', + id: 'txt2img', icon: , - content: , + content: , }, { - id: 'image', + id: 'img2img', icon: , content: , }, diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx index 5219d5b1e8..ce4ca4f438 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx @@ -14,8 +14,8 @@ import { import { setShouldShowParametersPanel } from 'features/ui/store/uiSlice'; import ResizableDrawer from './common/ResizableDrawer/ResizableDrawer'; import PinParametersPanelButton from './PinParametersPanelButton'; -import TextTabParameters from './tabs/text/TextTabParameters'; -import ImageTabParameters from './tabs/image/ImageTabParameters'; +import TextToImageTabParameters from './tabs/TextToImage/TextToImageTabParameters'; +import ImageToImageTabParameters from './tabs/ImageToImage/ImageToImageTabParameters'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import UnifiedCanvasParameters from './tabs/UnifiedCanvas/UnifiedCanvasParameters'; @@ -50,12 +50,12 @@ const ParametersDrawer = () => { }; const drawerContent = useMemo(() => { - if (activeTabName === 'text') { - return ; + if (activeTabName === 'txt2img') { + return ; } - if (activeTabName === 'image') { - return ; + if (activeTabName === 'img2img') { + return ; } if (activeTabName === 'unifiedCanvas') { diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPinnedWrapper.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPinnedWrapper.tsx index 82bf38f26c..407187294c 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPinnedWrapper.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPinnedWrapper.tsx @@ -3,10 +3,29 @@ import { PropsWithChildren, memo } from 'react'; import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; import OverlayScrollable from './common/OverlayScrollable'; import PinParametersPanelButton from './PinParametersPanelButton'; +import { createSelector } from '@reduxjs/toolkit'; +import { uiSelector } from '../store/uiSelectors'; +import { useAppSelector } from 'app/store/storeHooks'; + +const selector = createSelector(uiSelector, (ui) => { + const { shouldPinParametersPanel, shouldShowParametersPanel } = ui; + + return { + shouldPinParametersPanel, + shouldShowParametersPanel, + }; +}); type ParametersPinnedWrapperProps = PropsWithChildren; const ParametersPinnedWrapper = (props: ParametersPinnedWrapperProps) => { + const { shouldPinParametersPanel, shouldShowParametersPanel } = + useAppSelector(selector); + + if (!(shouldPinParametersPanel && shouldShowParametersPanel)) { + return null; + } + return ( { - const { - shouldPinGallery, - shouldShowGallery, - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - } = ui; - - return { - shouldPinGallery, - shouldShowGallery, - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - }; -}); - -const TextTab = () => { +const ImageToImageTab = () => { const dispatch = useAppDispatch(); const panelGroupRef = useRef(null); @@ -49,21 +22,11 @@ const TextTab = () => { panelGroupRef.current.setLayout([50, 50]); }, []); - const { - shouldPinGallery, - shouldShowGallery, - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - } = useAppSelector(selector); - return ( - {shouldPinParametersPanel && shouldShowParametersPanel && ( - - - - )} + + + { dispatch(requestCanvasRescale()); }} > - + @@ -98,4 +61,4 @@ const TextTab = () => { ); }; -export default memo(TextTab); +export default memo(ImageToImageTab); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx similarity index 96% rename from invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabCoreParameters.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx index 9f16b37e8e..97a4a2d580 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx @@ -24,7 +24,7 @@ const selector = createSelector( defaultSelectorOptions ); -const ImageTabCoreParameters = () => { +const ImageToImageTabCoreParameters = () => { const { shouldUseSliders } = useAppSelector(selector); return ( @@ -80,4 +80,4 @@ const ImageTabCoreParameters = () => { ); }; -export default memo(ImageTabCoreParameters); +export default memo(ImageToImageTabCoreParameters); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx similarity index 84% rename from invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabParameters.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx index 60b9d11822..3b3daeaa4c 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx @@ -7,15 +7,15 @@ import ParamVariationCollapse from 'features/parameters/components/Parameters/Va import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/ParamNoiseCollapse'; import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse'; -import ImageTabCoreParameters from './ImageTabCoreParameters'; +import ImageToImageTabCoreParameters from './ImageToImageTabCoreParameters'; -const ImageTabParameters = () => { +const ImageToImageTabParameters = () => { return ( <> - + @@ -25,4 +25,4 @@ const ImageTabParameters = () => { ); }; -export default memo(ImageTabParameters); +export default memo(ImageToImageTabParameters); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTab.tsx new file mode 100644 index 0000000000..87e77cc3ba --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTab.tsx @@ -0,0 +1,18 @@ +import { Flex } from '@chakra-ui/react'; +import { memo } from 'react'; +import TextToImageTabMain from './TextToImageTabMain'; +import TextToImageTabParameters from './TextToImageTabParameters'; +import ParametersPinnedWrapper from '../../ParametersPinnedWrapper'; + +const TextToImageTab = () => { + return ( + + + + + + + ); +}; + +export default memo(TextToImageTab); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx similarity index 95% rename from invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabCoreParameters.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx index 1b463a777b..d7edef148c 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx @@ -22,7 +22,7 @@ const selector = createSelector( defaultSelectorOptions ); -const TextTabCoreParameters = () => { +const TextToImageTabCoreParameters = () => { const { shouldUseSliders } = useAppSelector(selector); return ( @@ -74,4 +74,4 @@ const TextTabCoreParameters = () => { ); }; -export default memo(TextTabCoreParameters); +export default memo(TextToImageTabCoreParameters); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabMain.tsx similarity index 87% rename from invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabMain.tsx index 7970bb13f4..b6cfcf72c3 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabMain.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabMain.tsx @@ -1,7 +1,7 @@ import { Box, Flex } from '@chakra-ui/react'; import CurrentImageDisplay from 'features/gallery/components/CurrentImageDisplay'; -const TextTabMain = () => { +const TextToImageTabMain = () => { return ( { ); }; -export default TextTabMain; +export default TextToImageTabMain; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabParameters.tsx similarity index 86% rename from invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParameters.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabParameters.tsx index 1977fd9fa1..0976e3eef2 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTabParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabParameters.tsx @@ -8,15 +8,15 @@ import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/ import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; import ParamHiresCollapse from 'features/parameters/components/Parameters/Hires/ParamHiresCollapse'; import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse'; -import TextTabCoreParameters from './TextTabCoreParameters'; +import TextToImageTabCoreParameters from './TextToImageTabCoreParameters'; -const TextTabParameters = () => { +const TextToImageTabParameters = () => { return ( <> - + @@ -27,4 +27,4 @@ const TextTabParameters = () => { ); }; -export default memo(TextTabParameters); +export default memo(TextToImageTabParameters); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx index 999adb1d91..2d591d1ecc 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx @@ -1,54 +1,29 @@ -import { Box, Flex, TabPanel } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import { memo } from 'react'; -import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; -import PinParametersPanelButton from '../../PinParametersPanelButton'; -import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; import { createSelector } from '@reduxjs/toolkit'; import { uiSelector } from 'features/ui/store/uiSelectors'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; +import { useAppSelector } from 'app/store/storeHooks'; import UnifiedCanvasContent from './UnifiedCanvasContent'; -import ResizeHandle from '../ResizeHandle'; import UnifiedCanvasParameters from './UnifiedCanvasParameters'; import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; -import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; import ParametersPinnedWrapper from '../../ParametersPinnedWrapper'; const selector = createSelector(uiSelector, (ui) => { - const { - shouldPinGallery, - shouldShowGallery, - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldUseCanvasBetaLayout, - } = ui; + const { shouldUseCanvasBetaLayout } = ui; return { - shouldPinGallery, - shouldShowGallery, - shouldPinParametersPanel, - shouldShowParametersPanel, shouldUseCanvasBetaLayout, }; }); const UnifiedCanvasTab = () => { - const dispatch = useAppDispatch(); - const { - shouldPinGallery, - shouldShowGallery, - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldUseCanvasBetaLayout, - } = useAppSelector(selector); + const { shouldUseCanvasBetaLayout } = useAppSelector(selector); return ( - {shouldPinParametersPanel && shouldShowParametersPanel && ( - - - - )} + + + {shouldUseCanvasBetaLayout ? ( ) : ( diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabImageParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabImageParameters.tsx deleted file mode 100644 index 3d1fd3e950..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/image/ImageTabImageParameters.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { Box, Flex } from '@chakra-ui/react'; -import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; -import { memo } from 'react'; -import OverlayScrollable from '../../common/OverlayScrollable'; -import ParamPositiveConditioning from 'features/parameters/components/Parameters/ParamPositiveConditioning'; -import ParamNegativeConditioning from 'features/parameters/components/Parameters/ParamNegativeConditioning'; -import { createSelector } from '@reduxjs/toolkit'; -import { uiSelector } from 'features/ui/store/uiSelectors'; -import { useAppSelector } from 'app/store/storeHooks'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import ParamIterations from 'features/parameters/components/Parameters/ParamIterations'; -import ParamSteps from 'features/parameters/components/Parameters/ParamSteps'; -import ParamCFGScale from 'features/parameters/components/Parameters/ParamCFGScale'; -import ParamWidth from 'features/parameters/components/Parameters/ParamWidth'; -import ParamHeight from 'features/parameters/components/Parameters/ParamHeight'; -import ParamScheduler from 'features/parameters/components/Parameters/ParamScheduler'; -import ModelSelect from 'features/system/components/ModelSelect'; -import ParamSeedCollapse from 'features/parameters/components/Parameters/Seed/ParamSeedCollapse'; -import ParamVariationCollapse from 'features/parameters/components/Parameters/Variations/ParamVariationCollapse'; -import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/ParamNoiseCollapse'; -import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; -import ParamHiresCollapse from 'features/parameters/components/Parameters/Hires/ParamHiresCollapse'; -import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse'; -import InitialImagePreview from 'features/parameters/components/AdvancedParameters/ImageToImage/InitialImagePreview'; -import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength'; -import ImageToImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageFit'; -import InitialImageButtons from 'common/components/ImageToImageButtons'; - -const selector = createSelector( - uiSelector, - (ui) => { - const { shouldUseSliders } = ui; - - return { shouldUseSliders }; - }, - defaultSelectorOptions -); - -const ImageTabParameters = () => { - const { shouldUseSliders } = useAppSelector(selector); - - return ( - - - - - - - - ); -}; - -export default memo(ImageTabParameters); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx deleted file mode 100644 index e05f1fd11e..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/text/TextTab.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Box, Flex, Portal, TabPanel } from '@chakra-ui/react'; -import { memo } from 'react'; -import { Panel, PanelGroup } from 'react-resizable-panels'; -import PinParametersPanelButton from '../../PinParametersPanelButton'; -import { createSelector } from '@reduxjs/toolkit'; -import { uiSelector } from 'features/ui/store/uiSelectors'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; -import TextTabMain from './TextTabMain'; -import ResizeHandle from '../ResizeHandle'; -import TextTabParameters from './TextTabParameters'; -import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; -import ParametersPinnedWrapper from '../../ParametersPinnedWrapper'; - -const selector = createSelector(uiSelector, (ui) => { - const { - shouldPinGallery, - shouldShowGallery, - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - } = ui; - - return { - shouldPinGallery, - shouldShowGallery, - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - }; -}); - -const TextTab = () => { - const dispatch = useAppDispatch(); - const { - shouldPinGallery, - shouldShowGallery, - shouldPinParametersPanel, - shouldShowParametersPanel, - shouldShowImageParameters, - } = useAppSelector(selector); - - return ( - - {shouldPinParametersPanel && shouldShowParametersPanel && ( - - - - )} - - - ); -}; - -export default memo(TextTab); diff --git a/invokeai/frontend/web/src/features/ui/store/tabMap.ts b/invokeai/frontend/web/src/features/ui/store/tabMap.ts index 30bfb459a0..becf52886e 100644 --- a/invokeai/frontend/web/src/features/ui/store/tabMap.ts +++ b/invokeai/frontend/web/src/features/ui/store/tabMap.ts @@ -1,6 +1,6 @@ export const tabMap = [ - 'text', - 'image', + 'txt2img', + 'img2img', // 'generate', 'unifiedCanvas', 'nodes', diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index b585c5300b..60f65079cf 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -105,11 +105,11 @@ export const uiSlice = createSlice({ } }, openAccordionItemsChanged: (state, action: PayloadAction) => { - if (tabMap[state.activeTab] === 'text') { + if (tabMap[state.activeTab] === 'txt2img') { state.textTabAccordionState = action.payload; } - if (tabMap[state.activeTab] === 'image') { + if (tabMap[state.activeTab] === 'img2img') { state.imageTabAccordionState = action.payload; } From 15e57e3a3db999b23de7a369719b95eb09965369 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 May 2023 18:27:16 +1000 Subject: [PATCH 47/66] fix(ui): duplicate gallery in nodes editor --- .../ui/components/tabs/Nodes/NodesTab.tsx | 59 +------------------ 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx index c3f82a6b7c..aff0a7ce07 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx @@ -1,65 +1,8 @@ -import { TabPanel } from '@chakra-ui/react'; import { memo } from 'react'; -import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; -import PinParametersPanelButton from '../../PinParametersPanelButton'; -import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; -import { createSelector } from '@reduxjs/toolkit'; -import { uiSelector } from 'features/ui/store/uiSelectors'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; -import ResizeHandle from '../ResizeHandle'; import NodeEditor from 'features/nodes/components/NodeEditor'; -const selector = createSelector(uiSelector, (ui) => { - const { - shouldPinGallery, - shouldShowGallery, - shouldPinParametersPanel, - shouldShowParametersPanel, - } = ui; - - return { - shouldPinGallery, - shouldShowGallery, - shouldPinParametersPanel, - shouldShowParametersPanel, - }; -}); - const NodesTab = () => { - const dispatch = useAppDispatch(); - const { - shouldPinGallery, - shouldShowGallery, - shouldPinParametersPanel, - shouldShowParametersPanel, - } = useAppSelector(selector); - - return ( - - { - dispatch(requestCanvasRescale()); - }} - > - - - {shouldPinGallery && shouldShowGallery && ( - <> - - - - - - )} - - ); + return ; }; export default memo(NodesTab); From 7bfb5640adcb3bf7164d84d86528e4921f143ede Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Tue, 9 May 2023 19:20:10 +1200 Subject: [PATCH 48/66] cleanup(ui): Remove unused vars + minor bug fixes --- .../src/app/selectors/readinessSelector.ts | 2 +- .../enhancers/reduxRemember/unserialize.ts | 13 +---- invokeai/frontend/web/src/app/store/store.ts | 1 - .../frontend/web/src/app/store/storeHooks.ts | 2 +- .../frontend/web/src/app/types/invokeai.ts | 5 +- .../web/src/common/components/IAICollapse.tsx | 2 +- .../src/features/canvas/store/canvasSlice.ts | 2 - .../components/CurrentImageButtons.tsx | 52 ++++++++----------- .../gallery/components/HoverableImage.tsx | 9 ++-- .../gallery/components/ImageGalleryPanel.tsx | 9 +--- .../ImageMetadataViewer.tsx | 13 +---- .../gallery/store/gallerySelectors.ts | 15 ++---- .../features/gallery/store/uploadsSlice.ts | 2 +- .../lightbox/components/ReactPanZoomImage.tsx | 2 +- .../features/nodes/components/AddNodeMenu.tsx | 7 +-- .../features/nodes/components/FieldHandle.tsx | 20 +++---- .../src/features/nodes/components/Flow.tsx | 4 +- .../components/IAINode/IAINodeHeader.tsx | 2 +- .../nodes/components/InputFieldComponent.tsx | 2 +- .../fields/ImageInputFieldComponent.tsx | 7 ++- .../fields/ItemInputFieldComponent.tsx | 2 +- .../fields/ModelInputFieldComponent.tsx | 8 +-- .../nodes/components/panels/TopLeftPanel.tsx | 4 +- .../nodes/components/search/NodeSearch.tsx | 25 ++++----- .../src/features/nodes/store/nodesSlice.ts | 2 +- .../web/src/features/nodes/types/constants.ts | 1 - .../web/src/features/nodes/types/types.ts | 2 - .../nodes/util/fieldTemplateBuilders.ts | 2 - .../util/graphBuilders/buildCanvasGraph.ts | 16 +----- .../nodeBuilders/buildImageToImageNode.ts | 5 +- .../util/nodeBuilders/buildInpaintNode.ts | 9 +--- .../util/nodeBuilders/buildTextToImageNode.ts | 2 +- .../ImageToImage/ImageToImageSettings.tsx | 20 +------ .../ImageToImage/ImageToImageToggle.tsx | 5 +- .../ImageToImage/InitialImagePreview.tsx | 15 ++---- .../Seamless/ParamSeamlessCollapse.tsx | 3 +- .../components/Parameters/Seed/ParamSeed.tsx | 3 -- .../Parameters/Seed/ParamSeedShuffle.tsx | 2 - .../ProcessButtons/CancelButton.tsx | 3 -- .../ProcessButtons/ProcessButtons.tsx | 7 --- .../parameters/hooks/useParameters.ts | 6 +-- .../parameters/store/generationSlice.ts | 3 -- .../features/parameters/store/hiresSlice.ts | 1 - .../system/components/ModelSelect.tsx | 11 +--- .../features/system/components/SiteHeader.tsx | 1 - .../system/components/StatusIndicator.tsx | 1 - .../system/hooks/useIsApplicationReady.ts | 1 - .../features/system/store/modelSelectors.ts | 2 - .../src/features/system/store/modelSlice.ts | 3 +- .../src/features/system/store/systemSlice.ts | 31 +++-------- .../src/features/ui/components/InvokeTabs.tsx | 8 +-- .../ui/components/ParametersDrawer.tsx | 3 +- .../UnifiedCanvas/UnifiedCanvasWorkarea.tsx | 38 -------------- .../web/src/services/events/actions.ts | 2 +- .../services/util/deserializeImageField.ts | 4 +- .../src/services/util/makeGraphOfXImages.ts | 4 +- .../frontend/web/src/theme/util/constants.ts | 2 +- 57 files changed, 103 insertions(+), 325 deletions(-) diff --git a/invokeai/frontend/web/src/app/selectors/readinessSelector.ts b/invokeai/frontend/web/src/app/selectors/readinessSelector.ts index d70043e545..88863e880d 100644 --- a/invokeai/frontend/web/src/app/selectors/readinessSelector.ts +++ b/invokeai/frontend/web/src/app/selectors/readinessSelector.ts @@ -13,7 +13,7 @@ export const readinessSelector = createSelector( initialCanvasImageSelector, activeTabNameSelector, ], - (generation, system, initialCanvasImage, activeTabName) => { + (generation, system) => { const { prompt, shouldGenerateVariations, diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts index d775e06187..155a7786b3 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts @@ -1,28 +1,17 @@ -import { canvasPersistDenylist } from 'features/canvas/store/canvasPersistDenylist'; import { initialCanvasState } from 'features/canvas/store/canvasSlice'; -import { galleryPersistDenylist } from 'features/gallery/store/galleryPersistDenylist'; import { initialGalleryState } from 'features/gallery/store/gallerySlice'; -import { resultsPersistDenylist } from 'features/gallery/store/resultsPersistDenylist'; import { initialResultsState } from 'features/gallery/store/resultsSlice'; -import { uploadsPersistDenylist } from 'features/gallery/store/uploadsPersistDenylist'; import { initialUploadsState } from 'features/gallery/store/uploadsSlice'; -import { lightboxPersistDenylist } from 'features/lightbox/store/lightboxPersistDenylist'; import { initialLightboxState } from 'features/lightbox/store/lightboxSlice'; -import { nodesPersistDenylist } from 'features/nodes/store/nodesPersistDenylist'; import { initialNodesState } from 'features/nodes/store/nodesSlice'; -import { generationPersistDenylist } from 'features/parameters/store/generationPersistDenylist'; import { initialGenerationState } from 'features/parameters/store/generationSlice'; -import { postprocessingPersistDenylist } from 'features/parameters/store/postprocessingPersistDenylist'; import { initialPostprocessingState } from 'features/parameters/store/postprocessingSlice'; import { initialConfigState } from 'features/system/store/configSlice'; import { initialModelsState } from 'features/system/store/modelSlice'; -import { modelsPersistDenylist } from 'features/system/store/modelsPersistDenylist'; -import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist'; import { initialSystemState } from 'features/system/store/systemSlice'; import { initialHotkeysState } from 'features/ui/store/hotkeysSlice'; -import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist'; import { initialUIState } from 'features/ui/store/uiSlice'; -import { defaultsDeep, merge, omit } from 'lodash-es'; +import { defaultsDeep } from 'lodash-es'; import { UnserializeFunction } from 'redux-remember'; const initialStates: { diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index d5994be1a1..b89615b2c0 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -1,6 +1,5 @@ import { AnyAction, - Store, ThunkDispatch, combineReducers, configureStore, diff --git a/invokeai/frontend/web/src/app/store/storeHooks.ts b/invokeai/frontend/web/src/app/store/storeHooks.ts index 2d2d632caa..f0400c3a3c 100644 --- a/invokeai/frontend/web/src/app/store/storeHooks.ts +++ b/invokeai/frontend/web/src/app/store/storeHooks.ts @@ -1,5 +1,5 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; -import { AppDispatch, AppThunkDispatch, RootState } from 'app/store/store'; +import { AppThunkDispatch, RootState } from 'app/store/store'; // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch = () => useDispatch(); diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index f2be887c93..4023f7665d 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -12,13 +12,10 @@ * 'gfpgan'. */ -import { GalleryCategory } from 'features/gallery/store/gallerySlice'; import { SelectedImage } from 'features/parameters/store/actions'; -import { FacetoolType } from 'features/parameters/store/postprocessingSlice'; import { InvokeTabName } from 'features/ui/store/tabMap'; import { IRect } from 'konva/lib/types'; import { ImageResponseMetadata, ImageType } from 'services/api'; -import { AnyInvocation } from 'services/events/types'; import { O } from 'ts-toolbelt'; /** @@ -279,7 +276,7 @@ export type FoundModelResponse = { // export type SystemConfigResponse = SystemConfig; -export type ImageResultResponse = Omit<_Image, 'uuid'> & { +export type ImageResultResponse = Omit & { boundingBox?: IRect; generationMode: InvokeTabName; }; diff --git a/invokeai/frontend/web/src/common/components/IAICollapse.tsx b/invokeai/frontend/web/src/common/components/IAICollapse.tsx index 25d2dee56c..161caca24d 100644 --- a/invokeai/frontend/web/src/common/components/IAICollapse.tsx +++ b/invokeai/frontend/web/src/common/components/IAICollapse.tsx @@ -1,4 +1,4 @@ -import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; +import { ChevronUpIcon } from '@chakra-ui/icons'; import { Box, Collapse, Flex, Spacer, Switch } from '@chakra-ui/react'; import { PropsWithChildren, memo } from 'react'; diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index 67adcc1769..501dfe36d8 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -22,7 +22,6 @@ import { CanvasLayer, CanvasLayerState, CanvasMaskLine, - CanvasSession, CanvasState, CanvasTool, Dimensions, @@ -30,7 +29,6 @@ import { isCanvasBaseImage, isCanvasMaskLine, } from './canvasTypes'; -import { stringToArray } from 'konva/lib/shapes/Text'; export const initialLayerState: CanvasLayerState = { objects: [], diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index 456d8622f0..7118fd3f90 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -1,11 +1,10 @@ import { createSelector } from '@reduxjs/toolkit'; -import { get, isEqual, isNumber, isString } from 'lodash-es'; +import { isEqual, isString } from 'lodash-es'; import { ButtonGroup, Flex, FlexProps, - FormControl, Link, useDisclosure, useToast, @@ -15,19 +14,12 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import IAIIconButton from 'common/components/IAIIconButton'; import IAIPopover from 'common/components/IAIPopover'; -import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; -import { GalleryState } from 'features/gallery/store/gallerySlice'; + import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice'; -import { - initialImageChanged, - setAllParameters, - // setInitialImage, - setSeed, -} from 'features/parameters/store/generationSlice'; import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors'; import { systemSelector } from 'features/system/store/systemSelectors'; -import { SystemState } from 'features/system/store/systemSlice'; + import { activeTabNameSelector, uiSelector, @@ -165,31 +157,31 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { const toast = useToast(); const { t } = useTranslation(); - const { recallPrompt, recallSeed, sendToImageToImage } = useParameters(); + const { recallPrompt, recallSeed } = useParameters(); - const handleCopyImage = useCallback(async () => { - if (!image?.url) { - return; - } + // const handleCopyImage = useCallback(async () => { + // if (!image?.url) { + // return; + // } - const url = getUrl(image.url); + // const url = getUrl(image.url); - if (!url) { - return; - } + // if (!url) { + // return; + // } - const blob = await fetch(url).then((res) => res.blob()); - const data = [new ClipboardItem({ [blob.type]: blob })]; + // const blob = await fetch(url).then((res) => res.blob()); + // const data = [new ClipboardItem({ [blob.type]: blob })]; - await navigator.clipboard.write(data); + // await navigator.clipboard.write(data); - toast({ - title: t('toast.imageCopied'), - status: 'success', - duration: 2500, - isClosable: true, - }); - }, [getUrl, t, image?.url, toast]); + // toast({ + // title: t('toast.imageCopied'), + // status: 'success', + // duration: 2500, + // isClosable: true, + // }); + // }, [getUrl, t, image?.url, toast]); const handleCopyImageLink = useCallback(() => { const url = image diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index 52bb6856e1..3c2dcdf818 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -5,9 +5,7 @@ import { Image, MenuItem, MenuList, - Skeleton, useDisclosure, - useTheme, useToast, } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; @@ -98,17 +96,16 @@ const HoverableImage = memo((props: HoverableImageProps) => { } = useDisclosure(); const { image, isSelected } = props; - const { url, thumbnail, name, metadata } = image; + const { url, thumbnail, name } = image; const { getUrl } = useGetUrl(); const [isHovered, setIsHovered] = useState(false); const toast = useToast(); - const { direction } = useTheme(); + const { t } = useTranslation(); const { isFeatureEnabled: isLightboxEnabled } = useFeatureStatus('lightbox'); - const { recallSeed, recallPrompt, sendToImageToImage, recallInitialImage } = - useParameters(); + const { recallSeed, recallPrompt, recallInitialImage } = useParameters(); const handleMouseOver = () => setIsHovered(true); const handleMouseOut = () => setIsHovered(false); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx index 3fa761ca3b..cfb6ba0914 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx @@ -5,7 +5,6 @@ import { // selectPrevImage, setGalleryImageMinimumWidth, } from 'features/gallery/store/gallerySlice'; -import { InvokeTabName } from 'features/ui/store/tabMap'; import { clamp, isEqual } from 'lodash-es'; import { useHotkeys } from 'react-hotkeys-hook'; @@ -13,11 +12,7 @@ import { useHotkeys } from 'react-hotkeys-hook'; import './ImageGallery.css'; import ImageGalleryContent from './ImageGalleryContent'; import ResizableDrawer from 'features/ui/components/common/ResizableDrawer/ResizableDrawer'; -import { - setShouldShowGallery, - toggleGalleryPanel, - togglePinGalleryPanel, -} from 'features/ui/store/uiSlice'; +import { setShouldShowGallery } from 'features/ui/store/uiSlice'; import { createSelector } from '@reduxjs/toolkit'; import { activeTabNameSelector, @@ -26,8 +21,6 @@ import { import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; -import useResolution from 'common/hooks/useResolution'; -import { Flex } from '@chakra-ui/react'; import { memo } from 'react'; // const GALLERY_TAB_WIDTHS: Record< diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx index eedbf63081..4e96916976 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx @@ -3,7 +3,6 @@ import { Box, Center, Flex, - Heading, IconButton, Link, Text, @@ -19,8 +18,6 @@ import { setCfgScale, setHeight, setImg2imgStrength, - // setInitialImage, - setMaskPath, setPerlin, setSampler, setSeamless, @@ -31,15 +28,7 @@ import { setThreshold, setWidth, } from 'features/parameters/store/generationSlice'; -import { - setCodeformerFidelity, - setFacetoolStrength, - setFacetoolType, - setHiresFix, - setUpscalingDenoising, - setUpscalingLevel, - setUpscalingStrength, -} from 'features/parameters/store/postprocessingSlice'; +import { setHiresFix } from 'features/parameters/store/postprocessingSlice'; import { setShouldShowImageDetails } from 'features/ui/store/uiSlice'; import { memo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts index 082e644b43..3eeb2aa933 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts @@ -1,23 +1,14 @@ import { createSelector } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; -import { configSelector } from 'features/system/store/configSelectors'; -import { systemSelector } from 'features/system/store/systemSelectors'; + import { activeTabNameSelector, uiSelector, } from 'features/ui/store/uiSelectors'; import { isEqual } from 'lodash-es'; -import { - selectResultsAll, - selectResultsById, - selectResultsEntities, -} from './resultsSlice'; -import { - selectUploadsAll, - selectUploadsById, - selectUploadsEntities, -} from './uploadsSlice'; +import { selectResultsById, selectResultsEntities } from './resultsSlice'; +import { selectUploadsAll, selectUploadsById } from './uploadsSlice'; export const gallerySelector = (state: RootState) => state.gallery; diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts index fcad240058..d0a7821d9d 100644 --- a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts @@ -6,7 +6,7 @@ import { receivedUploadImagesPage, IMAGES_PER_PAGE, } from 'services/thunks/gallery'; -import { imageDeleted, imageUploaded } from 'services/thunks/image'; +import { imageDeleted } from 'services/thunks/image'; import { deserializeImageResponse } from 'services/util/deserializeImageResponse'; export const uploadsAdapter = createEntityAdapter({ diff --git a/invokeai/frontend/web/src/features/lightbox/components/ReactPanZoomImage.tsx b/invokeai/frontend/web/src/features/lightbox/components/ReactPanZoomImage.tsx index 1eea014dfd..9781625949 100644 --- a/invokeai/frontend/web/src/features/lightbox/components/ReactPanZoomImage.tsx +++ b/invokeai/frontend/web/src/features/lightbox/components/ReactPanZoomImage.tsx @@ -4,7 +4,7 @@ import * as InvokeAI from 'app/types/invokeai'; import { useGetUrl } from 'common/util/getUrl'; type ReactPanZoomProps = { - image: InvokeAI._Image; + image: InvokeAI.Image; styleClass?: string; alt?: string; ref?: React.Ref; diff --git a/invokeai/frontend/web/src/features/nodes/components/AddNodeMenu.tsx b/invokeai/frontend/web/src/features/nodes/components/AddNodeMenu.tsx index 3265a2620f..a4ce2f55f6 100644 --- a/invokeai/frontend/web/src/features/nodes/components/AddNodeMenu.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/AddNodeMenu.tsx @@ -1,5 +1,3 @@ -import { v4 as uuidv4 } from 'uuid'; - import 'reactflow/dist/style.css'; import { memo, useCallback } from 'react'; import { @@ -8,12 +6,11 @@ import { MenuButton, MenuList, MenuItem, - IconButton, } from '@chakra-ui/react'; -import { FaEllipsisV, FaPlus } from 'react-icons/fa'; +import { FaEllipsisV } from 'react-icons/fa'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { nodeAdded } from '../store/nodesSlice'; -import { cloneDeep, map } from 'lodash-es'; +import { map } from 'lodash-es'; import { RootState } from 'app/store/store'; import { useBuildInvocation } from '../hooks/useBuildInvocation'; import { addToast } from 'features/system/store/systemSlice'; diff --git a/invokeai/frontend/web/src/features/nodes/components/FieldHandle.tsx b/invokeai/frontend/web/src/features/nodes/components/FieldHandle.tsx index f21ab09be3..86099a7315 100644 --- a/invokeai/frontend/web/src/features/nodes/components/FieldHandle.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/FieldHandle.tsx @@ -1,12 +1,6 @@ import { Tooltip } from '@chakra-ui/react'; -import { CSSProperties, memo, useMemo } from 'react'; -import { - Handle, - Position, - Connection, - HandleType, - useReactFlow, -} from 'reactflow'; +import { CSSProperties, memo } from 'react'; +import { Handle, Position, Connection, HandleType } from 'reactflow'; import { FIELDS, HANDLE_TOOLTIP_OPEN_DELAY } from '../types/constants'; // import { useConnectionEventStyles } from '../hooks/useConnectionEventStyles'; import { InputFieldTemplate, OutputFieldTemplate } from '../types/types'; @@ -26,9 +20,9 @@ const outputHandleStyles: CSSProperties = { right: '-0.5rem', }; -const requiredConnectionStyles: CSSProperties = { - boxShadow: '0 0 0.5rem 0.5rem var(--invokeai-colors-error-400)', -}; +// const requiredConnectionStyles: CSSProperties = { +// boxShadow: '0 0 0.5rem 0.5rem var(--invokeai-colors-error-400)', +// }; type FieldHandleProps = { nodeId: string; @@ -39,8 +33,8 @@ type FieldHandleProps = { }; const FieldHandle = (props: FieldHandleProps) => { - const { nodeId, field, isValidConnection, handleType, styles } = props; - const { name, title, type, description } = field; + const { field, isValidConnection, handleType, styles } = props; + const { name, type } = field; return ( { style: { strokeWidth: 2 }, }} > - - {/* */} + diff --git a/invokeai/frontend/web/src/features/nodes/components/IAINode/IAINodeHeader.tsx b/invokeai/frontend/web/src/features/nodes/components/IAINode/IAINodeHeader.tsx index 2a61d4cc2b..f944c6e463 100644 --- a/invokeai/frontend/web/src/features/nodes/components/IAINode/IAINodeHeader.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/IAINode/IAINodeHeader.tsx @@ -1,6 +1,6 @@ import { Flex, Heading, Tooltip, Icon } from '@chakra-ui/react'; import { InvocationTemplate } from 'features/nodes/types/types'; -import { memo, MutableRefObject } from 'react'; +import { memo } from 'react'; import { FaInfoCircle } from 'react-icons/fa'; interface IAINodeHeaderProps { diff --git a/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx index 98698e2d81..9527708c40 100644 --- a/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx @@ -22,7 +22,7 @@ type InputFieldComponentProps = { // build an individual input element based on the schema const InputFieldComponent = (props: InputFieldComponentProps) => { const { nodeId, field, template } = props; - const { type, value } = field; + const { type } = field; if (type === 'string' && template.type === 'string') { return ( diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx index 74967c20d8..b43338f930 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx @@ -1,16 +1,16 @@ -import { Box, Image, Icon, Flex } from '@chakra-ui/react'; +import { Box, Image } from '@chakra-ui/react'; import { useAppDispatch } from 'app/store/storeHooks'; import SelectImagePlaceholder from 'common/components/SelectImagePlaceholder'; import { useGetUrl } from 'common/util/getUrl'; import useGetImageByNameAndType from 'features/gallery/hooks/useGetImageByName'; -import useGetImageByUuid from 'features/gallery/hooks/useGetImageByUuid'; + import { fieldValueChanged } from 'features/nodes/store/nodesSlice'; import { ImageInputFieldTemplate, ImageInputFieldValue, } from 'features/nodes/types/types'; import { DragEvent, memo, useCallback, useState } from 'react'; -import { FaImage } from 'react-icons/fa'; + import { ImageType } from 'services/api'; import { FieldComponentProps } from './types'; @@ -18,7 +18,6 @@ const ImageInputFieldComponent = ( props: FieldComponentProps ) => { const { nodeId, field } = props; - const { value } = field; const getImageByNameAndType = useGetImageByNameAndType(); const dispatch = useAppDispatch(); diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ItemInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ItemInputFieldComponent.tsx index 85ce887e50..fa8eb5a26d 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ItemInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ItemInputFieldComponent.tsx @@ -3,7 +3,7 @@ import { ItemInputFieldValue, } from 'features/nodes/types/types'; import { memo } from 'react'; -import { FaAddressCard, FaList } from 'react-icons/fa'; +import { FaAddressCard } from 'react-icons/fa'; import { FieldComponentProps } from './types'; const ItemInputFieldComponent = ( diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx index ea84fca43c..a1ef69de01 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx @@ -1,17 +1,13 @@ import { Select } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { fieldValueChanged } from 'features/nodes/store/nodesSlice'; import { ModelInputFieldTemplate, ModelInputFieldValue, } from 'features/nodes/types/types'; -import { - selectModelsById, - selectModelsIds, -} from 'features/system/store/modelSlice'; -import { isEqual, map } from 'lodash-es'; +import { selectModelsIds } from 'features/system/store/modelSlice'; +import { isEqual } from 'lodash-es'; import { ChangeEvent, memo } from 'react'; import { FieldComponentProps } from './types'; diff --git a/invokeai/frontend/web/src/features/nodes/components/panels/TopLeftPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/panels/TopLeftPanel.tsx index 2b89db000a..3fe72225eb 100644 --- a/invokeai/frontend/web/src/features/nodes/components/panels/TopLeftPanel.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/panels/TopLeftPanel.tsx @@ -1,10 +1,10 @@ import { memo } from 'react'; import { Panel } from 'reactflow'; -import AddNodeMenu from '../AddNodeMenu'; +import NodeSearch from '../search/NodeSearch'; const TopLeftPanel = () => ( - + ); diff --git a/invokeai/frontend/web/src/features/nodes/components/search/NodeSearch.tsx b/invokeai/frontend/web/src/features/nodes/components/search/NodeSearch.tsx index 66b1d72014..b06619e76f 100644 --- a/invokeai/frontend/web/src/features/nodes/components/search/NodeSearch.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/search/NodeSearch.tsx @@ -2,7 +2,6 @@ import { Box, Flex } from '@chakra-ui/layout'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIInput from 'common/components/IAIInput'; -import { Panel } from 'reactflow'; import { map } from 'lodash-es'; import { ChangeEvent, @@ -192,19 +191,17 @@ const NodeSearch = () => { }; return ( - - setShowNodeList(true)} - onBlur={searchInputBlurHandler} - ref={nodeSearchRef} - > - - {showNodeList && renderNodeList()} - - + setShowNodeList(true)} + onBlur={searchInputBlurHandler} + ref={nodeSearchRef} + > + + {showNodeList && renderNodeList()} + ); }; diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts index e39908f053..4ce0120c21 100644 --- a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts +++ b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts @@ -11,7 +11,7 @@ import { NodeChange, OnConnectStartParams, } from 'reactflow'; -import { ColorField, Graph, ImageField } from 'services/api'; +import { ImageField } from 'services/api'; import { receivedOpenAPISchema } from 'services/thunks/schema'; import { InvocationTemplate, InvocationValue } from '../types/types'; import { parseSchema } from '../util/parseSchema'; diff --git a/invokeai/frontend/web/src/features/nodes/types/constants.ts b/invokeai/frontend/web/src/features/nodes/types/constants.ts index 0e715cb8bb..7e4dadc21d 100644 --- a/invokeai/frontend/web/src/features/nodes/types/constants.ts +++ b/invokeai/frontend/web/src/features/nodes/types/constants.ts @@ -1,4 +1,3 @@ -import { getCSSVar } from '@chakra-ui/utils'; import { FieldType, FieldUIConfig } from './types'; export const HANDLE_TOOLTIP_OPEN_DELAY = 500; diff --git a/invokeai/frontend/web/src/features/nodes/types/types.ts b/invokeai/frontend/web/src/features/nodes/types/types.ts index 5cb67d1ef6..876ba95cac 100644 --- a/invokeai/frontend/web/src/features/nodes/types/types.ts +++ b/invokeai/frontend/web/src/features/nodes/types/types.ts @@ -1,9 +1,7 @@ -import { Image } from 'app/types/invokeai'; import { OpenAPIV3 } from 'openapi-types'; import { RgbaColor } from 'react-colorful'; import { ImageField } from 'services/api'; import { AnyInvocationType } from 'services/events/types'; -import { O } from 'ts-toolbelt'; export type InvocationValue = { id: string; diff --git a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts index 020873fe81..11f0087488 100644 --- a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts +++ b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts @@ -234,7 +234,6 @@ const buildEnumInputFieldTemplate = ({ }; const buildArrayInputFieldTemplate = ({ - schemaObject, baseField, }: BuildInputFieldArg): ArrayInputFieldTemplate => { const template: ArrayInputFieldTemplate = { @@ -249,7 +248,6 @@ const buildArrayInputFieldTemplate = ({ }; const buildItemInputFieldTemplate = ({ - schemaObject, baseField, }: BuildInputFieldArg): ItemInputFieldTemplate => { const template: ItemInputFieldTemplate = { diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts index 2ae31a261c..46bc510020 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts @@ -1,8 +1,6 @@ import { RootState } from 'app/store/store'; import { - DataURLToImageInvocation, Edge, - Graph, ImageToImageInvocation, InpaintInvocation, IterateInvocation, @@ -15,10 +13,8 @@ import { buildTxt2ImgNode } from '../nodeBuilders/buildTextToImageNode'; import { buildRangeNode } from '../nodeBuilders/buildRangeNode'; import { buildIterateNode } from '../nodeBuilders/buildIterateNode'; import { buildEdges } from '../edgeBuilders/buildEdges'; -import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; import { getCanvasData } from 'features/canvas/util/getCanvasData'; import { getGenerationMode } from '../getGenerationMode'; -import { v4 as uuidv4 } from 'uuid'; import { log } from 'app/logging/useLogger'; import { buildInpaintNode } from '../nodeBuilders/buildInpaintNode'; @@ -73,9 +69,7 @@ export const buildCanvasGraphAndBlobs = async ( } const { - baseDataURL, baseBlob, - maskDataURL, maskBlob, baseIsPartiallyTransparent, baseIsFullyTransparent, @@ -112,14 +106,8 @@ export const buildCanvasGraphAndBlobs = async ( } if (baseNode.type === 'inpaint') { - const { - seamSize, - seamBlur, - seamSteps, - seamStrength, - tileSize, - infillMethod, - } = state.generation; + const { seamSize, seamBlur, seamSteps, seamStrength, tileSize } = + state.generation; // generationParameters.invert_mask = shouldPreserveMaskedArea; // if (boundingBoxScale !== 'none') { diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts index 0e1937ced8..df5b65e296 100644 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts @@ -13,7 +13,7 @@ export const buildImg2ImgNode = ( overrides: O.Partial = {} ): ImageToImageInvocation => { const nodeId = uuidv4(); - const { generation, system, models } = state; + const { generation } = state; const { prompt, @@ -28,9 +28,6 @@ export const buildImg2ImgNode = ( img2imgStrength: strength, shouldFitToWidthHeight: fit, shouldRandomizeSeed, - shouldUseSeamless, - seamlessXAxis, - seamlessYAxis, } = generation; const initialImage = initialImageSelector(state); diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildInpaintNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildInpaintNode.ts index fc8d485e6d..9cd124ba9e 100644 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildInpaintNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildInpaintNode.ts @@ -1,11 +1,6 @@ import { v4 as uuidv4 } from 'uuid'; import { RootState } from 'app/store/store'; -import { - Edge, - ImageToImageInvocation, - InpaintInvocation, - TextToImageInvocation, -} from 'services/api'; +import { InpaintInvocation } from 'services/api'; import { initialImageSelector } from 'features/parameters/store/generationSelectors'; import { O } from 'ts-toolbelt'; @@ -14,7 +9,7 @@ export const buildInpaintNode = ( overrides: O.Partial = {} ): InpaintInvocation => { const nodeId = uuidv4(); - const { generation, system, models } = state; + const { generation, models } = state; const { selectedModelName } = models; diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildTextToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildTextToImageNode.ts index 711c64ed67..fe76531c59 100644 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildTextToImageNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildTextToImageNode.ts @@ -8,7 +8,7 @@ export const buildTxt2ImgNode = ( overrides: O.Partial = {} ): TextToImageInvocation => { const nodeId = uuidv4(); - const { generation, models } = state; + const { generation } = state; const { prompt, diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageSettings.tsx index ad6d31dd17..e8198c75ad 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageSettings.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageSettings.tsx @@ -1,27 +1,11 @@ -import { - Box, - ButtonGroup, - Collapse, - Flex, - Heading, - HStack, - Image, - Spacer, - useDisclosure, - VStack, -} from '@chakra-ui/react'; -import { motion } from 'framer-motion'; +import { VStack } from '@chakra-ui/react'; -import IAIButton from 'common/components/IAIButton'; import ImageToImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageFit'; import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength'; -import IAIIconButton from 'common/components/IAIIconButton'; import { useTranslation } from 'react-i18next'; import InitialImagePreview from './InitialImagePreview'; -import { useState } from 'react'; -import { FaUndo, FaUpload } from 'react-icons/fa'; -import InitialImageButtons from 'common/components/ImageToImageSettingsHeader'; +import InitialImageButtons from 'common/components/ImageToImageButtons'; export default function ImageToImageSettings() { const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageToggle.tsx index eeb0804b5d..f70e8e6602 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageToggle.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageToggle.tsx @@ -1,11 +1,9 @@ import { Flex } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISwitch from 'common/components/IAISwitch'; import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { isImageToImageEnabledChanged } from 'features/parameters/store/generationSlice'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { shouldShowImageParametersChanged } from 'features/ui/store/uiSlice'; import { ChangeEvent } from 'react'; @@ -25,8 +23,7 @@ const selector = createSelector( ); export default function ImageToImageToggle() { - const { isImageToImageEnabled, shouldShowImageParameters } = - useAppSelector(selector); + const { shouldShowImageParameters } = useAppSelector(selector); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx index 184e88a505..cb3ffc3590 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx @@ -1,24 +1,16 @@ -import { Box, Flex, Image, Spinner, Text } from '@chakra-ui/react'; +import { Flex, Image, Spinner } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import SelectImagePlaceholder from 'common/components/SelectImagePlaceholder'; import { useGetUrl } from 'common/util/getUrl'; -import useGetImageByNameAndType from 'features/gallery/hooks/useGetImageByName'; -import generationSlice, { - clearInitialImage, - initialImageChanged, -} from 'features/parameters/store/generationSlice'; +import { clearInitialImage } from 'features/parameters/store/generationSlice'; import { addToast } from 'features/system/store/systemSlice'; import { isEqual } from 'lodash-es'; import { DragEvent, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ImageType } from 'services/api'; import ImageToImageOverlay from 'common/components/ImageToImageOverlay'; -import { - generationSelector, - initialImageSelector, -} from 'features/parameters/store/generationSelectors'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; import { initialImageSelected } from 'features/parameters/store/actions'; const selector = createSelector( @@ -40,7 +32,6 @@ const InitialImagePreview = () => { const { t } = useTranslation(); const [isLoaded, setIsLoaded] = useState(false); - const getImageByNameAndType = useGetImageByNameAndType(); const onError = () => { dispatch( diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse.tsx index c8645d3607..e5b88c7a60 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse.tsx @@ -22,8 +22,7 @@ const selector = createSelector( const ParamSeamlessCollapse = () => { const { t } = useTranslation(); - const { shouldUseSeamless, seamlessXAxis, seamlessYAxis } = - useAppSelector(selector); + const { shouldUseSeamless } = useAppSelector(selector); const dispatch = useAppDispatch(); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeed.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeed.tsx index c5a00d32c8..d5ced67d0e 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeed.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeed.tsx @@ -1,12 +1,9 @@ -import { Flex, HStack } from '@chakra-ui/react'; import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAINumberInput from 'common/components/IAINumberInput'; import { setSeed } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; -import ParamSeedShuffle from './ParamSeedShuffle'; -import ParamSeedRandomize from './ParamSeedRandomize'; export default function ParamSeed() { const seed = useAppSelector((state: RootState) => state.generation.seed); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedShuffle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedShuffle.tsx index 9dba81104c..dd1f05e2f9 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedShuffle.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedShuffle.tsx @@ -3,11 +3,9 @@ import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; -import IAIIconButton from 'common/components/IAIIconButton'; import randomInt from 'common/util/randomInt'; import { setSeed } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; -import { FaRandom } from 'react-icons/fa'; export default function ParamSeedShuffle() { const dispatch = useAppDispatch(); diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx index 5cded54a21..23f2ae409a 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx @@ -20,7 +20,6 @@ import { MenuList, MenuOptionGroup, MenuItemOption, - IconButton, } from '@chakra-ui/react'; import { useHotkeys } from 'react-hotkeys-hook'; @@ -28,8 +27,6 @@ import { useTranslation } from 'react-i18next'; import { MdCancel, MdCancelScheduleSend } from 'react-icons/md'; import { sessionCanceled } from 'services/thunks/session'; -import { BiChevronDown } from 'react-icons/bi'; -import { FaChevronDown } from 'react-icons/fa'; import { ChevronDownIcon } from '@chakra-ui/icons'; const cancelButtonSelector = createSelector( diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/ProcessButtons.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/ProcessButtons.tsx index 2b4399866e..4449866ef2 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/ProcessButtons.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/ProcessButtons.tsx @@ -1,18 +1,11 @@ import { Flex } from '@chakra-ui/react'; -import { useAppSelector } from 'app/store/storeHooks'; -import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import CancelButton from './CancelButton'; import InvokeButton from './InvokeButton'; -import LoopbackButton from './Loopback'; -import IAICheckbox from 'common/components/IAICheckbox'; -import IAISwitch from 'common/components/IAISwitch'; /** * Buttons to start and cancel image generation. */ const ProcessButtons = () => { - const activeTabName = useAppSelector(activeTabNameSelector); - return ( diff --git a/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts b/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts index 23e969b0eb..7b8dc455ac 100644 --- a/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts +++ b/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts @@ -1,11 +1,11 @@ -import { UseToastOptions, useToast } from '@chakra-ui/react'; +import { useToast } from '@chakra-ui/react'; import { useAppDispatch } from 'app/store/storeHooks'; import { isFinite, isString } from 'lodash-es'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import useSetBothPrompts from './usePrompt'; -import { initialImageChanged, setSeed } from '../store/generationSlice'; -import { isImage, isImageField } from 'services/types/guards'; +import { setSeed } from '../store/generationSlice'; +import { isImageField } from 'services/types/guards'; import { NUMPY_RAND_MAX } from 'app/constants'; import { initialImageSelected } from '../store/actions'; import { Image } from 'app/types/invokeai'; diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 89891e96aa..948ba4cc1e 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,11 +1,8 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import * as InvokeAI from 'app/types/invokeai'; -import { getPromptAndNegative } from 'common/util/getPromptAndNegative'; import promptToString from 'common/util/promptToString'; -import { seedWeightsToString } from 'common/util/seedWeightPairs'; import { clamp } from 'lodash-es'; -import { ImageField, ImageType } from 'services/api'; export interface GenerationState { cfgScale: number; diff --git a/invokeai/frontend/web/src/features/parameters/store/hiresSlice.ts b/invokeai/frontend/web/src/features/parameters/store/hiresSlice.ts index 15098afed5..89827270d1 100644 --- a/invokeai/frontend/web/src/features/parameters/store/hiresSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/hiresSlice.ts @@ -1,6 +1,5 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import { FACETOOL_TYPES } from 'app/constants'; export interface HiresState { codeformerFidelity: number; diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index d6c7c154e0..e38fda2676 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -5,16 +5,9 @@ import { useTranslation } from 'react-i18next'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAISelect from 'common/components/IAISelect'; -import { - // modelSelected, - selectedModelSelector, - selectModelsById, - selectModelsIds, -} from '../store/modelSlice'; +import { selectModelsById, selectModelsIds } from '../store/modelSlice'; import { RootState } from 'app/store/store'; -import generationSlice, { - modelSelected, -} from 'features/parameters/store/generationSlice'; +import { modelSelected } from 'features/parameters/store/generationSlice'; import { generationSelector } from 'features/parameters/store/generationSelectors'; const selector = createSelector( diff --git a/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx b/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx index 350e1291aa..9b4159ecb6 100644 --- a/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx +++ b/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx @@ -1,6 +1,5 @@ import { Flex, Grid } from '@chakra-ui/react'; import { memo, useState } from 'react'; -import ModelSelect from './ModelSelect'; import StatusIndicator from './StatusIndicator'; import InvokeAILogoComponent from './InvokeAILogoComponent'; diff --git a/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx b/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx index 48418c9f19..18d71d2e41 100644 --- a/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx +++ b/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx @@ -43,7 +43,6 @@ const StatusIndicator = () => { currentIteration, totalIterations, statusTranslationKey, - currentStatusHasSteps, } = useAppSelector(statusIndicatorSelector); const { t } = useTranslation(); const ref = useRef(null); diff --git a/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts b/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts index cecd739278..6e62c3642b 100644 --- a/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts +++ b/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts @@ -1,5 +1,4 @@ import { createSelector } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; import { useAppSelector } from 'app/store/storeHooks'; import { useMemo } from 'react'; import { configSelector } from '../store/configSelectors'; diff --git a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts b/invokeai/frontend/web/src/features/system/store/modelSelectors.ts index 8b502fb3b6..f857bc85bc 100644 --- a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts +++ b/invokeai/frontend/web/src/features/system/store/modelSelectors.ts @@ -1,5 +1,3 @@ -import { createSelector } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; -import { reduce } from 'lodash-es'; export const modelSelector = (state: RootState) => state.models; diff --git a/invokeai/frontend/web/src/features/system/store/modelSlice.ts b/invokeai/frontend/web/src/features/system/store/modelSlice.ts index 6bd676762c..9c7194672d 100644 --- a/invokeai/frontend/web/src/features/system/store/modelSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/modelSlice.ts @@ -1,7 +1,6 @@ -import { createEntityAdapter, PayloadAction } from '@reduxjs/toolkit'; +import { createEntityAdapter } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; -import { keys, sample } from 'lodash-es'; import { CkptModelInfo, DiffusersModelInfo } from 'services/api'; import { receivedModels } from 'services/thunks/model'; diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 025cebf0a6..dec34d3a9c 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -15,7 +15,6 @@ import { } from 'services/events/actions'; import { ProgressImage } from 'services/events/types'; -import { initialImageChanged } from 'features/parameters/store/generationSlice'; import { makeToast } from '../hooks/useToastWatcher'; import { sessionCanceled, sessionInvoked } from 'services/thunks/session'; import { receivedModels } from 'services/thunks/model'; @@ -284,8 +283,7 @@ export const systemSlice = createSlice({ /** * Socket Connected */ - builder.addCase(socketConnected, (state, action) => { - const { timestamp } = action.payload; + builder.addCase(socketConnected, (state) => { state.isConnected = true; state.isCancelable = true; state.isProcessing = false; @@ -300,9 +298,7 @@ export const systemSlice = createSlice({ /** * Socket Disconnected */ - builder.addCase(socketDisconnected, (state, action) => { - const { timestamp } = action.payload; - + builder.addCase(socketDisconnected, (state) => { state.isConnected = false; state.isProcessing = false; state.isCancelable = true; @@ -317,7 +313,7 @@ export const systemSlice = createSlice({ /** * Invocation Started */ - builder.addCase(invocationStarted, (state, action) => { + builder.addCase(invocationStarted, (state) => { state.isCancelable = true; state.isProcessing = true; state.currentStatusHasSteps = false; @@ -332,14 +328,7 @@ export const systemSlice = createSlice({ * Generator Progress */ builder.addCase(generatorProgress, (state, action) => { - const { - step, - total_steps, - progress_image, - node, - source_node_id, - graph_execution_state_id, - } = action.payload.data; + const { step, total_steps, progress_image } = action.payload.data; state.isProcessing = true; state.isCancelable = true; @@ -356,7 +345,7 @@ export const systemSlice = createSlice({ * Invocation Complete */ builder.addCase(invocationComplete, (state, action) => { - const { data, timestamp } = action.payload; + const { data } = action.payload; // state.currentIteration = 0; // state.totalIterations = 0; @@ -374,9 +363,7 @@ export const systemSlice = createSlice({ /** * Invocation Error */ - builder.addCase(invocationError, (state, action) => { - const { data, timestamp } = action.payload; - + builder.addCase(invocationError, (state) => { state.isProcessing = false; state.isCancelable = true; // state.currentIteration = 0; @@ -410,8 +397,6 @@ export const systemSlice = createSlice({ * Session Canceled */ builder.addCase(sessionCanceled.fulfilled, (state, action) => { - const { timestamp } = action.payload; - state.canceledSession = action.meta.arg.sessionId; state.isProcessing = false; state.isCancelable = false; @@ -428,9 +413,7 @@ export const systemSlice = createSlice({ /** * Session Canceled */ - builder.addCase(graphExecutionStateComplete, (state, action) => { - const { timestamp } = action.payload; - + builder.addCase(graphExecutionStateComplete, (state) => { state.isProcessing = false; state.isCancelable = false; state.isCancelScheduled = false; diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index 47a250b933..559ea4f8c2 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -81,12 +81,8 @@ const InvokeTabs = () => { (state: RootState) => state.lightbox.isLightboxOpen ); - const { - shouldPinGallery, - shouldPinParametersPanel, - shouldShowGallery, - shouldShowParametersPanel, - } = useAppSelector((state: RootState) => state.ui); + const { shouldPinGallery, shouldPinParametersPanel, shouldShowGallery } = + useAppSelector((state: RootState) => state.ui); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx index ce4ca4f438..7a969bc396 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx @@ -1,8 +1,7 @@ -import { isEqual } from 'lodash-es'; import { createSelector } from '@reduxjs/toolkit'; import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { PropsWithChildren, memo, useMemo } from 'react'; +import { memo, useMemo } from 'react'; import { Box, Flex } from '@chakra-ui/react'; import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent'; import OverlayScrollable from './common/OverlayScrollable'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx index 455ff4a32b..dbf4041edf 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx @@ -1,19 +1,10 @@ -import { Box, Flex } from '@chakra-ui/react'; import { useAppSelector } from 'app/store/storeHooks'; import { memo } from 'react'; -import PinParametersPanelButton from '../../PinParametersPanelButton'; import { RootState } from 'app/store/store'; -import Scrollable from '../../common/Scrollable'; -import ParametersSlide from '../../ParametersDrawer'; -import UnifiedCanvasParameters from './UnifiedCanvasParameters'; import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; import UnifiedCanvasContent from './UnifiedCanvasContent'; const CanvasWorkspace = () => { - const shouldPinParametersPanel = useAppSelector( - (state: RootState) => state.ui.shouldPinParametersPanel - ); - const shouldUseCanvasBetaLayout = useAppSelector( (state: RootState) => state.ui.shouldUseCanvasBetaLayout ); @@ -23,35 +14,6 @@ const CanvasWorkspace = () => { ) : ( ); - - return ( - - {/* {shouldPinParametersPanel ? ( - - - - - - - ) : ( - - - - )} */} - {shouldUseCanvasBetaLayout ? ( - - ) : ( - - )} - - ); }; export default memo(CanvasWorkspace); diff --git a/invokeai/frontend/web/src/services/events/actions.ts b/invokeai/frontend/web/src/services/events/actions.ts index 192061aa73..84268773a9 100644 --- a/invokeai/frontend/web/src/services/events/actions.ts +++ b/invokeai/frontend/web/src/services/events/actions.ts @@ -1,4 +1,4 @@ -import { AnyAction, createAction } from '@reduxjs/toolkit'; +import { createAction } from '@reduxjs/toolkit'; import { GeneratorProgressEvent, GraphExecutionStateCompleteEvent, diff --git a/invokeai/frontend/web/src/services/util/deserializeImageField.ts b/invokeai/frontend/web/src/services/util/deserializeImageField.ts index adda71ccdd..74d63117a4 100644 --- a/invokeai/frontend/web/src/services/util/deserializeImageField.ts +++ b/invokeai/frontend/web/src/services/util/deserializeImageField.ts @@ -1,6 +1,4 @@ -import { Image } from 'app/types/invokeai'; -import { ImageField, ImageType } from 'services/api'; -import { AnyInvocation } from 'services/events/types'; +import { ImageType } from 'services/api'; export const buildImageUrls = ( imageType: ImageType, diff --git a/invokeai/frontend/web/src/services/util/makeGraphOfXImages.ts b/invokeai/frontend/web/src/services/util/makeGraphOfXImages.ts index 386ca972b1..837a053664 100644 --- a/invokeai/frontend/web/src/services/util/makeGraphOfXImages.ts +++ b/invokeai/frontend/web/src/services/util/makeGraphOfXImages.ts @@ -12,12 +12,12 @@ export const makeGraphOfXImages = (numberOfImages: string) => prompt: 'pizza', steps: 50, seed: 123, - sampler_name: 'ddim', + scheduler: 'ddim', }) ) .reduce( (acc, val: TextToImageInvocation) => { - acc.nodes![val.id] = val; + if (acc.nodes) acc.nodes[val.id] = val; return acc; }, { nodes: {} } as Graph diff --git a/invokeai/frontend/web/src/theme/util/constants.ts b/invokeai/frontend/web/src/theme/util/constants.ts index c73209ee75..722b899407 100644 --- a/invokeai/frontend/web/src/theme/util/constants.ts +++ b/invokeai/frontend/web/src/theme/util/constants.ts @@ -20,4 +20,4 @@ export const APP_TEXT_TO_IMAGE_HEIGHT = // option bar export const OPTIONS_BAR_MAX_WIDTH = '22.5rem'; -export const PARAMETERS_PANEL_WIDTH = '30rem'; +export const PARAMETERS_PANEL_WIDTH = '28rem'; From fcf9c630497adf9642d7d508a764adc1ce61c25d Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 May 2023 18:52:09 +1000 Subject: [PATCH 49/66] fix(ui): fix copying image link --- invokeai/frontend/web/public/locales/en.json | 1 + .../components/CurrentImageButtons.tsx | 29 +++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 13c99c22f2..e5827c2397 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -553,6 +553,7 @@ "downloadImageStarted": "Image Download Started", "imageCopied": "Image Copied", "imageLinkCopied": "Image Link Copied", + "problemCopyingImageLink": "Unable to Copy Image Link", "imageNotLoaded": "No Image Loaded", "imageNotLoadedDesc": "Could not find image", "imageSavedToGallery": "Image Saved to Gallery", diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index 7118fd3f90..a0ff83b8c6 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -184,13 +184,32 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { // }, [getUrl, t, image?.url, toast]); const handleCopyImageLink = useCallback(() => { - const url = image - ? shouldTransformUrls - ? getUrl(image.url) - : window.location.toString() + image.url - : ''; + const getImageUrl = () => { + if (!image) { + return; + } + + if (shouldTransformUrls) { + return getUrl(image.url); + } + + if (image.url.startsWith('http')) { + return image.url; + } + + return window.location.toString() + image.url; + }; + + const url = getImageUrl(); if (!url) { + toast({ + title: t('toast.problemCopyingImageLink'), + status: 'error', + duration: 2500, + isClosable: true, + }); + return; } From 3d1470399c94846e1baa9c208b32293371949188 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 May 2023 21:39:21 +1000 Subject: [PATCH 50/66] fix(ui): fix metadataviewer styling --- .../ImageMetadataViewer.tsx | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx index 4e96916976..4f34100fd6 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx @@ -35,6 +35,7 @@ import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { FaCopy } from 'react-icons/fa'; import { IoArrowUndoCircleOutline } from 'react-icons/io5'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; type MetadataItemProps = { isLink?: boolean; @@ -289,7 +290,7 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => { )} - + { Metadata JSON: - -
{metadataJSON}
-
+ + +
{metadataJSON}
+
+
); From fe8b5193de73fc2809d4b8922dbf1652b66e4d93 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 May 2023 21:39:50 +1000 Subject: [PATCH 51/66] feat(ui): half-baked use all parameters until we have a better system for metadata, this will remain half-baked --- .../components/CurrentImageButtons.tsx | 39 ++--- .../gallery/components/HoverableImage.tsx | 18 +-- .../parameters/hooks/useParameters.ts | 42 +++++- .../parameters/store/generationSlice.ts | 134 +----------------- .../store/setAllParametersReducer.ts | 61 ++++++++ 5 files changed, 118 insertions(+), 176 deletions(-) create mode 100644 invokeai/frontend/web/src/features/parameters/store/setAllParametersReducer.ts diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index a0ff83b8c6..32e631005d 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -61,6 +61,7 @@ import { initialImageSelected } from 'features/parameters/store/actions'; import { requestedImageDeletion } from '../store/actions'; import FaceRestoreSettings from 'features/parameters/components/Parameters/FaceRestore/FaceRestoreSettings'; import UpscaleSettings from 'features/parameters/components/Parameters/Upscale/UpscaleSettings'; +import { allParametersSet } from 'features/parameters/store/generationSlice'; const currentImageButtonsSelector = createSelector( [ @@ -157,7 +158,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { const toast = useToast(); const { t } = useTranslation(); - const { recallPrompt, recallSeed } = useParameters(); + const { recallPrompt, recallSeed, recallAllParameters } = useParameters(); // const handleCopyImage = useCallback(async () => { // if (!image?.url) { @@ -228,39 +229,15 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { }, [dispatch, shouldHidePreview]); const handleClickUseAllParameters = useCallback(() => { - if (!image) return; - // selectedImage.metadata && - // dispatch(setAllParameters(selectedImage.metadata)); - // if (selectedImage.metadata?.image.type === 'img2img') { - // dispatch(setActiveTab('img2img')); - // } else if (selectedImage.metadata?.image.type === 'txt2img') { - // dispatch(setActiveTab('txt2img')); - // } - }, [image]); + recallAllParameters(image); + }, [image, recallAllParameters]); useHotkeys( 'a', () => { - const type = image?.metadata?.invokeai?.node?.types; - if (isString(type) && ['txt2img', 'img2img'].includes(type)) { - handleClickUseAllParameters(); - toast({ - title: t('toast.parametersSet'), - status: 'success', - duration: 2500, - isClosable: true, - }); - } else { - toast({ - title: t('toast.parametersNotSet'), - description: t('toast.parametersNotSetDesc'), - status: 'error', - duration: 2500, - isClosable: true, - }); - } + handleClickUseAllParameters; }, - [image] + [image, recallAllParameters] ); const handleUseSeed = useCallback(() => { @@ -530,8 +507,8 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { tooltip={`${t('parameters.useAll')} (A)`} aria-label={`${t('parameters.useAll')} (A)`} isDisabled={ - !['txt2img', 'img2img'].includes( - image?.metadata?.sd_metadata?.type + !['txt2img', 'img2img', 'inpaint'].includes( + String(image?.metadata?.invokeai?.node?.type) ) } onClick={handleClickUseAllParameters} diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index 3c2dcdf818..d2fba22c3e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -105,7 +105,8 @@ const HoverableImage = memo((props: HoverableImageProps) => { const { t } = useTranslation(); const { isFeatureEnabled: isLightboxEnabled } = useFeatureStatus('lightbox'); - const { recallSeed, recallPrompt, recallInitialImage } = useParameters(); + const { recallSeed, recallPrompt, recallInitialImage, recallAllParameters } = + useParameters(); const handleMouseOver = () => setIsHovered(true); const handleMouseOut = () => setIsHovered(false); @@ -176,16 +177,9 @@ const HoverableImage = memo((props: HoverableImageProps) => { }); }; - const handleUseAllParameters = () => { - // metadata.invokeai?.node && - // dispatch(setAllParameters(metadata.invokeai?.node)); - // toast({ - // title: t('toast.parametersSet'), - // status: 'success', - // duration: 2500, - // isClosable: true, - // }); - }; + const handleUseAllParameters = useCallback(() => { + recallAllParameters(image); + }, [image, recallAllParameters]); const handleLightBox = () => { // dispatch(setCurrentImage(image)); @@ -239,7 +233,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { icon={} onClickCapture={handleUseAllParameters} isDisabled={ - !['txt2img', 'img2img'].includes( + !['txt2img', 'img2img', 'inpaint'].includes( String(image?.metadata?.invokeai?.node?.type) ) } diff --git a/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts b/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts index 7b8dc455ac..a093010343 100644 --- a/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts +++ b/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts @@ -4,11 +4,12 @@ import { isFinite, isString } from 'lodash-es'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import useSetBothPrompts from './usePrompt'; -import { setSeed } from '../store/generationSlice'; +import { allParametersSet, setSeed } from '../store/generationSlice'; import { isImageField } from 'services/types/guards'; import { NUMPY_RAND_MAX } from 'app/constants'; import { initialImageSelected } from '../store/actions'; import { Image } from 'app/types/invokeai'; +import { setActiveTab } from 'features/ui/store/uiSlice'; export const useParameters = () => { const dispatch = useAppDispatch(); @@ -110,5 +111,42 @@ export const useParameters = () => { [dispatch] ); - return { recallPrompt, recallSeed, recallInitialImage, sendToImageToImage }; + const recallAllParameters = useCallback( + (image: Image | undefined) => { + const type = image?.metadata?.invokeai?.node?.type; + if (['txt2img', 'img2img', 'inpaint'].includes(String(type))) { + dispatch(allParametersSet(image)); + + if (image?.metadata?.invokeai?.node?.type === 'img2img') { + dispatch(setActiveTab('img2img')); + } else if (image?.metadata?.invokeai?.node?.type === 'txt2img') { + dispatch(setActiveTab('txt2img')); + } + + toast({ + title: t('toast.parametersSet'), + status: 'success', + duration: 2500, + isClosable: true, + }); + } else { + toast({ + title: t('toast.parametersNotSet'), + description: t('toast.parametersNotSetDesc'), + status: 'error', + duration: 2500, + isClosable: true, + }); + } + }, + [t, toast, dispatch] + ); + + return { + recallPrompt, + recallSeed, + recallInitialImage, + sendToImageToImage, + recallAllParameters, + }; }; diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 948ba4cc1e..e1bb20c1ab 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -3,6 +3,7 @@ import { createSlice } from '@reduxjs/toolkit'; import * as InvokeAI from 'app/types/invokeai'; import promptToString from 'common/util/promptToString'; import { clamp } from 'lodash-es'; +import { setAllParametersReducer } from './setAllParametersReducer'; export interface GenerationState { cfgScale: number; @@ -187,131 +188,7 @@ export const generationSlice = createSlice({ state.shouldGenerateVariations = true; state.variationAmount = 0; }, - setAllTextToImageParameters: ( - state, - action: PayloadAction - ) => { - // const { - // sampler, - // prompt, - // seed, - // variations, - // steps, - // cfg_scale, - // threshold, - // perlin, - // seamless, - // _hires_fix, - // width, - // height, - // } = action.payload.image; - // if (variations && variations.length > 0) { - // state.seedWeights = seedWeightsToString(variations); - // state.shouldGenerateVariations = true; - // state.variationAmount = 0; - // } else { - // state.shouldGenerateVariations = false; - // } - // if (seed) { - // state.seed = seed; - // state.shouldRandomizeSeed = false; - // } - // if (prompt) state.prompt = promptToString(prompt); - // if (sampler) state.sampler = sampler; - // if (steps) state.steps = steps; - // if (cfg_scale) state.cfgScale = cfg_scale; - // if (typeof threshold === 'undefined') { - // state.threshold = 0; - // } else { - // state.threshold = threshold; - // } - // if (typeof perlin === 'undefined') { - // state.perlin = 0; - // } else { - // state.perlin = perlin; - // } - // if (typeof seamless === 'boolean') state.seamless = seamless; - // // if (typeof hires_fix === 'boolean') state.hiresFix = hires_fix; // TODO: Needs to be fixed after reorg - // if (width) state.width = width; - // if (height) state.height = height; - }, - setAllImageToImageParameters: ( - state, - action: PayloadAction - ) => { - // const { type, strength, fit, init_image_path, mask_image_path } = - // action.payload.image; - // if (type === 'img2img') { - // if (init_image_path) state.initialImage = init_image_path; - // if (mask_image_path) state.maskPath = mask_image_path; - // if (strength) state.img2imgStrength = strength; - // if (typeof fit === 'boolean') state.shouldFitToWidthHeight = fit; - // } - }, - setAllParameters: (state, action: PayloadAction) => { - // const { - // type, - // sampler, - // prompt, - // seed, - // variations, - // steps, - // cfg_scale, - // threshold, - // perlin, - // seamless, - // _hires_fix, - // width, - // height, - // strength, - // fit, - // init_image_path, - // mask_image_path, - // } = action.payload.image; - // if (type === 'img2img') { - // if (init_image_path) state.initialImage = init_image_path; - // if (mask_image_path) state.maskPath = mask_image_path; - // if (strength) state.img2imgStrength = strength; - // if (typeof fit === 'boolean') state.shouldFitToWidthHeight = fit; - // } - // if (variations && variations.length > 0) { - // state.seedWeights = seedWeightsToString(variations); - // state.shouldGenerateVariations = true; - // state.variationAmount = 0; - // } else { - // state.shouldGenerateVariations = false; - // } - // if (seed) { - // state.seed = seed; - // state.shouldRandomizeSeed = false; - // } - // if (prompt) { - // const [promptOnly, negativePrompt] = getPromptAndNegative(prompt); - // if (promptOnly) state.prompt = promptOnly; - // negativePrompt - // ? (state.negativePrompt = negativePrompt) - // : (state.negativePrompt = ''); - // } - // if (sampler) state.sampler = sampler; - // if (steps) state.steps = steps; - // if (cfg_scale) state.cfgScale = cfg_scale; - // if (typeof threshold === 'undefined') { - // state.threshold = 0; - // } else { - // state.threshold = threshold; - // } - // if (typeof perlin === 'undefined') { - // state.perlin = 0; - // } else { - // state.perlin = perlin; - // } - // if (typeof seamless === 'boolean') state.seamless = seamless; - // // if (typeof hires_fix === 'boolean') state.hiresFix = hires_fix; // TODO: Needs to be fixed after reorg - // if (width) state.width = width; - // if (height) state.height = height; - // // state.shouldRunESRGAN = false; // TODO: Needs to be fixed after reorg - // // state.shouldRunFacetool = false; // TODO: Needs to be fixed after reorg - }, + allParametersSet: setAllParametersReducer, resetParametersState: (state) => { return { ...state, @@ -321,12 +198,6 @@ export const generationSlice = createSlice({ setShouldRandomizeSeed: (state, action: PayloadAction) => { state.shouldRandomizeSeed = action.payload; }, - // setInitialImage: ( - // state, - // action: PayloadAction - // ) => { - // state.initialImage = action.payload; - // }, clearInitialImage: (state) => { state.initialImage = undefined; }, @@ -417,6 +288,7 @@ export const { setSeamless, setSeamlessXAxis, setSeamlessYAxis, + allParametersSet, } = generationSlice.actions; export default generationSlice.reducer; diff --git a/invokeai/frontend/web/src/features/parameters/store/setAllParametersReducer.ts b/invokeai/frontend/web/src/features/parameters/store/setAllParametersReducer.ts new file mode 100644 index 0000000000..7b02647ebc --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/store/setAllParametersReducer.ts @@ -0,0 +1,61 @@ +import { Draft, PayloadAction } from '@reduxjs/toolkit'; +import { Image } from 'app/types/invokeai'; +import { GenerationState } from './generationSlice'; +import { ImageToImageInvocation } from 'services/api'; + +export const setAllParametersReducer = ( + state: Draft, + action: PayloadAction +) => { + const node = action.payload?.metadata.invokeai?.node; + + if (!node) { + return; + } + + if ( + node.type === 'txt2img' || + node.type === 'img2img' || + node.type === 'inpaint' + ) { + const { cfg_scale, height, model, prompt, scheduler, seed, steps, width } = + node; + + if (cfg_scale !== undefined) { + state.cfgScale = Number(cfg_scale); + } + if (height !== undefined) { + state.height = Number(height); + } + if (model !== undefined) { + state.model = String(model); + } + if (prompt !== undefined) { + state.prompt = String(prompt); + } + if (scheduler !== undefined) { + state.sampler = String(scheduler); + } + if (seed !== undefined) { + state.seed = Number(seed); + state.shouldRandomizeSeed = false; + } + if (steps !== undefined) { + state.steps = Number(steps); + } + if (width !== undefined) { + state.width = Number(width); + } + } + + if (node.type === 'img2img') { + const { fit, image } = node as ImageToImageInvocation; + + if (fit !== undefined) { + state.shouldFitToWidthHeight = Boolean(fit); + } + // if (image !== undefined) { + // state.initialImage = image; + // } + } +}; From 2848c8397c5496243e571b3ad2effd988083da35 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 10 May 2023 00:14:32 +1000 Subject: [PATCH 52/66] fix(ui): fix missing images on reload issue - Mainly an issue for commercial due to incomplete metadata handling --- .../src/features/gallery/components/CurrentImagePreview.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index bbc4cd1a58..1f18fb9b9a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -60,8 +60,8 @@ const CurrentImagePreview = () => { : undefined} sx={{ objectFit: 'contain', From 75ccbaee9c24b1e29fd9820801e70411c87c9ac4 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 10 May 2023 11:31:04 +1000 Subject: [PATCH 53/66] fix(ui): disable invoke button as soon as pressed --- .../frontend/web/src/features/system/store/systemSlice.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index dec34d3a9c..6ead700923 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -23,6 +23,7 @@ import { LogLevelName } from 'roarr'; import { InvokeLogLevel } from 'app/logging/useLogger'; import { TFuncKey } from 'i18next'; import { t } from 'i18next'; +import { userInvoked } from 'app/store/actions'; export type CancelStrategy = 'immediate' | 'scheduled'; @@ -382,7 +383,10 @@ export const systemSlice = createSlice({ * Session Invoked - PENDING */ - builder.addCase(sessionInvoked.pending, (state) => { + builder.addCase(userInvoked, (state) => { + state.isProcessing = true; + state.isCancelable = true; + state.currentStatusHasSteps = false; state.statusTranslationKey = 'common.statusPreparing'; }); From e94d0b2d408bc0807445093d7839948ba6c9bad0 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 10 May 2023 13:58:18 +1000 Subject: [PATCH 54/66] fix(ui): fix janky gallery image delete --- .../gallery/components/HoverableImage.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index d2fba22c3e..2e5f166025 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -10,7 +10,7 @@ import { } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { imageSelected } from 'features/gallery/store/gallerySlice'; -import { DragEvent, memo, useCallback, useState } from 'react'; +import { DragEvent, MouseEvent, memo, useCallback, useState } from 'react'; import { FaCheck, FaExpand, FaImage, FaShare, FaTrash } from 'react-icons/fa'; import DeleteImageModal from './DeleteImageModal'; import { ContextMenu } from 'chakra-ui-contextmenu'; @@ -119,13 +119,17 @@ const HoverableImage = memo((props: HoverableImageProps) => { }, [dispatch, image, canDeleteImage]); // Opens the alert dialog to check if user is sure they want to delete - const handleInitiateDelete = useCallback(() => { - if (shouldConfirmOnDelete) { - onDeleteDialogOpen(); - } else { - handleDelete(); - } - }, [handleDelete, onDeleteDialogOpen, shouldConfirmOnDelete]); + const handleInitiateDelete = useCallback( + (e: MouseEvent) => { + e.stopPropagation(); + if (shouldConfirmOnDelete) { + onDeleteDialogOpen(); + } else { + handleDelete(); + } + }, + [handleDelete, onDeleteDialogOpen, shouldConfirmOnDelete] + ); const handleSelectImage = useCallback(() => { dispatch(imageSelected(image)); From fdc2232ea05de0f928cb9c6f213dbdd84190e99c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 10 May 2023 13:58:34 +1000 Subject: [PATCH 55/66] feat(ui): progress images in gallery and viewer --- invokeai/frontend/web/public/locales/en.json | 2 +- .../frontend/web/src/app/components/App.tsx | 3 +- .../middleware/devtools/actionSanitizer.ts | 1 + .../web/src/common/components/IAIPopover.tsx | 2 +- .../components/CurrentImageButtons.tsx | 28 ++--- .../components/CurrentImagePreview.tsx | 63 ++++++++--- .../components/GalleryProgressImage.tsx | 62 +++++++++++ .../ImageActionButtons/DeleteImageButton.tsx | 92 ++++++++++++++++ .../components/ImageGalleryContent.tsx | 100 ++++++++++++------ .../nodeBuilders/buildImageToImageNode.ts | 15 ++- ...ePreview.tsx => _ProgressImagePreview.tsx} | 0 .../SettingsModal/SettingsModal.tsx | 14 +-- .../system/components/StatusIndicator.tsx | 5 +- .../src/features/system/store/systemSlice.ts | 2 + .../web/src/features/ui/store/uiSlice.ts | 11 +- .../web/src/features/ui/store/uiTypes.ts | 2 +- .../web/src/theme/components/popover.ts | 3 - 17 files changed, 309 insertions(+), 96 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/GalleryProgressImage.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageActionButtons/DeleteImageButton.tsx rename invokeai/frontend/web/src/features/parameters/components/{ProgressImagePreview.tsx => _ProgressImagePreview.tsx} (100%) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index e5827c2397..e3f21e50a1 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -531,7 +531,7 @@ "useCanvasBeta": "Use Canvas Beta Layout", "enableImageDebugging": "Enable Image Debugging", "useSlidersForAll": "Use Sliders For All Options", - "autoShowProgress": "Auto Show Progress Images", + "showProgressInViewer": "Show Progress Images in Viewer", "resetWebUI": "Reset Web UI", "resetWebUIDesc1": "Resetting the web UI only resets the browser's local cache of your images and remembered settings. It does not delete any images from disk.", "resetWebUIDesc2": "If images aren't showing up in the gallery or something else isn't working, please try resetting before submitting an issue on GitHub.", diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index f65947a1fa..b49e44e554 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -27,7 +27,7 @@ import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys'; import { configChanged } from 'features/system/store/configSlice'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useLogger } from 'app/logging/useLogger'; -import ProgressImagePreview from 'features/parameters/components/ProgressImagePreview'; +import ProgressImagePreview from 'features/parameters/components/_ProgressImagePreview'; import ParametersDrawer from 'features/ui/components/ParametersDrawer'; const DEFAULT_CONFIG = {}; @@ -124,7 +124,6 @@ const App = ({ config = DEFAULT_CONFIG, children }: Props) => { - ); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts b/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts index c9d8e45886..24b85e0f83 100644 --- a/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts +++ b/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts @@ -10,6 +10,7 @@ export const actionSanitizer =
(action: A): A => { // Sanitize nodes as needed forEach(action.payload.nodes, (node, key) => { + // Don't log the whole freaking dataURL if (node.type === 'dataURL_image') { const { dataURL, ...rest } = node; sanitizedNodes[key] = { ...rest, dataURL: '' }; diff --git a/invokeai/frontend/web/src/common/components/IAIPopover.tsx b/invokeai/frontend/web/src/common/components/IAIPopover.tsx index ba3fbdd109..51562b969c 100644 --- a/invokeai/frontend/web/src/common/components/IAIPopover.tsx +++ b/invokeai/frontend/web/src/common/components/IAIPopover.tsx @@ -27,7 +27,7 @@ const IAIPopover = (props: IAIPopoverProps) => { return ( {triggerComponent} - + {hasArrow && } {children} diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index 32e631005d..2a3d12ce91 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -5,7 +5,13 @@ import { ButtonGroup, Flex, FlexProps, + IconButton, Link, + Menu, + MenuButton, + MenuItemOption, + MenuList, + MenuOptionGroup, useDisclosure, useToast, } from '@chakra-ui/react'; @@ -46,6 +52,7 @@ import { FaShare, FaShareAlt, FaTrash, + FaWrench, } from 'react-icons/fa'; import { gallerySelector, @@ -62,6 +69,7 @@ import { requestedImageDeletion } from '../store/actions'; import FaceRestoreSettings from 'features/parameters/components/Parameters/FaceRestore/FaceRestoreSettings'; import UpscaleSettings from 'features/parameters/components/Parameters/Upscale/UpscaleSettings'; import { allParametersSet } from 'features/parameters/store/generationSlice'; +import DeleteImageButton from './ImageActionButtons/DeleteImageButton'; const currentImageButtonsSelector = createSelector( [ @@ -451,7 +459,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { - : } tooltip={ !shouldHidePreview @@ -465,7 +473,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { } isChecked={shouldHidePreview} onClick={handlePreviewVisibility} - /> + /> */} {isLightboxEnabled && ( } @@ -592,23 +600,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { - } - tooltip={`${t('gallery.deleteImage')} (Del)`} - aria-label={`${t('gallery.deleteImage')} (Del)`} - isDisabled={!image || !isConnected} - colorScheme="error" - /> + - {image && ( - - )} ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index 1f18fb9b9a..c5bcee540a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, Image } from '@chakra-ui/react'; +import { Box, Flex, Image, Skeleton, useBoolean } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { useGetUrl } from 'common/util/getUrl'; @@ -10,16 +10,25 @@ import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer'; import NextPrevImageButtons from './NextPrevImageButtons'; import CurrentImageHidden from './CurrentImageHidden'; import { DragEvent, memo, useCallback } from 'react'; +import { systemSelector } from 'features/system/store/systemSelectors'; +import CurrentImageFallback from './CurrentImageFallback'; export const imagesSelector = createSelector( - [uiSelector, gallerySelector], - (ui, gallery) => { - const { shouldShowImageDetails, shouldHidePreview } = ui; + [uiSelector, gallerySelector, systemSelector], + (ui, gallery, system) => { + const { + shouldShowImageDetails, + shouldHidePreview, + shouldShowProgressInViewer, + } = ui; const { selectedImage } = gallery; + const { progressImage } = system; return { shouldShowImageDetails, shouldHidePreview, image: selectedImage, + progressImage, + shouldShowProgressInViewer, }; }, { @@ -30,10 +39,17 @@ export const imagesSelector = createSelector( ); const CurrentImagePreview = () => { - const { shouldShowImageDetails, image, shouldHidePreview } = - useAppSelector(imagesSelector); + const { + shouldShowImageDetails, + image, + shouldHidePreview, + progressImage, + shouldShowProgressInViewer, + } = useAppSelector(imagesSelector); const { getUrl } = useGetUrl(); + const [isLoaded, { on, off }] = useBoolean(); + const handleDragStart = useCallback( (e: DragEvent) => { if (!image) { @@ -56,13 +72,11 @@ const CurrentImagePreview = () => { height: '100%', }} > - {image && ( + {progressImage && shouldShowProgressInViewer ? ( : undefined} + src={progressImage.dataURL} + width={progressImage.width} + height={progressImage.height} sx={{ objectFit: 'contain', maxWidth: '100%', @@ -72,6 +86,31 @@ const CurrentImagePreview = () => { borderRadius: 'base', }} /> + ) : ( + image && ( + + ) : ( + + ) + } + sx={{ + objectFit: 'contain', + maxWidth: '100%', + maxHeight: '100%', + height: 'auto', + position: 'absolute', + borderRadius: 'base', + }} + /> + ) )} {shouldShowImageDetails && image && 'metadata' in image && ( { + const { shouldUseSingleGalleryColumn, galleryImageObjectFit } = gallery; + const { progressImage } = system; + + return { + progressImage, + shouldUseSingleGalleryColumn, + galleryImageObjectFit, + }; + }, + defaultSelectorOptions +); + +const GalleryProgressImage = () => { + const { progressImage, shouldUseSingleGalleryColumn, galleryImageObjectFit } = + useAppSelector(selector); + + if (!progressImage) { + return null; + } + + return ( + + + + ); +}; + +export default memo(GalleryProgressImage); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageActionButtons/DeleteImageButton.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageActionButtons/DeleteImageButton.tsx new file mode 100644 index 0000000000..6e35ccd63b --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/ImageActionButtons/DeleteImageButton.tsx @@ -0,0 +1,92 @@ +import { createSelector } from '@reduxjs/toolkit'; + +import { useDisclosure } from '@chakra-ui/react'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIIconButton from 'common/components/IAIIconButton'; +import { systemSelector } from 'features/system/store/systemSelectors'; + +import { useHotkeys } from 'react-hotkeys-hook'; +import { useTranslation } from 'react-i18next'; +import { FaTrash } from 'react-icons/fa'; +import { memo, useCallback } from 'react'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import DeleteImageModal from '../DeleteImageModal'; +import { requestedImageDeletion } from 'features/gallery/store/actions'; +import { Image } from 'app/types/invokeai'; + +const selector = createSelector( + [systemSelector], + (system) => { + const { isProcessing, isConnected, shouldConfirmOnDelete } = system; + + return { + canDeleteImage: isConnected && !isProcessing, + shouldConfirmOnDelete, + isProcessing, + isConnected, + }; + }, + defaultSelectorOptions +); + +type DeleteImageButtonProps = { + image: Image | undefined; +}; + +const DeleteImageButton = (props: DeleteImageButtonProps) => { + const { image } = props; + const dispatch = useAppDispatch(); + const { isProcessing, isConnected, canDeleteImage, shouldConfirmOnDelete } = + useAppSelector(selector); + + const { + isOpen: isDeleteDialogOpen, + onOpen: onDeleteDialogOpen, + onClose: onDeleteDialogClose, + } = useDisclosure(); + + const { t } = useTranslation(); + + const handleDelete = useCallback(() => { + if (canDeleteImage && image) { + dispatch(requestedImageDeletion(image)); + } + }, [image, canDeleteImage, dispatch]); + + const handleInitiateDelete = useCallback(() => { + if (shouldConfirmOnDelete) { + onDeleteDialogOpen(); + } else { + handleDelete(); + } + }, [shouldConfirmOnDelete, onDeleteDialogOpen, handleDelete]); + + useHotkeys('delete', handleInitiateDelete, [ + image, + shouldConfirmOnDelete, + isConnected, + isProcessing, + ]); + + return ( + <> + } + tooltip={`${t('gallery.deleteImage')} (Del)`} + aria-label={`${t('gallery.deleteImage')} (Del)`} + isDisabled={!image || !isConnected} + colorScheme="error" + /> + {image && ( + + )} + + ); +}; + +export default memo(DeleteImageButton); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 19e9c7c213..1426aff43d 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -5,6 +5,7 @@ import { FlexProps, Grid, Icon, + Image, Text, forwardRef, } from '@chakra-ui/react'; @@ -14,7 +15,10 @@ import IAICheckbox from 'common/components/IAICheckbox'; import IAIIconButton from 'common/components/IAIIconButton'; import IAIPopover from 'common/components/IAIPopover'; import IAISlider from 'common/components/IAISlider'; -import { imageGallerySelector } from 'features/gallery/store/gallerySelectors'; +import { + gallerySelector, + imageGallerySelector, +} from 'features/gallery/store/gallerySelectors'; import { setCurrentCategory, setGalleryImageMinimumWidth, @@ -50,30 +54,48 @@ import { uploadsAdapter } from '../store/uploadsSlice'; import { createSelector } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; import { Virtuoso, VirtuosoGrid } from 'react-virtuoso'; +import ProgressImagePreview from 'features/parameters/components/_ProgressImagePreview'; +import ProgressImage from 'features/parameters/components/ProgressImage'; +import { systemSelector } from 'features/system/store/systemSelectors'; +import { Image as ImageType } from 'app/types/invokeai'; +import { ProgressImage as ProgressImageType } from 'services/events/types'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import GalleryProgressImage from './GalleryProgressImage'; const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 290; +const PROGRESS_IMAGE_PLACEHOLDER = 'PROGRESS_IMAGE_PLACEHOLDER'; -const gallerySelector = createSelector( - [ - (state: RootState) => state.uploads, - (state: RootState) => state.results, - (state: RootState) => state.gallery, - ], - (uploads, results, gallery) => { +const selector = createSelector( + [(state: RootState) => state], + (state) => { + const { results, uploads, system, gallery } = state; const { currentCategory } = gallery; - return currentCategory === 'results' - ? { - images: resultsAdapter.getSelectors().selectAll(results), - isLoading: results.isLoading, - areMoreImagesAvailable: results.page < results.pages - 1, - } - : { - images: uploadsAdapter.getSelectors().selectAll(uploads), - isLoading: uploads.isLoading, - areMoreImagesAvailable: uploads.page < uploads.pages - 1, - }; - } + const tempImages: (ImageType | typeof PROGRESS_IMAGE_PLACEHOLDER)[] = []; + + if (system.progressImage) { + tempImages.push(PROGRESS_IMAGE_PLACEHOLDER); + } + + if (currentCategory === 'results') { + return { + images: tempImages.concat( + resultsAdapter.getSelectors().selectAll(results) + ), + isLoading: results.isLoading, + areMoreImagesAvailable: results.page < results.pages - 1, + }; + } + + return { + images: tempImages.concat( + uploadsAdapter.getSelectors().selectAll(uploads) + ), + isLoading: uploads.isLoading, + areMoreImagesAvailable: uploads.page < uploads.pages - 1, + }; + }, + defaultSelectorOptions ); const ImageGalleryContent = () => { @@ -108,7 +130,7 @@ const ImageGalleryContent = () => { } = useAppSelector(imageGallerySelector); const { images, areMoreImagesAvailable, isLoading } = - useAppSelector(gallerySelector); + useAppSelector(selector); const handleClickLoadMore = () => { if (currentCategory === 'results') { @@ -186,8 +208,6 @@ const ImageGalleryContent = () => { h: 'full', w: 'full', borderRadius: 'base', - // bg: 'base.850', - // p: 2, }} > { endReached={handleEndReached} scrollerRef={(ref) => setScrollerRef(ref)} itemContent={(index, image) => { - const { name } = image; - const isSelected = selectedImage?.name === name; + const isSelected = + image === PROGRESS_IMAGE_PLACEHOLDER + ? false + : selectedImage?.name === image?.name; return ( - + {image === PROGRESS_IMAGE_PLACEHOLDER ? ( + + ) : ( + + )} ); }} @@ -336,12 +364,16 @@ const ImageGalleryContent = () => { }} scrollerRef={setScroller} itemContent={(index, image) => { - const { name } = image; - const isSelected = selectedImage?.name === name; + const isSelected = + image === PROGRESS_IMAGE_PLACEHOLDER + ? false + : selectedImage?.name === image?.name; - return ( + return image === PROGRESS_IMAGE_PLACEHOLDER ? ( + + ) : ( diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts index df5b65e296..2d7b88a9ab 100644 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts @@ -28,13 +28,14 @@ export const buildImg2ImgNode = ( img2imgStrength: strength, shouldFitToWidthHeight: fit, shouldRandomizeSeed, + initialImage, } = generation; - const initialImage = initialImageSelector(state); + // const initialImage = initialImageSelector(state); if (!initialImage) { // TODO: handle this - // throw 'no initial image'; + throw 'no initial image'; } const imageToImageNode: ImageToImageInvocation = { @@ -47,12 +48,10 @@ export const buildImg2ImgNode = ( cfg_scale: cfgScale, scheduler: sampler as ImageToImageInvocation['scheduler'], model, - image: initialImage - ? { - image_name: initialImage.name, - image_type: initialImage.type, - } - : undefined, + image: { + image_name: initialImage.name, + image_type: initialImage.type, + }, strength, fit, }; diff --git a/invokeai/frontend/web/src/features/parameters/components/ProgressImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/_ProgressImagePreview.tsx similarity index 100% rename from invokeai/frontend/web/src/features/parameters/components/ProgressImagePreview.tsx rename to invokeai/frontend/web/src/features/parameters/components/_ProgressImagePreview.tsx diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx index bf4004a79d..0557228020 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -28,7 +28,7 @@ import { } from 'features/system/store/systemSlice'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { - setShouldAutoShowProgressImages, + setShouldShowProgressInViewer, setShouldUseCanvasBetaLayout, setShouldUseSliders, } from 'features/ui/store/uiSlice'; @@ -54,7 +54,7 @@ const selector = createSelector( const { shouldUseCanvasBetaLayout, shouldUseSliders, - shouldAutoShowProgressImages, + shouldShowProgressInViewer, } = ui; return { @@ -63,7 +63,7 @@ const selector = createSelector( enableImageDebugging, shouldUseCanvasBetaLayout, shouldUseSliders, - shouldAutoShowProgressImages, + shouldShowProgressInViewer, consoleLogLevel, shouldLogToConsole, }; @@ -114,7 +114,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => { enableImageDebugging, shouldUseCanvasBetaLayout, shouldUseSliders, - shouldAutoShowProgressImages, + shouldShowProgressInViewer, consoleLogLevel, shouldLogToConsole, } = useAppSelector(selector); @@ -197,10 +197,10 @@ const SettingsModal = ({ children }: SettingsModalProps) => { } /> ) => - dispatch(setShouldAutoShowProgressImages(e.target.checked)) + dispatch(setShouldShowProgressInViewer(e.target.checked)) } /> diff --git a/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx b/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx index 18d71d2e41..cd0a4eacc3 100644 --- a/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx +++ b/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx @@ -9,6 +9,7 @@ import { AnimatePresence, motion } from 'framer-motion'; import { useMemo, useRef } from 'react'; import { FaCircle } from 'react-icons/fa'; import { useHoverDirty } from 'react-use'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; const statusIndicatorSelector = createSelector( systemSelector, @@ -31,9 +32,7 @@ const statusIndicatorSelector = createSelector( currentStatusHasSteps, }; }, - { - memoizeOptions: { resultEqualityCheck: isEqual }, - } + defaultSelectorOptions ); const StatusIndicator = () => { diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 6ead700923..8e0ab1f3aa 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -354,6 +354,7 @@ export const systemSlice = createSlice({ state.currentStep = 0; state.totalSteps = 0; state.statusTranslationKey = 'common.statusProcessingComplete'; + state.progressImage = null; if (state.canceledSession === data.graph_execution_state_id) { state.isProcessing = false; @@ -373,6 +374,7 @@ export const systemSlice = createSlice({ state.currentStep = 0; state.totalSteps = 0; state.statusTranslationKey = 'common.statusError'; + state.progressImage = null; state.toastQueue.push( makeToast({ title: t('toast.serverError'), status: 'error' }) diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index 60f65079cf..470ad076a3 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -23,7 +23,7 @@ export const initialUIState: UIState = { canvasTabAccordionState: [], floatingProgressImageRect: { x: 0, y: 0, width: 0, height: 0 }, shouldShowProgressImages: false, - shouldAutoShowProgressImages: false, + shouldShowProgressInViewer: false, shouldShowImageParameters: false, }; @@ -135,11 +135,8 @@ export const uiSlice = createSlice({ setShouldShowProgressImages: (state, action: PayloadAction) => { state.shouldShowProgressImages = action.payload; }, - setShouldAutoShowProgressImages: ( - state, - action: PayloadAction - ) => { - state.shouldAutoShowProgressImages = action.payload; + setShouldShowProgressInViewer: (state, action: PayloadAction) => { + state.shouldShowProgressInViewer = action.payload; }, shouldShowImageParametersChanged: ( state, @@ -173,7 +170,7 @@ export const { floatingProgressImageMoved, floatingProgressImageResized, setShouldShowProgressImages, - setShouldAutoShowProgressImages, + setShouldShowProgressInViewer, shouldShowImageParametersChanged, } = uiSlice.actions; diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts index 59140cdfde..030ec4f1ce 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts @@ -31,6 +31,6 @@ export interface UIState { canvasTabAccordionState: number[]; floatingProgressImageRect: Rect; shouldShowProgressImages: boolean; - shouldAutoShowProgressImages: boolean; + shouldShowProgressInViewer: boolean; shouldShowImageParameters: boolean; } diff --git a/invokeai/frontend/web/src/theme/components/popover.ts b/invokeai/frontend/web/src/theme/components/popover.ts index 449f1d926c..c8d6ae20d8 100644 --- a/invokeai/frontend/web/src/theme/components/popover.ts +++ b/invokeai/frontend/web/src/theme/components/popover.ts @@ -20,9 +20,6 @@ const invokeAIContent = defineStyle((_props) => { minW: 'unset', width: 'unset', p: 4, - borderWidth: '2px', - borderStyle: 'solid', - borderColor: 'base.600', bg: 'base.800', }; }); From 31a78d571b19c18f604af3d3f06db775c17c70e0 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 10 May 2023 14:13:33 +1000 Subject: [PATCH 56/66] feat(ui): canvas antialiasing --- invokeai/frontend/web/public/locales/en.json | 3 ++- .../src/features/canvas/components/IAICanvas.tsx | 14 +++++++------- .../IAICanvasSettingsButtonPopover.tsx | 10 ++++++++++ .../web/src/features/canvas/store/canvasSlice.ts | 5 +++++ .../web/src/features/canvas/store/canvasTypes.ts | 5 +++-- .../UnifiedCanvasSettings.tsx | 9 +++++++++ 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index e3f21e50a1..71461d409a 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -650,7 +650,8 @@ "betaClear": "Clear", "betaDarkenOutside": "Darken Outside", "betaLimitToBox": "Limit To Box", - "betaPreserveMasked": "Preserve Masked" + "betaPreserveMasked": "Preserve Masked", + "antialiasing": "Antialiasing" }, "ui": { "showProgressImages": "Show Progress Images", diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvas.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvas.tsx index 1850b4bfe4..aa785c379d 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvas.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvas.tsx @@ -34,6 +34,7 @@ import IAICanvasStagingAreaToolbar from './IAICanvasStagingAreaToolbar'; import IAICanvasStatusText from './IAICanvasStatusText'; import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox'; import IAICanvasToolPreview from './IAICanvasToolPreview'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; const selector = createSelector( [canvasSelector, isStagingSelector], @@ -52,6 +53,7 @@ const selector = createSelector( shouldShowIntermediates, shouldShowGrid, shouldRestrictStrokesToBox, + shouldAntialias, } = canvas; let stageCursor: string | undefined = 'none'; @@ -80,13 +82,10 @@ const selector = createSelector( tool, isStaging, shouldShowIntermediates, + shouldAntialias, }; }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } + defaultSelectorOptions ); const ChakraStage = chakra(Stage, { @@ -106,6 +105,7 @@ const IAICanvas = () => { tool, isStaging, shouldShowIntermediates, + shouldAntialias, } = useAppSelector(selector); useCanvasHotkeys(); @@ -190,7 +190,7 @@ const IAICanvas = () => { id="base" ref={canvasBaseLayerRefCallback} listening={false} - imageSmoothingEnabled={false} + imageSmoothingEnabled={shouldAntialias} > @@ -201,7 +201,7 @@ const IAICanvas = () => { - + {!isStaging && ( { shouldShowIntermediates, shouldSnapToGrid, shouldRestrictStrokesToBox, + shouldAntialias, } = useAppSelector(canvasControlsSelector); useHotkeys( @@ -148,6 +152,12 @@ const IAICanvasSettingsButtonPopover = () => { dispatch(setShouldShowCanvasDebugInfo(e.target.checked)) } /> + + dispatch(setShouldAntialias(e.target.checked))} + /> diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index 501dfe36d8..037d353f42 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -66,6 +66,7 @@ export const initialCanvasState: CanvasState = { minimumStageScale: 1, pastLayerStates: [], scaledBoundingBoxDimensions: { width: 512, height: 512 }, + shouldAntialias: true, shouldAutoSave: false, shouldCropToBoundingBoxOnSave: false, shouldDarkenOutsideBoundingBox: false, @@ -796,6 +797,9 @@ export const canvasSlice = createSlice({ setShouldRestrictStrokesToBox: (state, action: PayloadAction) => { state.shouldRestrictStrokesToBox = action.payload; }, + setShouldAntialias: (state, action: PayloadAction) => { + state.shouldAntialias = action.payload; + }, setShouldCropToBoundingBoxOnSave: ( state, action: PayloadAction @@ -907,6 +911,7 @@ export const { setShouldRestrictStrokesToBox, stagingAreaInitialized, canvasSessionIdChanged, + setShouldAntialias, } = canvasSlice.actions; export default canvasSlice.reducer; diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts index 9194f065b2..2a6461aaf6 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts @@ -37,7 +37,7 @@ export type CanvasImage = { y: number; width: number; height: number; - image: InvokeAI._Image; + image: InvokeAI.Image; }; export type CanvasMaskLine = { @@ -132,7 +132,7 @@ export interface CanvasState { cursorPosition: Vector2d | null; doesCanvasNeedScaling: boolean; futureLayerStates: CanvasLayerState[]; - intermediateImage?: InvokeAI._Image; + intermediateImage?: InvokeAI.Image; isCanvasInitialized: boolean; isDrawing: boolean; isMaskEnabled: boolean; @@ -149,6 +149,7 @@ export interface CanvasState { minimumStageScale: number; pastLayerStates: CanvasLayerState[]; scaledBoundingBoxDimensions: Dimensions; + shouldAntialias: boolean; shouldAutoSave: boolean; shouldCropToBoundingBoxOnSave: boolean; shouldDarkenOutsideBoundingBox: boolean; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx index a2b21368d4..bfaa7cdae8 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx @@ -6,6 +6,7 @@ import IAIIconButton from 'common/components/IAIIconButton'; import IAIPopover from 'common/components/IAIPopover'; import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { + setShouldAntialias, setShouldAutoSave, setShouldCropToBoundingBoxOnSave, setShouldShowCanvasDebugInfo, @@ -27,6 +28,7 @@ export const canvasControlsSelector = createSelector( shouldCropToBoundingBoxOnSave, shouldShowCanvasDebugInfo, shouldShowIntermediates, + shouldAntialias, } = canvas; return { @@ -34,6 +36,7 @@ export const canvasControlsSelector = createSelector( shouldCropToBoundingBoxOnSave, shouldShowCanvasDebugInfo, shouldShowIntermediates, + shouldAntialias, }; }, { @@ -52,6 +55,7 @@ const UnifiedCanvasSettings = () => { shouldCropToBoundingBoxOnSave, shouldShowCanvasDebugInfo, shouldShowIntermediates, + shouldAntialias, } = useAppSelector(canvasControlsSelector); return ( @@ -95,6 +99,11 @@ const UnifiedCanvasSettings = () => { dispatch(setShouldShowCanvasDebugInfo(e.target.checked)) } /> + dispatch(setShouldAntialias(e.target.checked))} + /> From b42b630583c9cd2c1b5cfe745bef0624546ad5cd Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 10 May 2023 14:26:36 +1000 Subject: [PATCH 57/66] fix(ui): h/w disabled bug --- .../Parameters/Core/ParamHeight.tsx | 28 ++++++++----------- .../components/Parameters/Core/ParamWidth.tsx | 23 ++++++--------- .../ImageToImageTabCoreParameters.tsx | 18 ++++++------ 3 files changed, 29 insertions(+), 40 deletions(-) diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx index 9ba1aeaaf8..9501c8b475 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx @@ -1,6 +1,6 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISlider from 'common/components/IAISlider'; +import IAISlider, { IAIFullSliderProps } from 'common/components/IAISlider'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { setHeight } from 'features/parameters/store/generationSlice'; import { configSelector } from 'features/system/store/configSelectors'; @@ -13,8 +13,7 @@ const selector = createSelector( (generation, hotkeys, config) => { const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.height; - const { height, shouldFitToWidthHeight, isImageToImageEnabled } = - generation; + const { height } = generation; const step = hotkeys.shift ? fineStep : coarseStep; @@ -25,23 +24,18 @@ const selector = createSelector( sliderMax, inputMax, step, - shouldFitToWidthHeight, - isImageToImageEnabled, }; } ); -const ParamHeight = () => { - const { - height, - initial, - min, - sliderMax, - inputMax, - step, - shouldFitToWidthHeight, - isImageToImageEnabled, - } = useAppSelector(selector); +type ParamHeightProps = Omit< + IAIFullSliderProps, + 'label' | 'value' | 'onChange' +>; + +const ParamHeight = (props: ParamHeightProps) => { + const { height, initial, min, sliderMax, inputMax, step } = + useAppSelector(selector); const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -58,7 +52,6 @@ const ParamHeight = () => { return ( { withReset withSliderMarks sliderNumberInputProps={{ max: inputMax }} + {...props} /> ); }; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx index e8deb2ba70..b7d63038d1 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx @@ -1,6 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAISlider from 'common/components/IAISlider'; +import { IAIFullSliderProps } from 'common/components/IAISlider'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { setWidth } from 'features/parameters/store/generationSlice'; import { configSelector } from 'features/system/store/configSelectors'; @@ -13,7 +14,7 @@ const selector = createSelector( (generation, hotkeys, config) => { const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.width; - const { width, shouldFitToWidthHeight, isImageToImageEnabled } = generation; + const { width } = generation; const step = hotkeys.shift ? fineStep : coarseStep; @@ -24,23 +25,15 @@ const selector = createSelector( sliderMax, inputMax, step, - shouldFitToWidthHeight, - isImageToImageEnabled, }; } ); -const ParamWidth = () => { - const { - width, - initial, - min, - sliderMax, - inputMax, - step, - shouldFitToWidthHeight, - isImageToImageEnabled, - } = useAppSelector(selector); +type ParamWidthProps = Omit; + +const ParamWidth = (props: ParamWidthProps) => { + const { width, initial, min, sliderMax, inputMax, step } = + useAppSelector(selector); const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -57,7 +50,6 @@ const ParamWidth = () => { return ( { withReset withSliderMarks sliderNumberInputProps={{ max: inputMax }} + {...props} /> ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx index 97a4a2d580..5d85230140 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx @@ -13,19 +13,21 @@ import ParamSampler from 'features/parameters/components/Parameters/Core/ParamSa import ModelSelect from 'features/system/components/ModelSelect'; import ImageToImageStrength from 'features/parameters/components/Parameters/ImageToImage/ImageToImageStrength'; import ImageToImageFit from 'features/parameters/components/Parameters/ImageToImage/ImageToImageFit'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; const selector = createSelector( - uiSelector, - (ui) => { + [uiSelector, generationSelector], + (ui, generation) => { const { shouldUseSliders } = ui; + const { shouldFitToWidthHeight } = generation; - return { shouldUseSliders }; + return { shouldUseSliders, shouldFitToWidthHeight }; }, defaultSelectorOptions ); const ImageToImageTabCoreParameters = () => { - const { shouldUseSliders } = useAppSelector(selector); + const { shouldUseSliders, shouldFitToWidthHeight } = useAppSelector(selector); return ( { - - + + @@ -62,8 +64,8 @@ const ImageToImageTabCoreParameters = () => { - - + + From f0a3f07b4569fd95fec20809c2b24fac5efe9456 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 10 May 2023 14:38:05 +1000 Subject: [PATCH 58/66] feat(ui): antialias progress images --- invokeai/frontend/web/public/locales/en.json | 1 + .../gallery/components/CurrentImagePreview.tsx | 5 ++++- .../gallery/components/GalleryProgressImage.tsx | 12 +++++++++--- .../components/SettingsModal/SettingsModal.tsx | 13 +++++++++++++ .../web/src/features/system/store/systemSlice.ts | 9 +++++++++ 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 71461d409a..dccb77c267 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -532,6 +532,7 @@ "enableImageDebugging": "Enable Image Debugging", "useSlidersForAll": "Use Sliders For All Options", "showProgressInViewer": "Show Progress Images in Viewer", + "antialiasProgressImages": "Antialias Progress Images", "resetWebUI": "Reset Web UI", "resetWebUIDesc1": "Resetting the web UI only resets the browser's local cache of your images and remembered settings. It does not delete any images from disk.", "resetWebUIDesc2": "If images aren't showing up in the gallery or something else isn't working, please try resetting before submitting an issue on GitHub.", diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index c5bcee540a..a0fbd7c5d1 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -22,13 +22,14 @@ export const imagesSelector = createSelector( shouldShowProgressInViewer, } = ui; const { selectedImage } = gallery; - const { progressImage } = system; + const { progressImage, shouldAntialiasProgressImage } = system; return { shouldShowImageDetails, shouldHidePreview, image: selectedImage, progressImage, shouldShowProgressInViewer, + shouldAntialiasProgressImage, }; }, { @@ -45,6 +46,7 @@ const CurrentImagePreview = () => { shouldHidePreview, progressImage, shouldShowProgressInViewer, + shouldAntialiasProgressImage, } = useAppSelector(imagesSelector); const { getUrl } = useGetUrl(); @@ -84,6 +86,7 @@ const CurrentImagePreview = () => { height: 'auto', position: 'absolute', borderRadius: 'base', + imageRendering: shouldAntialiasProgressImage ? 'auto' : 'pixelated', }} /> ) : ( diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryProgressImage.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryProgressImage.tsx index c8797b2d0c..b812849c44 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryProgressImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryProgressImage.tsx @@ -10,20 +10,25 @@ const selector = createSelector( [systemSelector, gallerySelector], (system, gallery) => { const { shouldUseSingleGalleryColumn, galleryImageObjectFit } = gallery; - const { progressImage } = system; + const { progressImage, shouldAntialiasProgressImage } = system; return { progressImage, shouldUseSingleGalleryColumn, galleryImageObjectFit, + shouldAntialiasProgressImage, }; }, defaultSelectorOptions ); const GalleryProgressImage = () => { - const { progressImage, shouldUseSingleGalleryColumn, galleryImageObjectFit } = - useAppSelector(selector); + const { + progressImage, + shouldUseSingleGalleryColumn, + galleryImageObjectFit, + shouldAntialiasProgressImage, + } = useAppSelector(selector); if (!progressImage) { return null; @@ -53,6 +58,7 @@ const GalleryProgressImage = () => { maxWidth: '100%', maxHeight: '100%', borderRadius: 'base', + imageRendering: shouldAntialiasProgressImage ? 'auto' : 'pixelated', }} /> diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx index 0557228020..58e6684b04 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -23,6 +23,7 @@ import { setEnableImageDebugging, setShouldConfirmOnDelete, setShouldDisplayGuides, + shouldAntialiasProgressImageChanged, shouldLogToConsoleChanged, SystemState, } from 'features/system/store/systemSlice'; @@ -49,6 +50,7 @@ const selector = createSelector( enableImageDebugging, consoleLogLevel, shouldLogToConsole, + shouldAntialiasProgressImage, } = system; const { @@ -66,6 +68,7 @@ const selector = createSelector( shouldShowProgressInViewer, consoleLogLevel, shouldLogToConsole, + shouldAntialiasProgressImage, }; }, { @@ -117,6 +120,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => { shouldShowProgressInViewer, consoleLogLevel, shouldLogToConsole, + shouldAntialiasProgressImage, } = useAppSelector(selector); const handleClickResetWebUI = useCallback(() => { @@ -203,6 +207,15 @@ const SettingsModal = ({ children }: SettingsModalProps) => { dispatch(setShouldShowProgressInViewer(e.target.checked)) } /> + ) => + dispatch( + shouldAntialiasProgressImageChanged(e.target.checked) + ) + } + /> diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 8e0ab1f3aa..1aeb2a1939 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -90,6 +90,7 @@ export interface SystemState { */ infillMethods: InfillMethod[]; isPersisted: boolean; + shouldAntialiasProgressImage: boolean; } export const initialSystemState: SystemState = { @@ -111,6 +112,7 @@ export const initialSystemState: SystemState = { foundModels: null, openModel: null, progressImage: null, + shouldAntialiasProgressImage: false, sessionId: null, cancelType: 'immediate', isCancelScheduled: false, @@ -261,6 +263,12 @@ export const systemSlice = createSlice({ shouldLogToConsoleChanged: (state, action: PayloadAction) => { state.shouldLogToConsole = action.payload; }, + shouldAntialiasProgressImageChanged: ( + state, + action: PayloadAction + ) => { + state.shouldAntialiasProgressImage = action.payload; + }, isPersistedChanged: (state, action: PayloadAction) => { state.isPersisted = action.payload; }, @@ -471,6 +479,7 @@ export const { consoleLogLevelChanged, shouldLogToConsoleChanged, isPersistedChanged, + shouldAntialiasProgressImageChanged, } = systemSlice.actions; export default systemSlice.reducer; From d2edb7c40237d3e2f0fa44e4a5fb85923bd117f9 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 10 May 2023 14:42:27 +1000 Subject: [PATCH 59/66] build(ui): add yalc to gitignore --- invokeai/frontend/web/.gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/.gitignore b/invokeai/frontend/web/.gitignore index 83c5fcf2d7..cacf107e1b 100644 --- a/invokeai/frontend/web/.gitignore +++ b/invokeai/frontend/web/.gitignore @@ -34,4 +34,8 @@ stats.html !.yarn/plugins !.yarn/releases !.yarn/sdks -!.yarn/versions \ No newline at end of file +!.yarn/versions + +# Yalc +.yalc +yalc.lock \ No newline at end of file From f488b1a7f27dff230bee2b1316119dea96b1cbb4 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 10:57:08 +1000 Subject: [PATCH 60/66] fix(nodes): fix usage of Optional --- invokeai/app/invocations/collections.py | 2 +- invokeai/app/invocations/generate.py | 2 +- invokeai/app/invocations/infill.py | 8 ++++---- invokeai/app/invocations/latent.py | 2 +- invokeai/app/services/default_graphs.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/invokeai/app/invocations/collections.py b/invokeai/app/invocations/collections.py index 62f6ad8037..93130bfaad 100644 --- a/invokeai/app/invocations/collections.py +++ b/invokeai/app/invocations/collections.py @@ -50,7 +50,7 @@ class RandomRangeInvocation(BaseInvocation): default=np.iinfo(np.int32).max, description="The exclusive high value" ) size: int = Field(default=1, description="The number of values to generate") - seed: Optional[int] = Field( + seed: int = Field( ge=0, le=SEED_MAX, description="The seed for the RNG (omit for random)", diff --git a/invokeai/app/invocations/generate.py b/invokeai/app/invocations/generate.py index 2ca3e98dfa..9a29502048 100644 --- a/invokeai/app/invocations/generate.py +++ b/invokeai/app/invocations/generate.py @@ -47,7 +47,7 @@ class TextToImageInvocation(BaseInvocation, SDImageInvocation): # TODO: consider making prompt optional to enable providing prompt through a link # fmt: off prompt: Optional[str] = Field(description="The prompt to generate an image from") - seed: Optional[int] = Field(ge=0, le=SEED_MAX, description="The seed to use (omit for random)", default_factory=get_random_seed) + seed: int = Field(ge=0, le=SEED_MAX, description="The seed to use (omit for random)", default_factory=get_random_seed) steps: int = Field(default=30, gt=0, description="The number of steps to use to generate the image") width: int = Field(default=512, multiple_of=8, gt=0, description="The width of the resulting image", ) height: int = Field(default=512, multiple_of=8, gt=0, description="The height of the resulting image", ) diff --git a/invokeai/app/invocations/infill.py b/invokeai/app/invocations/infill.py index 17a48cf47b..ac055cef5b 100644 --- a/invokeai/app/invocations/infill.py +++ b/invokeai/app/invocations/infill.py @@ -125,7 +125,7 @@ class InfillColorInvocation(BaseInvocation): """Infills transparent areas of an image with a solid color""" type: Literal["infill_rgba"] = "infill_rgba" - image: ImageField = Field(default=None, description="The image to infill") + image: Optional[ImageField] = Field(default=None, description="The image to infill") color: Optional[ColorField] = Field( default=ColorField(r=127, g=127, b=127, a=255), description="The color to use to infill", @@ -163,9 +163,9 @@ class InfillTileInvocation(BaseInvocation): type: Literal["infill_tile"] = "infill_tile" - image: ImageField = Field(default=None, description="The image to infill") + image: Optional[ImageField] = Field(default=None, description="The image to infill") tile_size: int = Field(default=32, ge=1, description="The tile size (px)") - seed: Optional[int] = Field( + seed: int = Field( ge=0, le=SEED_MAX, description="The seed to use for tile generation (omit for random)", @@ -204,7 +204,7 @@ class InfillPatchMatchInvocation(BaseInvocation): type: Literal["infill_patchmatch"] = "infill_patchmatch" - image: ImageField = Field(default=None, description="The image to infill") + image: Optional[ImageField] = Field(default=None, description="The image to infill") def invoke(self, context: InvocationContext) -> ImageOutput: image = context.services.images.get( diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 861db7276f..c6ddcb0396 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -111,7 +111,7 @@ class NoiseInvocation(BaseInvocation): type: Literal["noise"] = "noise" # Inputs - seed: Optional[int] = Field(ge=0, le=SEED_MAX, description="The seed to use", default_factory=get_random_seed) + seed: int = Field(ge=0, le=SEED_MAX, description="The seed to use", default_factory=get_random_seed) width: int = Field(default=512, multiple_of=8, gt=0, description="The width of the resulting noise", ) height: int = Field(default=512, multiple_of=8, gt=0, description="The height of the resulting noise", ) diff --git a/invokeai/app/services/default_graphs.py b/invokeai/app/services/default_graphs.py index c8347c043f..0ac6b08b4d 100644 --- a/invokeai/app/services/default_graphs.py +++ b/invokeai/app/services/default_graphs.py @@ -51,7 +51,7 @@ def create_system_graphs(graph_library: ItemStorageABC[LibraryGraph]) -> list[Li graphs: list[LibraryGraph] = list() - text_to_image = graph_library.get(default_text_to_image_graph_id) + # text_to_image = graph_library.get(default_text_to_image_graph_id) # TODO: Check if the graph is the same as the default one, and if not, update it #if text_to_image is None: From 8ef49c264066fd61068aad4a4fcef35e764b5be8 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 11:12:39 +1000 Subject: [PATCH 61/66] fix(ui): fix canvas img2img if no init image selected --- .../src/app/selectors/readinessSelector.ts | 21 ++------ .../util/graphBuilders/buildCanvasGraph.ts | 2 - .../nodeBuilders/buildImageToImageNode.ts | 26 ++++++---- .../ImageToImage/ImageToImageStrength.tsx | 19 +++---- .../ImageToImage/ImageToImageToggle.tsx | 49 ------------------- .../ImageToImage/InitialImagePreview.tsx | 31 ++---------- .../parameters/store/generationSlice.ts | 10 ---- 7 files changed, 31 insertions(+), 127 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageToggle.tsx diff --git a/invokeai/frontend/web/src/app/selectors/readinessSelector.ts b/invokeai/frontend/web/src/app/selectors/readinessSelector.ts index 88863e880d..6fd212494f 100644 --- a/invokeai/frontend/web/src/app/selectors/readinessSelector.ts +++ b/invokeai/frontend/web/src/app/selectors/readinessSelector.ts @@ -1,26 +1,20 @@ import { createSelector } from '@reduxjs/toolkit'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { validateSeedWeights } from 'common/util/seedWeightPairs'; -import { initialCanvasImageSelector } from 'features/canvas/store/canvasSelectors'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { systemSelector } from 'features/system/store/systemSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { isEqual } from 'lodash-es'; export const readinessSelector = createSelector( - [ - generationSelector, - systemSelector, - initialCanvasImageSelector, - activeTabNameSelector, - ], - (generation, system) => { + [generationSelector, systemSelector, activeTabNameSelector], + (generation, system, activeTabName) => { const { prompt, shouldGenerateVariations, seedWeights, initialImage, seed, - isImageToImageEnabled, } = generation; const { isProcessing, isConnected } = system; @@ -34,7 +28,7 @@ export const readinessSelector = createSelector( reasonsWhyNotReady.push('Missing prompt'); } - if (isImageToImageEnabled && !initialImage) { + if (activeTabName === 'img2img' && !initialImage) { isReady = false; reasonsWhyNotReady.push('No initial image selected'); } @@ -64,10 +58,5 @@ export const readinessSelector = createSelector( // All good return { isReady, reasonsWhyNotReady }; }, - { - memoizeOptions: { - equalityCheck: isEqual, - resultEqualityCheck: isEqual, - }, - } + defaultSelectorOptions ); diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts index 46bc510020..45deed7070 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts @@ -79,8 +79,6 @@ export const buildCanvasGraphAndBlobs = async ( moduleLog.debug( { data: { - // baseDataURL, - // maskDataURL, baseIsPartiallyTransparent, baseIsFullyTransparent, doesMaskHaveBlackPixels, diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts index 2d7b88a9ab..c8b328370a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts @@ -5,8 +5,8 @@ import { ImageToImageInvocation, TextToImageInvocation, } from 'services/api'; -import { initialImageSelector } from 'features/parameters/store/generationSelectors'; import { O } from 'ts-toolbelt'; +import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; export const buildImg2ImgNode = ( state: RootState, @@ -15,6 +15,8 @@ export const buildImg2ImgNode = ( const nodeId = uuidv4(); const { generation } = state; + const activeTabName = activeTabNameSelector(state); + const { prompt, negativePrompt, @@ -33,11 +35,6 @@ export const buildImg2ImgNode = ( // const initialImage = initialImageSelector(state); - if (!initialImage) { - // TODO: handle this - throw 'no initial image'; - } - const imageToImageNode: ImageToImageInvocation = { id: nodeId, type: 'img2img', @@ -48,14 +45,23 @@ export const buildImg2ImgNode = ( cfg_scale: cfgScale, scheduler: sampler as ImageToImageInvocation['scheduler'], model, - image: { - image_name: initialImage.name, - image_type: initialImage.type, - }, strength, fit, }; + // on Canvas tab, we do not manually specific init image + if (activeTabName === 'img2img') { + if (!initialImage) { + // TODO: handle this more better + throw 'no initial image'; + } + + imageToImageNode.image = { + image_name: initialImage.name, + image_type: initialImage.type, + }; + } + if (!shouldRandomizeSeed) { imageToImageNode.seed = seed; } diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx index 284aa9a5c0..b467b15091 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx @@ -1,5 +1,6 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider from 'common/components/IAISlider'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { setImg2imgStrength } from 'features/parameters/store/generationSlice'; @@ -13,32 +14,25 @@ const selector = createSelector( (generation, hotkeys, config) => { const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.img2imgStrength; - const { img2imgStrength, isImageToImageEnabled } = generation; + const { img2imgStrength } = generation; const step = hotkeys.shift ? fineStep : coarseStep; return { img2imgStrength, - isImageToImageEnabled, initial, min, sliderMax, inputMax, step, }; - } + }, + defaultSelectorOptions ); const ImageToImageStrength = () => { - const { - img2imgStrength, - isImageToImageEnabled, - initial, - min, - sliderMax, - inputMax, - step, - } = useAppSelector(selector); + const { img2imgStrength, initial, min, sliderMax, inputMax, step } = + useAppSelector(selector); const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -64,7 +58,6 @@ const ImageToImageStrength = () => { withInput withSliderMarks withReset - isDisabled={!isImageToImageEnabled} sliderNumberInputProps={{ max: inputMax }} /> ); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageToggle.tsx deleted file mode 100644 index f70e8e6602..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageToggle.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import IAISwitch from 'common/components/IAISwitch'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { uiSelector } from 'features/ui/store/uiSelectors'; -import { shouldShowImageParametersChanged } from 'features/ui/store/uiSlice'; -import { ChangeEvent } from 'react'; -import { useTranslation } from 'react-i18next'; - -const selector = createSelector( - [uiSelector, generationSelector], - (ui, generation) => { - const { isImageToImageEnabled } = generation; - const { shouldShowImageParameters } = ui; - return { - isImageToImageEnabled, - shouldShowImageParameters, - }; - }, - defaultSelectorOptions -); - -export default function ImageToImageToggle() { - const { shouldShowImageParameters } = useAppSelector(selector); - - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const handleChange = (e: ChangeEvent) => - dispatch(shouldShowImageParametersChanged(e.target.checked)); - - return ( - - - - ); -} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx index cb3ffc3590..fbb833a14a 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx @@ -5,28 +5,27 @@ import SelectImagePlaceholder from 'common/components/SelectImagePlaceholder'; import { useGetUrl } from 'common/util/getUrl'; import { clearInitialImage } from 'features/parameters/store/generationSlice'; import { addToast } from 'features/system/store/systemSlice'; -import { isEqual } from 'lodash-es'; import { DragEvent, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ImageType } from 'services/api'; import ImageToImageOverlay from 'common/components/ImageToImageOverlay'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { initialImageSelected } from 'features/parameters/store/actions'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; const selector = createSelector( [generationSelector], (generation) => { - const { initialImage, isImageToImageEnabled } = generation; + const { initialImage } = generation; return { initialImage, - isImageToImageEnabled, }; }, - { memoizeOptions: { resultEqualityCheck: isEqual } } + defaultSelectorOptions ); const InitialImagePreview = () => { - const { initialImage, isImageToImageEnabled } = useAppSelector(selector); + const { initialImage } = useAppSelector(selector); const { getUrl } = useGetUrl(); const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -73,8 +72,6 @@ const InitialImagePreview = () => { sx={{ height: 'full', width: 'full', - opacity: isImageToImageEnabled ? 1 : 0.5, - filter: isImageToImageEnabled ? 'none' : 'auto', blur: '5px', position: 'relative', alignItems: 'center', @@ -107,26 +104,6 @@ const InitialImagePreview = () => { )} {!initialImage?.url && } - {/* {!isImageToImageEnabled && ( - - - Image to Image is Disabled - - - )} */} ); }; diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index e1bb20c1ab..b0adc578a0 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -35,7 +35,6 @@ export interface GenerationState { shouldUseSymmetry: boolean; horizontalSymmetrySteps: number; verticalSymmetrySteps: number; - isImageToImageEnabled: boolean; model: string; shouldUseSeamless: boolean; seamlessXAxis: boolean; @@ -71,7 +70,6 @@ export const initialGenerationState: GenerationState = { shouldUseSymmetry: false, horizontalSymmetrySteps: 0, verticalSymmetrySteps: 0, - isImageToImageEnabled: false, model: '', shouldUseSeamless: false, seamlessXAxis: true, @@ -233,10 +231,6 @@ export const generationSlice = createSlice({ }, initialImageChanged: (state, action: PayloadAction) => { state.initialImage = action.payload; - state.isImageToImageEnabled = true; - }, - isImageToImageEnabledChanged: (state, action: PayloadAction) => { - state.isImageToImageEnabled = action.payload; }, modelSelected: (state, action: PayloadAction) => { state.model = action.payload; @@ -249,9 +243,6 @@ export const { clearInitialImage, resetParametersState, resetSeed, - setAllImageToImageParameters, - setAllParameters, - setAllTextToImageParameters, setCfgScale, setHeight, setImg2imgStrength, @@ -282,7 +273,6 @@ export const { setHorizontalSymmetrySteps, setVerticalSymmetrySteps, initialImageChanged, - isImageToImageEnabledChanged, modelSelected, setShouldUseNoiseSettings, setSeamless, From 54b65f725f496c30722a830f206b2e8dc5304cdc Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 11:24:00 +1000 Subject: [PATCH 62/66] fix(ui): rescale canvas on gallery resize --- .../web/src/features/ui/components/InvokeTabs.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index 559ea4f8c2..d2bd4f6d51 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -13,11 +13,14 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice'; import { InvokeTabName } from 'features/ui/store/tabMap'; import { setActiveTab, togglePanels } from 'features/ui/store/uiSlice'; -import { memo, ReactNode, useMemo } from 'react'; +import { memo, ReactNode, useCallback, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { MdDeviceHub, MdGridOn } from 'react-icons/md'; import { GoTextSize } from 'react-icons/go'; -import { activeTabIndexSelector } from '../store/uiSelectors'; +import { + activeTabIndexSelector, + activeTabNameSelector, +} from '../store/uiSelectors'; import { useTranslation } from 'react-i18next'; import { ResourceKey } from 'i18next'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; @@ -76,6 +79,7 @@ const enabledTabsSelector = createSelector( const InvokeTabs = () => { const activeTab = useAppSelector(activeTabIndexSelector); + const activeTabName = useAppSelector(activeTabNameSelector); const enabledTabs = useAppSelector(enabledTabsSelector); const isLightBoxOpen = useAppSelector( (state: RootState) => state.lightbox.isLightboxOpen @@ -107,6 +111,12 @@ const InvokeTabs = () => { [shouldPinGallery, shouldPinParametersPanel] ); + const handleResizeGallery = useCallback(() => { + if (activeTabName === 'unifiedCanvas') { + dispatch(requestCanvasRescale()); + } + }, [dispatch, activeTabName]); + const tabs = useMemo( () => enabledTabs.map((tab) => ( @@ -167,6 +177,7 @@ const InvokeTabs = () => { <> Date: Thu, 11 May 2023 11:35:15 +1000 Subject: [PATCH 63/66] fix(ui): change tab to img2img when selected initial image --- invokeai/frontend/web/src/features/ui/store/uiSlice.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index 470ad076a3..b99ebb2c51 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -3,6 +3,8 @@ import { createSlice } from '@reduxjs/toolkit'; import { setActiveTabReducer } from './extraReducers'; import { InvokeTabName, tabMap } from './tabMap'; import { AddNewModelType, Coordinates, Rect, UIState } from './uiTypes'; +import { initialImageSelected } from 'features/parameters/store/actions'; +import { initialImageChanged } from 'features/parameters/store/generationSlice'; export const initialUIState: UIState = { activeTab: 0, @@ -145,6 +147,11 @@ export const uiSlice = createSlice({ state.shouldShowImageParameters = action.payload; }, }, + extraReducers(builder) { + builder.addCase(initialImageChanged, (state) => { + setActiveTabReducer(state, 'img2img'); + }); + }, }); export const { From e0d6946b6b7902e103ef94770e996f139fbe0f95 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 11:40:45 +1000 Subject: [PATCH 64/66] fix(nodes): fix metadata test - `progress_images` is no longer a parameter - `seamless` needs to be reworked as a model config, removed as a param --- tests/nodes/test_png_metadata_service.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/nodes/test_png_metadata_service.py b/tests/nodes/test_png_metadata_service.py index 3075af5a4b..c724074518 100644 --- a/tests/nodes/test_png_metadata_service.py +++ b/tests/nodes/test_png_metadata_service.py @@ -18,9 +18,7 @@ valid_metadata = { "height": 512, "cfg_scale": 7.5, "scheduler": "k_lms", - "seamless": False, "model": "stable-diffusion-1.5", - "progress_images": True, }, } From b0c41b482873d22fc80438331bb25be7236e7724 Mon Sep 17 00:00:00 2001 From: Mary Hipp Rogers Date: Wed, 10 May 2023 21:58:40 -0400 Subject: [PATCH 65/66] filter our websocket errors (#3382) Co-authored-by: Mary Hipp --- .../services/events/util/setEventListeners.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts index 1cd06c51ff..88bb11147c 100644 --- a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts +++ b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts @@ -82,11 +82,18 @@ export const setEventListeners = (arg: SetEventListenersArg) => { socket.on('connect_error', (error) => { if (error && error.message) { - dispatch( - addToast( - makeToast({ title: error.message, status: 'error', duration: 10000 }) - ) - ); + const data: string | undefined = (error as any).data; + if (data === 'ERR_UNAUTHENTICATED') { + dispatch( + addToast( + makeToast({ + title: error.message, + status: 'error', + duration: 10000, + }) + ) + ); + } } }); From 9e594f90185de88b8e9c20f8ecaf6030d3ae7f17 Mon Sep 17 00:00:00 2001 From: Eugene Date: Thu, 11 May 2023 00:34:12 -0400 Subject: [PATCH 66/66] pad conditioning tensors to same length fixes crash when prompt length is greater than 75 tokens --- invokeai/backend/prompting/conditioning.py | 1 + 1 file changed, 1 insertion(+) diff --git a/invokeai/backend/prompting/conditioning.py b/invokeai/backend/prompting/conditioning.py index f94f82ef72..71e51f1103 100644 --- a/invokeai/backend/prompting/conditioning.py +++ b/invokeai/backend/prompting/conditioning.py @@ -64,6 +64,7 @@ def get_uc_and_c_and_ec(prompt_string, step_count=-1): c, options = compel.build_conditioning_tensor_for_prompt_object(positive_prompt) uc, _ = compel.build_conditioning_tensor_for_prompt_object(negative_prompt) + [c, uc] = compel.pad_conditioning_tensors_to_same_length([c, uc]) # now build the "real" ec ec = InvokeAIDiffuserComponent.ExtraConditioningInfo(tokens_count_including_eos_bos=tokens_count,