From 8f80ba952064e7229d3697170408f101c37c7663 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Sat, 6 May 2023 23:09:24 -0400 Subject: [PATCH 001/123] update dependencies to get deterministic image generation --- installer/lib/installer.py | 4 ++-- pyproject.toml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/installer/lib/installer.py b/installer/lib/installer.py index 42079cd90c..96d8ce55e3 100644 --- a/installer/lib/installer.py +++ b/installer/lib/installer.py @@ -247,8 +247,8 @@ class InvokeAiInstance: pip[ "install", "--require-virtualenv", - "torch", - "torchvision", + "torch~=2.0.0", + "torchvision>=0.14.1", "--force-reinstall", "--find-links" if find_links is not None else None, find_links, diff --git a/pyproject.toml b/pyproject.toml index 0ab1e0ede6..9702f00202 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ dependencies = [ "clip_anytorch", # replacing "clip @ https://github.com/openai/CLIP/archive/eaa22acb90a5876642d0507623e859909230a52d.zip", "compel==1.0.5", "datasets", - "diffusers[torch]==0.15.1", + "diffusers[torch]~=0.16.1", "dnspython==2.2.1", "einops", "eventlet", @@ -72,7 +72,7 @@ dependencies = [ "scikit-image>=0.19", "send2trash", "test-tube>=0.7.5", - "torch>=1.13.1", + "torch~=2.0.0", "torchvision>=0.14.1", "torchmetrics", "transformers~=4.26", @@ -92,7 +92,7 @@ dependencies = [ ] "test" = ["pytest>6.0.0", "pytest-cov"] "xformers" = [ - "xformers~=0.0.16; sys_platform!='darwin'", + "xformers~=0.0.19; sys_platform!='darwin'", "triton; sys_platform=='linux'", ] From bc9491ab69b4e128223616e6aaa43e7156ff7edd Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Sun, 7 May 2023 15:21:24 -0400 Subject: [PATCH 002/123] bump compel version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9702f00202..2d685ffe02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "albumentations", "click", "clip_anytorch", # replacing "clip @ https://github.com/openai/CLIP/archive/eaa22acb90a5876642d0507623e859909230a52d.zip", - "compel==1.0.5", + "compel~=1.1.5", "datasets", "diffusers[torch]~=0.16.1", "dnspython==2.2.1", From bd0ad59c277316b186c643bf78727ef129d28252 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Sun, 7 May 2023 15:22:46 -0400 Subject: [PATCH 003/123] bump compel version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2d685ffe02..fd671fee23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "albumentations", "click", "clip_anytorch", # replacing "clip @ https://github.com/openai/CLIP/archive/eaa22acb90a5876642d0507623e859909230a52d.zip", - "compel~=1.1.5", + "compel~=1.1.5", "datasets", "diffusers[torch]~=0.16.1", "dnspython==2.2.1", From 45da85765cc5e524e8edf179fdc6eb76e14277df Mon Sep 17 00:00:00 2001 From: mauwii Date: Mon, 8 May 2023 21:10:20 +0200 Subject: [PATCH 004/123] add v2.3 branch to push trigger --- .github/workflows/mkdocs-material.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/mkdocs-material.yml b/.github/workflows/mkdocs-material.yml index be665aee54..7d6c9516fb 100644 --- a/.github/workflows/mkdocs-material.yml +++ b/.github/workflows/mkdocs-material.yml @@ -4,6 +4,7 @@ on: branches: - 'main' - 'development' + - 'refs/heads/v2.3' permissions: contents: write From df024dd982ed48275e25843371f2b45d38dc5313 Mon Sep 17 00:00:00 2001 From: mauwii Date: Mon, 8 May 2023 21:50:00 +0200 Subject: [PATCH 005/123] bring changes from v2.3 branch over - remove main and development branch from trigger - they would fail without the updated toml - cache pip environment - update install method --- .github/workflows/mkdocs-material.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mkdocs-material.yml b/.github/workflows/mkdocs-material.yml index 7d6c9516fb..d6388bce8d 100644 --- a/.github/workflows/mkdocs-material.yml +++ b/.github/workflows/mkdocs-material.yml @@ -2,8 +2,6 @@ name: mkdocs-material on: push: branches: - - 'main' - - 'development' - 'refs/heads/v2.3' permissions: @@ -13,6 +11,10 @@ jobs: mkdocs-material: if: github.event.pull_request.draft == false runs-on: ubuntu-latest + env: + REPO_URL: '${{ github.server_url }}/${{ github.repository }}' + REPO_NAME: '${{ github.repository }}' + SITE_URL: 'https://${{ github.repository_owner }}.github.io/InvokeAI' steps: - name: checkout sources uses: actions/checkout@v3 @@ -23,11 +25,15 @@ jobs: uses: actions/setup-python@v4 with: python-version: '3.10' + cache: pip + cache-dependency-path: pyproject.toml - name: install requirements + env: + PIP_USE_PEP517: 1 run: | python -m \ - pip install -r docs/requirements-mkdocs.txt + pip install ".[docs]" - name: confirm buildability run: | From 79d49853d26e2b599e52cae102c213c6ee171b48 Mon Sep 17 00:00:00 2001 From: Eugene Date: Mon, 8 May 2023 18:53:16 -0400 Subject: [PATCH 006/123] use websocket transport first for socket.io --- invokeai/frontend/web/src/services/events/middleware.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/invokeai/frontend/web/src/services/events/middleware.ts b/invokeai/frontend/web/src/services/events/middleware.ts index dab2e756f3..65652ae7ee 100644 --- a/invokeai/frontend/web/src/services/events/middleware.ts +++ b/invokeai/frontend/web/src/services/events/middleware.ts @@ -46,6 +46,8 @@ export const socketMiddleware = () => { // TODO: handle providing jwt to socket.io socketOptions.auth = { token: OpenAPI.TOKEN }; } + + socketOptions.transports = ['websocket', 'polling']; } const socket: Socket = io( From 1809990ed4db27c88750affb5af517100ae72f43 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Mon, 8 May 2023 15:11:29 -0400 Subject: [PATCH 007/123] if backend returns an error, show it in toast --- .../src/services/events/util/setEventListeners.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts index 89bbc717a3..581363e446 100644 --- a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts +++ b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts @@ -22,6 +22,8 @@ import { } from 'services/thunks/gallery'; import { receivedModels } from 'services/thunks/model'; import { receivedOpenAPISchema } from 'services/thunks/schema'; +import { makeToast } from '../../../features/system/hooks/useToastWatcher'; +import { addToast } from '../../../features/system/store/systemSlice'; type SetEventListenersArg = { socket: Socket; @@ -78,6 +80,16 @@ export const setEventListeners = (arg: SetEventListenersArg) => { } }); + socket.on('connect_error', (error) => { + if (error && error.message) { + dispatch( + addToast( + makeToast({ title: error.message, status: 'error', duration: 10000 }) + ) + ); + } + }); + /** * Disconnect */ From 853c83d0c203fd4b2f9cbf5fb6e1b5eecbfc2f07 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Mon, 8 May 2023 11:52:35 -0400 Subject: [PATCH 008/123] surface detail field for 403 errors --- .../src/features/system/store/systemSlice.ts | 7 ++++++ .../web/src/services/thunks/session.ts | 23 ++++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 41cb33f88c..a8c8da9bfb 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -384,6 +384,13 @@ export const systemSlice = createSlice({ state.statusTranslationKey = 'common.statusPreparing'; }); + builder.addCase(sessionInvoked.rejected, (state, action) => { + const error = action.payload as string | undefined; + state.toastQueue.push( + makeToast({ title: error || t('toast.serverError'), status: 'error' }) + ); + }); + /** * Session Canceled */ diff --git a/invokeai/frontend/web/src/services/thunks/session.ts b/invokeai/frontend/web/src/services/thunks/session.ts index af03045967..6bd7f01c26 100644 --- a/invokeai/frontend/web/src/services/thunks/session.ts +++ b/invokeai/frontend/web/src/services/thunks/session.ts @@ -101,17 +101,24 @@ export const nodeAdded = createAppAsyncThunk( */ export const sessionInvoked = createAppAsyncThunk( 'api/sessionInvoked', - async (arg: { sessionId: string }, _thunkApi) => { + async (arg: { sessionId: string }, { rejectWithValue }) => { const { sessionId } = arg; - const response = await SessionsService.invokeSession({ - sessionId, - all: true, - }); + try { + const response = await SessionsService.invokeSession({ + sessionId, + all: true, + }); + sessionLog.info({ arg, response }, `Session invoked (${sessionId})`); - sessionLog.info({ arg, response }, `Session invoked (${sessionId})`); - - return response; + return response; + } catch (error) { + const err = error as any; + if (err.status === 403) { + return rejectWithValue(err.body.detail); + } + throw error; + } } ); From 7dfa135b2c5b1b293c53caead6ed6538968d991d Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 10 May 2023 15:29:10 +1000 Subject: [PATCH 009/123] fix(nodes): fix #3306 Check if the cache has the object before deleting it. --- invokeai/app/services/image_storage.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/invokeai/app/services/image_storage.py b/invokeai/app/services/image_storage.py index c5e5cf8a8e..e2593dd473 100644 --- a/invokeai/app/services/image_storage.py +++ b/invokeai/app/services/image_storage.py @@ -270,4 +270,5 @@ class DiskImageStorage(ImageStorageBase): ) # TODO: this should refresh position for LRU cache if len(self.__cache) > self.__max_cache_size: cache_id = self.__cache_ids.get() - del self.__cache[cache_id] + if cache_id in self.__cache: + del self.__cache[cache_id] From f8e90ba3f099179e8003d933e211c8d8437b715f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 10 May 2023 15:52:43 +1000 Subject: [PATCH 010/123] feat(nodes): add ui build static route --- invokeai/app/api_app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index f935de542e..00afa8f7f0 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -126,6 +126,7 @@ app.openapi = custom_openapi # Override API doc favicons app.mount("/static", StaticFiles(directory="static/dream_web"), name="static") +app.mount("/", StaticFiles(directory="invokeai/frontend/web/dist", html=True), name="ui") @app.get("/docs", include_in_schema=False) def overridden_swagger(): From ee24ad7b1364063f9b8db4b91004bfa68da4235e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 10 May 2023 16:18:06 +1000 Subject: [PATCH 011/123] fix(nodes): fix broken docs routes --- invokeai/app/api_app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index 00afa8f7f0..2dc97df273 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -126,8 +126,6 @@ app.openapi = custom_openapi # Override API doc favicons app.mount("/static", StaticFiles(directory="static/dream_web"), name="static") -app.mount("/", StaticFiles(directory="invokeai/frontend/web/dist", html=True), name="ui") - @app.get("/docs", include_in_schema=False) def overridden_swagger(): return get_swagger_ui_html( @@ -145,6 +143,8 @@ def overridden_redoc(): redoc_favicon_url="/static/favicon.ico", ) +# Must mount *after* the other routes else it borks em +app.mount("/", StaticFiles(directory="invokeai/frontend/web/dist", html=True), name="ui") def invoke_api(): # Start our own event loop for eventing usage From fb6ef61a4d77543c85f9841970b5668046ee1dea Mon Sep 17 00:00:00 2001 From: Mary Hipp Rogers Date: Wed, 10 May 2023 10:30:17 -0400 Subject: [PATCH 012/123] change path for locale (#3381) Co-authored-by: Mary Hipp --- invokeai/frontend/web/src/i18n.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/i18n.ts b/invokeai/frontend/web/src/i18n.ts index faa30f7289..ed71d583b3 100644 --- a/invokeai/frontend/web/src/i18n.ts +++ b/invokeai/frontend/web/src/i18n.ts @@ -3,7 +3,7 @@ import LanguageDetector from 'i18next-browser-languagedetector'; import Backend from 'i18next-http-backend'; import { initReactI18next } from 'react-i18next'; -import translationEN from '../dist/locales/en.json'; +import translationEN from '../public/locales/en.json'; if (import.meta.env.MODE === 'package') { i18n.use(initReactI18next).init({ 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 013/123] 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 014/123] 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 015/123] 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 016/123] 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 017/123] 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 018/123] 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 019/123] 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 020/123] 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 021/123] 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 022/123] 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 023/123] 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 024/123] 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 025/123] 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 026/123] 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 027/123] 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 028/123] 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 029/123] 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 030/123] 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 031/123] 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 032/123] 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 033/123] 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 034/123] 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 035/123] 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 036/123] 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 037/123] 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 038/123] 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 039/123] 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 040/123] 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 041/123] 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 042/123] 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 043/123] 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 044/123] 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 045/123] 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 046/123] 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 047/123] 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 048/123] 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 049/123] 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 050/123] 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 051/123] 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 052/123] 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 053/123] 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 054/123] 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 055/123] 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 056/123] 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 057/123] 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 058/123] 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 059/123] 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 060/123] 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 061/123] 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 062/123] 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 063/123] 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 064/123] 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 065/123] 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 066/123] 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 067/123] 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 068/123] 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 069/123] 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 070/123] 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 071/123] 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 072/123] 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 073/123] 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 074/123] 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 075/123] 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 076/123] 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 077/123] 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 4333852c37b898c31626f682cd90aa82e066bac7 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 18:36:46 +1000 Subject: [PATCH 078/123] fix(nodes): fix missing `context` arg in LatentsToLatents --- invokeai/app/invocations/latent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index c6ddcb0396..40575c1f64 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -285,7 +285,7 @@ class LatentsToLatentsInvocation(TextToLatentsInvocation): self.dispatch_progress(context, source_node_id, state) model = self.get_model(context.services.model_manager) - conditioning_data = self.get_conditioning_data(model) + conditioning_data = self.get_conditioning_data(context, model) # TODO: Verify the noise is the right size From 93ced0bec6879494d01419d56444aa592755d7f5 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 20:23:02 +1000 Subject: [PATCH 079/123] feat(nodes): add w/h to latents outputs This reduces the number of nodes needed when working with latents (ie fewer plain integer value nodes) Also correct a few mistakes in the fields --- invokeai/app/invocations/latent.py | 65 +++++++++++++++++++----------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 40575c1f64..3b6eb95fa0 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -40,17 +40,40 @@ class LatentsField(BaseModel): class LatentsOutput(BaseInvocationOutput): """Base class for invocations that output latents""" #fmt: off - type: Literal["latent_output"] = "latent_output" - latents: LatentsField = Field(default=None, description="The output latents") + type: Literal["latents_output"] = "latents_output" + + # Inputs + latents: LatentsField = Field(default=None, description="The output latents") + width: int = Field(description="The width of the latents in pixels") + height: int = Field(description="The height of the latents in pixels") #fmt: on + +def build_latents_output(latents_name: str, latents: torch.Tensor): + return LatentsOutput( + latents=LatentsField(latents_name=latents_name), + width=latents.size()[3] * 8, + height=latents.size()[2] * 8, + ) + class NoiseOutput(BaseInvocationOutput): """Invocation noise output""" #fmt: off - type: Literal["noise_output"] = "noise_output" + type: Literal["noise_output"] = "noise_output" + + # Inputs noise: LatentsField = Field(default=None, description="The output noise") + width: int = Field(description="The width of the noise in pixels") + height: int = Field(description="The height of the noise in pixels") #fmt: on +def build_noise_output(latents_name: str, latents: torch.Tensor): + return NoiseOutput( + noise=LatentsField(latents_name=latents_name), + width=latents.size()[3] * 8, + height=latents.size()[2] * 8, + ) + # TODO: this seems like a hack scheduler_map = dict( @@ -130,9 +153,7 @@ class NoiseInvocation(BaseInvocation): name = f'{context.graph_execution_state_id}__{self.id}' context.services.latents.set(name, noise) - return NoiseOutput( - noise=LatentsField(latents_name=name) - ) + return build_noise_output(latents_name=name, latents=noise) # Text to image @@ -248,9 +269,7 @@ class TextToLatentsInvocation(BaseInvocation): name = f'{context.graph_execution_state_id}__{self.id}' context.services.latents.set(name, result_latents) - return LatentsOutput( - latents=LatentsField(latents_name=name) - ) + return build_latents_output(latents_name=name, latents=result_latents) class LatentsToLatentsInvocation(TextToLatentsInvocation): @@ -313,9 +332,7 @@ class LatentsToLatentsInvocation(TextToLatentsInvocation): name = f'{context.graph_execution_state_id}__{self.id}' context.services.latents.set(name, result_latents) - return LatentsOutput( - latents=LatentsField(latents_name=name) - ) + return build_latents_output(latents_name=name, latents=result_latents) # Latent to image @@ -379,11 +396,11 @@ class ResizeLatentsInvocation(BaseInvocation): type: Literal["lresize"] = "lresize" # Inputs - latents: Optional[LatentsField] = Field(description="The latents to resize") - width: int = Field(ge=64, multiple_of=8, description="The width to resize to (px)") - height: int = Field(ge=64, multiple_of=8, description="The height to resize to (px)") - mode: Optional[LATENTS_INTERPOLATION_MODE] = Field(default="bilinear", description="The interpolation mode") - antialias: Optional[bool] = Field(default=False, description="Whether or not to antialias (applied in bilinear and bicubic modes only)") + latents: Optional[LatentsField] = Field(description="The latents to resize") + width: int = Field(ge=64, multiple_of=8, description="The width to resize to (px)") + height: int = Field(ge=64, multiple_of=8, description="The height to resize to (px)") + mode: LATENTS_INTERPOLATION_MODE = Field(default="bilinear", description="The interpolation mode") + antialias: bool = Field(default=False, description="Whether or not to antialias (applied in bilinear and bicubic modes only)") def invoke(self, context: InvocationContext) -> LatentsOutput: latents = context.services.latents.get(self.latents.latents_name) @@ -400,7 +417,7 @@ class ResizeLatentsInvocation(BaseInvocation): name = f"{context.graph_execution_state_id}__{self.id}" context.services.latents.set(name, resized_latents) - return LatentsOutput(latents=LatentsField(latents_name=name)) + return build_latents_output(latents_name=name, latents=resized_latents) class ScaleLatentsInvocation(BaseInvocation): @@ -409,10 +426,10 @@ class ScaleLatentsInvocation(BaseInvocation): type: Literal["lscale"] = "lscale" # Inputs - latents: Optional[LatentsField] = Field(description="The latents to scale") - scale_factor: float = Field(gt=0, description="The factor by which to scale the latents") - mode: Optional[LATENTS_INTERPOLATION_MODE] = Field(default="bilinear", description="The interpolation mode") - antialias: Optional[bool] = Field(default=False, description="Whether or not to antialias (applied in bilinear and bicubic modes only)") + latents: Optional[LatentsField] = Field(description="The latents to scale") + scale_factor: float = Field(gt=0, description="The factor by which to scale the latents") + mode: LATENTS_INTERPOLATION_MODE = Field(default="bilinear", description="The interpolation mode") + antialias: bool = Field(default=False, description="Whether or not to antialias (applied in bilinear and bicubic modes only)") def invoke(self, context: InvocationContext) -> LatentsOutput: latents = context.services.latents.get(self.latents.latents_name) @@ -430,7 +447,7 @@ 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)) + return build_latents_output(latents_name=name, latents=resized_latents) class ImageToLatentsInvocation(BaseInvocation): @@ -474,4 +491,4 @@ class ImageToLatentsInvocation(BaseInvocation): name = f"{context.graph_execution_state_id}__{self.id}" context.services.latents.set(name, latents) - return LatentsOutput(latents=LatentsField(latents_name=name)) + return build_latents_output(latents_name=name, latents=latents) From 483f2ccb56aebc4f464c9eeac1f0362c35049255 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 20:31:48 +1000 Subject: [PATCH 080/123] feat(nodes): add RandomIntInvocation just outputs a single random int --- invokeai/app/invocations/math.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index afb0e75377..b7d2bc5af6 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -3,6 +3,9 @@ from typing import Literal from pydantic import BaseModel, Field +import numpy as np + +from invokeai.app.util.misc import get_random_seed from .baseinvocation import BaseInvocation, BaseInvocationOutput, InvocationContext, InvocationConfig @@ -73,3 +76,12 @@ class DivideInvocation(BaseInvocation, MathInvocationConfig): def invoke(self, context: InvocationContext) -> IntOutput: return IntOutput(a=int(self.a / self.b)) + + +class RandomIntInvocation(BaseInvocation): + """Outputs a single random integer.""" + #fmt: off + type: Literal["rand_int"] = "rand_int" + #fmt: on + def invoke(self, context: InvocationContext) -> IntOutput: + return IntOutput(a=np.random.randint(0, np.iinfo(np.int32).max)) \ No newline at end of file From 06b5800d28667ff085c90beb9088bfbfcdd3e3fa Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Thu, 11 May 2023 20:52:37 +1200 Subject: [PATCH 081/123] Add UniPC Scheduler --- invokeai/app/invocations/latent.py | 32 +++++++++--------- invokeai/backend/args.py | 1 + invokeai/backend/generate.py | 25 +++++++------- invokeai/backend/generator/base.py | 28 ++++++++-------- .../convert_ckpt_to_diffusers.py | 3 ++ .../stable_diffusion/diffusers_pipeline.py | 33 ++++++++++--------- invokeai/backend/web/modules/parameters.py | 1 + invokeai/frontend/web/src/app/constants.ts | 1 + 8 files changed, 67 insertions(+), 57 deletions(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index c6ddcb0396..82396e358a 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -54,16 +54,17 @@ class NoiseOutput(BaseInvocationOutput): # TODO: this seems like a hack scheduler_map = dict( - ddim=diffusers.DDIMScheduler, - dpmpp_2=diffusers.DPMSolverMultistepScheduler, - k_dpm_2=diffusers.KDPM2DiscreteScheduler, - k_dpm_2_a=diffusers.KDPM2AncestralDiscreteScheduler, - k_dpmpp_2=diffusers.DPMSolverMultistepScheduler, - k_euler=diffusers.EulerDiscreteScheduler, - k_euler_a=diffusers.EulerAncestralDiscreteScheduler, - k_heun=diffusers.HeunDiscreteScheduler, - k_lms=diffusers.LMSDiscreteScheduler, - plms=diffusers.PNDMScheduler, + ddim=(diffusers.DDIMScheduler, dict()), + dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict()), + k_dpm_2=(diffusers.KDPM2DiscreteScheduler, dict()), + k_dpm_2_a=(diffusers.KDPM2AncestralDiscreteScheduler, dict()), + k_dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict()), + k_euler=(diffusers.EulerDiscreteScheduler, dict()), + k_euler_a=(diffusers.EulerAncestralDiscreteScheduler, dict()), + k_heun=(diffusers.HeunDiscreteScheduler, dict()), + k_lms=(diffusers.LMSDiscreteScheduler, dict()), + plms=(diffusers.PNDMScheduler, dict()), + unipc=(diffusers.UniPCMultistepScheduler, dict(cpu_only=True)) ) @@ -73,8 +74,9 @@ SAMPLER_NAME_VALUES = Literal[ def get_scheduler(scheduler_name:str, model: StableDiffusionGeneratorPipeline)->Scheduler: - scheduler_class = scheduler_map.get(scheduler_name,'ddim') - scheduler = scheduler_class.from_config(model.scheduler.config) + scheduler_class, scheduler_extra_config = scheduler_map.get(scheduler_name,'ddim') + scheduler_config = {**model.scheduler.config, **scheduler_extra_config} + scheduler = scheduler_class.from_config(scheduler_config) # hack copied over from generate.py if not hasattr(scheduler, 'uses_inpainting_model'): scheduler.uses_inpainting_model = lambda: False @@ -293,11 +295,7 @@ class LatentsToLatentsInvocation(TextToLatentsInvocation): latent, device=model.device, dtype=latent.dtype ) - timesteps, _ = model.get_img2img_timesteps( - self.steps, - self.strength, - device=model.device, - ) + timesteps, _ = model.get_img2img_timesteps(self.steps, self.strength) result_latents, result_attention_map_saver = model.latents_from_embeddings( latents=initial_latents, diff --git a/invokeai/backend/args.py b/invokeai/backend/args.py index eb8b396ee0..8cbeea10ad 100644 --- a/invokeai/backend/args.py +++ b/invokeai/backend/args.py @@ -119,6 +119,7 @@ SAMPLER_CHOICES = [ "plms", # diffusers: "pndm", + "unipc" ] PRECISION_CHOICES = [ diff --git a/invokeai/backend/generate.py b/invokeai/backend/generate.py index 4f3df60f1c..237f609b2f 100644 --- a/invokeai/backend/generate.py +++ b/invokeai/backend/generate.py @@ -1049,27 +1049,28 @@ class Generate: # See https://github.com/huggingface/diffusers/issues/277#issuecomment-1371428672 scheduler_map = dict( - ddim=diffusers.DDIMScheduler, - dpmpp_2=diffusers.DPMSolverMultistepScheduler, - k_dpm_2=diffusers.KDPM2DiscreteScheduler, - k_dpm_2_a=diffusers.KDPM2AncestralDiscreteScheduler, + ddim=(diffusers.DDIMScheduler, dict()), + dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict()), + k_dpm_2=(diffusers.KDPM2DiscreteScheduler, dict()), + k_dpm_2_a=(diffusers.KDPM2AncestralDiscreteScheduler, dict()), # DPMSolverMultistepScheduler is technically not `k_` anything, as it is neither # the k-diffusers implementation nor included in EDM (Karras 2022), but we can # provide an alias for compatibility. - k_dpmpp_2=diffusers.DPMSolverMultistepScheduler, - k_euler=diffusers.EulerDiscreteScheduler, - k_euler_a=diffusers.EulerAncestralDiscreteScheduler, - k_heun=diffusers.HeunDiscreteScheduler, - k_lms=diffusers.LMSDiscreteScheduler, - plms=diffusers.PNDMScheduler, + k_dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict()), + k_euler=(diffusers.EulerDiscreteScheduler, dict()), + k_euler_a=(diffusers.EulerAncestralDiscreteScheduler, dict()), + k_heun=(diffusers.HeunDiscreteScheduler, dict()), + k_lms=(diffusers.LMSDiscreteScheduler, dict()), + plms=(diffusers.PNDMScheduler, dict()), + unipc=(diffusers.UniPCMultistepScheduler, dict(cpu_only=True)) ) if self.sampler_name in scheduler_map: - sampler_class = scheduler_map[self.sampler_name] + sampler_class, sampler_extra_config = scheduler_map[self.sampler_name] msg = ( f"Setting Sampler to {self.sampler_name} ({sampler_class.__name__})" ) - self.sampler = sampler_class.from_config(self.model.scheduler.config) + self.sampler = sampler_class.from_config({**self.model.scheduler.config, **sampler_extra_config}) else: msg = ( f" Unsupported Sampler: {self.sampler_name} "+ diff --git a/invokeai/backend/generator/base.py b/invokeai/backend/generator/base.py index 9887434e90..f898d18345 100644 --- a/invokeai/backend/generator/base.py +++ b/invokeai/backend/generator/base.py @@ -72,17 +72,18 @@ class InvokeAIGeneratorOutput: # old code that calls Generate will continue to work. class InvokeAIGenerator(metaclass=ABCMeta): scheduler_map = dict( - ddim=diffusers.DDIMScheduler, - dpmpp_2=diffusers.DPMSolverMultistepScheduler, - k_dpm_2=diffusers.KDPM2DiscreteScheduler, - k_dpm_2_a=diffusers.KDPM2AncestralDiscreteScheduler, - k_dpmpp_2=diffusers.DPMSolverMultistepScheduler, - k_euler=diffusers.EulerDiscreteScheduler, - k_euler_a=diffusers.EulerAncestralDiscreteScheduler, - k_heun=diffusers.HeunDiscreteScheduler, - k_lms=diffusers.LMSDiscreteScheduler, - plms=diffusers.PNDMScheduler, - ) + ddim=(diffusers.DDIMScheduler, dict()), + dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict()), + k_dpm_2=(diffusers.KDPM2DiscreteScheduler, dict()), + k_dpm_2_a=(diffusers.KDPM2AncestralDiscreteScheduler, dict()), + k_dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict()), + k_euler=(diffusers.EulerDiscreteScheduler, dict()), + k_euler_a=(diffusers.EulerAncestralDiscreteScheduler, dict()), + k_heun=(diffusers.HeunDiscreteScheduler, dict()), + k_lms=(diffusers.LMSDiscreteScheduler, dict()), + plms=(diffusers.PNDMScheduler, dict()), + unipc=(diffusers.UniPCMultistepScheduler, dict(cpu_only=True)) + ) def __init__(self, model_info: dict, @@ -181,8 +182,9 @@ class InvokeAIGenerator(metaclass=ABCMeta): return generator_class(model, self.params.precision) def get_scheduler(self, scheduler_name:str, model: StableDiffusionGeneratorPipeline)->Scheduler: - scheduler_class = self.scheduler_map.get(scheduler_name,'ddim') - scheduler = scheduler_class.from_config(model.scheduler.config) + scheduler_class, scheduler_extra_config = self.scheduler_map.get(scheduler_name,'ddim') + scheduler_config = {**model.scheduler.config, **scheduler_extra_config} + scheduler = scheduler_class.from_config(scheduler_config) # hack copied over from generate.py if not hasattr(scheduler, 'uses_inpainting_model'): scheduler.uses_inpainting_model = lambda: False diff --git a/invokeai/backend/model_management/convert_ckpt_to_diffusers.py b/invokeai/backend/model_management/convert_ckpt_to_diffusers.py index 8aec5a01d9..1ebd044d54 100644 --- a/invokeai/backend/model_management/convert_ckpt_to_diffusers.py +++ b/invokeai/backend/model_management/convert_ckpt_to_diffusers.py @@ -47,6 +47,7 @@ from diffusers import ( LDMTextToImagePipeline, LMSDiscreteScheduler, PNDMScheduler, + UniPCMultistepScheduler, StableDiffusionPipeline, UNet2DConditionModel, ) @@ -1209,6 +1210,8 @@ def load_pipeline_from_original_stable_diffusion_ckpt( scheduler = EulerAncestralDiscreteScheduler.from_config(scheduler.config) elif scheduler_type == "dpm": scheduler = DPMSolverMultistepScheduler.from_config(scheduler.config) + elif scheduler_type == 'unipc': + scheduler = UniPCMultistepScheduler.from_config(scheduler.config) elif scheduler_type == "ddim": scheduler = scheduler else: diff --git a/invokeai/backend/stable_diffusion/diffusers_pipeline.py b/invokeai/backend/stable_diffusion/diffusers_pipeline.py index 94ec9da7e8..c8a932b9e9 100644 --- a/invokeai/backend/stable_diffusion/diffusers_pipeline.py +++ b/invokeai/backend/stable_diffusion/diffusers_pipeline.py @@ -509,10 +509,13 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): run_id=None, callback: Callable[[PipelineIntermediateState], None] = None, ) -> tuple[torch.Tensor, Optional[AttentionMapSaver]]: + if self.scheduler.config.get("cpu_only", False): + scheduler_device = torch.device('cpu') + else: + scheduler_device = self._model_group.device_for(self.unet) + if timesteps is None: - self.scheduler.set_timesteps( - num_inference_steps, device=self._model_group.device_for(self.unet) - ) + self.scheduler.set_timesteps(num_inference_steps, device=scheduler_device) timesteps = self.scheduler.timesteps infer_latents_from_embeddings = GeneratorToCallbackinator( self.generate_latents_from_embeddings, PipelineIntermediateState @@ -725,12 +728,8 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): noise: torch.Tensor, run_id=None, callback=None, - ) -> InvokeAIStableDiffusionPipelineOutput: - timesteps, _ = self.get_img2img_timesteps( - num_inference_steps, - strength, - device=self._model_group.device_for(self.unet), - ) + ) -> InvokeAIStableDiffusionPipelineOutput: + timesteps, _ = self.get_img2img_timesteps(num_inference_steps, strength) result_latents, result_attention_maps = self.latents_from_embeddings( latents=initial_latents if strength < 1.0 else torch.zeros_like( initial_latents, device=initial_latents.device, dtype=initial_latents.dtype @@ -756,13 +755,19 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): return self.check_for_safety(output, dtype=conditioning_data.dtype) def get_img2img_timesteps( - self, num_inference_steps: int, strength: float, device + self, num_inference_steps: int, strength: float, device=None ) -> (torch.Tensor, int): img2img_pipeline = StableDiffusionImg2ImgPipeline(**self.components) assert img2img_pipeline.scheduler is self.scheduler - img2img_pipeline.scheduler.set_timesteps(num_inference_steps, device=device) + + if self.scheduler.config.get("cpu_only", False): + scheduler_device = torch.device('cpu') + else: + scheduler_device = self._model_group.device_for(self.unet) + + img2img_pipeline.scheduler.set_timesteps(num_inference_steps, device=scheduler_device) timesteps, adjusted_steps = img2img_pipeline.get_timesteps( - num_inference_steps, strength, device=device + num_inference_steps, strength, device=scheduler_device ) # Workaround for low strength resulting in zero timesteps. # TODO: submit upstream fix for zero-step img2img @@ -796,9 +801,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): if init_image.dim() == 3: init_image = init_image.unsqueeze(0) - timesteps, _ = self.get_img2img_timesteps( - num_inference_steps, strength, device=device - ) + timesteps, _ = self.get_img2img_timesteps(num_inference_steps, strength) # 6. Prepare latent variables # can't quite use upstream StableDiffusionImg2ImgPipeline.prepare_latents diff --git a/invokeai/backend/web/modules/parameters.py b/invokeai/backend/web/modules/parameters.py index 3c9c530dd2..9c06f68097 100644 --- a/invokeai/backend/web/modules/parameters.py +++ b/invokeai/backend/web/modules/parameters.py @@ -15,6 +15,7 @@ SAMPLER_CHOICES = [ "plms", # diffusers: "pndm", + "unipc" ] diff --git a/invokeai/frontend/web/src/app/constants.ts b/invokeai/frontend/web/src/app/constants.ts index 534ca9e29a..2d89ffe31e 100644 --- a/invokeai/frontend/web/src/app/constants.ts +++ b/invokeai/frontend/web/src/app/constants.ts @@ -11,6 +11,7 @@ export const DIFFUSERS_SCHEDULERS: Array = [ 'k_euler', 'k_euler_a', 'k_heun', + 'unipc', ]; // Valid image widths From d1029138d289a2713bb5b47ce2bdf30617342b24 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Thu, 11 May 2023 22:49:28 +1200 Subject: [PATCH 082/123] Default to DDIM if scheduler is missing --- invokeai/app/invocations/latent.py | 2 +- invokeai/backend/generator/base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 82396e358a..cbd39fdb5f 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -74,7 +74,7 @@ SAMPLER_NAME_VALUES = Literal[ def get_scheduler(scheduler_name:str, model: StableDiffusionGeneratorPipeline)->Scheduler: - scheduler_class, scheduler_extra_config = scheduler_map.get(scheduler_name,'ddim') + scheduler_class, scheduler_extra_config = scheduler_map.get(scheduler_name, scheduler_map['ddim']) scheduler_config = {**model.scheduler.config, **scheduler_extra_config} scheduler = scheduler_class.from_config(scheduler_config) # hack copied over from generate.py diff --git a/invokeai/backend/generator/base.py b/invokeai/backend/generator/base.py index f898d18345..f286ad0ad7 100644 --- a/invokeai/backend/generator/base.py +++ b/invokeai/backend/generator/base.py @@ -182,7 +182,7 @@ class InvokeAIGenerator(metaclass=ABCMeta): return generator_class(model, self.params.precision) def get_scheduler(self, scheduler_name:str, model: StableDiffusionGeneratorPipeline)->Scheduler: - scheduler_class, scheduler_extra_config = self.scheduler_map.get(scheduler_name,'ddim') + scheduler_class, scheduler_extra_config = self.scheduler_map.get(scheduler_name, self.scheduler_map['ddim']) scheduler_config = {**model.scheduler.config, **scheduler_extra_config} scheduler = scheduler_class.from_config(scheduler_config) # hack copied over from generate.py From 6cf308004a38dd20073bee22cdb65434c1e8f7d8 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 20:54:28 +1000 Subject: [PATCH 083/123] fix(nodes): remove Optionals on ImageOutputs --- invokeai/app/invocations/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/app/invocations/image.py b/invokeai/app/invocations/image.py index d32f96857d..8b4163c4c6 100644 --- a/invokeai/app/invocations/image.py +++ b/invokeai/app/invocations/image.py @@ -33,8 +33,8 @@ class ImageOutput(BaseInvocationOutput): # fmt: off type: Literal["image"] = "image" image: ImageField = Field(default=None, description="The output image") - width: Optional[int] = Field(default=None, description="The width of the image in pixels") - height: Optional[int] = Field(default=None, description="The height of the image in pixels") + width: int = Field(description="The width of the image in pixels") + height: int = Field(description="The height of the image in pixels") # fmt: on class Config: From f9384be59b80ddb75f8cd9ddb7f822f92a2cdd51 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 13:49:07 +1000 Subject: [PATCH 084/123] fix(ui): fix init image causing overflow --- .../components/CurrentImagePreview.tsx | 10 ++- .../ImageToImage/InitialImagePreview.tsx | 66 ++++++++----------- 2 files changed, 33 insertions(+), 43 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index a0fbd7c5d1..18fe166cd8 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, Skeleton, useBoolean } from '@chakra-ui/react'; +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'; @@ -50,8 +50,6 @@ const CurrentImagePreview = () => { } = useAppSelector(imagesSelector); const { getUrl } = useGetUrl(); - const [isLoaded, { on, off }] = useBoolean(); - const handleDragStart = useCallback( (e: DragEvent) => { if (!image) { @@ -67,11 +65,11 @@ const CurrentImagePreview = () => { return ( {progressImage && shouldShowProgressInViewer ? ( 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 fbb833a14a..757957934e 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 @@ -62,48 +62,40 @@ const InitialImagePreview = () => { sx={{ width: 'full', height: 'full', + position: 'relative', alignItems: 'center', justifyContent: 'center', - position: 'relative', }} onDrop={handleDrop} > - - {initialImage?.url && ( - <> - { - setIsLoaded(true); - }} - fallback={ - - - - } - /> - {isLoaded && } - - )} - {!initialImage?.url && } - + {initialImage?.url && ( + <> + { + setIsLoaded(true); + }} + fallback={ + + + + } + /> + {isLoaded && } + + )} + {!initialImage?.url && } ); }; From 3ffff023b2d63377397b4f6114cd0e209c48f179 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 12 May 2023 00:07:36 +1200 Subject: [PATCH 085/123] Add missing key to scheduler_map It was breaking coz the sampler was not being reset. So needs a key on each. Will simplify this later. --- invokeai/app/invocations/latent.py | 20 ++++++++++---------- invokeai/backend/generate.py | 20 ++++++++++---------- invokeai/backend/generator/base.py | 20 ++++++++++---------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index cbd39fdb5f..da0e6c0354 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -54,16 +54,16 @@ class NoiseOutput(BaseInvocationOutput): # TODO: this seems like a hack scheduler_map = dict( - ddim=(diffusers.DDIMScheduler, dict()), - dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict()), - k_dpm_2=(diffusers.KDPM2DiscreteScheduler, dict()), - k_dpm_2_a=(diffusers.KDPM2AncestralDiscreteScheduler, dict()), - k_dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict()), - k_euler=(diffusers.EulerDiscreteScheduler, dict()), - k_euler_a=(diffusers.EulerAncestralDiscreteScheduler, dict()), - k_heun=(diffusers.HeunDiscreteScheduler, dict()), - k_lms=(diffusers.LMSDiscreteScheduler, dict()), - plms=(diffusers.PNDMScheduler, dict()), + ddim=(diffusers.DDIMScheduler, dict(cpu_only=False)), + dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict(cpu_only=False)), + k_dpm_2=(diffusers.KDPM2DiscreteScheduler, dict(cpu_only=False)), + k_dpm_2_a=(diffusers.KDPM2AncestralDiscreteScheduler, dict(cpu_only=False)), + k_dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict(cpu_only=False)), + k_euler=(diffusers.EulerDiscreteScheduler, dict(cpu_only=False)), + k_euler_a=(diffusers.EulerAncestralDiscreteScheduler, dict(cpu_only=False)), + k_heun=(diffusers.HeunDiscreteScheduler, dict(cpu_only=False)), + k_lms=(diffusers.LMSDiscreteScheduler, dict(cpu_only=False)), + plms=(diffusers.PNDMScheduler, dict(cpu_only=False)), unipc=(diffusers.UniPCMultistepScheduler, dict(cpu_only=True)) ) diff --git a/invokeai/backend/generate.py b/invokeai/backend/generate.py index 237f609b2f..c64c0e3320 100644 --- a/invokeai/backend/generate.py +++ b/invokeai/backend/generate.py @@ -1049,19 +1049,19 @@ class Generate: # See https://github.com/huggingface/diffusers/issues/277#issuecomment-1371428672 scheduler_map = dict( - ddim=(diffusers.DDIMScheduler, dict()), - dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict()), - k_dpm_2=(diffusers.KDPM2DiscreteScheduler, dict()), - k_dpm_2_a=(diffusers.KDPM2AncestralDiscreteScheduler, dict()), + ddim=(diffusers.DDIMScheduler, dict(cpu_only=False)), + dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict(cpu_only=False)), + k_dpm_2=(diffusers.KDPM2DiscreteScheduler, dict(cpu_only=False)), + k_dpm_2_a=(diffusers.KDPM2AncestralDiscreteScheduler, dict(cpu_only=False)), # DPMSolverMultistepScheduler is technically not `k_` anything, as it is neither # the k-diffusers implementation nor included in EDM (Karras 2022), but we can # provide an alias for compatibility. - k_dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict()), - k_euler=(diffusers.EulerDiscreteScheduler, dict()), - k_euler_a=(diffusers.EulerAncestralDiscreteScheduler, dict()), - k_heun=(diffusers.HeunDiscreteScheduler, dict()), - k_lms=(diffusers.LMSDiscreteScheduler, dict()), - plms=(diffusers.PNDMScheduler, dict()), + k_dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict(cpu_only=False)), + k_euler=(diffusers.EulerDiscreteScheduler, dict(cpu_only=False)), + k_euler_a=(diffusers.EulerAncestralDiscreteScheduler, dict(cpu_only=False)), + k_heun=(diffusers.HeunDiscreteScheduler, dict(cpu_only=False)), + k_lms=(diffusers.LMSDiscreteScheduler, dict(cpu_only=False)), + plms=(diffusers.PNDMScheduler, dict(cpu_only=False)), unipc=(diffusers.UniPCMultistepScheduler, dict(cpu_only=True)) ) diff --git a/invokeai/backend/generator/base.py b/invokeai/backend/generator/base.py index f286ad0ad7..33a625de30 100644 --- a/invokeai/backend/generator/base.py +++ b/invokeai/backend/generator/base.py @@ -72,16 +72,16 @@ class InvokeAIGeneratorOutput: # old code that calls Generate will continue to work. class InvokeAIGenerator(metaclass=ABCMeta): scheduler_map = dict( - ddim=(diffusers.DDIMScheduler, dict()), - dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict()), - k_dpm_2=(diffusers.KDPM2DiscreteScheduler, dict()), - k_dpm_2_a=(diffusers.KDPM2AncestralDiscreteScheduler, dict()), - k_dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict()), - k_euler=(diffusers.EulerDiscreteScheduler, dict()), - k_euler_a=(diffusers.EulerAncestralDiscreteScheduler, dict()), - k_heun=(diffusers.HeunDiscreteScheduler, dict()), - k_lms=(diffusers.LMSDiscreteScheduler, dict()), - plms=(diffusers.PNDMScheduler, dict()), + ddim=(diffusers.DDIMScheduler, dict(cpu_only=False)), + dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict(cpu_only=False)), + k_dpm_2=(diffusers.KDPM2DiscreteScheduler, dict(cpu_only=False)), + k_dpm_2_a=(diffusers.KDPM2AncestralDiscreteScheduler, dict(cpu_only=False)), + k_dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict(cpu_only=False)), + k_euler=(diffusers.EulerDiscreteScheduler, dict(cpu_only=False)), + k_euler_a=(diffusers.EulerAncestralDiscreteScheduler, dict(cpu_only=False)), + k_heun=(diffusers.HeunDiscreteScheduler, dict(cpu_only=False)), + k_lms=(diffusers.LMSDiscreteScheduler, dict(cpu_only=False)), + plms=(diffusers.PNDMScheduler, dict(cpu_only=False)), unipc=(diffusers.UniPCMultistepScheduler, dict(cpu_only=True)) ) From 3493c8119b725ba25e0580be8d972dfdab93129b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 13:56:51 +1000 Subject: [PATCH 086/123] feat(ui): improve image preview css and fallback --- .../components/CurrentImagePreview.tsx | 16 +++-------- ...eFallback.tsx => ImageFallbackSpinner.tsx} | 6 ++-- .../ImageToImage/InitialImagePreview.tsx | 28 ++++++------------- 3 files changed, 15 insertions(+), 35 deletions(-) rename invokeai/frontend/web/src/features/gallery/components/{CurrentImageFallback.tsx => ImageFallbackSpinner.tsx} (72%) diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index 18fe166cd8..a86407d5c8 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 NextPrevImageButtons from './NextPrevImageButtons'; import CurrentImageHidden from './CurrentImageHidden'; import { DragEvent, memo, useCallback } from 'react'; import { systemSelector } from 'features/system/store/systemSelectors'; -import CurrentImageFallback from './CurrentImageFallback'; +import ImageFallbackSpinner from './ImageFallbackSpinner'; export const imagesSelector = createSelector( [uiSelector, gallerySelector, systemSelector], @@ -90,18 +90,10 @@ const CurrentImagePreview = () => { ) : ( image && ( - ) : ( - - ) - } + fallback={} + onDragStart={handleDragStart} sx={{ objectFit: 'contain', maxWidth: '100%', diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageFallback.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageFallbackSpinner.tsx similarity index 72% rename from invokeai/frontend/web/src/features/gallery/components/CurrentImageFallback.tsx rename to invokeai/frontend/web/src/features/gallery/components/ImageFallbackSpinner.tsx index abcf5d4b18..394ff9db15 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageFallback.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageFallbackSpinner.tsx @@ -1,8 +1,8 @@ import { Flex, Spinner, SpinnerProps } from '@chakra-ui/react'; -type CurrentImageFallbackProps = SpinnerProps; +type ImageFallbackSpinnerProps = SpinnerProps; -const CurrentImageFallback = (props: CurrentImageFallbackProps) => { +const ImageFallbackSpinner = (props: ImageFallbackSpinnerProps) => { const { size = 'xl', ...rest } = props; return ( @@ -21,4 +21,4 @@ const CurrentImageFallback = (props: CurrentImageFallbackProps) => { ); }; -export default CurrentImageFallback; +export default ImageFallbackSpinner; 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 757957934e..8c8a52c857 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,17 +1,18 @@ -import { Flex, Image, Spinner } from '@chakra-ui/react'; +import { Flex, Image } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; 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 { DragEvent, useCallback, useState } from 'react'; +import { DragEvent, useCallback } 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'; +import ImageFallbackSpinner from 'features/gallery/components/ImageFallbackSpinner'; const selector = createSelector( [generationSelector], @@ -30,8 +31,6 @@ const InitialImagePreview = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const [isLoaded, setIsLoaded] = useState(false); - const onError = () => { dispatch( addToast({ @@ -42,13 +41,10 @@ const InitialImagePreview = () => { }) ); dispatch(clearInitialImage()); - setIsLoaded(false); }; const handleDrop = useCallback( (e: DragEvent) => { - setIsLoaded(false); - const name = e.dataTransfer.getData('invokeai/imageName'); const type = e.dataTransfer.getData('invokeai/imageType') as ImageType; @@ -71,6 +67,10 @@ const InitialImagePreview = () => { {initialImage?.url && ( <> } + onError={onError} sx={{ objectFit: 'contain', maxWidth: '100%', @@ -79,20 +79,8 @@ const InitialImagePreview = () => { position: 'absolute', borderRadius: 'base', }} - src={getUrl(initialImage?.url)} - onError={onError} - onLoad={() => { - setIsLoaded(true); - }} - fallback={ - - - - } /> - {isLoaded && } + )} {!initialImage?.url && } From 40d4cabecd5e1644a85bf004e01bd5f46b325a65 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 14:18:35 +1000 Subject: [PATCH 087/123] feat(ui): improve image overlay --- .../components/ImageMetadataOverlay.tsx | 54 +++++++++++++++++++ .../common/components/ImageToImageOverlay.tsx | 37 ------------- .../components/CurrentImagePreview.tsx | 32 ++++++----- .../ImageToImage/InitialImagePreview.tsx | 4 +- 4 files changed, 74 insertions(+), 53 deletions(-) create mode 100644 invokeai/frontend/web/src/common/components/ImageMetadataOverlay.tsx delete mode 100644 invokeai/frontend/web/src/common/components/ImageToImageOverlay.tsx diff --git a/invokeai/frontend/web/src/common/components/ImageMetadataOverlay.tsx b/invokeai/frontend/web/src/common/components/ImageMetadataOverlay.tsx new file mode 100644 index 0000000000..64d5e1beef --- /dev/null +++ b/invokeai/frontend/web/src/common/components/ImageMetadataOverlay.tsx @@ -0,0 +1,54 @@ +import { Badge, Flex } from '@chakra-ui/react'; +import { Image } from 'app/types/invokeai'; +import { isNumber, isString } from 'lodash-es'; +import { useMemo } from 'react'; + +type ImageMetadataOverlayProps = { + image: Image; +}; + +const ImageMetadataOverlay = ({ image }: ImageMetadataOverlayProps) => { + const dimensions = useMemo(() => { + if (!isNumber(image.metadata?.width) || isNumber(!image.metadata?.height)) { + return; + } + + return `${image.metadata?.width} × ${image.metadata?.height}`; + }, [image.metadata]); + + const model = useMemo(() => { + if (!isString(image.metadata?.invokeai?.node?.model)) { + return; + } + + return image.metadata?.invokeai?.node?.model; + }, [image.metadata]); + + return ( + + {dimensions && ( + + {dimensions} + + )} + {model && ( + + {model} + + )} + + ); +}; + +export default ImageMetadataOverlay; diff --git a/invokeai/frontend/web/src/common/components/ImageToImageOverlay.tsx b/invokeai/frontend/web/src/common/components/ImageToImageOverlay.tsx deleted file mode 100644 index 9d864f5c9c..0000000000 --- a/invokeai/frontend/web/src/common/components/ImageToImageOverlay.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Badge, Box, Flex } from '@chakra-ui/react'; -import { Image } from 'app/types/invokeai'; - -type ImageToImageOverlayProps = { - image: Image; -}; - -const ImageToImageOverlay = ({ image }: ImageToImageOverlayProps) => { - return ( - - - - {image.metadata?.width} × {image.metadata?.height} - - - - ); -}; - -export default ImageToImageOverlay; diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index a86407d5c8..b1dbed5a81 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -12,6 +12,7 @@ import CurrentImageHidden from './CurrentImageHidden'; import { DragEvent, memo, useCallback } from 'react'; import { systemSelector } from 'features/system/store/systemSelectors'; import ImageFallbackSpinner from './ImageFallbackSpinner'; +import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; export const imagesSelector = createSelector( [uiSelector, gallerySelector, systemSelector], @@ -89,20 +90,23 @@ const CurrentImagePreview = () => { /> ) : ( image && ( - } - onDragStart={handleDragStart} - sx={{ - objectFit: 'contain', - maxWidth: '100%', - maxHeight: '100%', - height: 'auto', - position: 'absolute', - borderRadius: 'base', - }} - /> + <> + } + onDragStart={handleDragStart} + sx={{ + objectFit: 'contain', + maxWidth: '100%', + maxHeight: '100%', + height: 'auto', + position: 'absolute', + borderRadius: 'base', + }} + /> + + ) )} {shouldShowImageDetails && image && 'metadata' in image && ( 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 8c8a52c857..3de1e1cebb 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 @@ -8,7 +8,7 @@ import { addToast } from 'features/system/store/systemSlice'; import { DragEvent, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { ImageType } from 'services/api'; -import ImageToImageOverlay from 'common/components/ImageToImageOverlay'; +import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { initialImageSelected } from 'features/parameters/store/actions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; @@ -80,7 +80,7 @@ const InitialImagePreview = () => { borderRadius: 'base', }} /> - + )} {!initialImage?.url && } From 15c59e606f677e53a5af8154919c47ff5c89c510 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 18:45:06 +1000 Subject: [PATCH 088/123] feat(ui): add spinner to gallery progress images - otherwise you may think you can click it but you cannot --- .../src/features/gallery/components/GalleryProgressImage.tsx | 4 +++- .../web/src/features/gallery/components/HoverableImage.tsx | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryProgressImage.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryProgressImage.tsx index b812849c44..a2103eb8e2 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryProgressImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryProgressImage.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, Image } from '@chakra-ui/react'; +import { Flex, Image, Spinner } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; @@ -42,6 +42,7 @@ const GalleryProgressImage = () => { alignItems: 'center', justifyContent: 'center', aspectRatio: '1/1', + position: 'relative', }} > { imageRendering: shouldAntialiasProgressImage ? 'auto' : 'pixelated', }} /> + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index 2e5f166025..35ddefe181 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -278,6 +278,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { h: 'full', transition: 'transform 0.2s ease-out', aspectRatio: '1/1', + cursor: 'pointer', }} > Date: Thu, 11 May 2023 18:49:25 +1000 Subject: [PATCH 089/123] fix(ui): fix results not displaying - fix for commercial product --- .../features/gallery/store/gallerySlice.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index 2326295451..81705086b3 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -1,6 +1,7 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import { Image } from 'app/types/invokeai'; +import { imageReceived, thumbnailReceived } from 'services/thunks/image'; type GalleryImageObjectFitType = 'contain' | 'cover'; @@ -63,6 +64,29 @@ export const gallerySlice = createSlice({ state.shouldUseSingleGalleryColumn = action.payload; }, }, + extraReducers(builder) { + builder.addCase(imageReceived.fulfilled, (state, action) => { + // When we get an updated URL for an image, we need to update the selectedImage in gallery, + // which is currently its own object (instead of a reference to an image in results/uploads) + const { imagePath } = action.payload; + const { imageName } = action.meta.arg; + + if (state.selectedImage?.name === imageName) { + state.selectedImage.url = imagePath; + } + }); + + builder.addCase(thumbnailReceived.fulfilled, (state, action) => { + // When we get an updated URL for an image, we need to update the selectedImage in gallery, + // which is currently its own object (instead of a reference to an image in results/uploads) + const { thumbnailPath } = action.payload; + const { thumbnailName } = action.meta.arg; + + if (state.selectedImage?.name === thumbnailName) { + state.selectedImage.thumbnail = thumbnailPath; + } + }); + }, }); export const { From 7f5f4689ccef870d99666647dde9f67a2c09f7d0 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 19:31:08 +1000 Subject: [PATCH 090/123] fix(ui): clear progress image on cancel --- invokeai/frontend/web/src/features/system/store/systemSlice.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 1aeb2a1939..e9cbd21a15 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -418,6 +418,7 @@ export const systemSlice = createSlice({ state.currentStep = 0; state.totalSteps = 0; state.statusTranslationKey = 'common.statusConnected'; + state.progressImage = null; state.toastQueue.push( makeToast({ title: t('toast.canceled'), status: 'warning' }) From 9a383e456dbcdacc6eb6e79c043b2483d597c776 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 12 May 2023 00:40:03 +1200 Subject: [PATCH 091/123] Codesplit SCHEDULER_MAP for reusage --- invokeai/app/invocations/latent.py | 21 +++-------------- invokeai/backend/generate.py | 23 +++---------------- invokeai/backend/generator/base.py | 19 +++------------ .../stable_diffusion/schedulers/__init__.py | 1 + .../stable_diffusion/schedulers/schedulers.py | 17 ++++++++++++++ 5 files changed, 27 insertions(+), 54 deletions(-) create mode 100644 invokeai/backend/stable_diffusion/schedulers/__init__.py create mode 100644 invokeai/backend/stable_diffusion/schedulers/schedulers.py diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 4ce98f97f4..349a13bf3d 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -17,6 +17,7 @@ from ...backend.stable_diffusion.diffusion.shared_invokeai_diffusion import Post from ...backend.image_util.seamless import configure_model_padding 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 ...backend.stable_diffusion.schedulers import SCHEDULER_MAP from .baseinvocation import BaseInvocation, BaseInvocationOutput, InvocationContext, InvocationConfig import numpy as np from ..services.image_storage import ImageType @@ -52,29 +53,13 @@ class NoiseOutput(BaseInvocationOutput): #fmt: on -# TODO: this seems like a hack -scheduler_map = dict( - ddim=(diffusers.DDIMScheduler, dict(cpu_only=False)), - dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict(cpu_only=False)), - k_dpm_2=(diffusers.KDPM2DiscreteScheduler, dict(cpu_only=False)), - k_dpm_2_a=(diffusers.KDPM2AncestralDiscreteScheduler, dict(cpu_only=False)), - k_dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict(cpu_only=False)), - k_euler=(diffusers.EulerDiscreteScheduler, dict(cpu_only=False)), - k_euler_a=(diffusers.EulerAncestralDiscreteScheduler, dict(cpu_only=False)), - k_heun=(diffusers.HeunDiscreteScheduler, dict(cpu_only=False)), - k_lms=(diffusers.LMSDiscreteScheduler, dict(cpu_only=False)), - plms=(diffusers.PNDMScheduler, dict(cpu_only=False)), - unipc=(diffusers.UniPCMultistepScheduler, dict(cpu_only=True)) -) - - SAMPLER_NAME_VALUES = Literal[ - tuple(list(scheduler_map.keys())) + tuple(list(SCHEDULER_MAP.keys())) ] def get_scheduler(scheduler_name:str, model: StableDiffusionGeneratorPipeline)->Scheduler: - scheduler_class, scheduler_extra_config = scheduler_map.get(scheduler_name, scheduler_map['ddim']) + scheduler_class, scheduler_extra_config = SCHEDULER_MAP.get(scheduler_name, SCHEDULER_MAP['ddim']) scheduler_config = {**model.scheduler.config, **scheduler_extra_config} scheduler = scheduler_class.from_config(scheduler_config) # hack copied over from generate.py diff --git a/invokeai/backend/generate.py b/invokeai/backend/generate.py index c64c0e3320..dc5f9d9ed0 100644 --- a/invokeai/backend/generate.py +++ b/invokeai/backend/generate.py @@ -37,6 +37,7 @@ from .safety_checker import SafetyChecker from .prompting import get_uc_and_c_and_ec from .prompting.conditioning import log_tokenization from .stable_diffusion import HuggingFaceConceptsLibrary +from .stable_diffusion.schedulers import SCHEDULER_MAP from .util import choose_precision, choose_torch_device def fix_func(orig): @@ -1047,26 +1048,8 @@ class Generate: def _set_scheduler(self): default = self.model.scheduler - # See https://github.com/huggingface/diffusers/issues/277#issuecomment-1371428672 - scheduler_map = dict( - ddim=(diffusers.DDIMScheduler, dict(cpu_only=False)), - dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict(cpu_only=False)), - k_dpm_2=(diffusers.KDPM2DiscreteScheduler, dict(cpu_only=False)), - k_dpm_2_a=(diffusers.KDPM2AncestralDiscreteScheduler, dict(cpu_only=False)), - # DPMSolverMultistepScheduler is technically not `k_` anything, as it is neither - # the k-diffusers implementation nor included in EDM (Karras 2022), but we can - # provide an alias for compatibility. - k_dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict(cpu_only=False)), - k_euler=(diffusers.EulerDiscreteScheduler, dict(cpu_only=False)), - k_euler_a=(diffusers.EulerAncestralDiscreteScheduler, dict(cpu_only=False)), - k_heun=(diffusers.HeunDiscreteScheduler, dict(cpu_only=False)), - k_lms=(diffusers.LMSDiscreteScheduler, dict(cpu_only=False)), - plms=(diffusers.PNDMScheduler, dict(cpu_only=False)), - unipc=(diffusers.UniPCMultistepScheduler, dict(cpu_only=True)) - ) - - if self.sampler_name in scheduler_map: - sampler_class, sampler_extra_config = scheduler_map[self.sampler_name] + if self.sampler_name in SCHEDULER_MAP: + sampler_class, sampler_extra_config = SCHEDULER_MAP[self.sampler_name] msg = ( f"Setting Sampler to {self.sampler_name} ({sampler_class.__name__})" ) diff --git a/invokeai/backend/generator/base.py b/invokeai/backend/generator/base.py index 33a625de30..317110b35b 100644 --- a/invokeai/backend/generator/base.py +++ b/invokeai/backend/generator/base.py @@ -31,6 +31,7 @@ from ..util.util import rand_perlin_2d from ..safety_checker import SafetyChecker from ..prompting.conditioning import get_uc_and_c_and_ec from ..stable_diffusion.diffusers_pipeline import StableDiffusionGeneratorPipeline +from ..stable_diffusion.schedulers import SCHEDULER_MAP downsampling = 8 @@ -71,20 +72,6 @@ class InvokeAIGeneratorOutput: # we are interposing a wrapper around the original Generator classes so that # old code that calls Generate will continue to work. class InvokeAIGenerator(metaclass=ABCMeta): - scheduler_map = dict( - ddim=(diffusers.DDIMScheduler, dict(cpu_only=False)), - dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict(cpu_only=False)), - k_dpm_2=(diffusers.KDPM2DiscreteScheduler, dict(cpu_only=False)), - k_dpm_2_a=(diffusers.KDPM2AncestralDiscreteScheduler, dict(cpu_only=False)), - k_dpmpp_2=(diffusers.DPMSolverMultistepScheduler, dict(cpu_only=False)), - k_euler=(diffusers.EulerDiscreteScheduler, dict(cpu_only=False)), - k_euler_a=(diffusers.EulerAncestralDiscreteScheduler, dict(cpu_only=False)), - k_heun=(diffusers.HeunDiscreteScheduler, dict(cpu_only=False)), - k_lms=(diffusers.LMSDiscreteScheduler, dict(cpu_only=False)), - plms=(diffusers.PNDMScheduler, dict(cpu_only=False)), - unipc=(diffusers.UniPCMultistepScheduler, dict(cpu_only=True)) - ) - def __init__(self, model_info: dict, params: InvokeAIGeneratorBasicParams=InvokeAIGeneratorBasicParams(), @@ -176,13 +163,13 @@ class InvokeAIGenerator(metaclass=ABCMeta): ''' Return list of all the schedulers that we currently handle. ''' - return list(self.scheduler_map.keys()) + return list(SCHEDULER_MAP.keys()) def load_generator(self, model: StableDiffusionGeneratorPipeline, generator_class: Type[Generator]): return generator_class(model, self.params.precision) def get_scheduler(self, scheduler_name:str, model: StableDiffusionGeneratorPipeline)->Scheduler: - scheduler_class, scheduler_extra_config = self.scheduler_map.get(scheduler_name, self.scheduler_map['ddim']) + scheduler_class, scheduler_extra_config = SCHEDULER_MAP.get(scheduler_name, SCHEDULER_MAP['ddim']) scheduler_config = {**model.scheduler.config, **scheduler_extra_config} scheduler = scheduler_class.from_config(scheduler_config) # hack copied over from generate.py diff --git a/invokeai/backend/stable_diffusion/schedulers/__init__.py b/invokeai/backend/stable_diffusion/schedulers/__init__.py new file mode 100644 index 0000000000..b2df0df231 --- /dev/null +++ b/invokeai/backend/stable_diffusion/schedulers/__init__.py @@ -0,0 +1 @@ +from .schedulers import SCHEDULER_MAP \ No newline at end of file diff --git a/invokeai/backend/stable_diffusion/schedulers/schedulers.py b/invokeai/backend/stable_diffusion/schedulers/schedulers.py new file mode 100644 index 0000000000..00d3675fe5 --- /dev/null +++ b/invokeai/backend/stable_diffusion/schedulers/schedulers.py @@ -0,0 +1,17 @@ +from diffusers import DDIMScheduler, DPMSolverMultistepScheduler, KDPM2DiscreteScheduler, \ + KDPM2AncestralDiscreteScheduler, EulerDiscreteScheduler, EulerAncestralDiscreteScheduler, \ + HeunDiscreteScheduler, LMSDiscreteScheduler, PNDMScheduler, UniPCMultistepScheduler + +SCHEDULER_MAP = dict( + ddim=(DDIMScheduler, dict(cpu_only=False)), + dpmpp_2=(DPMSolverMultistepScheduler, dict(cpu_only=False)), + k_dpm_2=(KDPM2DiscreteScheduler, dict(cpu_only=False)), + k_dpm_2_a=(KDPM2AncestralDiscreteScheduler, dict(cpu_only=False)), + k_dpmpp_2=(DPMSolverMultistepScheduler, dict(cpu_only=False)), + k_euler=(EulerDiscreteScheduler, dict(cpu_only=False)), + k_euler_a=(EulerAncestralDiscreteScheduler, dict(cpu_only=False)), + k_heun=(HeunDiscreteScheduler, dict(cpu_only=False)), + k_lms=(LMSDiscreteScheduler, dict(cpu_only=False)), + plms=(PNDMScheduler, dict(cpu_only=False)), + unipc=(UniPCMultistepScheduler, dict(cpu_only=True)) +) From 9af385468d9bb07f674f5bf6235b06d9a6b6cded Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 22:42:13 +1000 Subject: [PATCH 092/123] feat(ui): expand config options now may disable individual SD features eg Noise, Variation, etc - stuff which is not ready for consumption in commercial. --- .../frontend/web/src/app/types/invokeai.ts | 9 +++--- .../components/CurrentImageButtons.tsx | 17 +++++----- .../gallery/components/HoverableImage.tsx | 13 +++++--- .../Parameters/Hires/ParamHiresCollapse.tsx | 7 +++++ .../Parameters/Noise/ParamNoiseCollapse.tsx | 8 +++++ .../Seamless/ParamSeamlessCollapse.tsx | 7 +++++ .../Symmetry/ParamSymmetryCollapse.tsx | 7 +++++ .../Variations/ParamVariationCollapse.tsx | 7 +++++ .../features/system/hooks/useFeatureStatus.ts | 31 +++++++++++++++---- .../src/features/system/store/configSlice.ts | 1 + 10 files changed, 86 insertions(+), 21 deletions(-) diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 4023f7665d..982509a9ec 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -321,11 +321,11 @@ export type AppFeature = /** * A disable-able Stable Diffusion feature */ -export type StableDiffusionFeature = - | 'noiseConfig' - | 'variations' +export type SDFeature = + | 'noise' + | 'variation' | 'symmetry' - | 'tiling' + | 'seamless' | 'hires'; /** @@ -343,6 +343,7 @@ export type AppConfig = { shouldFetchImages: boolean; disabledTabs: InvokeTabName[]; disabledFeatures: AppFeature[]; + disabledSDFeatures: SDFeature[]; canRestoreDeletedImagesFromBin: boolean; sd: { iterations: { diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index 2a3d12ce91..e76f3fa41e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -152,6 +152,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { } = useAppSelector(currentImageButtonsSelector); const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled; + const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled; const isUpscalingEnabled = useFeatureStatus('upscaling').isFeatureEnabled; const isFaceRestoreEnabled = useFeatureStatus('faceRestore').isFeatureEnabled; @@ -429,13 +430,15 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { > {t('parameters.sendToImg2Img')} - } - > - {t('parameters.sendToUnifiedCanvas')} - + {isCanvasEnabled && ( + } + > + {t('parameters.sendToUnifiedCanvas')} + + )} {/* { const toast = useToast(); const { t } = useTranslation(); - const { isFeatureEnabled: isLightboxEnabled } = useFeatureStatus('lightbox'); + + const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled; + const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled; + const { recallSeed, recallPrompt, recallInitialImage, recallAllParameters } = useParameters(); @@ -250,9 +253,11 @@ const HoverableImage = memo((props: HoverableImageProps) => { > {t('parameters.sendToImg2Img')} - } onClickCapture={handleSendToCanvas}> - {t('parameters.sendToUnifiedCanvas')} - + {isCanvasEnabled && ( + } onClickCapture={handleSendToCanvas}> + {t('parameters.sendToUnifiedCanvas')} + + )} } onClickCapture={onDeleteDialogOpen}> {t('gallery.deleteImage')} 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 9c1f3a3f14..b4b077ad6c 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 @@ -6,6 +6,7 @@ import IAICollapse from 'common/components/IAICollapse'; import { memo } from 'react'; import { ParamHiresStrength } from './ParamHiresStrength'; import { setHiresFix } from 'features/parameters/store/postprocessingSlice'; +import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; const ParamHiresCollapse = () => { const { t } = useTranslation(); @@ -13,10 +14,16 @@ const ParamHiresCollapse = () => { (state: RootState) => state.postprocessing.hiresFix ); + const isHiresEnabled = useFeatureStatus('hires').isFeatureEnabled; + const dispatch = useAppDispatch(); const handleToggle = () => dispatch(setHiresFix(!hiresFix)); + if (!isHiresEnabled) { + return null; + } + return ( { const { t } = useTranslation(); + + const isNoiseEnabled = useFeatureStatus('noise').isFeatureEnabled; + const shouldUseNoiseSettings = useAppSelector( (state: RootState) => state.generation.shouldUseNoiseSettings ); @@ -19,6 +23,10 @@ const ParamNoiseCollapse = () => { const handleToggle = () => dispatch(setShouldUseNoiseSettings(!shouldUseNoiseSettings)); + if (!isNoiseEnabled) { + return null; + } + return ( { const { t } = useTranslation(); const { shouldUseSeamless } = useAppSelector(selector); + const isSeamlessEnabled = useFeatureStatus('seamless').isFeatureEnabled; + const dispatch = useAppDispatch(); const handleToggle = () => dispatch(setSeamless(!shouldUseSeamless)); + if (!isSeamlessEnabled) { + return null; + } + return ( { const { t } = useTranslation(); @@ -15,10 +16,16 @@ const ParamSymmetryCollapse = () => { (state: RootState) => state.generation.shouldUseSymmetry ); + const isSymmetryEnabled = useFeatureStatus('symmetry').isFeatureEnabled; + const dispatch = useAppDispatch(); const handleToggle = () => dispatch(setShouldUseSymmetry(!shouldUseSymmetry)); + if (!isSymmetryEnabled) { + return null; + } + return ( { const { t } = useTranslation(); @@ -14,11 +15,17 @@ const ParamVariationCollapse = () => { (state: RootState) => state.generation.shouldGenerateVariations ); + const isVariationEnabled = useFeatureStatus('variation').isFeatureEnabled; + const dispatch = useAppDispatch(); const handleToggle = () => dispatch(setShouldGenerateVariations(!shouldGenerateVariations)); + if (!isVariationEnabled) { + return null; + } + return ( { + const disabledTabs = useAppSelector( + (state: RootState) => state.config.disabledTabs + ); -export const useFeatureStatus = (feature: AppFeature) => { const disabledFeatures = useAppSelector( (state: RootState) => state.config.disabledFeatures ); + const disabledSDFeatures = useAppSelector( + (state: RootState) => state.config.disabledSDFeatures + ); + const isFeatureDisabled = useMemo( - () => disabledFeatures.includes(feature), - [disabledFeatures, feature] + () => + disabledFeatures.includes(feature as AppFeature) || + disabledSDFeatures.includes(feature as SDFeature) || + disabledTabs.includes(feature as InvokeTabName), + [disabledFeatures, disabledSDFeatures, disabledTabs, feature] ); const isFeatureEnabled = useMemo( - () => !disabledFeatures.includes(feature), - [disabledFeatures, feature] + () => + !( + disabledFeatures.includes(feature as AppFeature) || + disabledSDFeatures.includes(feature as SDFeature) || + disabledTabs.includes(feature as InvokeTabName) + ), + [disabledFeatures, disabledSDFeatures, disabledTabs, feature] ); return { isFeatureDisabled, isFeatureEnabled }; diff --git a/invokeai/frontend/web/src/features/system/store/configSlice.ts b/invokeai/frontend/web/src/features/system/store/configSlice.ts index b773692908..7b3a1b1eea 100644 --- a/invokeai/frontend/web/src/features/system/store/configSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/configSlice.ts @@ -8,6 +8,7 @@ export const initialConfigState: AppConfig = { shouldFetchImages: false, disabledTabs: [], disabledFeatures: [], + disabledSDFeatures: [], canRestoreDeletedImagesFromBin: true, sd: { iterations: { From 799cd071747d98c3121bb2d9d2a22903475a0bf7 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 11 May 2023 22:45:53 +1000 Subject: [PATCH 093/123] feat(ui): make core parameters layout consistent --- .../Parameters/ImageToImage/ImageToImageStrength.tsx | 2 +- .../tabs/ImageToImage/ImageToImageTabCoreParameters.tsx | 4 ++-- .../tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) 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 b467b15091..eea5ec3c27 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 @@ -47,7 +47,7 @@ const ImageToImageStrength = () => { return ( { - - @@ -74,6 +72,8 @@ const ImageToImageTabCoreParameters = () => { + + 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 index cc03ef560d..74949a399d 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx @@ -62,8 +62,6 @@ const UnifiedCanvasCoreParameters = () => { - - @@ -72,8 +70,9 @@ const UnifiedCanvasCoreParameters = () => { + + - )} From 95c36445647f5953331fd74eb5620398eb99ffb7 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 11 May 2023 10:09:01 -0400 Subject: [PATCH 094/123] fix it again --- invokeai/frontend/web/src/i18n.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/i18n.ts b/invokeai/frontend/web/src/i18n.ts index f28365db20..71d4dfb35f 100644 --- a/invokeai/frontend/web/src/i18n.ts +++ b/invokeai/frontend/web/src/i18n.ts @@ -3,7 +3,7 @@ import LanguageDetector from 'i18next-browser-languagedetector'; import Backend from 'i18next-http-backend'; import { initReactI18next } from 'react-i18next'; -import translationEN from '../dist/locales/en.json'; +import translationEN from '../public/locales/en.json'; import { LOCALSTORAGE_PREFIX } from 'app/store/constants'; if (import.meta.env.MODE === 'package') { From 8a836247c82650a2edae75c8d2248d31a31cf7c5 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 12 May 2023 02:23:33 +1200 Subject: [PATCH 095/123] Add DPMPP Single, Euler Karras and DPMPP2 Multi Karras Schedulers --- invokeai/app/invocations/latent.py | 7 +++++- invokeai/backend/args.py | 20 +++++++-------- invokeai/backend/generator/base.py | 7 +++++- .../stable_diffusion/schedulers/schedulers.py | 25 +++++++++++-------- invokeai/backend/web/modules/parameters.py | 20 +++++++-------- invokeai/frontend/web/src/app/constants.ts | 12 +++++---- .../frontend/web/src/app/types/invokeai.ts | 19 ++++++++------ 7 files changed, 64 insertions(+), 46 deletions(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 349a13bf3d..75fa719c07 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -60,8 +60,13 @@ SAMPLER_NAME_VALUES = Literal[ def get_scheduler(scheduler_name:str, model: StableDiffusionGeneratorPipeline)->Scheduler: scheduler_class, scheduler_extra_config = SCHEDULER_MAP.get(scheduler_name, SCHEDULER_MAP['ddim']) - scheduler_config = {**model.scheduler.config, **scheduler_extra_config} + + scheduler_config = model.scheduler.config + if "_backup" in scheduler_config: + scheduler_config = scheduler_config["_backup"] + scheduler_config = {**scheduler_config, **scheduler_extra_config, "_backup": scheduler_config} scheduler = scheduler_class.from_config(scheduler_config) + # hack copied over from generate.py if not hasattr(scheduler, 'uses_inpainting_model'): scheduler.uses_inpainting_model = lambda: False diff --git a/invokeai/backend/args.py b/invokeai/backend/args.py index 8cbeea10ad..0efaf6fd26 100644 --- a/invokeai/backend/args.py +++ b/invokeai/backend/args.py @@ -108,18 +108,18 @@ APP_VERSION = invokeai.version.__version__ SAMPLER_CHOICES = [ "ddim", - "k_dpm_2_a", - "k_dpm_2", - "k_dpmpp_2_a", - "k_dpmpp_2", - "k_euler_a", - "k_euler", - "k_heun", "k_lms", "plms", - # diffusers: - "pndm", - "unipc" + "k_heun", + "k_euler", + "euler_karras", + "k_euler_a", + "k_dpm_2", + "k_dpm_2_a", + "dpmpp_2s", + "k_dpmpp_2", + "k_dpmpp_2_karras", + "unipc", ] PRECISION_CHOICES = [ diff --git a/invokeai/backend/generator/base.py b/invokeai/backend/generator/base.py index 317110b35b..8f5b1a8395 100644 --- a/invokeai/backend/generator/base.py +++ b/invokeai/backend/generator/base.py @@ -170,8 +170,13 @@ class InvokeAIGenerator(metaclass=ABCMeta): def get_scheduler(self, scheduler_name:str, model: StableDiffusionGeneratorPipeline)->Scheduler: scheduler_class, scheduler_extra_config = SCHEDULER_MAP.get(scheduler_name, SCHEDULER_MAP['ddim']) - scheduler_config = {**model.scheduler.config, **scheduler_extra_config} + + scheduler_config = model.scheduler.config + if "_backup" in scheduler_config: + scheduler_config = scheduler_config["_backup"] + scheduler_config = {**scheduler_config, **scheduler_extra_config, "_backup": scheduler_config} scheduler = scheduler_class.from_config(scheduler_config) + # hack copied over from generate.py if not hasattr(scheduler, 'uses_inpainting_model'): scheduler.uses_inpainting_model = lambda: False diff --git a/invokeai/backend/stable_diffusion/schedulers/schedulers.py b/invokeai/backend/stable_diffusion/schedulers/schedulers.py index 00d3675fe5..98d73ae10b 100644 --- a/invokeai/backend/stable_diffusion/schedulers/schedulers.py +++ b/invokeai/backend/stable_diffusion/schedulers/schedulers.py @@ -1,17 +1,20 @@ from diffusers import DDIMScheduler, DPMSolverMultistepScheduler, KDPM2DiscreteScheduler, \ KDPM2AncestralDiscreteScheduler, EulerDiscreteScheduler, EulerAncestralDiscreteScheduler, \ - HeunDiscreteScheduler, LMSDiscreteScheduler, PNDMScheduler, UniPCMultistepScheduler + HeunDiscreteScheduler, LMSDiscreteScheduler, PNDMScheduler, UniPCMultistepScheduler, \ + DPMSolverSinglestepScheduler SCHEDULER_MAP = dict( - ddim=(DDIMScheduler, dict(cpu_only=False)), - dpmpp_2=(DPMSolverMultistepScheduler, dict(cpu_only=False)), - k_dpm_2=(KDPM2DiscreteScheduler, dict(cpu_only=False)), - k_dpm_2_a=(KDPM2AncestralDiscreteScheduler, dict(cpu_only=False)), - k_dpmpp_2=(DPMSolverMultistepScheduler, dict(cpu_only=False)), - k_euler=(EulerDiscreteScheduler, dict(cpu_only=False)), - k_euler_a=(EulerAncestralDiscreteScheduler, dict(cpu_only=False)), - k_heun=(HeunDiscreteScheduler, dict(cpu_only=False)), - k_lms=(LMSDiscreteScheduler, dict(cpu_only=False)), - plms=(PNDMScheduler, dict(cpu_only=False)), + ddim=(DDIMScheduler, dict()), + k_lms=(LMSDiscreteScheduler, dict()), + plms=(PNDMScheduler, dict()), + k_euler=(EulerDiscreteScheduler, dict(use_karras_sigmas=False)), + euler_karras=(EulerDiscreteScheduler, dict(use_karras_sigmas=True)), + k_euler_a=(EulerAncestralDiscreteScheduler, dict()), + k_dpm_2=(KDPM2DiscreteScheduler, dict()), + k_dpm_2_a=(KDPM2AncestralDiscreteScheduler, dict()), + dpmpp_2s=(DPMSolverSinglestepScheduler, dict()), + k_dpmpp_2=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=False)), + k_dpmpp_2_karras=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=True)), + k_heun=(HeunDiscreteScheduler, dict()), unipc=(UniPCMultistepScheduler, dict(cpu_only=True)) ) diff --git a/invokeai/backend/web/modules/parameters.py b/invokeai/backend/web/modules/parameters.py index 9c06f68097..f25864765b 100644 --- a/invokeai/backend/web/modules/parameters.py +++ b/invokeai/backend/web/modules/parameters.py @@ -4,18 +4,18 @@ from .parse_seed_weights import parse_seed_weights SAMPLER_CHOICES = [ "ddim", - "k_dpm_2_a", - "k_dpm_2", - "k_dpmpp_2_a", - "k_dpmpp_2", - "k_euler_a", - "k_euler", - "k_heun", "k_lms", "plms", - # diffusers: - "pndm", - "unipc" + "k_heun", + "k_euler", + "euler_karras", + "k_euler_a", + "k_dpm_2", + "k_dpm_2_a", + "dpmpp_2s", + "k_dpmpp_2", + "k_dpmpp_2_karras", + "unipc", ] diff --git a/invokeai/frontend/web/src/app/constants.ts b/invokeai/frontend/web/src/app/constants.ts index 2d89ffe31e..d9a606435c 100644 --- a/invokeai/frontend/web/src/app/constants.ts +++ b/invokeai/frontend/web/src/app/constants.ts @@ -2,15 +2,17 @@ export const DIFFUSERS_SCHEDULERS: Array = [ 'ddim', - 'plms', 'k_lms', - 'dpmpp_2', + 'plms', + 'k_heun', + 'k_euler', + 'euler_karras', + 'k_euler_a', 'k_dpm_2', 'k_dpm_2_a', + 'dpmpp_2s', 'k_dpmpp_2', - 'k_euler', - 'k_euler_a', - 'k_heun', + 'k_dpmpp_2_karras', 'unipc', ]; diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 4023f7665d..0d8fe4bbdc 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -47,15 +47,18 @@ export type CommonGeneratedImageMetadata = { postprocessing: null | Array; sampler: | 'ddim' - | 'k_dpm_2_a' - | 'k_dpm_2' - | 'k_dpmpp_2_a' - | 'k_dpmpp_2' - | 'k_euler_a' - | 'k_euler' - | 'k_heun' | 'k_lms' - | 'plms'; + | 'plms' + | 'k_heun' + | 'k_euler' + | 'euler_karras' + | 'k_euler_a' + | 'k_dpm_2' + | 'k_dpm_2_a' + | 'dpmpp_2s' + | 'k_dpmpp_2' + | 'k_dpmpp_2_karras' + | 'unipc'; prompt: Prompt; seed: number; variations: SeedWeights; From b928d7a6e68810b91221e7ee8cb731042cade695 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 12 May 2023 02:59:43 +1200 Subject: [PATCH 096/123] Change scheduler names to be accurate _a = Ancestral _k = Karras --- invokeai/backend/args.py | 20 +++++++++---------- .../stable_diffusion/schedulers/schedulers.py | 20 +++++++++---------- invokeai/backend/web/modules/parameters.py | 20 +++++++++---------- invokeai/frontend/web/src/app/constants.ts | 20 +++++++++---------- .../frontend/web/src/app/types/invokeai.ts | 20 +++++++++---------- 5 files changed, 50 insertions(+), 50 deletions(-) diff --git a/invokeai/backend/args.py b/invokeai/backend/args.py index 0efaf6fd26..0925e19edf 100644 --- a/invokeai/backend/args.py +++ b/invokeai/backend/args.py @@ -108,17 +108,17 @@ APP_VERSION = invokeai.version.__version__ SAMPLER_CHOICES = [ "ddim", - "k_lms", - "plms", - "k_heun", - "k_euler", - "euler_karras", - "k_euler_a", - "k_dpm_2", - "k_dpm_2_a", + "lms", + "pndm", + "heun", + "euler", + "euler_k", + "euler_a", + "kdpm_2", + "kdpm_2_a", "dpmpp_2s", - "k_dpmpp_2", - "k_dpmpp_2_karras", + "dpmpp_2m", + "dpmpp_2m_k", "unipc", ] diff --git a/invokeai/backend/stable_diffusion/schedulers/schedulers.py b/invokeai/backend/stable_diffusion/schedulers/schedulers.py index 98d73ae10b..5df6d045c1 100644 --- a/invokeai/backend/stable_diffusion/schedulers/schedulers.py +++ b/invokeai/backend/stable_diffusion/schedulers/schedulers.py @@ -5,16 +5,16 @@ from diffusers import DDIMScheduler, DPMSolverMultistepScheduler, KDPM2DiscreteS SCHEDULER_MAP = dict( ddim=(DDIMScheduler, dict()), - k_lms=(LMSDiscreteScheduler, dict()), - plms=(PNDMScheduler, dict()), - k_euler=(EulerDiscreteScheduler, dict(use_karras_sigmas=False)), - euler_karras=(EulerDiscreteScheduler, dict(use_karras_sigmas=True)), - k_euler_a=(EulerAncestralDiscreteScheduler, dict()), - k_dpm_2=(KDPM2DiscreteScheduler, dict()), - k_dpm_2_a=(KDPM2AncestralDiscreteScheduler, dict()), + lms=(LMSDiscreteScheduler, dict()), + pndm=(PNDMScheduler, dict()), + heun=(HeunDiscreteScheduler, dict()), + euler=(EulerDiscreteScheduler, dict(use_karras_sigmas=False)), + euler_k=(EulerDiscreteScheduler, dict(use_karras_sigmas=True)), + euler_a=(EulerAncestralDiscreteScheduler, dict()), + kdpm_2=(KDPM2DiscreteScheduler, dict()), + kdpm_2_a=(KDPM2AncestralDiscreteScheduler, dict()), dpmpp_2s=(DPMSolverSinglestepScheduler, dict()), - k_dpmpp_2=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=False)), - k_dpmpp_2_karras=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=True)), - k_heun=(HeunDiscreteScheduler, dict()), + dpmpp_2m=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=False)), + dpmpp_2m_k=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=True)), unipc=(UniPCMultistepScheduler, dict(cpu_only=True)) ) diff --git a/invokeai/backend/web/modules/parameters.py b/invokeai/backend/web/modules/parameters.py index f25864765b..22bc2f353e 100644 --- a/invokeai/backend/web/modules/parameters.py +++ b/invokeai/backend/web/modules/parameters.py @@ -4,17 +4,17 @@ from .parse_seed_weights import parse_seed_weights SAMPLER_CHOICES = [ "ddim", - "k_lms", - "plms", - "k_heun", - "k_euler", - "euler_karras", - "k_euler_a", - "k_dpm_2", - "k_dpm_2_a", + "lms", + "pndm", + "heun", + "euler", + "euler_k", + "euler_a", + "kdpm_2", + "kdpm_2_a", "dpmpp_2s", - "k_dpmpp_2", - "k_dpmpp_2_karras", + "dpmpp_2m", + "dpmpp_2m_k", "unipc", ] diff --git a/invokeai/frontend/web/src/app/constants.ts b/invokeai/frontend/web/src/app/constants.ts index d9a606435c..ab1c313abe 100644 --- a/invokeai/frontend/web/src/app/constants.ts +++ b/invokeai/frontend/web/src/app/constants.ts @@ -2,17 +2,17 @@ export const DIFFUSERS_SCHEDULERS: Array = [ 'ddim', - 'k_lms', - 'plms', - 'k_heun', - 'k_euler', - 'euler_karras', - 'k_euler_a', - 'k_dpm_2', - 'k_dpm_2_a', + 'lms', + 'pndm', + 'heun', + 'euler', + 'euler_k', + 'euler_a', + 'kdpm_2', + 'kdpm_2_a', 'dpmpp_2s', - 'k_dpmpp_2', - 'k_dpmpp_2_karras', + 'dpmpp_2m', + 'dpmpp_2m_k', 'unipc', ]; diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 0d8fe4bbdc..998bb00d47 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -47,17 +47,17 @@ export type CommonGeneratedImageMetadata = { postprocessing: null | Array; sampler: | 'ddim' - | 'k_lms' - | 'plms' - | 'k_heun' - | 'k_euler' - | 'euler_karras' - | 'k_euler_a' - | 'k_dpm_2' - | 'k_dpm_2_a' + | 'lms' + | 'pndm' + | 'heun' + | 'euler' + | 'euler_k' + | 'euler_a' + | 'kdpm_2' + | 'kdpm_2_a' | 'dpmpp_2s' - | 'k_dpmpp_2' - | 'k_dpmpp_2_karras' + | 'dpmpp_2m' + | 'dpmpp_2m_k' | 'unipc'; prompt: Prompt; seed: number; From 46ca7718d9e69f559bdec6cd0f679db5d2fc7bd9 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 12 May 2023 03:10:30 +1200 Subject: [PATCH 097/123] Add DEIS Scheduler --- invokeai/backend/args.py | 1 + invokeai/backend/stable_diffusion/schedulers/schedulers.py | 3 ++- invokeai/backend/web/modules/parameters.py | 1 + invokeai/frontend/web/src/app/constants.ts | 1 + invokeai/frontend/web/src/app/types/invokeai.ts | 1 + 5 files changed, 6 insertions(+), 1 deletion(-) diff --git a/invokeai/backend/args.py b/invokeai/backend/args.py index 0925e19edf..4af9e43f42 100644 --- a/invokeai/backend/args.py +++ b/invokeai/backend/args.py @@ -108,6 +108,7 @@ APP_VERSION = invokeai.version.__version__ SAMPLER_CHOICES = [ "ddim", + "deis", "lms", "pndm", "heun", diff --git a/invokeai/backend/stable_diffusion/schedulers/schedulers.py b/invokeai/backend/stable_diffusion/schedulers/schedulers.py index 5df6d045c1..752e7cdb0c 100644 --- a/invokeai/backend/stable_diffusion/schedulers/schedulers.py +++ b/invokeai/backend/stable_diffusion/schedulers/schedulers.py @@ -1,10 +1,11 @@ from diffusers import DDIMScheduler, DPMSolverMultistepScheduler, KDPM2DiscreteScheduler, \ KDPM2AncestralDiscreteScheduler, EulerDiscreteScheduler, EulerAncestralDiscreteScheduler, \ HeunDiscreteScheduler, LMSDiscreteScheduler, PNDMScheduler, UniPCMultistepScheduler, \ - DPMSolverSinglestepScheduler + DPMSolverSinglestepScheduler, DEISMultistepScheduler SCHEDULER_MAP = dict( ddim=(DDIMScheduler, dict()), + deis=(DEISMultistepScheduler, dict()), lms=(LMSDiscreteScheduler, dict()), pndm=(PNDMScheduler, dict()), heun=(HeunDiscreteScheduler, dict()), diff --git a/invokeai/backend/web/modules/parameters.py b/invokeai/backend/web/modules/parameters.py index 22bc2f353e..940732898d 100644 --- a/invokeai/backend/web/modules/parameters.py +++ b/invokeai/backend/web/modules/parameters.py @@ -4,6 +4,7 @@ from .parse_seed_weights import parse_seed_weights SAMPLER_CHOICES = [ "ddim", + "deis", "lms", "pndm", "heun", diff --git a/invokeai/frontend/web/src/app/constants.ts b/invokeai/frontend/web/src/app/constants.ts index ab1c313abe..f0dc48f6d5 100644 --- a/invokeai/frontend/web/src/app/constants.ts +++ b/invokeai/frontend/web/src/app/constants.ts @@ -2,6 +2,7 @@ export const DIFFUSERS_SCHEDULERS: Array = [ 'ddim', + 'deis', 'lms', 'pndm', 'heun', diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 998bb00d47..2ae221b0a8 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -47,6 +47,7 @@ export type CommonGeneratedImageMetadata = { postprocessing: null | Array; sampler: | 'ddim' + | 'deis' | 'lms' | 'pndm' | 'heun' From 4b957edfecbcfa1545c99d6252ecb5aeaa159d80 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 12 May 2023 03:18:34 +1200 Subject: [PATCH 098/123] Add DDPM Scheduler --- invokeai/backend/args.py | 1 + invokeai/backend/stable_diffusion/schedulers/schedulers.py | 3 ++- invokeai/backend/web/modules/parameters.py | 1 + invokeai/frontend/web/src/app/constants.ts | 1 + invokeai/frontend/web/src/app/types/invokeai.ts | 1 + 5 files changed, 6 insertions(+), 1 deletion(-) diff --git a/invokeai/backend/args.py b/invokeai/backend/args.py index 4af9e43f42..856272c45b 100644 --- a/invokeai/backend/args.py +++ b/invokeai/backend/args.py @@ -108,6 +108,7 @@ APP_VERSION = invokeai.version.__version__ SAMPLER_CHOICES = [ "ddim", + "ddpm", "deis", "lms", "pndm", diff --git a/invokeai/backend/stable_diffusion/schedulers/schedulers.py b/invokeai/backend/stable_diffusion/schedulers/schedulers.py index 752e7cdb0c..fab28aca8c 100644 --- a/invokeai/backend/stable_diffusion/schedulers/schedulers.py +++ b/invokeai/backend/stable_diffusion/schedulers/schedulers.py @@ -1,10 +1,11 @@ from diffusers import DDIMScheduler, DPMSolverMultistepScheduler, KDPM2DiscreteScheduler, \ KDPM2AncestralDiscreteScheduler, EulerDiscreteScheduler, EulerAncestralDiscreteScheduler, \ HeunDiscreteScheduler, LMSDiscreteScheduler, PNDMScheduler, UniPCMultistepScheduler, \ - DPMSolverSinglestepScheduler, DEISMultistepScheduler + DPMSolverSinglestepScheduler, DEISMultistepScheduler, DDPMScheduler SCHEDULER_MAP = dict( ddim=(DDIMScheduler, dict()), + ddpm=(DDPMScheduler, dict()), deis=(DEISMultistepScheduler, dict()), lms=(LMSDiscreteScheduler, dict()), pndm=(PNDMScheduler, dict()), diff --git a/invokeai/backend/web/modules/parameters.py b/invokeai/backend/web/modules/parameters.py index 940732898d..72211857a3 100644 --- a/invokeai/backend/web/modules/parameters.py +++ b/invokeai/backend/web/modules/parameters.py @@ -4,6 +4,7 @@ from .parse_seed_weights import parse_seed_weights SAMPLER_CHOICES = [ "ddim", + "ddpm", "deis", "lms", "pndm", diff --git a/invokeai/frontend/web/src/app/constants.ts b/invokeai/frontend/web/src/app/constants.ts index f0dc48f6d5..a2dc04fd22 100644 --- a/invokeai/frontend/web/src/app/constants.ts +++ b/invokeai/frontend/web/src/app/constants.ts @@ -2,6 +2,7 @@ export const DIFFUSERS_SCHEDULERS: Array = [ 'ddim', + 'ddpm', 'deis', 'lms', 'pndm', diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 2ae221b0a8..b79504d8f2 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -47,6 +47,7 @@ export type CommonGeneratedImageMetadata = { postprocessing: null | Array; sampler: | 'ddim' + | 'ddpm' | 'deis' | 'lms' | 'pndm' From f7dc171c4fb33525919993cc2cb415ba19051170 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 12 May 2023 03:44:20 +1200 Subject: [PATCH 099/123] Rename default schedulers across the app --- invokeai/app/invocations/generate.py | 2 +- invokeai/app/invocations/latent.py | 2 +- invokeai/backend/args.py | 2 +- invokeai/backend/generate.py | 2 +- .../frontend/web/src/features/nodes/examples/iterationGraph.ts | 2 +- .../web/src/features/parameters/store/generationSlice.ts | 2 +- tests/inpainting/original.json | 2 +- tests/nodes/test_png_metadata_service.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/invokeai/app/invocations/generate.py b/invokeai/app/invocations/generate.py index 9a29502048..bc72bbe2b3 100644 --- a/invokeai/app/invocations/generate.py +++ b/invokeai/app/invocations/generate.py @@ -52,7 +52,7 @@ class TextToImageInvocation(BaseInvocation, SDImageInvocation): 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" ) + scheduler: SAMPLER_NAME_VALUES = Field(default="lms", description="The scheduler to use" ) model: str = Field(default="", description="The model to use (currently ignored)") # fmt: on diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 75fa719c07..60c77b5286 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -140,7 +140,7 @@ class TextToLatentsInvocation(BaseInvocation): noise: Optional[LatentsField] = Field(description="The noise to use") 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" ) + scheduler: SAMPLER_NAME_VALUES = Field(default="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'") diff --git a/invokeai/backend/args.py b/invokeai/backend/args.py index 856272c45b..db6fbe08df 100644 --- a/invokeai/backend/args.py +++ b/invokeai/backend/args.py @@ -634,7 +634,7 @@ class Args(object): choices=SAMPLER_CHOICES, metavar="SAMPLER_NAME", help=f'Set the default sampler. Supported samplers: {", ".join(SAMPLER_CHOICES)}', - default="k_lms", + default="lms", ) render_group.add_argument( "--log_tokenization", diff --git a/invokeai/backend/generate.py b/invokeai/backend/generate.py index dc5f9d9ed0..3db987bca8 100644 --- a/invokeai/backend/generate.py +++ b/invokeai/backend/generate.py @@ -142,7 +142,7 @@ class Generate: model=None, conf="configs/models.yaml", embedding_path=None, - sampler_name="k_lms", + sampler_name="lms", ddim_eta=0.0, # deterministic full_precision=False, precision="auto", diff --git a/invokeai/frontend/web/src/features/nodes/examples/iterationGraph.ts b/invokeai/frontend/web/src/features/nodes/examples/iterationGraph.ts index 46ee5289b2..3a5b6fb888 100644 --- a/invokeai/frontend/web/src/features/nodes/examples/iterationGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/examples/iterationGraph.ts @@ -20,7 +20,7 @@ export const iterationGraph = { model: '', progress_images: false, prompt: 'dog', - sampler_name: 'k_lms', + sampler_name: 'lms', seamless: false, steps: 11, type: 'txt2img', diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index b0adc578a0..5ad0e4973c 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -51,7 +51,7 @@ export const initialGenerationState: GenerationState = { perlin: 0, prompt: '', negativePrompt: '', - sampler: 'k_lms', + sampler: 'lms', seamBlur: 16, seamSize: 96, seamSteps: 30, diff --git a/tests/inpainting/original.json b/tests/inpainting/original.json index f057f6d0dc..1a9320da03 100644 --- a/tests/inpainting/original.json +++ b/tests/inpainting/original.json @@ -23,7 +23,7 @@ ], "threshold": 0, "postprocessing": null, - "sampler": "k_lms", + "sampler": "lms", "variations": [], "type": "txt2img" } diff --git a/tests/nodes/test_png_metadata_service.py b/tests/nodes/test_png_metadata_service.py index c724074518..975e716fa9 100644 --- a/tests/nodes/test_png_metadata_service.py +++ b/tests/nodes/test_png_metadata_service.py @@ -17,7 +17,7 @@ valid_metadata = { "width": 512, "height": 512, "cfg_scale": 7.5, - "scheduler": "k_lms", + "scheduler": "lms", "model": "stable-diffusion-1.5", }, } From 27dc07d95ac9d090eb02d7fc847e3fd687f5c877 Mon Sep 17 00:00:00 2001 From: Sergey Borisov Date: Thu, 11 May 2023 18:49:27 +0300 Subject: [PATCH 100/123] Set zero eta by default(fix ddim scheduler error) --- invokeai/app/invocations/latent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 60c77b5286..825847cf79 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -208,7 +208,7 @@ class TextToLatentsInvocation(BaseInvocation): h_symmetry_time_pct=None,#h_symmetry_time_pct, v_symmetry_time_pct=None#v_symmetry_time_pct, ), - ).add_scheduler_args_if_applicable(model.scheduler, eta=None)#ddim_eta) + ).add_scheduler_args_if_applicable(model.scheduler, eta=0.0)#ddim_eta) return conditioning_data From 97127e560e8732ae25347e6b7dc1e6e1cb0c7d3b Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 12 May 2023 04:51:58 +1200 Subject: [PATCH 101/123] Disable dpmpp_2s in img2img & unifiedCanvas ... until upstream bug is fixed. --- invokeai/frontend/web/src/app/constants.ts | 6 ++++++ .../components/Parameters/Core/ParamSampler.tsx | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/app/constants.ts b/invokeai/frontend/web/src/app/constants.ts index a2dc04fd22..189fbc9dd4 100644 --- a/invokeai/frontend/web/src/app/constants.ts +++ b/invokeai/frontend/web/src/app/constants.ts @@ -18,6 +18,12 @@ export const DIFFUSERS_SCHEDULERS: Array = [ 'unipc', ]; +export const IMG2IMG_DIFFUSERS_SCHEDULERS = DIFFUSERS_SCHEDULERS.filter( + (scheduler) => { + return scheduler !== 'dpmpp_2s'; + } +); + // Valid image widths export const WIDTHS: Array = Array.from(Array(64)).map( (_x, i) => (i + 1) * 64 diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSampler.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSampler.tsx index 5a20f54438..9bd22d9abe 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSampler.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSampler.tsx @@ -1,8 +1,12 @@ -import { DIFFUSERS_SCHEDULERS } from 'app/constants'; +import { + DIFFUSERS_SCHEDULERS, + IMG2IMG_DIFFUSERS_SCHEDULERS, +} from 'app/constants'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAISelect from 'common/components/IAISelect'; import { setSampler } from 'features/parameters/store/generationSlice'; +import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { ChangeEvent, memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -10,6 +14,9 @@ const ParamSampler = () => { const sampler = useAppSelector( (state: RootState) => state.generation.sampler ); + + const activeTabName = useAppSelector(activeTabNameSelector); + const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -23,7 +30,11 @@ const ParamSampler = () => { label={t('parameters.sampler')} value={sampler} onChange={handleChange} - validValues={DIFFUSERS_SCHEDULERS} + validValues={ + activeTabName === 'img2img' || activeTabName == 'unifiedCanvas' + ? IMG2IMG_DIFFUSERS_SCHEDULERS + : DIFFUSERS_SCHEDULERS + } minWidth={36} /> ); From 6de2f66b50b1be7dd38f93da84de909bf75788ad Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 11:11:59 +1000 Subject: [PATCH 102/123] docs(ui): update ui readme --- invokeai/frontend/web/docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/docs/README.md b/invokeai/frontend/web/docs/README.md index 787725cdda..323dcc5bc7 100644 --- a/invokeai/frontend/web/docs/README.md +++ b/invokeai/frontend/web/docs/README.md @@ -37,7 +37,7 @@ From `invokeai/frontend/web/` run `yarn install` to get everything set up. Start everything in dev mode: 1. Start the dev server: `yarn dev` -2. Start the InvokeAI UI per usual: `invokeai --web` +2. Start the InvokeAI Nodes backend: `python scripts/invokeai-new.py --web # run from the repo root` 3. Point your browser to the dev server address e.g. ### Production builds From 4caa1f19b2e81a3c54e0aa04abf0df73b17cbaa2 Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Thu, 11 May 2023 19:06:02 -0700 Subject: [PATCH 103/123] fix(model manager): fix string formatting error on model checksum timer --- invokeai/backend/model_management/model_manager.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index a0a899a319..4f94395a86 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -30,7 +30,7 @@ from diffusers import ( UNet2DConditionModel, SchedulerMixin, logging as dlogging, -) +) from huggingface_hub import scan_cache_dir from omegaconf import OmegaConf from omegaconf.dictconfig import DictConfig @@ -68,7 +68,7 @@ class SDModelComponent(Enum): scheduler="scheduler" safety_checker="safety_checker" feature_extractor="feature_extractor" - + DEFAULT_MAX_MODELS = 2 class ModelManager(object): @@ -182,7 +182,7 @@ class ModelManager(object): vae from the model currently in the GPU. """ return self._get_sub_model(model_name, SDModelComponent.vae) - + def get_model_tokenizer(self, model_name: str=None)->CLIPTokenizer: """Given a model name identified in models.yaml, load the model into GPU if necessary and return its assigned CLIPTokenizer. If no @@ -190,12 +190,12 @@ class ModelManager(object): currently in the GPU. """ return self._get_sub_model(model_name, SDModelComponent.tokenizer) - + def get_model_unet(self, model_name: str=None)->UNet2DConditionModel: """Given a model name identified in models.yaml, load the model into GPU if necessary and return its assigned UNet2DConditionModel. If no model name is provided, return the UNet from the model - currently in the GPU. + currently in the GPU. """ return self._get_sub_model(model_name, SDModelComponent.unet) @@ -222,7 +222,7 @@ class ModelManager(object): currently in the GPU. """ return self._get_sub_model(model_name, SDModelComponent.scheduler) - + def _get_sub_model( self, model_name: str=None, @@ -1228,7 +1228,7 @@ class ModelManager(object): sha.update(chunk) hash = sha.hexdigest() toc = time.time() - self.logger.debug(f"sha256 = {hash} ({count} files hashed in", "%4.2fs)" % (toc - tic)) + self.logger.debug(f"sha256 = {hash} ({count} files hashed in {toc - tic:4.2f}s)") with open(hashpath, "w") as f: f.write(hash) return hash From ebec200ba6ac8e295d4c22015f1e6dc23421879a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 13:56:02 +1000 Subject: [PATCH 104/123] Remove unused import --- invokeai/app/invocations/math.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index b7d2bc5af6..98f87d2dd4 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -5,8 +5,6 @@ from typing import Literal from pydantic import BaseModel, Field import numpy as np -from invokeai.app.util.misc import get_random_seed - from .baseinvocation import BaseInvocation, BaseInvocationOutput, InvocationContext, InvocationConfig @@ -84,4 +82,4 @@ class RandomIntInvocation(BaseInvocation): type: Literal["rand_int"] = "rand_int" #fmt: on def invoke(self, context: InvocationContext) -> IntOutput: - return IntOutput(a=np.random.randint(0, np.iinfo(np.int32).max)) \ No newline at end of file + return IntOutput(a=np.random.randint(0, np.iinfo(np.int32).max)) From 7dc9d18052294751dece6c9768976a637ddfe483 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 11:48:37 +1000 Subject: [PATCH 105/123] fix(ui): do not show intermediates uploads in gallery --- .../middleware/listenerMiddleware/listeners/imageUploaded.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 index 7d578356f4..c32da2e710 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -6,9 +6,12 @@ import { imageUploaded } from 'services/thunks/image'; export const addImageUploadedListener = () => { startAppListening({ - actionCreator: imageUploaded.fulfilled, + predicate: (action): action is ReturnType => + imageUploaded.fulfilled.match(action) && + action.payload.response.image_type !== 'intermediates', effect: (action, { dispatch, getState }) => { const { response } = action.payload; + const state = getState(); const image = deserializeImageResponse(response); From 65b527eb20914c1ef37e9b91bc09ecc3b7a6f9b4 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 11:50:57 +1000 Subject: [PATCH 106/123] fix(ui): do not show progress images in uploads gallery category --- .../components/ImageGalleryContent.tsx | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 1426aff43d..b37cd5ca62 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -54,11 +54,7 @@ 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'; @@ -71,13 +67,13 @@ const selector = createSelector( const { results, uploads, system, gallery } = state; const { currentCategory } = gallery; - const tempImages: (ImageType | typeof PROGRESS_IMAGE_PLACEHOLDER)[] = []; - - if (system.progressImage) { - tempImages.push(PROGRESS_IMAGE_PLACEHOLDER); - } - if (currentCategory === 'results') { + const tempImages: (ImageType | typeof PROGRESS_IMAGE_PLACEHOLDER)[] = []; + + if (system.progressImage) { + tempImages.push(PROGRESS_IMAGE_PLACEHOLDER); + } + return { images: tempImages.concat( resultsAdapter.getSelectors().selectAll(results) @@ -88,9 +84,7 @@ const selector = createSelector( } return { - images: tempImages.concat( - uploadsAdapter.getSelectors().selectAll(uploads) - ), + images: uploadsAdapter.getSelectors().selectAll(uploads), isLoading: uploads.isLoading, areMoreImagesAvailable: uploads.page < uploads.pages - 1, }; From e4fb9cb33fdbb89336ade1a821250771aaa6b14e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 11:51:24 +1000 Subject: [PATCH 107/123] 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/models/ImageToImageInvocation.ts | 2 +- .../api/models/InfillColorInvocation.ts | 2 +- .../api/models/InfillPatchMatchInvocation.ts | 2 +- .../services/api/models/InpaintInvocation.ts | 2 +- .../api/models/LatentsToLatentsInvocation.ts | 10 ++++++++- .../api/models/TextToImageInvocation.ts | 2 +- .../api/models/TextToLatentsInvocation.ts | 10 ++++++++- .../api/schemas/$DataURLToImageInvocation.ts | 21 ------------------- .../web/src/services/api/schemas/$Graph.ts | 2 -- .../api/schemas/$InfillColorInvocation.ts | 2 +- .../schemas/$InfillPatchMatchInvocation.ts | 2 +- .../schemas/$LatentsToLatentsInvocation.ts | 8 +++++++ .../api/schemas/$TextToLatentsInvocation.ts | 8 +++++++ .../services/api/services/SessionsService.ts | 5 ++--- 17 files changed, 44 insertions(+), 58 deletions(-) delete mode 100644 invokeai/frontend/web/src/services/api/models/DataURLToImageInvocation.ts delete 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 24eeb458b6..0671808245 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -19,7 +19,6 @@ export type { ConditioningField } from './models/ConditioningField'; export type { CreateModelRequest } from './models/CreateModelRequest'; export type { CropImageInvocation } from './models/CropImageInvocation'; export type { CvInpaintInvocation } from './models/CvInpaintInvocation'; -export type { DataURLToImageInvocation } from './models/DataURLToImageInvocation'; export type { DiffusersModelInfo } from './models/DiffusersModelInfo'; export type { DivideInvocation } from './models/DivideInvocation'; export type { Edge } from './models/Edge'; @@ -92,7 +91,6 @@ export { $ConditioningField } from './schemas/$ConditioningField'; 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 deleted file mode 100644 index b1e35d9e0c..0000000000 --- a/invokeai/frontend/web/src/services/api/models/DataURLToImageInvocation.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Outputs an image from a 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 9d56276e62..08d993e477 100644 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ b/invokeai/frontend/web/src/services/api/models/Graph.ts @@ -8,7 +8,6 @@ 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'; import type { DivideInvocation } from './DivideInvocation'; import type { Edge } from './Edge'; import type { GraphInvocation } from './GraphInvocation'; @@ -48,7 +47,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/ImageToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts index 0dfb893213..09479388a7 100644 --- a/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts @@ -40,7 +40,7 @@ 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'; + scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; /** * The model to use (currently ignored) */ diff --git a/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts index a0335eab89..157c976e11 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts @@ -6,7 +6,7 @@ import type { ColorField } from './ColorField'; import type { ImageField } from './ImageField'; /** - * Infills transparent areas of an image with a color + * Infills transparent areas of an image with a solid color */ export type InfillColorInvocation = { /** diff --git a/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts index 6d6c1074a5..a4c18ade5d 100644 --- a/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts @@ -5,7 +5,7 @@ import type { ImageField } from './ImageField'; /** - * Infills transparent areas of an image with tiles of the image + * Infills transparent areas of an image using the PatchMatch algorithm */ export type InfillPatchMatchInvocation = { /** diff --git a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts index c4b125902a..9ef7fcb36e 100644 --- a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts @@ -41,7 +41,7 @@ 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'; + scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; /** * The model to use (currently ignored) */ diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts index 7795ce2b21..425eeaf3d5 100644 --- a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts @@ -37,11 +37,19 @@ 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'; + scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; /** * The model to use (currently ignored) */ model?: string; + /** + * Whether or not to generate an image that can tile without seams + */ + seamless?: boolean; + /** + * The axes to tile the image on, 'x' and/or 'y' + */ + seamless_axes?: string; /** * The latents to use as a base image */ diff --git a/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts index d928515c76..64e072bf4a 100644 --- a/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts @@ -38,7 +38,7 @@ 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'; + scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; /** * The model to use (currently ignored) */ diff --git a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts index a67170d6c8..f939070763 100644 --- a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts @@ -37,10 +37,18 @@ 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'; + scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; /** * The model to use (currently ignored) */ model?: string; + /** + * Whether or not to generate an image that can tile without seams + */ + seamless?: boolean; + /** + * The axes to tile the image on, 'x' and/or 'y' + */ + seamless_axes?: string; }; diff --git a/invokeai/frontend/web/src/services/api/schemas/$DataURLToImageInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$DataURLToImageInvocation.ts deleted file mode 100644 index f875cd0a11..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$DataURLToImageInvocation.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $DataURLToImageInvocation = { - description: `Outputs an image from a 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 f3d5fe0edd..b526def648 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$Graph.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$Graph.ts @@ -15,8 +15,6 @@ export const $Graph = { type: 'LoadImageInvocation', }, { type: 'ShowImageInvocation', - }, { - type: 'DataURLToImageInvocation', }, { type: 'CropImageInvocation', }, { diff --git a/invokeai/frontend/web/src/services/api/schemas/$InfillColorInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$InfillColorInvocation.ts index 42af32c9b2..a4f639f280 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$InfillColorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$InfillColorInvocation.ts @@ -2,7 +2,7 @@ /* tslint:disable */ /* eslint-disable */ export const $InfillColorInvocation = { - description: `Infills transparent areas of an image with a color`, + description: `Infills transparent areas of an image with a solid color`, properties: { id: { type: 'string', diff --git a/invokeai/frontend/web/src/services/api/schemas/$InfillPatchMatchInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$InfillPatchMatchInvocation.ts index 0278dafd35..bc62cb829f 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$InfillPatchMatchInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$InfillPatchMatchInvocation.ts @@ -2,7 +2,7 @@ /* tslint:disable */ /* eslint-disable */ export const $InfillPatchMatchInvocation = { - description: `Infills transparent areas of an image with tiles of the image`, + description: `Infills transparent areas of an image using the PatchMatch algorithm`, properties: { id: { type: 'string', diff --git a/invokeai/frontend/web/src/services/api/schemas/$LatentsToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$LatentsToLatentsInvocation.ts index 38df3ad5cc..47f28bed61 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$LatentsToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$LatentsToLatentsInvocation.ts @@ -48,6 +48,14 @@ export const $LatentsToLatentsInvocation = { type: 'string', description: `The model to use (currently ignored)`, }, + 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'`, + }, latents: { type: 'all-of', description: `The latents to use as a base image`, diff --git a/invokeai/frontend/web/src/services/api/schemas/$TextToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$TextToLatentsInvocation.ts index 1080890606..5ff7b44129 100644 --- a/invokeai/frontend/web/src/services/api/schemas/$TextToLatentsInvocation.ts +++ b/invokeai/frontend/web/src/services/api/schemas/$TextToLatentsInvocation.ts @@ -48,5 +48,13 @@ export const $TextToLatentsInvocation = { type: 'string', description: `The model to use (currently ignored)`, }, + 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'`, + }, }, } 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 694f3822cb..c16e7ecd3d 100644 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ b/invokeai/frontend/web/src/services/api/services/SessionsService.ts @@ -7,7 +7,6 @@ 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'; import type { DivideInvocation } from '../models/DivideInvocation'; import type { Edge } from '../models/Edge'; import type { Graph } from '../models/Graph'; @@ -150,7 +149,7 @@ export class SessionsService { * The id of the session */ sessionId: string, - 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), + requestBody: (LoadImageInvocation | ShowImageInvocation | 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', @@ -187,7 +186,7 @@ export class SessionsService { * The path to the node in the graph */ nodePath: string, - 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), + requestBody: (LoadImageInvocation | ShowImageInvocation | 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 df5ba75c1425a3f6671bc4d13ccf215b35ecb3da Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 17:43:14 +1000 Subject: [PATCH 108/123] feat(ui): use custom dark mode localStorage key --- invokeai/frontend/web/src/app/components/App.tsx | 9 +-------- .../web/src/app/components/ThemeLocaleProvider.tsx | 14 ++++++++++++-- invokeai/frontend/web/src/theme/theme.ts | 2 ++ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index b49e44e554..57d507c89f 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -7,7 +7,7 @@ import useToastWatcher from 'features/system/hooks/useToastWatcher'; 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 { Box, Flex, Grid, Portal } from '@chakra-ui/react'; import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants'; import GalleryDrawer from 'features/gallery/components/ImageGalleryPanel'; import Lightbox from 'features/lightbox/components/Lightbox'; @@ -41,15 +41,12 @@ const App = ({ config = DEFAULT_CONFIG, children }: Props) => { useGlobalHotkeys(); const log = useLogger(); - const currentTheme = useAppSelector((state) => state.ui.currentTheme); - const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled; const isApplicationReady = useIsApplicationReady(); const [loadingOverridden, setLoadingOverridden] = useState(false); - const { setColorMode } = useColorMode(); const dispatch = useAppDispatch(); useEffect(() => { @@ -57,10 +54,6 @@ const App = ({ config = DEFAULT_CONFIG, children }: Props) => { dispatch(configChanged(config)); }, [dispatch, config, log]); - useEffect(() => { - setColorMode(['light'].includes(currentTheme) ? 'light' : 'dark'); - }, [setColorMode, currentTheme]); - const handleOverrideClicked = useCallback(() => { setLoadingOverridden(true); }, []); diff --git a/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx b/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx index f0e2e75240..11f20c1275 100644 --- a/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx +++ b/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx @@ -1,4 +1,8 @@ -import { ChakraProvider, extendTheme } from '@chakra-ui/react'; +import { + ChakraProvider, + createLocalStorageManager, + extendTheme, +} from '@chakra-ui/react'; import { ReactNode, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { theme as invokeAITheme } from 'theme/theme'; @@ -32,6 +36,8 @@ const THEMES = { ocean: oceanBlueColors, }; +const manager = createLocalStorageManager('@@invokeai-color-mode'); + function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) { const { i18n } = useTranslation(); @@ -51,7 +57,11 @@ function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) { document.body.dir = direction; }, [direction]); - return {children}; + return ( + + {children} + + ); } export default ThemeLocaleProvider; diff --git a/invokeai/frontend/web/src/theme/theme.ts b/invokeai/frontend/web/src/theme/theme.ts index c5b127f040..986024aa39 100644 --- a/invokeai/frontend/web/src/theme/theme.ts +++ b/invokeai/frontend/web/src/theme/theme.ts @@ -23,6 +23,8 @@ import { textareaTheme } from './components/textarea'; export const theme: ThemeOverride = { config: { cssVarPrefix: 'invokeai', + initialColorMode: 'dark', + useSystemColorMode: false, }, styles: { global: (_props: StyleFunctionProps) => ({ From da364f34445db8071326fef7ec5a451e6914d69c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 17:44:48 +1000 Subject: [PATCH 109/123] feat(ui): use variable font reduces package build's CSS by an order of magnitude --- .../frontend/web/src/app/components/InvokeAIUI.tsx | 9 --------- .../web/src/app/components/ThemeLocaleProvider.tsx | 11 ++--------- invokeai/frontend/web/src/theme/theme.ts | 2 +- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx index d9d99de6cf..f0303cfc99 100644 --- a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx +++ b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx @@ -2,15 +2,6 @@ import React, { lazy, memo, PropsWithChildren, useEffect } from 'react'; import { Provider } from 'react-redux'; import { store } from 'app/store/store'; import { OpenAPI } from 'services/api'; -import '@fontsource/inter/100.css'; -import '@fontsource/inter/200.css'; -import '@fontsource/inter/300.css'; -import '@fontsource/inter/400.css'; -import '@fontsource/inter/500.css'; -import '@fontsource/inter/600.css'; -import '@fontsource/inter/700.css'; -import '@fontsource/inter/800.css'; -import '@fontsource/inter/900.css'; import Loading from '../../common/components/Loading/Loading'; import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares'; diff --git a/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx b/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx index 11f20c1275..6aa38fc15b 100644 --- a/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx +++ b/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx @@ -13,15 +13,8 @@ import { greenTeaThemeColors } from 'theme/colors/greenTea'; import { invokeAIThemeColors } from 'theme/colors/invokeAI'; import { lightThemeColors } from 'theme/colors/lightTheme'; import { oceanBlueColors } from 'theme/colors/oceanBlue'; -import '@fontsource/inter/100.css'; -import '@fontsource/inter/200.css'; -import '@fontsource/inter/300.css'; -import '@fontsource/inter/400.css'; -import '@fontsource/inter/500.css'; -import '@fontsource/inter/600.css'; -import '@fontsource/inter/700.css'; -import '@fontsource/inter/800.css'; -import '@fontsource/inter/900.css'; + +import '@fontsource/inter/variable.css'; import 'overlayscrollbars/overlayscrollbars.css'; import 'theme/css/overlayscrollbars.css'; diff --git a/invokeai/frontend/web/src/theme/theme.ts b/invokeai/frontend/web/src/theme/theme.ts index 986024aa39..90f26387ab 100644 --- a/invokeai/frontend/web/src/theme/theme.ts +++ b/invokeai/frontend/web/src/theme/theme.ts @@ -41,7 +41,7 @@ export const theme: ThemeOverride = { }, direction: 'ltr', fonts: { - body: `'Inter', sans-serif`, + body: `'InterVariable', sans-serif`, }, breakpoints: { base: '0em', // 0px and onwards From 108ce06c623ea6e16d05e652577f4be7b25a5cb7 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 17:45:56 +1000 Subject: [PATCH 110/123] feat(ui): change custom header to be a prop instead of children --- .../frontend/web/src/app/components/App.tsx | 18 ++++++------------ .../web/src/app/components/InvokeAIUI.tsx | 13 ++++++++++--- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index 57d507c89f..b920698f14 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -11,14 +11,8 @@ import { Box, Flex, Grid, Portal } from '@chakra-ui/react'; import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants'; import GalleryDrawer from 'features/gallery/components/ImageGalleryPanel'; import Lightbox from 'features/lightbox/components/Lightbox'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { - memo, - PropsWithChildren, - useCallback, - useEffect, - useState, -} from 'react'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { memo, ReactNode, useCallback, useEffect, useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import Loading from 'common/components/Loading/Loading'; import { useIsApplicationReady } from 'features/system/hooks/useIsApplicationReady'; @@ -27,16 +21,16 @@ 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 ParametersDrawer from 'features/ui/components/ParametersDrawer'; const DEFAULT_CONFIG = {}; -interface Props extends PropsWithChildren { +interface Props { config?: PartialAppConfig; + headerComponent?: ReactNode; } -const App = ({ config = DEFAULT_CONFIG, children }: Props) => { +const App = ({ config = DEFAULT_CONFIG, headerComponent }: Props) => { useToastWatcher(); useGlobalHotkeys(); const log = useLogger(); @@ -70,7 +64,7 @@ const App = ({ config = DEFAULT_CONFIG, children }: Props) => { w={APP_WIDTH} h={APP_HEIGHT} > - {children || } + {headerComponent || } { +const InvokeAIUI = ({ apiUrl, token, config, headerComponent }: Props) => { useEffect(() => { // configure API client token if (token) { @@ -48,7 +55,7 @@ const InvokeAIUI = ({ apiUrl, token, config, children }: Props) => { }> - {children} + From 14070d674ea1b1458c028c2feecfc67a4b0d88f4 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 17:48:20 +1000 Subject: [PATCH 111/123] build(ui): add style injection plugin when building for package, CSS is all in JS files. when used as a package, it is then injected into the page. bit of a hack to missing CSS in commercial product --- invokeai/frontend/web/config/vite.package.config.ts | 5 ++++- invokeai/frontend/web/tsconfig.json | 8 +------- invokeai/frontend/web/yarn.lock | 5 +++++ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/invokeai/frontend/web/config/vite.package.config.ts b/invokeai/frontend/web/config/vite.package.config.ts index 5865461b06..f87cce0bc9 100644 --- a/invokeai/frontend/web/config/vite.package.config.ts +++ b/invokeai/frontend/web/config/vite.package.config.ts @@ -5,6 +5,7 @@ import { PluginOption, UserConfig } from 'vite'; import dts from 'vite-plugin-dts'; import eslint from 'vite-plugin-eslint'; import tsconfigPaths from 'vite-tsconfig-paths'; +import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'; export const packageConfig: UserConfig = { base: './', @@ -16,9 +17,10 @@ export const packageConfig: UserConfig = { dts({ insertTypesEntry: true, }), + cssInjectedByJsPlugin(), ], build: { - chunkSizeWarningLimit: 1500, + cssCodeSplit: true, lib: { entry: path.resolve(__dirname, '../src/index.ts'), name: 'InvokeAIUI', @@ -30,6 +32,7 @@ export const packageConfig: UserConfig = { globals: { react: 'React', 'react-dom': 'ReactDOM', + '@emotion/react': 'EmotionReact', }, }, }, diff --git a/invokeai/frontend/web/tsconfig.json b/invokeai/frontend/web/tsconfig.json index 3c777b9318..8276f461eb 100644 --- a/invokeai/frontend/web/tsconfig.json +++ b/invokeai/frontend/web/tsconfig.json @@ -20,13 +20,7 @@ "*": ["./src/*"] } }, - "include": [ - "src/**/*.ts", - "src/**/*.tsx", - "*.d.ts", - "src/app/store/middleware/listenerMiddleware", - "src/features/nodes/util/edgeBuilders" - ], + "include": ["src/**/*.ts", "src/**/*.tsx", "*.d.ts"], "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 90bc4055ac..2313cf91c7 100644 --- a/invokeai/frontend/web/yarn.lock +++ b/invokeai/frontend/web/yarn.lock @@ -6673,6 +6673,11 @@ validator@^13.7.0: resolved "https://registry.yarnpkg.com/validator/-/validator-13.9.0.tgz#33e7b85b604f3bbce9bb1a05d5c3e22e1c2ff855" integrity sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA== +vite-plugin-css-injected-by-js@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.1.1.tgz#8324412636cf6fdada1a86f595aa2e78458e5ddb" + integrity sha512-mwrFvEEy0TuH8Ul0cb2HgjmNboQ/JnEFy+kHCWqAJph3ikMOiIuyYVdx0JO4nEIWJyzSnc4TTdmoTulsikvJEg== + vite-plugin-dts@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/vite-plugin-dts/-/vite-plugin-dts-2.3.0.tgz#6ab2edf56f48261bfede03958704bfaee2fca3e4" From 35e0863bdb90138de8999604eb8bdd9ee6fc4f3a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 17:53:59 +1000 Subject: [PATCH 112/123] fix(ui): fix tab icon sizes --- .../frontend/web/src/features/ui/components/InvokeTabs.tsx | 4 ++-- 1 file changed, 2 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 d2bd4f6d51..c346723266 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -45,12 +45,12 @@ export interface InvokeTabInfo { const tabs: InvokeTabInfo[] = [ { id: 'txt2img', - icon: , + icon: , content: , }, { id: 'img2img', - icon: , + icon: , content: , }, { From 032aa1d59ca95b9320ccb863fcc645d48f0edc07 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 18:50:54 +1000 Subject: [PATCH 113/123] fix(ui): excise most `zIndex`s our stacking contexts are accurate, `zIndex` isn't needed --- .../frontend/web/src/features/system/components/ProgressBar.tsx | 1 - .../web/src/features/ui/components/FloatingGalleryButton.tsx | 1 - .../features/ui/components/FloatingParametersPanelButtons.tsx | 1 - .../ui/components/common/ResizableDrawer/ResizableDrawer.tsx | 2 +- 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx b/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx index 03e78965a3..4584bee644 100644 --- a/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx +++ b/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx @@ -36,7 +36,6 @@ const ProgressBar = () => { aria-label={t('accessibility.invokeProgressBar')} isIndeterminate={isProcessing && !currentStatusHasSteps} height={PROGRESS_BAR_THICKNESS} - zIndex={99} /> ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx index bcbcc1cefc..c816c9b39e 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx @@ -44,7 +44,6 @@ const FloatingGalleryButton = () => { pos: 'absolute', top: '50%', transform: 'translate(0, -50%)', - zIndex: 31, p: 0, insetInlineEnd: 0, px: 3, diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx index 95ac1257c0..4f4995ffa7 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx @@ -73,7 +73,6 @@ const FloatingParametersPanelButtons = () => { Date: Fri, 12 May 2023 18:55:59 +1000 Subject: [PATCH 114/123] fix(ui): add missing package --- invokeai/frontend/web/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index 561efdd8e9..13f79f4a44 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -145,6 +145,7 @@ "terser": "^5.17.1", "ts-toolbelt": "^9.6.0", "vite": "^4.3.3", + "vite-plugin-css-injected-by-js": "^3.1.1", "vite-plugin-dts": "^2.3.0", "vite-plugin-eslint": "^1.8.1", "vite-tsconfig-paths": "^4.2.0", From 4d6eea7e81aafb5fcd7acd67951f233b884768f2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 19:35:03 +1000 Subject: [PATCH 115/123] feat(ui): store language in redux --- .../frontend/web/src/app/components/App.tsx | 11 +++- .../system/components/LanguagePicker.tsx | 60 ++++++++++--------- .../features/system/store/systemSelectors.ts | 21 ++++--- .../src/features/system/store/systemSlice.ts | 7 +++ invokeai/frontend/web/src/i18n.ts | 10 ++-- 5 files changed, 64 insertions(+), 45 deletions(-) diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index b920698f14..3fbcbc49ea 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -11,7 +11,7 @@ import { Box, Flex, Grid, Portal } from '@chakra-ui/react'; import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants'; import GalleryDrawer from 'features/gallery/components/ImageGalleryPanel'; import Lightbox from 'features/lightbox/components/Lightbox'; -import { useAppDispatch } from 'app/store/storeHooks'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { memo, ReactNode, useCallback, useEffect, useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import Loading from 'common/components/Loading/Loading'; @@ -22,6 +22,8 @@ import { configChanged } from 'features/system/store/configSlice'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useLogger } from 'app/logging/useLogger'; import ParametersDrawer from 'features/ui/components/ParametersDrawer'; +import { languageSelector } from 'features/system/store/systemSelectors'; +import i18n from 'i18n'; const DEFAULT_CONFIG = {}; @@ -33,6 +35,9 @@ interface Props { const App = ({ config = DEFAULT_CONFIG, headerComponent }: Props) => { useToastWatcher(); useGlobalHotkeys(); + + const language = useAppSelector(languageSelector); + const log = useLogger(); const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled; @@ -43,6 +48,10 @@ const App = ({ config = DEFAULT_CONFIG, headerComponent }: Props) => { const dispatch = useAppDispatch(); + useEffect(() => { + i18n.changeLanguage(language); + }, [language]); + useEffect(() => { log.info({ namespace: 'App', data: config }, 'Received config'); dispatch(configChanged(config)); diff --git a/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx b/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx index c69d4f132b..d1608972f0 100644 --- a/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx +++ b/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx @@ -6,46 +6,50 @@ import IAIIconButton from 'common/components/IAIIconButton'; import IAIPopover from 'common/components/IAIPopover'; import { useTranslation } from 'react-i18next'; import { FaCheck, FaLanguage } from 'react-icons/fa'; +import i18n from 'i18n'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { languageSelector } from '../store/systemSelectors'; +import { languageChanged } from '../store/systemSlice'; + +export const LANGUAGES = { + ar: i18n.t('common.langArabic', { lng: 'ar' }), + nl: i18n.t('common.langDutch', { lng: 'nl' }), + en: i18n.t('common.langEnglish', { lng: 'en' }), + fr: i18n.t('common.langFrench', { lng: 'fr' }), + de: i18n.t('common.langGerman', { lng: 'de' }), + he: i18n.t('common.langHebrew', { lng: 'he' }), + it: i18n.t('common.langItalian', { lng: 'it' }), + ja: i18n.t('common.langJapanese', { lng: 'ja' }), + ko: i18n.t('common.langKorean', { lng: 'ko' }), + pl: i18n.t('common.langPolish', { lng: 'pl' }), + pt_BR: i18n.t('common.langBrPortuguese', { lng: 'pt_BR' }), + pt: i18n.t('common.langPortuguese', { lng: 'pt' }), + ru: i18n.t('common.langRussian', { lng: 'ru' }), + zh_CN: i18n.t('common.langSimplifiedChinese', { lng: 'zh_CN' }), + es: i18n.t('common.langSpanish', { lng: 'es' }), + uk: i18n.t('common.langUkranian', { lng: 'ua' }), +}; export default function LanguagePicker() { - const { t, i18n } = useTranslation(); - const LANGUAGES = { - ar: t('common.langArabic', { lng: 'ar' }), - nl: t('common.langDutch', { lng: 'nl' }), - en: t('common.langEnglish', { lng: 'en' }), - fr: t('common.langFrench', { lng: 'fr' }), - de: t('common.langGerman', { lng: 'de' }), - he: t('common.langHebrew', { lng: 'he' }), - it: t('common.langItalian', { lng: 'it' }), - ja: t('common.langJapanese', { lng: 'ja' }), - ko: t('common.langKorean', { lng: 'ko' }), - pl: t('common.langPolish', { lng: 'pl' }), - pt_BR: t('common.langBrPortuguese', { lng: 'pt_BR' }), - pt: t('common.langPortuguese', { lng: 'pt' }), - ru: t('common.langRussian', { lng: 'ru' }), - zh_CN: t('common.langSimplifiedChinese', { lng: 'zh_CN' }), - es: t('common.langSpanish', { lng: 'es' }), - uk: t('common.langUkranian', { lng: 'ua' }), - }; + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const language = useAppSelector(languageSelector); const renderLanguagePicker = () => { const languagesToRender: ReactNode[] = []; Object.keys(LANGUAGES).forEach((lang) => { + const l = lang as keyof typeof LANGUAGES; languagesToRender.push( - ) : undefined - } - onClick={() => i18n.changeLanguage(lang)} - aria-label={LANGUAGES[lang as keyof typeof LANGUAGES]} + isChecked={language === l} + leftIcon={language === l ? : undefined} + onClick={() => dispatch(languageChanged(l))} + aria-label={LANGUAGES[l]} size="sm" minWidth="200px" > - {LANGUAGES[lang as keyof typeof LANGUAGES]} + {LANGUAGES[l]} ); }); diff --git a/invokeai/frontend/web/src/features/system/store/systemSelectors.ts b/invokeai/frontend/web/src/features/system/store/systemSelectors.ts index 68265aa2dc..d9fd836ece 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSelectors.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSelectors.ts @@ -1,6 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; -import { isEqual, reduce, pickBy } from 'lodash-es'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { reduce, pickBy } from 'lodash-es'; export const systemSelector = (state: RootState) => state.system; @@ -22,11 +23,7 @@ export const activeModelSelector = createSelector( ); return { ...model_list[activeModel], name: activeModel }; }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } + defaultSelectorOptions ); export const diffusersModelsSelector = createSelector( @@ -42,9 +39,11 @@ export const diffusersModelsSelector = createSelector( return diffusersModels; }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } + defaultSelectorOptions +); + +export const languageSelector = createSelector( + systemSelector, + (system) => system.language, + defaultSelectorOptions ); diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index e9cbd21a15..5cc6ca3a43 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -24,6 +24,7 @@ import { InvokeLogLevel } from 'app/logging/useLogger'; import { TFuncKey } from 'i18next'; import { t } from 'i18next'; import { userInvoked } from 'app/store/actions'; +import { LANGUAGES } from '../components/LanguagePicker'; export type CancelStrategy = 'immediate' | 'scheduled'; @@ -91,6 +92,7 @@ export interface SystemState { infillMethods: InfillMethod[]; isPersisted: boolean; shouldAntialiasProgressImage: boolean; + language: keyof typeof LANGUAGES; } export const initialSystemState: SystemState = { @@ -125,6 +127,7 @@ export const initialSystemState: SystemState = { canceledSession: '', infillMethods: ['tile', 'patchmatch'], isPersisted: false, + language: 'en', }; export const systemSlice = createSlice({ @@ -272,6 +275,9 @@ export const systemSlice = createSlice({ isPersistedChanged: (state, action: PayloadAction) => { state.isPersisted = action.payload; }, + languageChanged: (state, action: PayloadAction) => { + state.language = action.payload; + }, }, extraReducers(builder) { /** @@ -481,6 +487,7 @@ export const { shouldLogToConsoleChanged, isPersistedChanged, shouldAntialiasProgressImageChanged, + languageChanged, } = systemSlice.actions; export default systemSlice.reducer; diff --git a/invokeai/frontend/web/src/i18n.ts b/invokeai/frontend/web/src/i18n.ts index 71d4dfb35f..68b457eabe 100644 --- a/invokeai/frontend/web/src/i18n.ts +++ b/invokeai/frontend/web/src/i18n.ts @@ -21,11 +21,11 @@ if (import.meta.env.MODE === 'package') { } else { i18n .use(Backend) - .use( - new LanguageDetector(null, { - lookupLocalStorage: `${LOCALSTORAGE_PREFIX}lng`, - }) - ) + // .use( + // new LanguageDetector(null, { + // lookupLocalStorage: `${LOCALSTORAGE_PREFIX}lng`, + // }) + // ) .use(initReactI18next) .init({ fallbackLng: 'en', From 7d582553f21b6b11d86148a778296467c4206e29 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 19:50:34 +1000 Subject: [PATCH 116/123] feat(ui): use chakra menu for language picker --- .../system/components/LanguagePicker.tsx | 70 ++++++++----------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx b/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx index d1608972f0..e5544c9bb9 100644 --- a/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx +++ b/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx @@ -1,15 +1,19 @@ -import type { ReactNode } from 'react'; - -import { VStack } from '@chakra-ui/react'; -import IAIButton from 'common/components/IAIButton'; +import { + Menu, + MenuButton, + MenuItemOption, + MenuList, + MenuOptionGroup, + Tooltip, +} from '@chakra-ui/react'; import IAIIconButton from 'common/components/IAIIconButton'; -import IAIPopover from 'common/components/IAIPopover'; import { useTranslation } from 'react-i18next'; -import { FaCheck, FaLanguage } from 'react-icons/fa'; +import { FaLanguage } from 'react-icons/fa'; import i18n from 'i18n'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { languageSelector } from '../store/systemSelectors'; import { languageChanged } from '../store/systemSlice'; +import { map } from 'lodash-es'; export const LANGUAGES = { ar: i18n.t('common.langArabic', { lng: 'ar' }), @@ -35,43 +39,29 @@ export default function LanguagePicker() { const dispatch = useAppDispatch(); const language = useAppSelector(languageSelector); - const renderLanguagePicker = () => { - const languagesToRender: ReactNode[] = []; - Object.keys(LANGUAGES).forEach((lang) => { - const l = lang as keyof typeof LANGUAGES; - languagesToRender.push( - : undefined} - onClick={() => dispatch(languageChanged(l))} - aria-label={LANGUAGES[l]} - size="sm" - minWidth="200px" - > - {LANGUAGES[l]} - - ); - }); - - return languagesToRender; - }; - return ( - + + } - size="sm" variant="link" - data-variant="link" - fontSize={26} + aria-label={t('common.languagePickerLabel')} /> - } - > - {renderLanguagePicker()} - + + + {map(LANGUAGES, (languageName, l: keyof typeof LANGUAGES) => ( + dispatch(languageChanged(l))} + > + {languageName} + + ))} + + + + ); } From eebaa50710bcd79496f06ea8eafae7408c8f3b9c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 19:52:21 +1000 Subject: [PATCH 117/123] fix(ui): fix language picker tooltip --- invokeai/frontend/web/public/locales/en.json | 2 +- .../system/components/LanguagePicker.tsx | 34 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index dccb77c267..3592e141d0 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -25,7 +25,7 @@ "common": { "hotkeysLabel": "Hotkeys", "themeLabel": "Theme", - "languagePickerLabel": "Language Picker", + "languagePickerLabel": "Language", "reportBugLabel": "Report Bug", "githubLabel": "Github", "discordLabel": "Discord", diff --git a/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx b/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx index e5544c9bb9..d34ee581ed 100644 --- a/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx +++ b/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx @@ -40,28 +40,28 @@ export default function LanguagePicker() { const language = useAppSelector(languageSelector); return ( - - + + } variant="link" aria-label={t('common.languagePickerLabel')} /> - - - {map(LANGUAGES, (languageName, l: keyof typeof LANGUAGES) => ( - dispatch(languageChanged(l))} - > - {languageName} - - ))} - - - - + + + + {map(LANGUAGES, (languageName, l: keyof typeof LANGUAGES) => ( + dispatch(languageChanged(l))} + > + {languageName} + + ))} + + + ); } From 78cf70eaad86e457de122d049cd604091fc15563 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 20:04:10 +1000 Subject: [PATCH 118/123] fix(ui): tweak lang picker style --- .../src/features/system/components/LanguagePicker.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx b/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx index d34ee581ed..3e4e423c3f 100644 --- a/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx +++ b/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx @@ -1,4 +1,5 @@ import { + IconButton, Menu, MenuButton, MenuItemOption, @@ -6,14 +7,13 @@ import { MenuOptionGroup, Tooltip, } from '@chakra-ui/react'; -import IAIIconButton from 'common/components/IAIIconButton'; import { useTranslation } from 'react-i18next'; -import { FaLanguage } from 'react-icons/fa'; import i18n from 'i18n'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { languageSelector } from '../store/systemSelectors'; import { languageChanged } from '../store/systemSlice'; import { map } from 'lodash-es'; +import { IoLanguage } from 'react-icons/io5'; export const LANGUAGES = { ar: i18n.t('common.langArabic', { lng: 'ar' }), @@ -43,10 +43,12 @@ export default function LanguagePicker() { } + as={IconButton} + icon={} variant="link" aria-label={t('common.languagePickerLabel')} + fontSize={22} + minWidth={8} /> From 60a565d7deebf2ba1e056e1ed020e8919b8619e5 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 12 May 2023 20:04:29 +1000 Subject: [PATCH 119/123] feat(ui): use chakra menu for theme changer --- .../system/components/ThemeChanger.tsx | 91 +++++++++---------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/invokeai/frontend/web/src/features/system/components/ThemeChanger.tsx b/invokeai/frontend/web/src/features/system/components/ThemeChanger.tsx index ff825e9bf0..d9426eecf2 100644 --- a/invokeai/frontend/web/src/features/system/components/ThemeChanger.tsx +++ b/invokeai/frontend/web/src/features/system/components/ThemeChanger.tsx @@ -1,13 +1,26 @@ -import { VStack } from '@chakra-ui/react'; +import { + IconButton, + Menu, + MenuButton, + MenuItemOption, + MenuList, + MenuOptionGroup, + Tooltip, +} from '@chakra-ui/react'; 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 IAIPopover from 'common/components/IAIPopover'; import { setCurrentTheme } from 'features/ui/store/uiSlice'; -import type { ReactNode } from 'react'; +import i18n from 'i18n'; +import { map } from 'lodash-es'; import { useTranslation } from 'react-i18next'; -import { FaCheck, FaPalette } from 'react-icons/fa'; +import { FaPalette } from 'react-icons/fa'; + +export const THEMES = { + dark: i18n.t('common.darkTheme'), + light: i18n.t('common.lightTheme'), + green: i18n.t('common.greenTheme'), + ocean: i18n.t('common.oceanTheme'), +}; export default function ThemeChanger() { const { t } = useTranslation(); @@ -17,51 +30,31 @@ export default function ThemeChanger() { (state: RootState) => state.ui.currentTheme ); - const THEMES = { - dark: t('common.darkTheme'), - light: t('common.lightTheme'), - green: t('common.greenTheme'), - ocean: t('common.oceanTheme'), - }; - - const handleChangeTheme = (theme: string) => { - dispatch(setCurrentTheme(theme)); - }; - - const renderThemeOptions = () => { - const themesToRender: ReactNode[] = []; - - Object.keys(THEMES).forEach((theme) => { - themesToRender.push( - : undefined} - size="sm" - onClick={() => handleChangeTheme(theme)} - key={theme} - > - {THEMES[theme as keyof typeof THEMES]} - - ); - }); - - return themesToRender; - }; - return ( - + + } + variant="link" + aria-label={t('common.themeLabel')} + fontSize={20} + minWidth={8} /> - } - > - {renderThemeOptions()} - + + + + {map(THEMES, (themeName, themeKey: keyof typeof THEMES) => ( + dispatch(setCurrentTheme(themeKey))} + > + {themeName} + + ))} + + + ); } From e5b7dd63e9494ad5ccb74286c23b4196bc226861 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 May 2023 23:39:29 +1000 Subject: [PATCH 120/123] fix(nodes): temporarily disable librarygraphs - Do not retrieve graph from DB until we resolve the issue of changing node schemas causing application to fail to start up due to invalid graphs --- invokeai/app/services/default_graphs.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/invokeai/app/services/default_graphs.py b/invokeai/app/services/default_graphs.py index 0ac6b08b4d..5eda5e957d 100644 --- a/invokeai/app/services/default_graphs.py +++ b/invokeai/app/services/default_graphs.py @@ -48,13 +48,14 @@ def create_text_to_image() -> LibraryGraph: def create_system_graphs(graph_library: ItemStorageABC[LibraryGraph]) -> list[LibraryGraph]: """Creates the default system graphs, or adds new versions if the old ones don't match""" - + + # TODO: Uncomment this when we are ready to fix this up to prevent breaking changes graphs: list[LibraryGraph] = list() # 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: + # # TODO: Check if the graph is the same as the default one, and if not, update it + # #if text_to_image is None: text_to_image = create_text_to_image() graph_library.set(text_to_image) From ad0bb3f61a6f832ef4d770a51e2339f334c52e9b Mon Sep 17 00:00:00 2001 From: Eugene Date: Tue, 9 May 2023 23:52:07 -0400 Subject: [PATCH 121/123] fix: queue error should not crash InvocationProcessor 1. if retrieving an item from the queue raises an exception, the InvocationProcessor thread crashes, but the API continues running in a non-functional state. This fixes the issue 2. when there are no items in the queue, sleep 1 second before checking again. 3. Also ensures the thread isn't crashed if an exception is raised from invoker, and emits the error event Intentionally using base Exceptions because for now we don't know which specific exception to expect. Fixes (sort of)? #3222 --- invokeai/app/services/processor.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/invokeai/app/services/processor.py b/invokeai/app/services/processor.py index 35cbcd5068..5d292af3c9 100644 --- a/invokeai/app/services/processor.py +++ b/invokeai/app/services/processor.py @@ -1,3 +1,4 @@ +import time import traceback from threading import Event, Thread, BoundedSemaphore @@ -6,6 +7,7 @@ from .invocation_queue import InvocationQueueItem from .invoker import InvocationProcessorABC, Invoker from ..models.exceptions import CanceledException +import invokeai.backend.util.logging as logger class DefaultInvocationProcessor(InvocationProcessorABC): __invoker_thread: Thread __stop_event: Event @@ -34,8 +36,14 @@ class DefaultInvocationProcessor(InvocationProcessorABC): try: self.__threadLimit.acquire() while not stop_event.is_set(): - queue_item: InvocationQueueItem = self.__invoker.services.queue.get() + try: + queue_item: InvocationQueueItem = self.__invoker.services.queue.get() + except Exception as e: + logger.debug("Exception while getting from queue: %s" % e) + if not queue_item: # Probably stopping + # do not hammer the queue + time.sleep(1) continue graph_execution_state = ( @@ -124,7 +132,16 @@ class DefaultInvocationProcessor(InvocationProcessorABC): # Queue any further commands if invoking all is_complete = graph_execution_state.is_complete() if queue_item.invoke_all and not is_complete: - self.__invoker.invoke(graph_execution_state, invoke_all=True) + try: + self.__invoker.invoke(graph_execution_state, invoke_all=True) + except Exception as e: + logger.error("Error while invoking: %s" % e) + self.__invoker.services.events.emit_invocation_error( + graph_execution_state_id=graph_execution_state.id, + node=invocation.dict(), + source_node_id=source_node_id, + error=traceback.format_exc() + ) elif is_complete: self.__invoker.services.events.emit_graph_execution_complete( graph_execution_state.id From 63db3fc22fb83b06cc0208d6d1a917700e9e87f5 Mon Sep 17 00:00:00 2001 From: Eugene Brodsky Date: Fri, 12 May 2023 17:45:01 -0400 Subject: [PATCH 122/123] reduce queue check interval to 0.5s --- invokeai/app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/app/services/processor.py b/invokeai/app/services/processor.py index 5d292af3c9..9e3b5a0a30 100644 --- a/invokeai/app/services/processor.py +++ b/invokeai/app/services/processor.py @@ -43,7 +43,7 @@ class DefaultInvocationProcessor(InvocationProcessorABC): if not queue_item: # Probably stopping # do not hammer the queue - time.sleep(1) + time.sleep(0.5) continue graph_execution_state = ( From 47a088d685d1d3cc82482317ea5356a60af077f4 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Fri, 12 May 2023 12:49:59 -0400 Subject: [PATCH 123/123] rehydrate selectedImage URL when results and uploads are fetched --- .../features/gallery/store/gallerySlice.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index 81705086b3..1ae5ee8aee 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -2,6 +2,10 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import { Image } from 'app/types/invokeai'; import { imageReceived, thumbnailReceived } from 'services/thunks/image'; +import { + receivedResultImagesPage, + receivedUploadImagesPage, +} from '../../../services/thunks/gallery'; type GalleryImageObjectFitType = 'contain' | 'cover'; @@ -86,6 +90,30 @@ export const gallerySlice = createSlice({ state.selectedImage.thumbnail = thumbnailPath; } }); + builder.addCase(receivedResultImagesPage.fulfilled, (state, action) => { + // rehydrate selectedImage URL when results list comes in + // solves case when outdated URL is in local storage + if (state.selectedImage) { + const selectedImageInResults = action.payload.items.find( + (image) => image.image_name === state.selectedImage!.name + ); + if (selectedImageInResults) { + state.selectedImage.url = selectedImageInResults.image_url; + } + } + }); + builder.addCase(receivedUploadImagesPage.fulfilled, (state, action) => { + // rehydrate selectedImage URL when results list comes in + // solves case when outdated URL is in local storage + if (state.selectedImage) { + const selectedImageInResults = action.payload.items.find( + (image) => image.image_name === state.selectedImage!.name + ); + if (selectedImageInResults) { + state.selectedImage.url = selectedImageInResults.image_url; + } + } + }); }, });