Merge branch 'main' into lstein/new-model-manager

This commit is contained in:
Lincoln Stein 2023-05-03 13:30:50 -04:00 committed by GitHub
commit 8a0ec0fa0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
289 changed files with 5503 additions and 4716 deletions

View File

@ -46,8 +46,8 @@ class TextToImageInvocation(BaseInvocation, SDImageInvocation):
prompt: Optional[str] = Field(description="The prompt to generate an image from") 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: 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=10, gt=0, description="The number of steps to use to generate the image")
width: int = Field(default=512, multiple_of=64, gt=0, description="The width of the resulting 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=64, gt=0, description="The height 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, 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="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: bool = Field(default=False, description="Whether or not to generate an image that can tile without seams", )
@ -150,6 +150,9 @@ class ImageToImageInvocation(TextToImageInvocation):
) )
mask = None mask = None
if self.fit:
image = image.resize((self.width, self.height))
# Handle invalid model parameter # Handle invalid model parameter
model = choose_model(context.services.model_manager, self.model) model = choose_model(context.services.model_manager, self.model)

View File

@ -113,8 +113,8 @@ class NoiseInvocation(BaseInvocation):
# Inputs # Inputs
seed: int = Field(ge=0, le=np.iinfo(np.uint32).max, description="The seed to use", default_factory=random_seed) seed: int = Field(ge=0, le=np.iinfo(np.uint32).max, description="The seed to use", default_factory=random_seed)
width: int = Field(default=512, multiple_of=64, gt=0, description="The width of the resulting noise", ) width: int = Field(default=512, multiple_of=8, gt=0, description="The width of the resulting noise", )
height: int = Field(default=512, multiple_of=64, gt=0, description="The height of the resulting noise", ) height: int = Field(default=512, multiple_of=8, gt=0, description="The height of the resulting noise", )
# Schema customisation # Schema customisation

View File

@ -23,8 +23,6 @@ def create_text_to_image() -> LibraryGraph:
edges=[ edges=[
Edge(source=EdgeConnection(node_id='width', field='a'), destination=EdgeConnection(node_id='3', field='width')), Edge(source=EdgeConnection(node_id='width', field='a'), destination=EdgeConnection(node_id='3', field='width')),
Edge(source=EdgeConnection(node_id='height', field='a'), destination=EdgeConnection(node_id='3', field='height')), Edge(source=EdgeConnection(node_id='height', field='a'), destination=EdgeConnection(node_id='3', field='height')),
Edge(source=EdgeConnection(node_id='width', field='a'), destination=EdgeConnection(node_id='4', field='width')),
Edge(source=EdgeConnection(node_id='height', field='a'), destination=EdgeConnection(node_id='4', field='height')),
Edge(source=EdgeConnection(node_id='3', field='noise'), destination=EdgeConnection(node_id='4', field='noise')), Edge(source=EdgeConnection(node_id='3', field='noise'), destination=EdgeConnection(node_id='4', field='noise')),
Edge(source=EdgeConnection(node_id='4', field='latents'), destination=EdgeConnection(node_id='5', field='latents')), Edge(source=EdgeConnection(node_id='4', field='latents'), destination=EdgeConnection(node_id='5', field='latents')),
] ]

View File

@ -71,18 +71,12 @@ class Invoker:
for service in vars(self.services): for service in vars(self.services):
self.__start_service(getattr(self.services, service)) self.__start_service(getattr(self.services, service))
for service in vars(self.services):
self.__start_service(getattr(self.services, service))
def stop(self) -> None: def stop(self) -> None:
"""Stops the invoker. A new invoker will have to be created to execute further.""" """Stops the invoker. A new invoker will have to be created to execute further."""
# First stop all services # First stop all services
for service in vars(self.services): for service in vars(self.services):
self.__stop_service(getattr(self.services, service)) self.__stop_service(getattr(self.services, service))
for service in vars(self.services):
self.__stop_service(getattr(self.services, service))
self.services.queue.put(None) self.services.queue.put(None)

View File

@ -1,5 +1,5 @@
import traceback import traceback
from threading import Event, Thread from threading import Event, Thread, BoundedSemaphore
from ..invocations.baseinvocation import InvocationContext from ..invocations.baseinvocation import InvocationContext
from .invocation_queue import InvocationQueueItem from .invocation_queue import InvocationQueueItem
@ -10,8 +10,11 @@ class DefaultInvocationProcessor(InvocationProcessorABC):
__invoker_thread: Thread __invoker_thread: Thread
__stop_event: Event __stop_event: Event
__invoker: Invoker __invoker: Invoker
__threadLimit: BoundedSemaphore
def start(self, invoker) -> None: def start(self, invoker) -> None:
# if we do want multithreading at some point, we could make this configurable
self.__threadLimit = BoundedSemaphore(1)
self.__invoker = invoker self.__invoker = invoker
self.__stop_event = Event() self.__stop_event = Event()
self.__invoker_thread = Thread( self.__invoker_thread = Thread(
@ -20,7 +23,7 @@ class DefaultInvocationProcessor(InvocationProcessorABC):
kwargs=dict(stop_event=self.__stop_event), kwargs=dict(stop_event=self.__stop_event),
) )
self.__invoker_thread.daemon = ( self.__invoker_thread.daemon = (
True # TODO: probably better to just not use threads? True # TODO: make async and do not use threads
) )
self.__invoker_thread.start() self.__invoker_thread.start()
@ -29,6 +32,7 @@ class DefaultInvocationProcessor(InvocationProcessorABC):
def __process(self, stop_event: Event): def __process(self, stop_event: Event):
try: try:
self.__threadLimit.acquire()
while not stop_event.is_set(): while not stop_event.is_set():
queue_item: InvocationQueueItem = self.__invoker.services.queue.get() queue_item: InvocationQueueItem = self.__invoker.services.queue.get()
if not queue_item: # Probably stopping if not queue_item: # Probably stopping
@ -127,4 +131,6 @@ class DefaultInvocationProcessor(InvocationProcessorABC):
) )
except KeyboardInterrupt: except KeyboardInterrupt:
... # Log something? pass # Log something? KeyboardInterrupt is probably not going to be seen by the processor
finally:
self.__threadLimit.release()

View File

@ -0,0 +1,40 @@
import react from '@vitejs/plugin-react-swc';
import { visualizer } from 'rollup-plugin-visualizer';
import { PluginOption, UserConfig } from 'vite';
import eslint from 'vite-plugin-eslint';
import tsconfigPaths from 'vite-tsconfig-paths';
export const appConfig: UserConfig = {
base: './',
plugins: [
react(),
eslint(),
tsconfigPaths(),
visualizer() as unknown as PluginOption,
],
build: {
chunkSizeWarningLimit: 1500,
},
server: {
// Proxy HTTP requests to the flask server
proxy: {
// Proxy socket.io to the nodes socketio server
'/ws/socket.io': {
target: 'ws://127.0.0.1:9090',
ws: true,
},
// Proxy openapi schema definiton
'/openapi.json': {
target: 'http://127.0.0.1:9090/openapi.json',
rewrite: (path) => path.replace(/^\/openapi.json/, ''),
changeOrigin: true,
},
// proxy nodes api
'/api/v1': {
target: 'http://127.0.0.1:9090/api/v1',
rewrite: (path) => path.replace(/^\/api\/v1/, ''),
changeOrigin: true,
},
},
},
};

View File

@ -0,0 +1,47 @@
import react from '@vitejs/plugin-react-swc';
import path from 'path';
import { visualizer } from 'rollup-plugin-visualizer';
import { PluginOption, UserConfig } from 'vite';
import dts from 'vite-plugin-dts';
import eslint from 'vite-plugin-eslint';
import tsconfigPaths from 'vite-tsconfig-paths';
export const packageConfig: UserConfig = {
base: './',
plugins: [
react(),
eslint(),
tsconfigPaths(),
visualizer() as unknown as PluginOption,
dts({
insertTypesEntry: true,
}),
],
build: {
chunkSizeWarningLimit: 1500,
lib: {
entry: path.resolve(__dirname, '../src/index.ts'),
name: 'InvokeAIUI',
fileName: (format) => `invoke-ai-ui.${format}.js`,
},
rollupOptions: {
external: ['react', 'react-dom', '@emotion/react'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
},
},
resolve: {
alias: {
app: path.resolve(__dirname, '../src/app'),
assets: path.resolve(__dirname, '../src/assets'),
common: path.resolve(__dirname, '../src/common'),
features: path.resolve(__dirname, '../src/features'),
services: path.resolve(__dirname, '../src/services'),
theme: path.resolve(__dirname, '../src/theme'),
},
},
};

View File

@ -1,96 +0,0 @@
import React, { PropsWithChildren } from 'react';
import { IAIPopoverProps } from '../web/src/common/components/IAIPopover';
import { IAIIconButtonProps } from '../web/src/common/components/IAIIconButton';
import { InvokeTabName } from 'features/ui/store/tabMap';
import { PartialAppConfig } from 'app/invokeai';
export {};
declare module 'redux-socket.io-middleware';
declare global {
/* eslint-disable @typescript-eslint/no-explicit-any */
interface Array<T> {
/**
* Returns the value of the last element in the array where predicate is true, and undefined
* otherwise.
* @param predicate findLast calls predicate once for each element of the array, in descending
* order, until it finds one where predicate returns true. If such an element is found, findLast
* immediately returns that element value. Otherwise, findLast returns undefined.
* @param thisArg If provided, it will be used as the this value for each invocation of
* predicate. If it is not provided, undefined is used instead.
*/
findLast<S extends T>(
predicate: (value: T, index: number, array: T[]) => value is S,
thisArg?: any
): S | undefined;
findLast(
predicate: (value: T, index: number, array: T[]) => unknown,
thisArg?: any
): T | undefined;
/**
* Returns the index of the last element in the array where predicate is true, and -1
* otherwise.
* @param predicate findLastIndex calls predicate once for each element of the array, in descending
* order, until it finds one where predicate returns true. If such an element is found,
* findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1.
* @param thisArg If provided, it will be used as the this value for each invocation of
* predicate. If it is not provided, undefined is used instead.
*/
findLastIndex(
predicate: (value: T, index: number, array: T[]) => unknown,
thisArg?: any
): number;
}
/* eslint-enable @typescript-eslint/no-explicit-any */
}
declare module '@invoke-ai/invoke-ai-ui' {
declare class ThemeChanger extends React.Component<ThemeChangerProps> {
public constructor(props: ThemeChangerProps);
}
declare class InvokeAiLogoComponent extends React.Component<InvokeAILogoComponentProps> {
public constructor(props: InvokeAILogoComponentProps);
}
declare class IAIPopover extends React.Component<IAIPopoverProps> {
public constructor(props: IAIPopoverProps);
}
declare class IAIIconButton extends React.Component<IAIIconButtonProps> {
public constructor(props: IAIIconButtonProps);
}
declare class SettingsModal extends React.Component<SettingsModalProps> {
public constructor(props: SettingsModalProps);
}
declare class StatusIndicator extends React.Component<StatusIndicatorProps> {
public constructor(props: StatusIndicatorProps);
}
declare class ModelSelect extends React.Component<ModelSelectProps> {
public constructor(props: ModelSelectProps);
}
}
interface InvokeProps extends PropsWithChildren {
apiUrl?: string;
token?: string;
config?: PartialAppConfig;
}
declare function Invoke(props: InvokeProps): JSX.Element;
export {
ThemeChanger,
InvokeAiLogoComponent,
IAIPopover,
IAIIconButton,
SettingsModal,
StatusIndicator,
ModelSelect,
};
export = Invoke;

View File

@ -1,7 +1,23 @@
{ {
"name": "invoke-ai-ui", "name": "@invoke-ai/invoke-ai-ui",
"private": true, "private": true,
"version": "0.0.1", "version": "0.0.1",
"publishConfig": {
"access": "restricted",
"registry": "https://npm.pkg.github.com"
},
"main": "./dist/invoke-ai-ui.umd.js",
"module": "./dist/invoke-ai-ui.es.js",
"exports": {
".": {
"import": "./dist/invoke-ai-ui.es.js",
"require": "./dist/invoke-ai-ui.umd.js"
}
},
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": { "scripts": {
"prepare": "cd ../../../ && husky install invokeai/frontend/web/.husky", "prepare": "cd ../../../ && husky install invokeai/frontend/web/.husky",
"dev": "concurrently \"vite dev\" \"yarn run theme:watch\"", "dev": "concurrently \"vite dev\" \"yarn run theme:watch\"",
@ -40,81 +56,96 @@
}, },
"dependencies": { "dependencies": {
"@chakra-ui/anatomy": "^2.1.1", "@chakra-ui/anatomy": "^2.1.1",
"@chakra-ui/cli": "^2.3.0", "@chakra-ui/icons": "^2.0.19",
"@chakra-ui/icons": "^2.0.17", "@chakra-ui/react": "^2.6.0",
"@chakra-ui/react": "^2.5.1", "@chakra-ui/styled-system": "^2.9.0",
"@chakra-ui/styled-system": "^2.6.1",
"@chakra-ui/theme-tools": "^2.0.16", "@chakra-ui/theme-tools": "^2.0.16",
"@dagrejs/graphlib": "^2.1.12", "@dagrejs/graphlib": "^2.1.12",
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6", "@emotion/styled": "^11.10.6",
"@fontsource/inter": "^4.5.15", "@fontsource/inter": "^4.5.15",
"@reduxjs/toolkit": "^1.9.3", "@reduxjs/toolkit": "^1.9.5",
"@roarr/browser-log-writer": "^1.1.5",
"chakra-ui-contextmenu": "^1.0.5", "chakra-ui-contextmenu": "^1.0.5",
"dateformat": "^5.0.3", "dateformat": "^5.0.3",
"formik": "^2.2.9", "formik": "^2.2.9",
"framer-motion": "^9.0.4", "framer-motion": "^10.12.4",
"fuse.js": "^6.6.2", "fuse.js": "^6.6.2",
"i18next": "^22.4.10", "i18next": "^22.4.15",
"i18next-browser-languagedetector": "^7.0.1", "i18next-browser-languagedetector": "^7.0.1",
"i18next-http-backend": "^2.1.1", "i18next-http-backend": "^2.2.0",
"konva": "^8.4.2", "konva": "^9.0.1",
"lodash": "^4.17.21", "lodash-es": "^4.17.21",
"patch-package": "^6.5.1", "overlayscrollbars": "^2.1.1",
"overlayscrollbars-react": "^0.5.0",
"patch-package": "^7.0.0",
"re-resizable": "^6.9.9", "re-resizable": "^6.9.9",
"react": "^18.2.0", "react": "^18.2.0",
"react-colorful": "^5.6.1", "react-colorful": "^5.6.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-hotkeys-hook": "4.3.5", "react-hotkeys-hook": "4.4.0",
"react-i18next": "^12.1.5", "react-i18next": "^12.2.2",
"react-icons": "^4.7.1", "react-icons": "^4.7.1",
"react-konva": "^18.2.4", "react-konva": "^18.2.7",
"react-konva-utils": "^0.3.2", "react-konva-utils": "^1.0.4",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"react-rnd": "^10.4.1",
"react-transition-group": "^4.4.5", "react-transition-group": "^4.4.5",
"react-zoom-pan-pinch": "^2.6.1", "react-use": "^17.4.0",
"react-virtuoso": "^4.3.5",
"react-zoom-pan-pinch": "^3.0.7",
"reactflow": "^11.7.0", "reactflow": "^11.7.0",
"redux-deep-persist": "^1.0.7", "redux-deep-persist": "^1.0.7",
"redux-dynamic-middlewares": "^2.2.0", "redux-dynamic-middlewares": "^2.2.0",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"roarr": "^7.15.0",
"serialize-error": "^11.0.0",
"socket.io-client": "^4.6.0", "socket.io-client": "^4.6.0",
"use-image": "^1.1.0", "use-image": "^1.1.0",
"uuid": "^9.0.0" "uuid": "^9.0.0"
}, },
"peerDependencies": {
"@chakra-ui/cli": "^2.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ts-toolbelt": "^9.6.0"
},
"devDependencies": { "devDependencies": {
"@chakra-ui/cli": "^2.4.0",
"@types/dateformat": "^5.0.0", "@types/dateformat": "^5.0.0",
"@types/lodash": "^4.14.194", "@types/lodash-es": "^4.14.194",
"@types/react": "^18.0.28", "@types/node": "^18.16.2",
"@types/react-dom": "^18.0.11", "@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"@types/react-transition-group": "^4.4.5", "@types/react-transition-group": "^4.4.5",
"@types/uuid": "^9.0.0", "@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.52.0", "@typescript-eslint/eslint-plugin": "^5.59.1",
"@typescript-eslint/parser": "^5.52.0", "@typescript-eslint/parser": "^5.59.1",
"@vitejs/plugin-react-swc": "^3.2.0", "@vitejs/plugin-react-swc": "^3.3.0",
"axios": "^1.3.4", "axios": "^1.4.0",
"babel-plugin-transform-imports": "^2.0.0", "babel-plugin-transform-imports": "^2.0.0",
"concurrently": "^7.6.0", "concurrently": "^8.0.1",
"eslint": "^8.34.0", "eslint": "^8.39.0",
"eslint-config-prettier": "^8.6.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2", "eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"husky": "^8.0.3", "husky": "^8.0.3",
"lint-staged": "^13.1.2", "lint-staged": "^13.2.2",
"madge": "^6.0.0", "madge": "^6.0.0",
"openapi-types": "^12.1.0", "openapi-types": "^12.1.0",
"openapi-typescript-codegen": "^0.23.0", "openapi-typescript-codegen": "^0.24.0",
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"prettier": "^2.8.4", "prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.9.0", "rollup-plugin-visualizer": "^5.9.0",
"terser": "^5.16.4", "terser": "^5.17.1",
"ts-toolbelt": "^9.6.0", "ts-toolbelt": "^9.6.0",
"typescript": "4.9.5", "vite": "^4.3.3",
"vite": "^4.1.2", "vite-plugin-dts": "^2.3.0",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.0.5", "vite-tsconfig-paths": "^4.2.0",
"yarn": "^1.22.19" "yarn": "^1.22.19"
} }
} }

View File

@ -527,10 +527,15 @@
"useCanvasBeta": "Use Canvas Beta Layout", "useCanvasBeta": "Use Canvas Beta Layout",
"enableImageDebugging": "Enable Image Debugging", "enableImageDebugging": "Enable Image Debugging",
"useSlidersForAll": "Use Sliders For All Options", "useSlidersForAll": "Use Sliders For All Options",
"autoShowProgress": "Auto Show Progress Images",
"resetWebUI": "Reset Web UI", "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.", "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.", "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.",
"resetComplete": "Web UI has been reset. Refresh the page to reload." "resetComplete": "Web UI has been reset. Refresh the page to reload.",
"consoleLogLevel": "Log Level",
"shouldLogToConsole": "Console Logging",
"developer": "Developer",
"general": "General"
}, },
"toast": { "toast": {
"serverError": "Server Error", "serverError": "Server Error",
@ -641,5 +646,9 @@
"betaDarkenOutside": "Darken Outside", "betaDarkenOutside": "Darken Outside",
"betaLimitToBox": "Limit To Box", "betaLimitToBox": "Limit To Box",
"betaPreserveMasked": "Preserve Masked" "betaPreserveMasked": "Preserve Masked"
},
"ui": {
"showProgressImages": "Show Progress Images",
"hideProgressImages": "Hide Progress Images"
} }
} }

View File

@ -1,9 +1,7 @@
import ImageUploader from 'common/components/ImageUploader'; import ImageUploader from 'common/components/ImageUploader';
import Console from 'features/system/components/Console';
import ProgressBar from 'features/system/components/ProgressBar'; import ProgressBar from 'features/system/components/ProgressBar';
import SiteHeader from 'features/system/components/SiteHeader'; import SiteHeader from 'features/system/components/SiteHeader';
import InvokeTabs from 'features/ui/components/InvokeTabs'; import InvokeTabs from 'features/ui/components/InvokeTabs';
import { keepGUIAlive } from './utils';
import useToastWatcher from 'features/system/hooks/useToastWatcher'; import useToastWatcher from 'features/system/hooks/useToastWatcher';
@ -13,25 +11,34 @@ import { Box, Flex, Grid, Portal, useColorMode } from '@chakra-ui/react';
import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants'; import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants';
import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel'; import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel';
import Lightbox from 'features/lightbox/components/Lightbox'; import Lightbox from 'features/lightbox/components/Lightbox';
import { useAppDispatch, useAppSelector } from './storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { PropsWithChildren, useCallback, useEffect, useState } from 'react'; import {
memo,
PropsWithChildren,
useCallback,
useEffect,
useState,
} from 'react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import Loading from 'common/components/Loading/Loading'; import Loading from 'common/components/Loading/Loading';
import { useIsApplicationReady } from 'features/system/hooks/useIsApplicationReady'; import { useIsApplicationReady } from 'features/system/hooks/useIsApplicationReady';
import { PartialAppConfig } from './invokeai'; import { PartialAppConfig } from 'app/types/invokeai';
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys'; import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
import { configChanged } from 'features/system/store/configSlice'; import { configChanged } from 'features/system/store/configSlice';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useLogger } from 'app/logging/useLogger';
import ProgressImagePreview from 'features/parameters/components/ProgressImagePreview';
keepGUIAlive(); const DEFAULT_CONFIG = {};
interface Props extends PropsWithChildren { interface Props extends PropsWithChildren {
config?: PartialAppConfig; config?: PartialAppConfig;
} }
const App = ({ config = {}, children }: Props) => { const App = ({ config = DEFAULT_CONFIG, children }: Props) => {
useToastWatcher(); useToastWatcher();
useGlobalHotkeys(); useGlobalHotkeys();
const log = useLogger();
const currentTheme = useAppSelector((state) => state.ui.currentTheme); const currentTheme = useAppSelector((state) => state.ui.currentTheme);
@ -45,9 +52,9 @@ const App = ({ config = {}, children }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
useEffect(() => { useEffect(() => {
console.log('Received config: ', config); log.info({ namespace: 'App', data: config }, 'Received config');
dispatch(configChanged(config)); dispatch(configChanged(config));
}, [dispatch, config]); }, [dispatch, config, log]);
useEffect(() => { useEffect(() => {
setColorMode(['light'].includes(currentTheme) ? 'light' : 'dark'); setColorMode(['light'].includes(currentTheme) ? 'light' : 'dark');
@ -58,7 +65,7 @@ const App = ({ config = {}, children }: Props) => {
}, []); }, []);
return ( return (
<Grid w="100vw" h="100vh" position="relative"> <Grid w="100vw" h="100vh" position="relative" overflow="hidden">
{isLightboxEnabled && <Lightbox />} {isLightboxEnabled && <Lightbox />}
<ImageUploader> <ImageUploader>
<ProgressBar /> <ProgressBar />
@ -114,11 +121,9 @@ const App = ({ config = {}, children }: Props) => {
<Portal> <Portal>
<FloatingGalleryButton /> <FloatingGalleryButton />
</Portal> </Portal>
<Portal> <ProgressImagePreview />
<Console />
</Portal>
</Grid> </Grid>
); );
}; };
export default App; export default memo(App);

View File

@ -1,8 +1,8 @@
import React, { lazy, PropsWithChildren, useEffect } from 'react'; import React, { lazy, memo, PropsWithChildren, useEffect } from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react'; import { PersistGate } from 'redux-persist/integration/react';
import { buildMiddleware, store } from './app/store'; import { store } from 'app/store/store';
import { persistor } from './persistor'; import { persistor } from '../store/persistor';
import { OpenAPI } from 'services/api'; import { OpenAPI } from 'services/api';
import '@fontsource/inter/100.css'; import '@fontsource/inter/100.css';
import '@fontsource/inter/200.css'; import '@fontsource/inter/200.css';
@ -14,14 +14,15 @@ import '@fontsource/inter/700.css';
import '@fontsource/inter/800.css'; import '@fontsource/inter/800.css';
import '@fontsource/inter/900.css'; import '@fontsource/inter/900.css';
import Loading from './common/components/Loading/Loading'; import Loading from '../../common/components/Loading/Loading';
import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares'; import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
import { PartialAppConfig } from 'app/invokeai'; import { PartialAppConfig } from 'app/types/invokeai';
import './i18n'; import '../../i18n';
import { socketMiddleware } from 'services/events/middleware';
const App = lazy(() => import('./app/App')); const App = lazy(() => import('./App'));
const ThemeLocaleProvider = lazy(() => import('./app/ThemeLocaleProvider')); const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
interface Props extends PropsWithChildren { interface Props extends PropsWithChildren {
apiUrl?: string; apiUrl?: string;
@ -29,7 +30,7 @@ interface Props extends PropsWithChildren {
config?: PartialAppConfig; config?: PartialAppConfig;
} }
export default function Component({ apiUrl, token, config, children }: Props) { const InvokeAIUI = ({ apiUrl, token, config, children }: Props) => {
useEffect(() => { useEffect(() => {
// configure API client token // configure API client token
if (token) { if (token) {
@ -50,7 +51,7 @@ export default function Component({ apiUrl, token, config, children }: Props) {
// the `apiUrl`/`token` dynamically. // the `apiUrl`/`token` dynamically.
// rebuild socket middleware with token and apiUrl // rebuild socket middleware with token and apiUrl
addMiddleware(buildMiddleware()); addMiddleware(socketMiddleware());
}, [apiUrl, token]); }, [apiUrl, token]);
return ( return (
@ -66,4 +67,6 @@ export default function Component({ apiUrl, token, config, children }: Props) {
</Provider> </Provider>
</React.StrictMode> </React.StrictMode>
); );
} };
export default memo(InvokeAIUI);

View File

@ -2,8 +2,8 @@ import { ChakraProvider, extendTheme } from '@chakra-ui/react';
import { ReactNode, useEffect } from 'react'; import { ReactNode, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { theme as invokeAITheme } from 'theme/theme'; import { theme as invokeAITheme } from 'theme/theme';
import { RootState } from './store'; import { RootState } from 'app/store/store';
import { useAppSelector } from './storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { greenTeaThemeColors } from 'theme/colors/greenTea'; import { greenTeaThemeColors } from 'theme/colors/greenTea';
import { invokeAIThemeColors } from 'theme/colors/invokeAI'; import { invokeAIThemeColors } from 'theme/colors/invokeAI';
@ -18,6 +18,8 @@ import '@fontsource/inter/600.css';
import '@fontsource/inter/700.css'; import '@fontsource/inter/700.css';
import '@fontsource/inter/800.css'; import '@fontsource/inter/800.css';
import '@fontsource/inter/900.css'; import '@fontsource/inter/900.css';
import 'overlayscrollbars/overlayscrollbars.css';
import 'theme/css/overlayscrollbars.css';
type ThemeLocaleProviderProps = { type ThemeLocaleProviderProps = {
children: ReactNode; children: ReactNode;

View File

@ -1,23 +1,6 @@
// TODO: use Enums? // TODO: use Enums?
import { InProgressImageType } from 'features/system/store/systemSlice'; export const DIFFUSERS_SCHEDULERS: Array<string> = [
// Valid samplers
export const SAMPLERS: Array<string> = [
'ddim',
'plms',
'k_lms',
'k_dpm_2',
'k_dpm_2_a',
'k_dpmpp_2',
'k_dpmpp_2_a',
'k_euler',
'k_euler_a',
'k_heun',
];
// Valid Diffusers Samplers
export const DIFFUSERS_SAMPLERS: Array<string> = [
'ddim', 'ddim',
'plms', 'plms',
'k_lms', 'k_lms',
@ -48,17 +31,8 @@ export const UPSCALING_LEVELS: Array<{ key: string; value: number }> = [
export const NUMPY_RAND_MIN = 0; export const NUMPY_RAND_MIN = 0;
export const NUMPY_RAND_MAX = 4294967295; export const NUMPY_RAND_MAX = 2147483647;
export const FACETOOL_TYPES = ['gfpgan', 'codeformer'] as const; export const FACETOOL_TYPES = ['gfpgan', 'codeformer'] as const;
export const IN_PROGRESS_IMAGE_TYPES: Array<{
key: string;
value: InProgressImageType;
}> = [
{ key: 'None', value: 'none' },
{ key: 'Fast', value: 'latents' },
{ key: 'Accurate', value: 'full-res' },
];
export const NODE_MIN_WIDTH = 250; export const NODE_MIN_WIDTH = 250;

View File

@ -0,0 +1,94 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { systemSelector } from 'features/system/store/systemSelectors';
import { isEqual } from 'lodash-es';
import { useEffect } from 'react';
import { LogLevelName, ROARR, Roarr } from 'roarr';
import { createLogWriter } from '@roarr/browser-log-writer';
// Base logging context includes only the package name
const baseContext = { package: '@invoke-ai/invoke-ai-ui' };
// Create browser log writer
ROARR.write = createLogWriter();
// Module-scoped logger - can be imported and used anywhere
export let log = Roarr.child(baseContext);
// Translate human-readable log levels to numbers, used for log filtering
export const LOG_LEVEL_MAP: Record<LogLevelName, number> = {
trace: 10,
debug: 20,
info: 30,
warn: 40,
error: 50,
fatal: 60,
};
export const VALID_LOG_LEVELS = [
'trace',
'debug',
'info',
'warn',
'error',
'fatal',
] as const;
export type InvokeLogLevel = (typeof VALID_LOG_LEVELS)[number];
const selector = createSelector(
systemSelector,
(system) => {
const { app_version, consoleLogLevel, shouldLogToConsole } = system;
return {
version: app_version,
consoleLogLevel,
shouldLogToConsole,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
export const useLogger = () => {
const { version, consoleLogLevel, shouldLogToConsole } =
useAppSelector(selector);
// The provided Roarr browser log writer uses localStorage to config logging to console
useEffect(() => {
if (shouldLogToConsole) {
// Enable console log output
localStorage.setItem('ROARR_LOG', 'true');
// Use a filter to show only logs of the given level
localStorage.setItem(
'ROARR_FILTER',
`context.logLevel:>=${LOG_LEVEL_MAP[consoleLogLevel]}`
);
} else {
// Disable console log output
localStorage.setItem('ROARR_LOG', 'false');
}
ROARR.write = createLogWriter();
}, [consoleLogLevel, shouldLogToConsole]);
// Update the module-scoped logger context as needed
useEffect(() => {
const newContext: Record<string, any> = {
...baseContext,
};
if (version) {
newContext.version = version;
}
log = Roarr.child(newContext);
}, [version]);
// Use the logger within components - no different than just importing it directly
return log;
};

View File

@ -4,7 +4,7 @@ import { initialCanvasImageSelector } from 'features/canvas/store/canvasSelector
import { generationSelector } from 'features/parameters/store/generationSelectors'; import { generationSelector } from 'features/parameters/store/generationSelectors';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
export const readinessSelector = createSelector( export const readinessSelector = createSelector(
[ [

View File

@ -1,65 +1,67 @@
import { createAction } from '@reduxjs/toolkit'; // import { createAction } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/invokeai'; // import * as InvokeAI from 'app/types/invokeai';
import { GalleryCategory } from 'features/gallery/store/gallerySlice'; // import { GalleryCategory } from 'features/gallery/store/gallerySlice';
import { InvokeTabName } from 'features/ui/store/tabMap'; // import { InvokeTabName } from 'features/ui/store/tabMap';
/** // /**
* We can't use redux-toolkit's createSlice() to make these actions, // * We can't use redux-toolkit's createSlice() to make these actions,
* because they have no associated reducer. They only exist to dispatch // * because they have no associated reducer. They only exist to dispatch
* requests to the server via socketio. These actions will be handled // * requests to the server via socketio. These actions will be handled
* by the middleware. // * by the middleware.
*/ // */
export const generateImage = createAction<InvokeTabName>( // export const generateImage = createAction<InvokeTabName>(
'socketio/generateImage' // 'socketio/generateImage'
); // );
export const runESRGAN = createAction<InvokeAI._Image>('socketio/runESRGAN'); // export const runESRGAN = createAction<InvokeAI._Image>('socketio/runESRGAN');
export const runFacetool = createAction<InvokeAI._Image>( // export const runFacetool = createAction<InvokeAI._Image>(
'socketio/runFacetool' // 'socketio/runFacetool'
); // );
export const deleteImage = createAction<InvokeAI._Image>( // export const deleteImage = createAction<InvokeAI._Image>(
'socketio/deleteImage' // 'socketio/deleteImage'
); // );
export const requestImages = createAction<GalleryCategory>( // export const requestImages = createAction<GalleryCategory>(
'socketio/requestImages' // 'socketio/requestImages'
); // );
export const requestNewImages = createAction<GalleryCategory>( // export const requestNewImages = createAction<GalleryCategory>(
'socketio/requestNewImages' // 'socketio/requestNewImages'
); // );
export const cancelProcessing = createAction<undefined>( // export const cancelProcessing = createAction<undefined>(
'socketio/cancelProcessing' // 'socketio/cancelProcessing'
); // );
export const requestSystemConfig = createAction<undefined>( // export const requestSystemConfig = createAction<undefined>(
'socketio/requestSystemConfig' // 'socketio/requestSystemConfig'
); // );
export const searchForModels = createAction<string>('socketio/searchForModels'); // export const searchForModels = createAction<string>('socketio/searchForModels');
export const addNewModel = createAction< // export const addNewModel = createAction<
InvokeAI.InvokeModelConfigProps | InvokeAI.InvokeDiffusersModelConfigProps // InvokeAI.InvokeModelConfigProps | InvokeAI.InvokeDiffusersModelConfigProps
>('socketio/addNewModel'); // >('socketio/addNewModel');
export const deleteModel = createAction<string>('socketio/deleteModel'); // export const deleteModel = createAction<string>('socketio/deleteModel');
export const convertToDiffusers = // export const convertToDiffusers =
createAction<InvokeAI.InvokeModelConversionProps>( // createAction<InvokeAI.InvokeModelConversionProps>(
'socketio/convertToDiffusers' // 'socketio/convertToDiffusers'
); // );
export const mergeDiffusersModels = // export const mergeDiffusersModels =
createAction<InvokeAI.InvokeModelMergingProps>( // createAction<InvokeAI.InvokeModelMergingProps>(
'socketio/mergeDiffusersModels' // 'socketio/mergeDiffusersModels'
); // );
export const requestModelChange = createAction<string>( // export const requestModelChange = createAction<string>(
'socketio/requestModelChange' // 'socketio/requestModelChange'
); // );
export const saveStagingAreaImageToGallery = createAction<string>( // export const saveStagingAreaImageToGallery = createAction<string>(
'socketio/saveStagingAreaImageToGallery' // 'socketio/saveStagingAreaImageToGallery'
); // );
export const emptyTempFolder = createAction<undefined>( // export const emptyTempFolder = createAction<undefined>(
'socketio/requestEmptyTempFolder' // 'socketio/requestEmptyTempFolder'
); // );
export default {};

View File

@ -1,208 +1,209 @@
import { AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit'; // import { AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/invokeai'; // import * as InvokeAI from 'app/types/invokeai';
import type { RootState } from 'app/store'; // import type { RootState } from 'app/store/store';
import { // import {
frontendToBackendParameters, // frontendToBackendParameters,
FrontendToBackendParametersConfig, // FrontendToBackendParametersConfig,
} from 'common/util/parameterTranslation'; // } from 'common/util/parameterTranslation';
import dateFormat from 'dateformat'; // import dateFormat from 'dateformat';
import { // import {
GalleryCategory, // GalleryCategory,
GalleryState, // GalleryState,
removeImage, // removeImage,
} from 'features/gallery/store/gallerySlice'; // } from 'features/gallery/store/gallerySlice';
import { // import {
addLogEntry, // generationRequested,
generationRequested, // modelChangeRequested,
modelChangeRequested, // modelConvertRequested,
modelConvertRequested, // modelMergingRequested,
modelMergingRequested, // setIsProcessing,
setIsProcessing, // } from 'features/system/store/systemSlice';
} from 'features/system/store/systemSlice'; // import { InvokeTabName } from 'features/ui/store/tabMap';
import { InvokeTabName } from 'features/ui/store/tabMap'; // import { Socket } from 'socket.io-client';
import { Socket } from 'socket.io-client';
/** // /**
* Returns an object containing all functions which use `socketio.emit()`. // * Returns an object containing all functions which use `socketio.emit()`.
* i.e. those which make server requests. // * i.e. those which make server requests.
*/ // */
const makeSocketIOEmitters = ( // const makeSocketIOEmitters = (
store: MiddlewareAPI<Dispatch<AnyAction>, RootState>, // store: MiddlewareAPI<Dispatch<AnyAction>, RootState>,
socketio: Socket // socketio: Socket
) => { // ) => {
// We need to dispatch actions to redux and get pieces of state from the store. // // We need to dispatch actions to redux and get pieces of state from the store.
const { dispatch, getState } = store; // const { dispatch, getState } = store;
return { // return {
emitGenerateImage: (generationMode: InvokeTabName) => { // emitGenerateImage: (generationMode: InvokeTabName) => {
dispatch(setIsProcessing(true)); // dispatch(setIsProcessing(true));
const state: RootState = getState(); // const state: RootState = getState();
const { // const {
generation: generationState, // generation: generationState,
postprocessing: postprocessingState, // postprocessing: postprocessingState,
system: systemState, // system: systemState,
canvas: canvasState, // canvas: canvasState,
} = state; // } = state;
const frontendToBackendParametersConfig: FrontendToBackendParametersConfig = // const frontendToBackendParametersConfig: FrontendToBackendParametersConfig =
{ // {
generationMode, // generationMode,
generationState, // generationState,
postprocessingState, // postprocessingState,
canvasState, // canvasState,
systemState, // systemState,
}; // };
dispatch(generationRequested()); // dispatch(generationRequested());
const { generationParameters, esrganParameters, facetoolParameters } = // const { generationParameters, esrganParameters, facetoolParameters } =
frontendToBackendParameters(frontendToBackendParametersConfig); // frontendToBackendParameters(frontendToBackendParametersConfig);
socketio.emit( // socketio.emit(
'generateImage', // 'generateImage',
generationParameters, // generationParameters,
esrganParameters, // esrganParameters,
facetoolParameters // facetoolParameters
); // );
// we need to truncate the init_mask base64 else it takes up the whole log // // we need to truncate the init_mask base64 else it takes up the whole log
// TODO: handle maintaining masks for reproducibility in future // // TODO: handle maintaining masks for reproducibility in future
if (generationParameters.init_mask) { // if (generationParameters.init_mask) {
generationParameters.init_mask = generationParameters.init_mask // generationParameters.init_mask = generationParameters.init_mask
.substr(0, 64) // .substr(0, 64)
.concat('...'); // .concat('...');
} // }
if (generationParameters.init_img) { // if (generationParameters.init_img) {
generationParameters.init_img = generationParameters.init_img // generationParameters.init_img = generationParameters.init_img
.substr(0, 64) // .substr(0, 64)
.concat('...'); // .concat('...');
} // }
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Image generation requested: ${JSON.stringify({ // message: `Image generation requested: ${JSON.stringify({
...generationParameters, // ...generationParameters,
...esrganParameters, // ...esrganParameters,
...facetoolParameters, // ...facetoolParameters,
})}`, // })}`,
}) // })
); // );
}, // },
emitRunESRGAN: (imageToProcess: InvokeAI._Image) => { // emitRunESRGAN: (imageToProcess: InvokeAI._Image) => {
dispatch(setIsProcessing(true)); // dispatch(setIsProcessing(true));
const { // const {
postprocessing: { // postprocessing: {
upscalingLevel, // upscalingLevel,
upscalingDenoising, // upscalingDenoising,
upscalingStrength, // upscalingStrength,
}, // },
} = getState(); // } = getState();
const esrganParameters = { // const esrganParameters = {
upscale: [upscalingLevel, upscalingDenoising, upscalingStrength], // upscale: [upscalingLevel, upscalingDenoising, upscalingStrength],
}; // };
socketio.emit('runPostprocessing', imageToProcess, { // socketio.emit('runPostprocessing', imageToProcess, {
type: 'esrgan', // type: 'esrgan',
...esrganParameters, // ...esrganParameters,
}); // });
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `ESRGAN upscale requested: ${JSON.stringify({ // message: `ESRGAN upscale requested: ${JSON.stringify({
file: imageToProcess.url, // file: imageToProcess.url,
...esrganParameters, // ...esrganParameters,
})}`, // })}`,
}) // })
); // );
}, // },
emitRunFacetool: (imageToProcess: InvokeAI._Image) => { // emitRunFacetool: (imageToProcess: InvokeAI._Image) => {
dispatch(setIsProcessing(true)); // dispatch(setIsProcessing(true));
const { // const {
postprocessing: { facetoolType, facetoolStrength, codeformerFidelity }, // postprocessing: { facetoolType, facetoolStrength, codeformerFidelity },
} = getState(); // } = getState();
const facetoolParameters: Record<string, unknown> = { // const facetoolParameters: Record<string, unknown> = {
facetool_strength: facetoolStrength, // facetool_strength: facetoolStrength,
}; // };
if (facetoolType === 'codeformer') { // if (facetoolType === 'codeformer') {
facetoolParameters.codeformer_fidelity = codeformerFidelity; // facetoolParameters.codeformer_fidelity = codeformerFidelity;
} // }
socketio.emit('runPostprocessing', imageToProcess, { // socketio.emit('runPostprocessing', imageToProcess, {
type: facetoolType, // type: facetoolType,
...facetoolParameters, // ...facetoolParameters,
}); // });
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Face restoration (${facetoolType}) requested: ${JSON.stringify( // message: `Face restoration (${facetoolType}) requested: ${JSON.stringify(
{ // {
file: imageToProcess.url, // file: imageToProcess.url,
...facetoolParameters, // ...facetoolParameters,
} // }
)}`, // )}`,
}) // })
); // );
}, // },
emitDeleteImage: (imageToDelete: InvokeAI._Image) => { // emitDeleteImage: (imageToDelete: InvokeAI._Image) => {
const { url, uuid, category, thumbnail } = imageToDelete; // const { url, uuid, category, thumbnail } = imageToDelete;
dispatch(removeImage(imageToDelete)); // dispatch(removeImage(imageToDelete));
socketio.emit('deleteImage', url, thumbnail, uuid, category); // socketio.emit('deleteImage', url, thumbnail, uuid, category);
}, // },
emitRequestImages: (category: GalleryCategory) => { // emitRequestImages: (category: GalleryCategory) => {
const gallery: GalleryState = getState().gallery; // const gallery: GalleryState = getState().gallery;
const { earliest_mtime } = gallery.categories[category]; // const { earliest_mtime } = gallery.categories[category];
socketio.emit('requestImages', category, earliest_mtime); // socketio.emit('requestImages', category, earliest_mtime);
}, // },
emitRequestNewImages: (category: GalleryCategory) => { // emitRequestNewImages: (category: GalleryCategory) => {
const gallery: GalleryState = getState().gallery; // const gallery: GalleryState = getState().gallery;
const { latest_mtime } = gallery.categories[category]; // const { latest_mtime } = gallery.categories[category];
socketio.emit('requestLatestImages', category, latest_mtime); // socketio.emit('requestLatestImages', category, latest_mtime);
}, // },
emitCancelProcessing: () => { // emitCancelProcessing: () => {
socketio.emit('cancel'); // socketio.emit('cancel');
}, // },
emitRequestSystemConfig: () => { // emitRequestSystemConfig: () => {
socketio.emit('requestSystemConfig'); // socketio.emit('requestSystemConfig');
}, // },
emitSearchForModels: (modelFolder: string) => { // emitSearchForModels: (modelFolder: string) => {
socketio.emit('searchForModels', modelFolder); // socketio.emit('searchForModels', modelFolder);
}, // },
emitAddNewModel: (modelConfig: InvokeAI.InvokeModelConfigProps) => { // emitAddNewModel: (modelConfig: InvokeAI.InvokeModelConfigProps) => {
socketio.emit('addNewModel', modelConfig); // socketio.emit('addNewModel', modelConfig);
}, // },
emitDeleteModel: (modelName: string) => { // emitDeleteModel: (modelName: string) => {
socketio.emit('deleteModel', modelName); // socketio.emit('deleteModel', modelName);
}, // },
emitConvertToDiffusers: ( // emitConvertToDiffusers: (
modelToConvert: InvokeAI.InvokeModelConversionProps // modelToConvert: InvokeAI.InvokeModelConversionProps
) => { // ) => {
dispatch(modelConvertRequested()); // dispatch(modelConvertRequested());
socketio.emit('convertToDiffusers', modelToConvert); // socketio.emit('convertToDiffusers', modelToConvert);
}, // },
emitMergeDiffusersModels: ( // emitMergeDiffusersModels: (
modelMergeInfo: InvokeAI.InvokeModelMergingProps // modelMergeInfo: InvokeAI.InvokeModelMergingProps
) => { // ) => {
dispatch(modelMergingRequested()); // dispatch(modelMergingRequested());
socketio.emit('mergeDiffusersModels', modelMergeInfo); // socketio.emit('mergeDiffusersModels', modelMergeInfo);
}, // },
emitRequestModelChange: (modelName: string) => { // emitRequestModelChange: (modelName: string) => {
dispatch(modelChangeRequested()); // dispatch(modelChangeRequested());
socketio.emit('requestModelChange', modelName); // socketio.emit('requestModelChange', modelName);
}, // },
emitSaveStagingAreaImageToGallery: (url: string) => { // emitSaveStagingAreaImageToGallery: (url: string) => {
socketio.emit('requestSaveStagingAreaImageToGallery', url); // socketio.emit('requestSaveStagingAreaImageToGallery', url);
}, // },
emitRequestEmptyTempFolder: () => { // emitRequestEmptyTempFolder: () => {
socketio.emit('requestEmptyTempFolder'); // socketio.emit('requestEmptyTempFolder');
}, // },
}; // };
}; // };
export default makeSocketIOEmitters; // export default makeSocketIOEmitters;
export default {};

View File

@ -1,501 +1,502 @@
import { AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit'; // import { AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit';
import dateFormat from 'dateformat'; // import dateFormat from 'dateformat';
import i18n from 'i18n'; // import i18n from 'i18n';
import { v4 as uuidv4 } from 'uuid'; // import { v4 as uuidv4 } from 'uuid';
import * as InvokeAI from 'app/invokeai'; // import * as InvokeAI from 'app/types/invokeai';
import { // import {
addLogEntry, // addToast,
addToast, // errorOccurred,
errorOccurred, // processingCanceled,
processingCanceled, // setCurrentStatus,
setCurrentStatus, // setFoundModels,
setFoundModels, // setIsCancelable,
setIsCancelable, // setIsConnected,
setIsConnected, // setIsProcessing,
setIsProcessing, // setModelList,
setModelList, // setSearchFolder,
setSearchFolder, // setSystemConfig,
setSystemConfig, // setSystemStatus,
setSystemStatus, // } from 'features/system/store/systemSlice';
} from 'features/system/store/systemSlice';
import { // import {
addGalleryImages, // addGalleryImages,
addImage, // addImage,
clearIntermediateImage, // clearIntermediateImage,
GalleryState, // GalleryState,
removeImage, // removeImage,
setIntermediateImage, // setIntermediateImage,
} from 'features/gallery/store/gallerySlice'; // } from 'features/gallery/store/gallerySlice';
import type { RootState } from 'app/store'; // import type { RootState } from 'app/store/store';
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice'; // import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
import { // import {
clearInitialImage, // clearInitialImage,
initialImageSelected, // initialImageSelected,
setInfillMethod, // setInfillMethod,
// setInitialImage, // // setInitialImage,
setMaskPath, // setMaskPath,
} from 'features/parameters/store/generationSlice'; // } from 'features/parameters/store/generationSlice';
import { tabMap } from 'features/ui/store/tabMap'; // import { tabMap } from 'features/ui/store/tabMap';
import { // import {
requestImages, // requestImages,
requestNewImages, // requestNewImages,
requestSystemConfig, // requestSystemConfig,
} from './actions'; // } from './actions';
/** // /**
* Returns an object containing listener callbacks for socketio events. // * Returns an object containing listener callbacks for socketio events.
* TODO: This file is large, but simple. Should it be split up further? // * TODO: This file is large, but simple. Should it be split up further?
*/ // */
const makeSocketIOListeners = ( // const makeSocketIOListeners = (
store: MiddlewareAPI<Dispatch<AnyAction>, RootState> // store: MiddlewareAPI<Dispatch<AnyAction>, RootState>
) => { // ) => {
const { dispatch, getState } = store; // const { dispatch, getState } = store;
return { // return {
/** // /**
* Callback to run when we receive a 'connect' event. // * Callback to run when we receive a 'connect' event.
*/ // */
onConnect: () => { // onConnect: () => {
try { // try {
dispatch(setIsConnected(true)); // dispatch(setIsConnected(true));
dispatch(setCurrentStatus(i18n.t('common.statusConnected'))); // dispatch(setCurrentStatus(i18n.t('common.statusConnected')));
dispatch(requestSystemConfig()); // dispatch(requestSystemConfig());
const gallery: GalleryState = getState().gallery; // const gallery: GalleryState = getState().gallery;
if (gallery.categories.result.latest_mtime) { // if (gallery.categories.result.latest_mtime) {
dispatch(requestNewImages('result')); // dispatch(requestNewImages('result'));
} else { // } else {
dispatch(requestImages('result')); // dispatch(requestImages('result'));
}
if (gallery.categories.user.latest_mtime) {
dispatch(requestNewImages('user'));
} else {
dispatch(requestImages('user'));
}
} catch (e) {
console.error(e);
}
},
/**
* Callback to run when we receive a 'disconnect' event.
*/
onDisconnect: () => {
try {
dispatch(setIsConnected(false));
dispatch(setCurrentStatus(i18n.t('common.statusDisconnected')));
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Disconnected from server`,
level: 'warning',
})
);
} catch (e) {
console.error(e);
}
},
/**
* Callback to run when we receive a 'generationResult' event.
*/
onGenerationResult: (data: InvokeAI.ImageResultResponse) => {
try {
const state = getState();
const { activeTab } = state.ui;
const { shouldLoopback } = state.postprocessing;
const { boundingBox: _, generationMode, ...rest } = data;
const newImage = {
uuid: uuidv4(),
...rest,
};
if (['txt2img', 'img2img'].includes(generationMode)) {
dispatch(
addImage({
category: 'result',
image: { ...newImage, category: 'result' },
})
);
}
if (generationMode === 'unifiedCanvas' && data.boundingBox) {
const { boundingBox } = data;
dispatch(
addImageToStagingArea({
image: { ...newImage, category: 'temp' },
boundingBox,
})
);
if (state.canvas.shouldAutoSave) {
dispatch(
addImage({
image: { ...newImage, category: 'result' },
category: 'result',
})
);
}
}
// TODO: fix
// if (shouldLoopback) {
// const activeTabName = tabMap[activeTab];
// switch (activeTabName) {
// case 'img2img': {
// dispatch(initialImageSelected(newImage.uuid));
// // dispatch(setInitialImage(newImage));
// break;
// }
// }
// } // }
dispatch(clearIntermediateImage()); // if (gallery.categories.user.latest_mtime) {
// dispatch(requestNewImages('user'));
// } else {
// dispatch(requestImages('user'));
// }
// } catch (e) {
// console.error(e);
// }
// },
// /**
// * Callback to run when we receive a 'disconnect' event.
// */
// onDisconnect: () => {
// try {
// dispatch(setIsConnected(false));
// dispatch(setCurrentStatus(i18n.t('common.statusDisconnected')));
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Image generated: ${data.url}`, // message: `Disconnected from server`,
}) // level: 'warning',
); // })
} catch (e) { // );
console.error(e); // } catch (e) {
} // console.error(e);
}, // }
/** // },
* Callback to run when we receive a 'intermediateResult' event. // /**
*/ // * Callback to run when we receive a 'generationResult' event.
onIntermediateResult: (data: InvokeAI.ImageResultResponse) => { // */
try { // onGenerationResult: (data: InvokeAI.ImageResultResponse) => {
dispatch( // try {
setIntermediateImage({ // const state = getState();
uuid: uuidv4(), // const { activeTab } = state.ui;
...data, // const { shouldLoopback } = state.postprocessing;
category: 'result', // const { boundingBox: _, generationMode, ...rest } = data;
})
);
if (!data.isBase64) {
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Intermediate image generated: ${data.url}`,
})
);
}
} catch (e) {
console.error(e);
}
},
/**
* Callback to run when we receive an 'esrganResult' event.
*/
onPostprocessingResult: (data: InvokeAI.ImageResultResponse) => {
try {
dispatch(
addImage({
category: 'result',
image: {
uuid: uuidv4(),
...data,
category: 'result',
},
})
);
dispatch( // const newImage = {
addLogEntry({ // uuid: uuidv4(),
timestamp: dateFormat(new Date(), 'isoDateTime'), // ...rest,
message: `Postprocessed: ${data.url}`, // };
})
);
} catch (e) {
console.error(e);
}
},
/**
* Callback to run when we receive a 'progressUpdate' event.
* TODO: Add additional progress phases
*/
onProgressUpdate: (data: InvokeAI.SystemStatus) => {
try {
dispatch(setIsProcessing(true));
dispatch(setSystemStatus(data));
} catch (e) {
console.error(e);
}
},
/**
* Callback to run when we receive a 'progressUpdate' event.
*/
onError: (data: InvokeAI.ErrorResponse) => {
const { message, additionalData } = data;
if (additionalData) { // if (['txt2img', 'img2img'].includes(generationMode)) {
// TODO: handle more data than short message // dispatch(
} // addImage({
// category: 'result',
// image: { ...newImage, category: 'result' },
// })
// );
// }
try { // if (generationMode === 'unifiedCanvas' && data.boundingBox) {
dispatch( // const { boundingBox } = data;
addLogEntry({ // dispatch(
timestamp: dateFormat(new Date(), 'isoDateTime'), // addImageToStagingArea({
message: `Server error: ${message}`, // image: { ...newImage, category: 'temp' },
level: 'error', // boundingBox,
}) // })
); // );
dispatch(errorOccurred());
dispatch(clearIntermediateImage());
} catch (e) {
console.error(e);
}
},
/**
* Callback to run when we receive a 'galleryImages' event.
*/
onGalleryImages: (data: InvokeAI.GalleryImagesResponse) => {
const { images, areMoreImagesAvailable, category } = data;
/** // if (state.canvas.shouldAutoSave) {
* the logic here ideally would be in the reducer but we have a side effect: // dispatch(
* generating a uuid. so the logic needs to be here, outside redux. // addImage({
*/ // image: { ...newImage, category: 'result' },
// category: 'result',
// })
// );
// }
// }
// Generate a UUID for each image // // TODO: fix
const preparedImages = images.map((image): InvokeAI._Image => { // // if (shouldLoopback) {
return { // // const activeTabName = tabMap[activeTab];
uuid: uuidv4(), // // switch (activeTabName) {
...image, // // case 'img2img': {
}; // // dispatch(initialImageSelected(newImage.uuid));
}); // // // dispatch(setInitialImage(newImage));
// // break;
// // }
// // }
// // }
dispatch( // dispatch(clearIntermediateImage());
addGalleryImages({
images: preparedImages,
areMoreImagesAvailable,
category,
})
);
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Loaded ${images.length} images`, // message: `Image generated: ${data.url}`,
}) // })
); // );
}, // } catch (e) {
/** // console.error(e);
* Callback to run when we receive a 'processingCanceled' event. // }
*/ // },
onProcessingCanceled: () => { // /**
dispatch(processingCanceled()); // * Callback to run when we receive a 'intermediateResult' event.
// */
// onIntermediateResult: (data: InvokeAI.ImageResultResponse) => {
// try {
// dispatch(
// setIntermediateImage({
// uuid: uuidv4(),
// ...data,
// category: 'result',
// })
// );
// if (!data.isBase64) {
// dispatch(
// addLogEntry({
// timestamp: dateFormat(new Date(), 'isoDateTime'),
// message: `Intermediate image generated: ${data.url}`,
// })
// );
// }
// } catch (e) {
// console.error(e);
// }
// },
// /**
// * Callback to run when we receive an 'esrganResult' event.
// */
// onPostprocessingResult: (data: InvokeAI.ImageResultResponse) => {
// try {
// dispatch(
// addImage({
// category: 'result',
// image: {
// uuid: uuidv4(),
// ...data,
// category: 'result',
// },
// })
// );
const { intermediateImage } = getState().gallery; // dispatch(
// addLogEntry({
// timestamp: dateFormat(new Date(), 'isoDateTime'),
// message: `Postprocessed: ${data.url}`,
// })
// );
// } catch (e) {
// console.error(e);
// }
// },
// /**
// * Callback to run when we receive a 'progressUpdate' event.
// * TODO: Add additional progress phases
// */
// onProgressUpdate: (data: InvokeAI.SystemStatus) => {
// try {
// dispatch(setIsProcessing(true));
// dispatch(setSystemStatus(data));
// } catch (e) {
// console.error(e);
// }
// },
// /**
// * Callback to run when we receive a 'progressUpdate' event.
// */
// onError: (data: InvokeAI.ErrorResponse) => {
// const { message, additionalData } = data;
if (intermediateImage) { // if (additionalData) {
if (!intermediateImage.isBase64) { // // TODO: handle more data than short message
dispatch( // }
addImage({
category: 'result',
image: intermediateImage,
})
);
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Intermediate image saved: ${intermediateImage.url}`,
})
);
}
dispatch(clearIntermediateImage());
}
dispatch( // try {
addLogEntry({ // dispatch(
timestamp: dateFormat(new Date(), 'isoDateTime'), // addLogEntry({
message: `Processing canceled`, // timestamp: dateFormat(new Date(), 'isoDateTime'),
level: 'warning', // message: `Server error: ${message}`,
}) // level: 'error',
); // })
}, // );
/** // dispatch(errorOccurred());
* Callback to run when we receive a 'imageDeleted' event. // dispatch(clearIntermediateImage());
*/ // } catch (e) {
onImageDeleted: (data: InvokeAI.ImageDeletedResponse) => { // console.error(e);
const { url } = data; // }
// },
// /**
// * Callback to run when we receive a 'galleryImages' event.
// */
// onGalleryImages: (data: InvokeAI.GalleryImagesResponse) => {
// const { images, areMoreImagesAvailable, category } = data;
// remove image from gallery // /**
dispatch(removeImage(data)); // * the logic here ideally would be in the reducer but we have a side effect:
// * generating a uuid. so the logic needs to be here, outside redux.
// */
// remove references to image in options // // Generate a UUID for each image
const { // const preparedImages = images.map((image): InvokeAI._Image => {
generation: { initialImage, maskPath }, // return {
} = getState(); // uuid: uuidv4(),
// ...image,
// };
// });
if ( // dispatch(
initialImage === url || // addGalleryImages({
(initialImage as InvokeAI._Image)?.url === url // images: preparedImages,
) { // areMoreImagesAvailable,
dispatch(clearInitialImage()); // category,
} // })
// );
if (maskPath === url) { // dispatch(
dispatch(setMaskPath('')); // addLogEntry({
} // timestamp: dateFormat(new Date(), 'isoDateTime'),
// message: `Loaded ${images.length} images`,
// })
// );
// },
// /**
// * Callback to run when we receive a 'processingCanceled' event.
// */
// onProcessingCanceled: () => {
// dispatch(processingCanceled());
dispatch( // const { intermediateImage } = getState().gallery;
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Image deleted: ${url}`,
})
);
},
onSystemConfig: (data: InvokeAI.SystemConfig) => {
dispatch(setSystemConfig(data));
if (!data.infill_methods.includes('patchmatch')) {
dispatch(setInfillMethod(data.infill_methods[0]));
}
},
onFoundModels: (data: InvokeAI.FoundModelResponse) => {
const { search_folder, found_models } = data;
dispatch(setSearchFolder(search_folder));
dispatch(setFoundModels(found_models));
},
onNewModelAdded: (data: InvokeAI.ModelAddedResponse) => {
const { new_model_name, model_list, update } = data;
dispatch(setModelList(model_list));
dispatch(setIsProcessing(false));
dispatch(setCurrentStatus(i18n.t('modelManager.modelAdded')));
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Model Added: ${new_model_name}`,
level: 'info',
})
);
dispatch(
addToast({
title: !update
? `${i18n.t('modelManager.modelAdded')}: ${new_model_name}`
: `${i18n.t('modelManager.modelUpdated')}: ${new_model_name}`,
status: 'success',
duration: 2500,
isClosable: true,
})
);
},
onModelDeleted: (data: InvokeAI.ModelDeletedResponse) => {
const { deleted_model_name, model_list } = data;
dispatch(setModelList(model_list));
dispatch(setIsProcessing(false));
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `${i18n.t(
'modelManager.modelAdded'
)}: ${deleted_model_name}`,
level: 'info',
})
);
dispatch(
addToast({
title: `${i18n.t(
'modelManager.modelEntryDeleted'
)}: ${deleted_model_name}`,
status: 'success',
duration: 2500,
isClosable: true,
})
);
},
onModelConverted: (data: InvokeAI.ModelConvertedResponse) => {
const { converted_model_name, model_list } = data;
dispatch(setModelList(model_list));
dispatch(setCurrentStatus(i18n.t('common.statusModelConverted')));
dispatch(setIsProcessing(false));
dispatch(setIsCancelable(true));
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Model converted: ${converted_model_name}`,
level: 'info',
})
);
dispatch(
addToast({
title: `${i18n.t(
'modelManager.modelConverted'
)}: ${converted_model_name}`,
status: 'success',
duration: 2500,
isClosable: true,
})
);
},
onModelsMerged: (data: InvokeAI.ModelsMergedResponse) => {
const { merged_models, merged_model_name, model_list } = data;
dispatch(setModelList(model_list));
dispatch(setCurrentStatus(i18n.t('common.statusMergedModels')));
dispatch(setIsProcessing(false));
dispatch(setIsCancelable(true));
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Models merged: ${merged_models}`,
level: 'info',
})
);
dispatch(
addToast({
title: `${i18n.t('modelManager.modelsMerged')}: ${merged_model_name}`,
status: 'success',
duration: 2500,
isClosable: true,
})
);
},
onModelChanged: (data: InvokeAI.ModelChangeResponse) => {
const { model_name, model_list } = data;
dispatch(setModelList(model_list));
dispatch(setCurrentStatus(i18n.t('common.statusModelChanged')));
dispatch(setIsProcessing(false));
dispatch(setIsCancelable(true));
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Model changed: ${model_name}`,
level: 'info',
})
);
},
onModelChangeFailed: (data: InvokeAI.ModelChangeResponse) => {
const { model_name, model_list } = data;
dispatch(setModelList(model_list));
dispatch(setIsProcessing(false));
dispatch(setIsCancelable(true));
dispatch(errorOccurred());
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Model change failed: ${model_name}`,
level: 'error',
})
);
},
onTempFolderEmptied: () => {
dispatch(
addToast({
title: i18n.t('toast.tempFoldersEmptied'),
status: 'success',
duration: 2500,
isClosable: true,
})
);
},
};
};
export default makeSocketIOListeners; // if (intermediateImage) {
// if (!intermediateImage.isBase64) {
// dispatch(
// addImage({
// category: 'result',
// image: intermediateImage,
// })
// );
// dispatch(
// addLogEntry({
// timestamp: dateFormat(new Date(), 'isoDateTime'),
// message: `Intermediate image saved: ${intermediateImage.url}`,
// })
// );
// }
// dispatch(clearIntermediateImage());
// }
// dispatch(
// addLogEntry({
// timestamp: dateFormat(new Date(), 'isoDateTime'),
// message: `Processing canceled`,
// level: 'warning',
// })
// );
// },
// /**
// * Callback to run when we receive a 'imageDeleted' event.
// */
// onImageDeleted: (data: InvokeAI.ImageDeletedResponse) => {
// const { url } = data;
// // remove image from gallery
// dispatch(removeImage(data));
// // remove references to image in options
// const {
// generation: { initialImage, maskPath },
// } = getState();
// if (
// initialImage === url ||
// (initialImage as InvokeAI._Image)?.url === url
// ) {
// dispatch(clearInitialImage());
// }
// if (maskPath === url) {
// dispatch(setMaskPath(''));
// }
// dispatch(
// addLogEntry({
// timestamp: dateFormat(new Date(), 'isoDateTime'),
// message: `Image deleted: ${url}`,
// })
// );
// },
// onSystemConfig: (data: InvokeAI.SystemConfig) => {
// dispatch(setSystemConfig(data));
// if (!data.infill_methods.includes('patchmatch')) {
// dispatch(setInfillMethod(data.infill_methods[0]));
// }
// },
// onFoundModels: (data: InvokeAI.FoundModelResponse) => {
// const { search_folder, found_models } = data;
// dispatch(setSearchFolder(search_folder));
// dispatch(setFoundModels(found_models));
// },
// onNewModelAdded: (data: InvokeAI.ModelAddedResponse) => {
// const { new_model_name, model_list, update } = data;
// dispatch(setModelList(model_list));
// dispatch(setIsProcessing(false));
// dispatch(setCurrentStatus(i18n.t('modelManager.modelAdded')));
// dispatch(
// addLogEntry({
// timestamp: dateFormat(new Date(), 'isoDateTime'),
// message: `Model Added: ${new_model_name}`,
// level: 'info',
// })
// );
// dispatch(
// addToast({
// title: !update
// ? `${i18n.t('modelManager.modelAdded')}: ${new_model_name}`
// : `${i18n.t('modelManager.modelUpdated')}: ${new_model_name}`,
// status: 'success',
// duration: 2500,
// isClosable: true,
// })
// );
// },
// onModelDeleted: (data: InvokeAI.ModelDeletedResponse) => {
// const { deleted_model_name, model_list } = data;
// dispatch(setModelList(model_list));
// dispatch(setIsProcessing(false));
// dispatch(
// addLogEntry({
// timestamp: dateFormat(new Date(), 'isoDateTime'),
// message: `${i18n.t(
// 'modelManager.modelAdded'
// )}: ${deleted_model_name}`,
// level: 'info',
// })
// );
// dispatch(
// addToast({
// title: `${i18n.t(
// 'modelManager.modelEntryDeleted'
// )}: ${deleted_model_name}`,
// status: 'success',
// duration: 2500,
// isClosable: true,
// })
// );
// },
// onModelConverted: (data: InvokeAI.ModelConvertedResponse) => {
// const { converted_model_name, model_list } = data;
// dispatch(setModelList(model_list));
// dispatch(setCurrentStatus(i18n.t('common.statusModelConverted')));
// dispatch(setIsProcessing(false));
// dispatch(setIsCancelable(true));
// dispatch(
// addLogEntry({
// timestamp: dateFormat(new Date(), 'isoDateTime'),
// message: `Model converted: ${converted_model_name}`,
// level: 'info',
// })
// );
// dispatch(
// addToast({
// title: `${i18n.t(
// 'modelManager.modelConverted'
// )}: ${converted_model_name}`,
// status: 'success',
// duration: 2500,
// isClosable: true,
// })
// );
// },
// onModelsMerged: (data: InvokeAI.ModelsMergedResponse) => {
// const { merged_models, merged_model_name, model_list } = data;
// dispatch(setModelList(model_list));
// dispatch(setCurrentStatus(i18n.t('common.statusMergedModels')));
// dispatch(setIsProcessing(false));
// dispatch(setIsCancelable(true));
// dispatch(
// addLogEntry({
// timestamp: dateFormat(new Date(), 'isoDateTime'),
// message: `Models merged: ${merged_models}`,
// level: 'info',
// })
// );
// dispatch(
// addToast({
// title: `${i18n.t('modelManager.modelsMerged')}: ${merged_model_name}`,
// status: 'success',
// duration: 2500,
// isClosable: true,
// })
// );
// },
// onModelChanged: (data: InvokeAI.ModelChangeResponse) => {
// const { model_name, model_list } = data;
// dispatch(setModelList(model_list));
// dispatch(setCurrentStatus(i18n.t('common.statusModelChanged')));
// dispatch(setIsProcessing(false));
// dispatch(setIsCancelable(true));
// dispatch(
// addLogEntry({
// timestamp: dateFormat(new Date(), 'isoDateTime'),
// message: `Model changed: ${model_name}`,
// level: 'info',
// })
// );
// },
// onModelChangeFailed: (data: InvokeAI.ModelChangeResponse) => {
// const { model_name, model_list } = data;
// dispatch(setModelList(model_list));
// dispatch(setIsProcessing(false));
// dispatch(setIsCancelable(true));
// dispatch(errorOccurred());
// dispatch(
// addLogEntry({
// timestamp: dateFormat(new Date(), 'isoDateTime'),
// message: `Model change failed: ${model_name}`,
// level: 'error',
// })
// );
// },
// onTempFolderEmptied: () => {
// dispatch(
// addToast({
// title: i18n.t('toast.tempFoldersEmptied'),
// status: 'success',
// duration: 2500,
// isClosable: true,
// })
// );
// },
// };
// };
// export default makeSocketIOListeners;
export default {};

View File

@ -1,246 +1,248 @@
import { Middleware } from '@reduxjs/toolkit'; // import { Middleware } from '@reduxjs/toolkit';
import { io } from 'socket.io-client'; // import { io } from 'socket.io-client';
import makeSocketIOEmitters from './emitters'; // import makeSocketIOEmitters from './emitters';
import makeSocketIOListeners from './listeners'; // import makeSocketIOListeners from './listeners';
import * as InvokeAI from 'app/invokeai'; // import * as InvokeAI from 'app/types/invokeai';
/** // /**
* Creates a socketio middleware to handle communication with server. // * Creates a socketio middleware to handle communication with server.
* // *
* Special `socketio/actionName` actions are created in actions.ts and // * Special `socketio/actionName` actions are created in actions.ts and
* exported for use by the application, which treats them like any old // * exported for use by the application, which treats them like any old
* action, using `dispatch` to dispatch them. // * action, using `dispatch` to dispatch them.
* // *
* These actions are intercepted here, where `socketio.emit()` calls are // * These actions are intercepted here, where `socketio.emit()` calls are
* made on their behalf - see `emitters.ts`. The emitter functions // * made on their behalf - see `emitters.ts`. The emitter functions
* are the outbound communication to the server. // * are the outbound communication to the server.
* // *
* Listeners are also established here - see `listeners.ts`. The listener // * Listeners are also established here - see `listeners.ts`. The listener
* functions receive communication from the server and usually dispatch // * functions receive communication from the server and usually dispatch
* some new action to handle whatever data was sent from the server. // * some new action to handle whatever data was sent from the server.
*/ // */
export const socketioMiddleware = () => { // export const socketioMiddleware = () => {
const { origin } = new URL(window.location.href); // const { origin } = new URL(window.location.href);
const socketio = io(origin, { // const socketio = io(origin, {
timeout: 60000, // timeout: 60000,
path: `${window.location.pathname}socket.io`, // path: `${window.location.pathname}socket.io`,
}); // });
socketio.disconnect(); // socketio.disconnect();
let areListenersSet = false; // let areListenersSet = false;
const middleware: Middleware = (store) => (next) => (action) => { // const middleware: Middleware = (store) => (next) => (action) => {
const { // const {
onConnect, // onConnect,
onDisconnect, // onDisconnect,
onError, // onError,
onPostprocessingResult, // onPostprocessingResult,
onGenerationResult, // onGenerationResult,
onIntermediateResult, // onIntermediateResult,
onProgressUpdate, // onProgressUpdate,
onGalleryImages, // onGalleryImages,
onProcessingCanceled, // onProcessingCanceled,
onImageDeleted, // onImageDeleted,
onSystemConfig, // onSystemConfig,
onModelChanged, // onModelChanged,
onFoundModels, // onFoundModels,
onNewModelAdded, // onNewModelAdded,
onModelDeleted, // onModelDeleted,
onModelConverted, // onModelConverted,
onModelsMerged, // onModelsMerged,
onModelChangeFailed, // onModelChangeFailed,
onTempFolderEmptied, // onTempFolderEmptied,
} = makeSocketIOListeners(store); // } = makeSocketIOListeners(store);
const { // const {
emitGenerateImage, // emitGenerateImage,
emitRunESRGAN, // emitRunESRGAN,
emitRunFacetool, // emitRunFacetool,
emitDeleteImage, // emitDeleteImage,
emitRequestImages, // emitRequestImages,
emitRequestNewImages, // emitRequestNewImages,
emitCancelProcessing, // emitCancelProcessing,
emitRequestSystemConfig, // emitRequestSystemConfig,
emitSearchForModels, // emitSearchForModels,
emitAddNewModel, // emitAddNewModel,
emitDeleteModel, // emitDeleteModel,
emitConvertToDiffusers, // emitConvertToDiffusers,
emitMergeDiffusersModels, // emitMergeDiffusersModels,
emitRequestModelChange, // emitRequestModelChange,
emitSaveStagingAreaImageToGallery, // emitSaveStagingAreaImageToGallery,
emitRequestEmptyTempFolder, // emitRequestEmptyTempFolder,
} = makeSocketIOEmitters(store, socketio); // } = makeSocketIOEmitters(store, socketio);
/** // /**
* If this is the first time the middleware has been called (e.g. during store setup), // * If this is the first time the middleware has been called (e.g. during store setup),
* initialize all our socket.io listeners. // * initialize all our socket.io listeners.
*/ // */
if (!areListenersSet) { // if (!areListenersSet) {
socketio.on('connect', () => onConnect()); // socketio.on('connect', () => onConnect());
socketio.on('disconnect', () => onDisconnect()); // socketio.on('disconnect', () => onDisconnect());
socketio.on('error', (data: InvokeAI.ErrorResponse) => onError(data)); // socketio.on('error', (data: InvokeAI.ErrorResponse) => onError(data));
socketio.on('generationResult', (data: InvokeAI.ImageResultResponse) => // socketio.on('generationResult', (data: InvokeAI.ImageResultResponse) =>
onGenerationResult(data) // onGenerationResult(data)
); // );
socketio.on( // socketio.on(
'postprocessingResult', // 'postprocessingResult',
(data: InvokeAI.ImageResultResponse) => onPostprocessingResult(data) // (data: InvokeAI.ImageResultResponse) => onPostprocessingResult(data)
); // );
socketio.on('intermediateResult', (data: InvokeAI.ImageResultResponse) => // socketio.on('intermediateResult', (data: InvokeAI.ImageResultResponse) =>
onIntermediateResult(data) // onIntermediateResult(data)
); // );
socketio.on('progressUpdate', (data: InvokeAI.SystemStatus) => // socketio.on('progressUpdate', (data: InvokeAI.SystemStatus) =>
onProgressUpdate(data) // onProgressUpdate(data)
); // );
socketio.on('galleryImages', (data: InvokeAI.GalleryImagesResponse) => // socketio.on('galleryImages', (data: InvokeAI.GalleryImagesResponse) =>
onGalleryImages(data) // onGalleryImages(data)
); // );
socketio.on('processingCanceled', () => { // socketio.on('processingCanceled', () => {
onProcessingCanceled(); // onProcessingCanceled();
}); // });
socketio.on('imageDeleted', (data: InvokeAI.ImageDeletedResponse) => { // socketio.on('imageDeleted', (data: InvokeAI.ImageDeletedResponse) => {
onImageDeleted(data); // onImageDeleted(data);
}); // });
socketio.on('systemConfig', (data: InvokeAI.SystemConfig) => { // socketio.on('systemConfig', (data: InvokeAI.SystemConfig) => {
onSystemConfig(data); // onSystemConfig(data);
}); // });
socketio.on('foundModels', (data: InvokeAI.FoundModelResponse) => { // socketio.on('foundModels', (data: InvokeAI.FoundModelResponse) => {
onFoundModels(data); // onFoundModels(data);
}); // });
socketio.on('newModelAdded', (data: InvokeAI.ModelAddedResponse) => { // socketio.on('newModelAdded', (data: InvokeAI.ModelAddedResponse) => {
onNewModelAdded(data); // onNewModelAdded(data);
}); // });
socketio.on('modelDeleted', (data: InvokeAI.ModelDeletedResponse) => { // socketio.on('modelDeleted', (data: InvokeAI.ModelDeletedResponse) => {
onModelDeleted(data); // onModelDeleted(data);
}); // });
socketio.on('modelConverted', (data: InvokeAI.ModelConvertedResponse) => { // socketio.on('modelConverted', (data: InvokeAI.ModelConvertedResponse) => {
onModelConverted(data); // onModelConverted(data);
}); // });
socketio.on('modelsMerged', (data: InvokeAI.ModelsMergedResponse) => { // socketio.on('modelsMerged', (data: InvokeAI.ModelsMergedResponse) => {
onModelsMerged(data); // onModelsMerged(data);
}); // });
socketio.on('modelChanged', (data: InvokeAI.ModelChangeResponse) => { // socketio.on('modelChanged', (data: InvokeAI.ModelChangeResponse) => {
onModelChanged(data); // onModelChanged(data);
}); // });
socketio.on('modelChangeFailed', (data: InvokeAI.ModelChangeResponse) => { // socketio.on('modelChangeFailed', (data: InvokeAI.ModelChangeResponse) => {
onModelChangeFailed(data); // onModelChangeFailed(data);
}); // });
socketio.on('tempFolderEmptied', () => { // socketio.on('tempFolderEmptied', () => {
onTempFolderEmptied(); // onTempFolderEmptied();
}); // });
areListenersSet = true; // areListenersSet = true;
} // }
/** // /**
* Handle redux actions caught by middleware. // * Handle redux actions caught by middleware.
*/ // */
switch (action.type) { // switch (action.type) {
case 'socketio/generateImage': { // case 'socketio/generateImage': {
emitGenerateImage(action.payload); // emitGenerateImage(action.payload);
break; // break;
} // }
case 'socketio/runESRGAN': { // case 'socketio/runESRGAN': {
emitRunESRGAN(action.payload); // emitRunESRGAN(action.payload);
break; // break;
} // }
case 'socketio/runFacetool': { // case 'socketio/runFacetool': {
emitRunFacetool(action.payload); // emitRunFacetool(action.payload);
break; // break;
} // }
case 'socketio/deleteImage': { // case 'socketio/deleteImage': {
emitDeleteImage(action.payload); // emitDeleteImage(action.payload);
break; // break;
} // }
case 'socketio/requestImages': { // case 'socketio/requestImages': {
emitRequestImages(action.payload); // emitRequestImages(action.payload);
break; // break;
} // }
case 'socketio/requestNewImages': { // case 'socketio/requestNewImages': {
emitRequestNewImages(action.payload); // emitRequestNewImages(action.payload);
break; // break;
} // }
case 'socketio/cancelProcessing': { // case 'socketio/cancelProcessing': {
emitCancelProcessing(); // emitCancelProcessing();
break; // break;
} // }
case 'socketio/requestSystemConfig': { // case 'socketio/requestSystemConfig': {
emitRequestSystemConfig(); // emitRequestSystemConfig();
break; // break;
} // }
case 'socketio/searchForModels': { // case 'socketio/searchForModels': {
emitSearchForModels(action.payload); // emitSearchForModels(action.payload);
break; // break;
} // }
case 'socketio/addNewModel': { // case 'socketio/addNewModel': {
emitAddNewModel(action.payload); // emitAddNewModel(action.payload);
break; // break;
} // }
case 'socketio/deleteModel': { // case 'socketio/deleteModel': {
emitDeleteModel(action.payload); // emitDeleteModel(action.payload);
break; // break;
} // }
case 'socketio/convertToDiffusers': { // case 'socketio/convertToDiffusers': {
emitConvertToDiffusers(action.payload); // emitConvertToDiffusers(action.payload);
break; // break;
} // }
case 'socketio/mergeDiffusersModels': { // case 'socketio/mergeDiffusersModels': {
emitMergeDiffusersModels(action.payload); // emitMergeDiffusersModels(action.payload);
break; // break;
} // }
case 'socketio/requestModelChange': { // case 'socketio/requestModelChange': {
emitRequestModelChange(action.payload); // emitRequestModelChange(action.payload);
break; // break;
} // }
case 'socketio/saveStagingAreaImageToGallery': { // case 'socketio/saveStagingAreaImageToGallery': {
emitSaveStagingAreaImageToGallery(action.payload); // emitSaveStagingAreaImageToGallery(action.payload);
break; // break;
} // }
case 'socketio/requestEmptyTempFolder': { // case 'socketio/requestEmptyTempFolder': {
emitRequestEmptyTempFolder(); // emitRequestEmptyTempFolder();
break; // break;
} // }
} // }
next(action); // next(action);
}; // };
return middleware; // return middleware;
}; // };
export default {};

View File

@ -1,4 +1,4 @@
import { store } from 'app/store'; import { store } from 'app/store/store';
import { persistStore } from 'redux-persist'; import { persistStore } from 'redux-persist';
export const persistor = persistStore(store); export const persistor = persistStore(store);

View File

@ -19,8 +19,6 @@ import hotkeysReducer from 'features/ui/store/hotkeysSlice';
import modelsReducer from 'features/system/store/modelSlice'; import modelsReducer from 'features/system/store/modelSlice';
import nodesReducer from 'features/nodes/store/nodesSlice'; import nodesReducer from 'features/nodes/store/nodesSlice';
import { socketioMiddleware } from './socketio/middleware';
import { socketMiddleware } from 'services/events/middleware';
import { canvasDenylist } from 'features/canvas/store/canvasPersistDenylist'; import { canvasDenylist } from 'features/canvas/store/canvasPersistDenylist';
import { galleryDenylist } from 'features/gallery/store/galleryPersistDenylist'; import { galleryDenylist } from 'features/gallery/store/galleryPersistDenylist';
import { generationDenylist } from 'features/parameters/store/generationPersistDenylist'; import { generationDenylist } from 'features/parameters/store/generationPersistDenylist';
@ -28,8 +26,10 @@ import { lightboxDenylist } from 'features/lightbox/store/lightboxPersistDenylis
import { modelsDenylist } from 'features/system/store/modelsPersistDenylist'; import { modelsDenylist } from 'features/system/store/modelsPersistDenylist';
import { nodesDenylist } from 'features/nodes/store/nodesPersistDenylist'; import { nodesDenylist } from 'features/nodes/store/nodesPersistDenylist';
import { postprocessingDenylist } from 'features/parameters/store/postprocessingPersistDenylist'; import { postprocessingDenylist } from 'features/parameters/store/postprocessingPersistDenylist';
import { systemDenylist } from 'features/system/store/systemPersistsDenylist'; import { systemDenylist } from 'features/system/store/systemPersistDenylist';
import { uiDenylist } from 'features/ui/store/uiPersistDenylist'; import { uiDenylist } from 'features/ui/store/uiPersistDenylist';
import { resultsDenylist } from 'features/gallery/store/resultsPersistDenylist';
import { uploadsDenylist } from 'features/gallery/store/uploadsPersistDenylist';
/** /**
* redux-persist provides an easy and reliable way to persist state across reloads. * redux-persist provides an easy and reliable way to persist state across reloads.
@ -82,19 +82,18 @@ const rootPersistConfig = getPersistConfig({
'hotkeys', 'hotkeys',
'config', 'config',
], ],
debounce: 300,
}); });
const persistedReducer = persistReducer(rootPersistConfig, rootReducer); const persistedReducer = persistReducer(rootPersistConfig, rootReducer);
// TODO: rip the old middleware out when nodes is complete // TODO: rip the old middleware out when nodes is complete
export function buildMiddleware() { // export function buildMiddleware() {
if (import.meta.env.MODE === 'nodes' || import.meta.env.MODE === 'package') { // if (import.meta.env.MODE === 'nodes' || import.meta.env.MODE === 'package') {
return socketMiddleware(); // return socketMiddleware();
} else { // } else {
return socketioMiddleware(); // return socketioMiddleware();
} // }
} // }
export const store = configureStore({ export const store = configureStore({
reducer: persistedReducer, reducer: persistedReducer,
@ -114,6 +113,7 @@ export const store = configureStore({
'canvas/setBoundingBoxDimensions', 'canvas/setBoundingBoxDimensions',
'canvas/setIsDrawing', 'canvas/setIsDrawing',
'canvas/addPointToCurrentLine', 'canvas/addPointToCurrentLine',
'socket/generatorProgress',
], ],
}, },
}); });

View File

@ -1,5 +1,5 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import { AppDispatch, RootState } from './store'; import { AppDispatch, RootState } from 'app/store/store';
// Use throughout your app instead of plain `useDispatch` and `useSelector` // Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch; export const useAppDispatch: () => AppDispatch = useDispatch;

View File

@ -1,5 +1,5 @@
import { createAsyncThunk } from '@reduxjs/toolkit'; import { createAsyncThunk } from '@reduxjs/toolkit';
import { AppDispatch, RootState } from './store'; import { AppDispatch, RootState } from 'app/store/store';
// https://redux-toolkit.js.org/usage/usage-with-typescript#defining-a-pre-typed-createasyncthunk // https://redux-toolkit.js.org/usage/usage-with-typescript#defining-a-pre-typed-createasyncthunk
export const createAppAsyncThunk = createAsyncThunk.withTypes<{ export const createAppAsyncThunk = createAsyncThunk.withTypes<{

View File

@ -12,10 +12,11 @@
* 'gfpgan'. * 'gfpgan'.
*/ */
import { GalleryCategory } from 'features/gallery/store/gallerySlice';
import { FacetoolType } from 'features/parameters/store/postprocessingSlice'; import { FacetoolType } from 'features/parameters/store/postprocessingSlice';
import { InvokeTabName } from 'features/ui/store/tabMap'; import { InvokeTabName } from 'features/ui/store/tabMap';
import { IRect } from 'konva/lib/types'; import { IRect } from 'konva/lib/types';
import { ImageMetadata, ImageType } from 'services/api'; import { ImageResponseMetadata, ImageType } from 'services/api';
import { AnyInvocation } from 'services/events/types'; import { AnyInvocation } from 'services/events/types';
import { O } from 'ts-toolbelt'; import { O } from 'ts-toolbelt';
@ -28,24 +29,24 @@ import { O } from 'ts-toolbelt';
* TODO: Better documentation of types. * TODO: Better documentation of types.
*/ */
export declare type PromptItem = { export type PromptItem = {
prompt: string; prompt: string;
weight: number; weight: number;
}; };
// TECHDEBT: We need to retain compatibility with plain prompt strings and the structure Prompt type // TECHDEBT: We need to retain compatibility with plain prompt strings and the structure Prompt type
export declare type Prompt = Array<PromptItem> | string; export type Prompt = Array<PromptItem> | string;
export declare type SeedWeightPair = { export type SeedWeightPair = {
seed: number; seed: number;
weight: number; weight: number;
}; };
export declare type SeedWeights = Array<SeedWeightPair>; export type SeedWeights = Array<SeedWeightPair>;
// All generated images contain these metadata. // All generated images contain these metadata.
export declare type CommonGeneratedImageMetadata = { export type CommonGeneratedImageMetadata = {
postprocessing: null | Array<ESRGANMetadata | GFPGANMetadata>; postprocessing: null | Array<ESRGANMetadata | FacetoolMetadata>;
sampler: sampler:
| 'ddim' | 'ddim'
| 'k_dpm_2_a' | 'k_dpm_2_a'
@ -70,11 +71,11 @@ export declare type CommonGeneratedImageMetadata = {
}; };
// txt2img and img2img images have some unique attributes. // txt2img and img2img images have some unique attributes.
export declare type Txt2ImgMetadata = GeneratedImageMetadata & { export type Txt2ImgMetadata = CommonGeneratedImageMetadata & {
type: 'txt2img'; type: 'txt2img';
}; };
export declare type Img2ImgMetadata = GeneratedImageMetadata & { export type Img2ImgMetadata = CommonGeneratedImageMetadata & {
type: 'img2img'; type: 'img2img';
orig_hash: string; orig_hash: string;
strength: number; strength: number;
@ -84,102 +85,80 @@ export declare type Img2ImgMetadata = GeneratedImageMetadata & {
}; };
// Superset of generated image metadata types. // Superset of generated image metadata types.
export declare type GeneratedImageMetadata = Txt2ImgMetadata | Img2ImgMetadata; export type GeneratedImageMetadata = Txt2ImgMetadata | Img2ImgMetadata;
// All post processed images contain these metadata. // All post processed images contain these metadata.
export declare type CommonPostProcessedImageMetadata = { export type CommonPostProcessedImageMetadata = {
orig_path: string; orig_path: string;
orig_hash: string; orig_hash: string;
}; };
// esrgan and gfpgan images have some unique attributes. // esrgan and gfpgan images have some unique attributes.
export declare type ESRGANMetadata = CommonPostProcessedImageMetadata & { export type ESRGANMetadata = CommonPostProcessedImageMetadata & {
type: 'esrgan'; type: 'esrgan';
scale: 2 | 4; scale: 2 | 4;
strength: number; strength: number;
denoise_str: number; denoise_str: number;
}; };
export declare type FacetoolMetadata = CommonPostProcessedImageMetadata & { export type FacetoolMetadata = CommonPostProcessedImageMetadata & {
type: 'gfpgan' | 'codeformer'; type: 'gfpgan' | 'codeformer';
strength: number; strength: number;
fidelity?: number; fidelity?: number;
}; };
// Superset of all postprocessed image metadata types.. // Superset of all postprocessed image metadata types..
export declare type PostProcessedImageMetadata = export type PostProcessedImageMetadata = ESRGANMetadata | FacetoolMetadata;
| ESRGANMetadata
| FacetoolMetadata;
// Metadata includes the system config and image metadata. // Metadata includes the system config and image metadata.
export declare type Metadata = SystemGenerationMetadata & { // export type Metadata = SystemGenerationMetadata & {
image: GeneratedImageMetadata | PostProcessedImageMetadata; // image: GeneratedImageMetadata | PostProcessedImageMetadata;
}; // };
// An Image has a UUID, url, modified timestamp, width, height and maybe metadata
export declare type _Image = {
uuid: string;
url: string;
thumbnail: string;
mtime: number;
metadata?: Metadata;
width: number;
height: number;
category: GalleryCategory;
isBase64?: boolean;
dreamPrompt?: 'string';
name?: string;
};
/** /**
* ResultImage * ResultImage
*/ */
export declare type Image = { export type Image = {
name: string; name: string;
type: ImageType; type: ImageType;
url: string; url: string;
thumbnail: string; thumbnail: string;
metadata: ImageMetadata; metadata: ImageResponseMetadata;
};
// GalleryImages is an array of Image.
export declare type GalleryImages = {
images: Array<_Image>;
}; };
/** /**
* Types related to the system status. * Types related to the system status.
*/ */
// This represents the processing status of the backend. // // This represents the processing status of the backend.
export declare type SystemStatus = { // export type SystemStatus = {
isProcessing: boolean; // isProcessing: boolean;
currentStep: number; // currentStep: number;
totalSteps: number; // totalSteps: number;
currentIteration: number; // currentIteration: number;
totalIterations: number; // totalIterations: number;
currentStatus: string; // currentStatus: string;
currentStatusHasSteps: boolean; // currentStatusHasSteps: boolean;
hasError: boolean; // hasError: boolean;
}; // };
export declare type SystemGenerationMetadata = { // export type SystemGenerationMetadata = {
model: string; // model: string;
model_weights?: string; // model_weights?: string;
model_id?: string; // model_id?: string;
model_hash: string; // model_hash: string;
app_id: string; // app_id: string;
app_version: string; // app_version: string;
}; // };
export declare type SystemConfig = SystemGenerationMetadata & { // export type SystemConfig = SystemGenerationMetadata & {
model_list: ModelList; // model_list: ModelList;
infill_methods: string[]; // infill_methods: string[];
}; // };
export declare type ModelStatus = 'active' | 'cached' | 'not loaded'; export type ModelStatus = 'active' | 'cached' | 'not loaded';
export declare type Model = { export type Model = {
status: ModelStatus; status: ModelStatus;
description: string; description: string;
weights: string; weights: string;
@ -191,7 +170,7 @@ export declare type Model = {
format?: string; format?: string;
}; };
export declare type DiffusersModel = { export type DiffusersModel = {
status: ModelStatus; status: ModelStatus;
description: string; description: string;
repo_id?: string; repo_id?: string;
@ -204,14 +183,14 @@ export declare type DiffusersModel = {
default?: boolean; default?: boolean;
}; };
export declare type ModelList = Record<string, Model & DiffusersModel>; export type ModelList = Record<string, Model & DiffusersModel>;
export declare type FoundModel = { export type FoundModel = {
name: string; name: string;
location: string; location: string;
}; };
export declare type InvokeModelConfigProps = { export type InvokeModelConfigProps = {
name: string | undefined; name: string | undefined;
description: string | undefined; description: string | undefined;
config: string | undefined; config: string | undefined;
@ -223,7 +202,7 @@ export declare type InvokeModelConfigProps = {
format: string | undefined; format: string | undefined;
}; };
export declare type InvokeDiffusersModelConfigProps = { export type InvokeDiffusersModelConfigProps = {
name: string | undefined; name: string | undefined;
description: string | undefined; description: string | undefined;
repo_id: string | undefined; repo_id: string | undefined;
@ -236,13 +215,13 @@ export declare type InvokeDiffusersModelConfigProps = {
}; };
}; };
export declare type InvokeModelConversionProps = { export type InvokeModelConversionProps = {
model_name: string; model_name: string;
save_location: string; save_location: string;
custom_location: string | null; custom_location: string | null;
}; };
export declare type InvokeModelMergingProps = { export type InvokeModelMergingProps = {
models_to_merge: string[]; models_to_merge: string[];
alpha: number; alpha: number;
interp: 'weighted_sum' | 'sigmoid' | 'inv_sigmoid' | 'add_difference'; interp: 'weighted_sum' | 'sigmoid' | 'inv_sigmoid' | 'add_difference';
@ -255,48 +234,48 @@ export declare type InvokeModelMergingProps = {
* These types type data received from the server via socketio. * These types type data received from the server via socketio.
*/ */
export declare type ModelChangeResponse = { export type ModelChangeResponse = {
model_name: string; model_name: string;
model_list: ModelList; model_list: ModelList;
}; };
export declare type ModelConvertedResponse = { export type ModelConvertedResponse = {
converted_model_name: string; converted_model_name: string;
model_list: ModelList; model_list: ModelList;
}; };
export declare type ModelsMergedResponse = { export type ModelsMergedResponse = {
merged_models: string[]; merged_models: string[];
merged_model_name: string; merged_model_name: string;
model_list: ModelList; model_list: ModelList;
}; };
export declare type ModelAddedResponse = { export type ModelAddedResponse = {
new_model_name: string; new_model_name: string;
model_list: ModelList; model_list: ModelList;
update: boolean; update: boolean;
}; };
export declare type ModelDeletedResponse = { export type ModelDeletedResponse = {
deleted_model_name: string; deleted_model_name: string;
model_list: ModelList; model_list: ModelList;
}; };
export declare type FoundModelResponse = { export type FoundModelResponse = {
search_folder: string; search_folder: string;
found_models: FoundModel[]; found_models: FoundModel[];
}; };
export declare type SystemStatusResponse = SystemStatus; // export type SystemStatusResponse = SystemStatus;
export declare type SystemConfigResponse = SystemConfig; // export type SystemConfigResponse = SystemConfig;
export declare type ImageResultResponse = Omit<_Image, 'uuid'> & { export type ImageResultResponse = Omit<_Image, 'uuid'> & {
boundingBox?: IRect; boundingBox?: IRect;
generationMode: InvokeTabName; generationMode: InvokeTabName;
}; };
export declare type ImageUploadResponse = { export type ImageUploadResponse = {
// image: Omit<Image, 'uuid' | 'metadata' | 'category'>; // image: Omit<Image, 'uuid' | 'metadata' | 'category'>;
url: string; url: string;
mtime: number; mtime: number;
@ -306,33 +285,16 @@ export declare type ImageUploadResponse = {
// bbox: [number, number, number, number]; // bbox: [number, number, number, number];
}; };
export declare type ErrorResponse = { export type ErrorResponse = {
message: string; message: string;
additionalData?: string; additionalData?: string;
}; };
export declare type GalleryImagesResponse = { export type ImageUrlResponse = {
images: Array<Omit<_Image, 'uuid'>>;
areMoreImagesAvailable: boolean;
category: GalleryCategory;
};
export declare type ImageDeletedResponse = {
uuid: string;
url: string;
category: GalleryCategory;
};
export declare type ImageUrlResponse = {
url: string; url: string;
}; };
export declare type UploadImagePayload = { export type UploadOutpaintingMergeImagePayload = {
file: File;
destination?: ImageUploadDestination;
};
export declare type UploadOutpaintingMergeImagePayload = {
dataURL: string; dataURL: string;
name: string; name: string;
}; };
@ -340,7 +302,7 @@ export declare type UploadOutpaintingMergeImagePayload = {
/** /**
* A disable-able application feature * A disable-able application feature
*/ */
export declare type AppFeature = export type AppFeature =
| 'faceRestore' | 'faceRestore'
| 'upscaling' | 'upscaling'
| 'lightbox' | 'lightbox'
@ -353,7 +315,7 @@ export declare type AppFeature =
/** /**
* A disable-able Stable Diffusion feature * A disable-able Stable Diffusion feature
*/ */
export declare type StableDiffusionFeature = export type StableDiffusionFeature =
| 'noiseConfig' | 'noiseConfig'
| 'variations' | 'variations'
| 'symmetry' | 'symmetry'
@ -364,7 +326,7 @@ export declare type StableDiffusionFeature =
* Configuration options for the InvokeAI UI. * Configuration options for the InvokeAI UI.
* Distinct from system settings which may be changed inside the app. * Distinct from system settings which may be changed inside the app.
*/ */
export declare type AppConfig = { export type AppConfig = {
/** /**
* Whether or not URLs should be transformed to use a different host * Whether or not URLs should be transformed to use a different host
*/ */
@ -428,4 +390,4 @@ export declare type AppConfig = {
}; };
}; };
export declare type PartialAppConfig = O.Partial<AppConfig, 'deep'>; export type PartialAppConfig = O.Partial<AppConfig, 'deep'>;

View File

@ -1,25 +0,0 @@
export function keepGUIAlive() {
async function getRequest(url = '') {
const response = await fetch(url, {
method: 'GET',
cache: 'no-cache',
});
return response;
}
const keepAliveServer = () => {
const url = document.location;
const route = '/flaskwebgui-keep-server-alive';
getRequest(url + route).then((data) => {
return data;
});
};
if (!import.meta.env.NODE_ENV || import.meta.env.NODE_ENV === 'production') {
document.addEventListener('DOMContentLoaded', () => {
const intervalRequest = 3 * 1000;
keepAliveServer();
setInterval(keepAliveServer, intervalRequest);
});
}
}

View File

@ -8,7 +8,7 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { Feature, useFeatureHelpInfo } from 'app/features'; import { Feature, useFeatureHelpInfo } from 'app/features';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { SystemState } from 'features/system/store/systemSlice'; import { SystemState } from 'features/system/store/systemSlice';
import { memo, ReactElement } from 'react'; import { memo, ReactElement } from 'react';

View File

@ -14,7 +14,7 @@ import {
Tooltip, Tooltip,
TooltipProps, TooltipProps,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { clamp } from 'lodash'; import { clamp } from 'lodash-es';
import { FocusEvent, memo, useEffect, useState } from 'react'; import { FocusEvent, memo, useEffect, useState } from 'react';

View File

@ -16,13 +16,23 @@ type IAISelectProps = SelectProps & {
validValues: validValues:
| Array<number | string> | Array<number | string>
| Array<{ key: string; value: string | number }>; | Array<{ key: string; value: string | number }>;
horizontal?: boolean;
spaceEvenly?: boolean;
}; };
/** /**
* Customized Chakra FormControl + Select multi-part component. * Customized Chakra FormControl + Select multi-part component.
*/ */
const IAISelect = (props: IAISelectProps) => { const IAISelect = (props: IAISelectProps) => {
const { label, isDisabled, validValues, tooltip, tooltipProps, ...rest } = const {
props; label,
isDisabled,
validValues,
tooltip,
tooltipProps,
horizontal,
spaceEvenly,
...rest
} = props;
return ( return (
<FormControl <FormControl
isDisabled={isDisabled} isDisabled={isDisabled}
@ -32,10 +42,28 @@ const IAISelect = (props: IAISelectProps) => {
e.nativeEvent.stopPropagation(); e.nativeEvent.stopPropagation();
e.nativeEvent.cancelBubble = true; e.nativeEvent.cancelBubble = true;
}} }}
sx={
horizontal
? {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 4,
}
: {}
}
> >
{label && <FormLabel>{label}</FormLabel>} {label && (
<FormLabel sx={spaceEvenly ? { flexBasis: 0, flexGrow: 1 } : {}}>
{label}
</FormLabel>
)}
<Tooltip label={tooltip} {...tooltipProps}> <Tooltip label={tooltip} {...tooltipProps}>
<Select {...rest}> <Select
{...rest}
rootProps={{ sx: spaceEvenly ? { flexBasis: 0, flexGrow: 1 } : {} }}
>
{validValues.map((opt) => { {validValues.map((opt) => {
return typeof opt === 'string' || typeof opt === 'number' ? ( return typeof opt === 'string' || typeof opt === 'number' ? (
<IAIOption key={opt} value={opt}> <IAIOption key={opt} value={opt}>

View File

@ -23,7 +23,7 @@ import {
Tooltip, Tooltip,
TooltipProps, TooltipProps,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { clamp } from 'lodash'; import { clamp } from 'lodash-es';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@ -233,7 +233,7 @@ const IAISlider = (props: IAIFullSliderProps) => {
hidden={hideTooltip} hidden={hideTooltip}
{...sliderTooltipProps} {...sliderTooltipProps}
> >
<SliderThumb {...sliderThumbProps} /> <SliderThumb {...sliderThumbProps} zIndex={0} />
</Tooltip> </Tooltip>
</Slider> </Slider>

View File

@ -1,32 +1,11 @@
import { Badge, Box, ButtonGroup, Flex } from '@chakra-ui/react'; import { Badge, Box, Flex } from '@chakra-ui/react';
import { RootState } from 'app/store'; import { Image } from 'app/types/invokeai';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { clearInitialImage } from 'features/parameters/store/generationSlice';
import { useCallback } from 'react';
import IAIIconButton from 'common/components/IAIIconButton';
import { FaUndo, FaUpload } from 'react-icons/fa';
import { useTranslation } from 'react-i18next';
import { Image } from 'app/invokeai';
type ImageToImageOverlayProps = { type ImageToImageOverlayProps = {
setIsLoaded: (isLoaded: boolean) => void;
image: Image; image: Image;
}; };
const ImageToImageOverlay = ({ const ImageToImageOverlay = ({ image }: ImageToImageOverlayProps) => {
setIsLoaded,
image,
}: ImageToImageOverlayProps) => {
const isImageToImageEnabled = useAppSelector(
(state: RootState) => state.generation.isImageToImageEnabled
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleResetInitialImage = useCallback(() => {
dispatch(clearInitialImage());
setIsLoaded(false);
}, [dispatch, setIsLoaded]);
return ( return (
<Box <Box
sx={{ sx={{

View File

@ -1,34 +1,13 @@
import { import { ButtonGroup, Flex, Spacer, Text } from '@chakra-ui/react';
Box,
ButtonGroup,
Collapse,
Flex,
Heading,
HStack,
Image,
Spacer,
Text,
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 IAIIconButton from 'common/components/IAIIconButton';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaUndo, FaUpload } from 'react-icons/fa'; import { FaUndo, FaUpload } from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { RootState } from 'app/store';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { clearInitialImage } from 'features/parameters/store/generationSlice'; import { clearInitialImage } from 'features/parameters/store/generationSlice';
const ImageToImageSettingsHeader = () => { const ImageToImageSettingsHeader = () => {
const isImageToImageEnabled = useAppSelector(
(state: RootState) => state.generation.isImageToImageEnabled
);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -1,6 +1,6 @@
import { Box, useToast } from '@chakra-ui/react'; import { Box, useToast } from '@chakra-ui/react';
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext'; import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import useImageUploader from 'common/hooks/useImageUploader'; import useImageUploader from 'common/hooks/useImageUploader';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { ResourceKey } from 'i18next'; import { ResourceKey } from 'i18next';

View File

@ -1,5 +1,6 @@
import { Flex, Image, Spinner } from '@chakra-ui/react'; import { Flex, Image, Spinner } from '@chakra-ui/react';
import InvokeAILogoImage from 'assets/images/logo.png'; import InvokeAILogoImage from 'assets/images/logo.png';
import { memo } from 'react';
// This component loads before the theme so we cannot use theme tokens here // This component loads before the theme so we cannot use theme tokens here
@ -29,4 +30,4 @@ const Loading = () => {
); );
}; };
export default Loading; export default memo(Loading);

View File

@ -1,8 +1,8 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store'; import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice'; import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { isHotkeyPressed, useHotkeys } from 'react-hotkeys-hook'; import { isHotkeyPressed, useHotkeys } from 'react-hotkeys-hook';
const globalHotkeysSelector = createSelector( const globalHotkeysSelector = createSelector(

View File

@ -1,4 +1,4 @@
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/types/invokeai';
import promptToString from './promptToString'; import promptToString from './promptToString';
export function getPromptAndNegative(inputPrompt: InvokeAI.Prompt) { export function getPromptAndNegative(inputPrompt: InvokeAI.Prompt) {

View File

@ -1,5 +1,6 @@
import { RootState } from 'app/store'; import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useCallback } from 'react';
import { OpenAPI } from 'services/api'; import { OpenAPI } from 'services/api';
export const getUrlAlt = (url: string, shouldTransformUrls: boolean) => { export const getUrlAlt = (url: string, shouldTransformUrls: boolean) => {
@ -15,14 +16,19 @@ export const useGetUrl = () => {
(state: RootState) => state.config.shouldTransformUrls (state: RootState) => state.config.shouldTransformUrls
); );
return { const getUrl = useCallback(
shouldTransformUrls, (url?: string) => {
getUrl: (url?: string) => {
if (OpenAPI.BASE && shouldTransformUrls) { if (OpenAPI.BASE && shouldTransformUrls) {
return [OpenAPI.BASE, url].join('/'); return [OpenAPI.BASE, url].join('/');
} }
return url; return url;
}, },
[shouldTransformUrls]
);
return {
shouldTransformUrls,
getUrl,
}; };
}; };

View File

@ -1,4 +1,4 @@
import { forEach, size } from 'lodash'; import { forEach, size } from 'lodash-es';
import { ImageField, LatentsField } from 'services/api'; import { ImageField, LatentsField } from 'services/api';
const OBJECT_TYPESTRING = '[object Object]'; const OBJECT_TYPESTRING = '[object Object]';

View File

@ -1,4 +1,4 @@
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/types/invokeai';
const promptToString = (prompt: InvokeAI.Prompt): string => { const promptToString = (prompt: InvokeAI.Prompt): string => {
if (typeof prompt === 'string') { if (typeof prompt === 'string') {

View File

@ -1,4 +1,4 @@
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/types/invokeai';
export const stringToSeedWeights = ( export const stringToSeedWeights = (
string: string string: string

View File

@ -1,20 +0,0 @@
import Component from './component';
import InvokeAiLogoComponent from './features/system/components/InvokeAILogoComponent';
import ThemeChanger from './features/system/components/ThemeChanger';
import IAIPopover from './common/components/IAIPopover';
import IAIIconButton from './common/components/IAIIconButton';
import SettingsModal from './features/system/components/SettingsModal/SettingsModal';
import StatusIndicator from './features/system/components/StatusIndicator';
import ModelSelect from 'features/system/components/ModelSelect';
export default Component;
export {
InvokeAiLogoComponent,
ThemeChanger,
IAIPopover,
IAIIconButton,
SettingsModal,
StatusIndicator,
ModelSelect,
};

View File

@ -1,4 +1,4 @@
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIAlertDialog from 'common/components/IAIAlertDialog'; import IAIAlertDialog from 'common/components/IAIAlertDialog';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import { clearCanvasHistory } from 'features/canvas/store/canvasSlice'; import { clearCanvasHistory } from 'features/canvas/store/canvasSlice';

View File

@ -1,6 +1,6 @@
import { Box, chakra, Flex } from '@chakra-ui/react'; import { Box, chakra, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { import {
canvasSelector, canvasSelector,
isStagingSelector, isStagingSelector,
@ -8,7 +8,7 @@ import {
import Konva from 'konva'; import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node'; import { KonvaEventObject } from 'konva/lib/Node';
import { Vector2d } from 'konva/lib/types'; import { Vector2d } from 'konva/lib/types';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { useCallback, useRef } from 'react'; import { useCallback, useRef } from 'react';
import { Layer, Stage } from 'react-konva'; import { Layer, Stage } from 'react-konva';

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { Group, Rect } from 'react-konva'; import { Group, Rect } from 'react-konva';
import { canvasSelector } from '../store/canvasSelectors'; import { canvasSelector } from '../store/canvasSelectors';

View File

@ -2,10 +2,10 @@
import { useToken } from '@chakra-ui/react'; import { useToken } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store'; import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { isEqual, range } from 'lodash'; import { isEqual, range } from 'lodash-es';
import { ReactNode, useCallback, useLayoutEffect, useState } from 'react'; import { ReactNode, useCallback, useLayoutEffect, useState } from 'react';
import { Group, Line as KonvaLine } from 'react-konva'; import { Group, Line as KonvaLine } from 'react-konva';

View File

@ -1,10 +1,10 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store'; import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl'; import { useGetUrl } from 'common/util/getUrl';
import { GalleryState } from 'features/gallery/store/gallerySlice'; import { GalleryState } from 'features/gallery/store/gallerySlice';
import { ImageConfig } from 'konva/lib/shapes/Image'; import { ImageConfig } from 'konva/lib/shapes/Image';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Image as KonvaImage } from 'react-konva'; import { Image as KonvaImage } from 'react-konva';

View File

@ -1,12 +1,12 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { RectConfig } from 'konva/lib/shapes/Rect'; import { RectConfig } from 'konva/lib/shapes/Rect';
import { Rect } from 'react-konva'; import { Rect } from 'react-konva';
import { rgbaColorToString } from 'features/canvas/util/colorToString'; import { rgbaColorToString } from 'features/canvas/util/colorToString';
import Konva from 'konva'; import Konva from 'konva';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash-es';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
export const canvasMaskCompositerSelector = createSelector( export const canvasMaskCompositerSelector = createSelector(

View File

@ -1,8 +1,8 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { GroupConfig } from 'konva/lib/Group'; import { GroupConfig } from 'konva/lib/Group';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { Group, Line } from 'react-konva'; import { Group, Line } from 'react-konva';
import { isCanvasMaskLine } from '../store/canvasTypes'; import { isCanvasMaskLine } from '../store/canvasTypes';

View File

@ -1,9 +1,9 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl'; import { useGetUrl } from 'common/util/getUrl';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { rgbaColorToString } from 'features/canvas/util/colorToString'; import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { Group, Line, Rect } from 'react-konva'; import { Group, Line, Rect } from 'react-konva';
import { import {

View File

@ -1,6 +1,6 @@
import { Flex, Spinner } from '@chakra-ui/react'; import { Flex, Spinner } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
canvasSelector, canvasSelector,
initialCanvasImageSelector, initialCanvasImageSelector,

View File

@ -1,9 +1,9 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl'; import { useGetUrl } from 'common/util/getUrl';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { GroupConfig } from 'konva/lib/Group'; import { GroupConfig } from 'konva/lib/Group';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { Group, Rect } from 'react-konva'; import { Group, Rect } from 'react-konva';
import IAICanvasImage from './IAICanvasImage'; import IAICanvasImage from './IAICanvasImage';

View File

@ -1,7 +1,7 @@
import { ButtonGroup, Flex } from '@chakra-ui/react'; import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { saveStagingAreaImageToGallery } from 'app/socketio/actions'; // import { saveStagingAreaImageToGallery } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { import {
@ -12,7 +12,7 @@ import {
setShouldShowStagingImage, setShouldShowStagingImage,
setShouldShowStagingOutline, setShouldShowStagingOutline,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';

View File

@ -1,8 +1,8 @@
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import roundToHundreth from '../util/roundToHundreth'; import roundToHundreth from '../util/roundToHundreth';

View File

@ -1,9 +1,9 @@
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import roundToHundreth from 'features/canvas/util/roundToHundreth'; import roundToHundreth from 'features/canvas/util/roundToHundreth';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -1,9 +1,9 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { rgbaColorToString } from 'features/canvas/util/colorToString'; import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { GroupConfig } from 'konva/lib/Group'; import { GroupConfig } from 'konva/lib/Group';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { Circle, Group } from 'react-konva'; import { Circle, Group } from 'react-konva';
import { import {

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
roundDownToMultiple, roundDownToMultiple,
roundToMultiple, roundToMultiple,
@ -16,7 +16,7 @@ import Konva from 'konva';
import { GroupConfig } from 'konva/lib/Group'; import { GroupConfig } from 'konva/lib/Group';
import { KonvaEventObject } from 'konva/lib/Node'; import { KonvaEventObject } from 'konva/lib/Node';
import { Vector2d } from 'konva/lib/types'; import { Vector2d } from 'konva/lib/types';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { Group, Rect, Transformer } from 'react-konva'; import { Group, Rect, Transformer } from 'react-konva';

View File

@ -1,6 +1,6 @@
import { ButtonGroup, Flex } from '@chakra-ui/react'; import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAICheckbox from 'common/components/IAICheckbox'; import IAICheckbox from 'common/components/IAICheckbox';
import IAIColorPicker from 'common/components/IAIColorPicker'; import IAIColorPicker from 'common/components/IAIColorPicker';
@ -18,7 +18,7 @@ import {
setShouldPreserveMaskedArea, setShouldPreserveMaskedArea,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { rgbaColorToString } from 'features/canvas/util/colorToString'; import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
@ -9,7 +9,7 @@ import { FaRedo } from 'react-icons/fa';
import { redo } from 'features/canvas/store/canvasSlice'; import { redo } from 'features/canvas/store/canvasSlice';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const canvasRedoSelector = createSelector( const canvasRedoSelector = createSelector(

View File

@ -1,6 +1,6 @@
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAICheckbox from 'common/components/IAICheckbox'; import IAICheckbox from 'common/components/IAICheckbox';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover'; import IAIPopover from 'common/components/IAIPopover';
@ -16,7 +16,7 @@ import {
setShouldSnapToGrid, setShouldSnapToGrid,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import EmptyTempFolderButtonModal from 'features/system/components/ClearTempFolderButtonModal'; import EmptyTempFolderButtonModal from 'features/system/components/ClearTempFolderButtonModal';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { ChangeEvent } from 'react'; import { ChangeEvent } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';

View File

@ -1,6 +1,6 @@
import { ButtonGroup, Flex } from '@chakra-ui/react'; import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIColorPicker from 'common/components/IAIColorPicker'; import IAIColorPicker from 'common/components/IAIColorPicker';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover'; import IAIPopover from 'common/components/IAIPopover';
@ -17,7 +17,7 @@ import {
setTool, setTool,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { clamp, isEqual } from 'lodash'; import { clamp, isEqual } from 'lodash-es';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -1,6 +1,6 @@
import { ButtonGroup, Flex } from '@chakra-ui/react'; import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import IAISelect from 'common/components/IAISelect'; import IAISelect from 'common/components/IAISelect';
import useImageUploader from 'common/hooks/useImageUploader'; import useImageUploader from 'common/hooks/useImageUploader';
@ -24,7 +24,7 @@ import {
import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas'; import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { ChangeEvent } from 'react'; import { ChangeEvent } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
@ -9,7 +9,7 @@ import { undo } from 'features/canvas/store/canvasSlice';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const canvasUndoSelector = createSelector( const canvasUndoSelector = createSelector(

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
canvasSelector, canvasSelector,
isStagingSelector, isStagingSelector,
@ -9,7 +9,7 @@ import {
setStageCoordinates, setStageCoordinates,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { KonvaEventObject } from 'konva/lib/Node'; import { KonvaEventObject } from 'konva/lib/Node';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { useCallback } from 'react'; import { useCallback } from 'react';

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
canvasSelector, canvasSelector,
isStagingSelector, isStagingSelector,
@ -13,7 +13,7 @@ import {
setTool, setTool,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { useRef } from 'react'; import { useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
canvasSelector, canvasSelector,
isStagingSelector, isStagingSelector,
@ -12,7 +12,7 @@ import {
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import Konva from 'konva'; import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node'; import { KonvaEventObject } from 'konva/lib/Node';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { MutableRefObject, useCallback } from 'react'; import { MutableRefObject, useCallback } from 'react';
import getScaledCursorPosition from '../util/getScaledCursorPosition'; import getScaledCursorPosition from '../util/getScaledCursorPosition';

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
canvasSelector, canvasSelector,
isStagingSelector, isStagingSelector,
@ -11,7 +11,7 @@ import {
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import Konva from 'konva'; import Konva from 'konva';
import { Vector2d } from 'konva/lib/types'; import { Vector2d } from 'konva/lib/types';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { MutableRefObject, useCallback } from 'react'; import { MutableRefObject, useCallback } from 'react';
import getScaledCursorPosition from '../util/getScaledCursorPosition'; import getScaledCursorPosition from '../util/getScaledCursorPosition';

View File

@ -1,4 +1,4 @@
import { useAppDispatch } from 'app/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { mouseLeftCanvas } from 'features/canvas/store/canvasSlice'; import { mouseLeftCanvas } from 'features/canvas/store/canvasSlice';
import { useCallback } from 'react'; import { useCallback } from 'react';

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
canvasSelector, canvasSelector,
isStagingSelector, isStagingSelector,
@ -12,7 +12,7 @@ import {
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import Konva from 'konva'; import Konva from 'konva';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { MutableRefObject, useCallback } from 'react'; import { MutableRefObject, useCallback } from 'react';
import getScaledCursorPosition from '../util/getScaledCursorPosition'; import getScaledCursorPosition from '../util/getScaledCursorPosition';

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { import {
setStageCoordinates, setStageCoordinates,
@ -7,7 +7,7 @@ import {
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import Konva from 'konva'; import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node'; import { KonvaEventObject } from 'konva/lib/Node';
import { clamp, isEqual } from 'lodash'; import { clamp, isEqual } from 'lodash-es';
import { MutableRefObject, useCallback } from 'react'; import { MutableRefObject, useCallback } from 'react';
import { import {

View File

@ -1,4 +1,4 @@
import { useAppDispatch } from 'app/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import Konva from 'konva'; import Konva from 'konva';
import { import {
commitColorPickerColor, commitColorPickerColor,

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store'; import { RootState } from 'app/store/store';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { CanvasImage, CanvasState, isCanvasBaseImage } from './canvasTypes'; import { CanvasImage, CanvasState, isCanvasBaseImage } from './canvasTypes';

View File

@ -1,12 +1,12 @@
import type { PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/types/invokeai';
import { import {
roundDownToMultiple, roundDownToMultiple,
roundToMultiple, roundToMultiple,
} from 'common/util/roundDownToMultiple'; } from 'common/util/roundDownToMultiple';
import { IRect, Vector2d } from 'konva/lib/types'; import { IRect, Vector2d } from 'konva/lib/types';
import { clamp, cloneDeep } from 'lodash'; import { clamp, cloneDeep } from 'lodash-es';
// //
import { RgbaColor } from 'react-colorful'; import { RgbaColor } from 'react-colorful';
import calculateCoordinates from '../util/calculateCoordinates'; import calculateCoordinates from '../util/calculateCoordinates';

View File

@ -1,4 +1,4 @@
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/types/invokeai';
import { IRect, Vector2d } from 'konva/lib/types'; import { IRect, Vector2d } from 'konva/lib/types';
import { RgbaColor } from 'react-colorful'; import { RgbaColor } from 'react-colorful';

View File

@ -1,7 +1,7 @@
import { AnyAction, ThunkAction } from '@reduxjs/toolkit'; import { AnyAction, ThunkAction } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/types/invokeai';
import { RootState } from 'app/store'; import { RootState } from 'app/store/store';
import { addImage } from 'features/gallery/store/gallerySlice'; // import { addImage } from 'features/gallery/store/gallerySlice';
import { import {
addToast, addToast,
setCurrentStatus, setCurrentStatus,

View File

@ -1,6 +1,6 @@
import { AppDispatch, AppGetState } from 'app/store'; import { AppDispatch, AppGetState } from 'app/store/store';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { debounce } from 'lodash'; import { debounce } from 'lodash-es';
import { setDoesCanvasNeedScaling } from '../canvasSlice'; import { setDoesCanvasNeedScaling } from '../canvasSlice';
const debouncedCanvasScale = debounce((dispatch: AppDispatch) => { const debouncedCanvasScale = debounce((dispatch: AppDispatch) => {

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { isEqual } from 'lodash'; import { get, isEqual, isNumber, isString } from 'lodash-es';
import { import {
ButtonGroup, ButtonGroup,
@ -10,8 +10,8 @@ import {
useDisclosure, useDisclosure,
useToast, useToast,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { runESRGAN, runFacetool } from 'app/socketio/actions'; // import { runESRGAN, runFacetool } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover'; import IAIPopover from 'common/components/IAIPopover';
@ -63,11 +63,11 @@ import {
} from '../store/gallerySelectors'; } from '../store/gallerySelectors';
import DeleteImageModal from './DeleteImageModal'; import DeleteImageModal from './DeleteImageModal';
import { useCallback } from 'react'; import { useCallback } from 'react';
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
import { useGetUrl } from 'common/util/getUrl'; import { useGetUrl } from 'common/util/getUrl';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { imageDeleted } from 'services/thunks/image'; import { imageDeleted } from 'services/thunks/image';
import { useParameters } from 'features/parameters/hooks/useParameters';
const currentImageButtonsSelector = createSelector( const currentImageButtonsSelector = createSelector(
[ [
@ -112,6 +112,8 @@ const currentImageButtonsSelector = createSelector(
isLightboxOpen, isLightboxOpen,
shouldHidePreview, shouldHidePreview,
image, image,
seed: image?.metadata?.invokeai?.node?.seed,
prompt: image?.metadata?.invokeai?.node?.prompt,
}; };
}, },
{ {
@ -161,18 +163,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
const toast = useToast(); const toast = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const setBothPrompts = useSetBothPrompts();
const handleClickUseAsInitialImage = () => { const { recallPrompt, recallSeed, sendToImageToImage } = useParameters();
if (!image) return;
if (isLightboxOpen) dispatch(setIsLightboxOpen(false));
dispatch(initialImageSelected(image.name));
// dispatch(setInitialImage(currentImage));
// dispatch(setActiveTab('img2img')); const handleCopyImage = useCallback(async () => {
};
const handleCopyImage = async () => {
if (!image?.url) { if (!image?.url) {
return; return;
} }
@ -194,9 +188,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
duration: 2500, duration: 2500,
isClosable: true, isClosable: true,
}); });
}; }, [getUrl, t, image?.url, toast]);
const handleCopyImageLink = () => { const handleCopyImageLink = useCallback(() => {
const url = image const url = image
? shouldTransformUrls ? shouldTransformUrls
? getUrl(image.url) ? getUrl(image.url)
@ -215,37 +209,13 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
isClosable: true, isClosable: true,
}); });
}); });
}; }, [toast, shouldTransformUrls, getUrl, t, image]);
useHotkeys( const handlePreviewVisibility = useCallback(() => {
'shift+i',
() => {
if (image) {
handleClickUseAsInitialImage();
toast({
title: t('toast.sentToImageToImage'),
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: t('toast.imageNotLoaded'),
description: t('toast.imageNotLoadedDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image]
);
const handlePreviewVisibility = () => {
dispatch(setShouldHidePreview(!shouldHidePreview)); dispatch(setShouldHidePreview(!shouldHidePreview));
}; }, [dispatch, shouldHidePreview]);
const handleClickUseAllParameters = () => { const handleClickUseAllParameters = useCallback(() => {
if (!image) return; if (!image) return;
// selectedImage.metadata && // selectedImage.metadata &&
// dispatch(setAllParameters(selectedImage.metadata)); // dispatch(setAllParameters(selectedImage.metadata));
@ -254,12 +224,13 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
// } else if (selectedImage.metadata?.image.type === 'txt2img') { // } else if (selectedImage.metadata?.image.type === 'txt2img') {
// dispatch(setActiveTab('txt2img')); // dispatch(setActiveTab('txt2img'));
// } // }
}; }, [image]);
useHotkeys( useHotkeys(
'a', 'a',
() => { () => {
if (['txt2img', 'img2img'].includes(image?.metadata?.sd_metadata?.type)) { const type = image?.metadata?.invokeai?.node?.types;
if (isString(type) && ['txt2img', 'img2img'].includes(type)) {
handleClickUseAllParameters(); handleClickUseAllParameters();
toast({ toast({
title: t('toast.parametersSet'), title: t('toast.parametersSet'),
@ -280,67 +251,27 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
[image] [image]
); );
const handleClickUseSeed = () => { const handleUseSeed = useCallback(() => {
image?.metadata && dispatch(setSeed(image.metadata.sd_metadata.seed)); recallSeed(image?.metadata?.invokeai?.node?.seed);
}; }, [image, recallSeed]);
useHotkeys( useHotkeys('s', handleUseSeed, [image]);
's',
() => {
if (image?.metadata?.sd_metadata?.seed) {
handleClickUseSeed();
toast({
title: t('toast.seedSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: t('toast.seedNotSet'),
description: t('toast.seedNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image]
);
const handleClickUsePrompt = useCallback(() => { const handleUsePrompt = useCallback(() => {
if (image?.metadata?.sd_metadata?.prompt) { recallPrompt(image?.metadata?.invokeai?.node?.prompt);
setBothPrompts(image?.metadata?.sd_metadata?.prompt); }, [image, recallPrompt]);
}
}, [image?.metadata?.sd_metadata?.prompt, setBothPrompts]);
useHotkeys( useHotkeys('p', handleUsePrompt, [image]);
'p',
() => {
if (image?.metadata?.sd_metadata?.prompt) {
handleClickUsePrompt();
toast({
title: t('toast.promptSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: t('toast.promptNotSet'),
description: t('toast.promptNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image]
);
const handleClickUpscale = () => { const handleSendToImageToImage = useCallback(() => {
sendToImageToImage(image);
}, [image, sendToImageToImage]);
useHotkeys('shift+i', handleSendToImageToImage, [image]);
const handleClickUpscale = useCallback(() => {
// selectedImage && dispatch(runESRGAN(selectedImage)); // selectedImage && dispatch(runESRGAN(selectedImage));
}; }, []);
useHotkeys( useHotkeys(
'Shift+U', 'Shift+U',
@ -369,9 +300,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
] ]
); );
const handleClickFixFaces = () => { const handleClickFixFaces = useCallback(() => {
// selectedImage && dispatch(runFacetool(selectedImage)); // selectedImage && dispatch(runFacetool(selectedImage));
}; }, []);
useHotkeys( useHotkeys(
'Shift+R', 'Shift+R',
@ -401,10 +332,12 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
] ]
); );
const handleClickShowImageDetails = () => const handleClickShowImageDetails = useCallback(
dispatch(setShouldShowImageDetails(!shouldShowImageDetails)); () => dispatch(setShouldShowImageDetails(!shouldShowImageDetails)),
[dispatch, shouldShowImageDetails]
);
const handleSendToCanvas = () => { const handleSendToCanvas = useCallback(() => {
if (!image) return; if (!image) return;
if (isLightboxOpen) dispatch(setIsLightboxOpen(false)); if (isLightboxOpen) dispatch(setIsLightboxOpen(false));
@ -421,7 +354,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
duration: 2500, duration: 2500,
isClosable: true, isClosable: true,
}); });
}; }, [image, isLightboxOpen, dispatch, activeTabName, toast, t]);
useHotkeys( useHotkeys(
'i', 'i',
@ -440,19 +373,19 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
[image, shouldShowImageDetails] [image, shouldShowImageDetails]
); );
const handleInitiateDelete = () => { const handleDelete = useCallback(() => {
if (canDeleteImage && image) {
dispatch(imageDeleted({ imageType: image.type, imageName: image.name }));
}
}, [image, canDeleteImage, dispatch]);
const handleInitiateDelete = useCallback(() => {
if (shouldConfirmOnDelete) { if (shouldConfirmOnDelete) {
onDeleteDialogOpen(); onDeleteDialogOpen();
} else { } else {
handleDelete(); handleDelete();
} }
}; }, [shouldConfirmOnDelete, onDeleteDialogOpen, handleDelete]);
const handleDelete = () => {
if (canDeleteImage && image) {
dispatch(imageDeleted({ imageType: image.type, imageName: image.name }));
}
};
useHotkeys('delete', handleInitiateDelete, [ useHotkeys('delete', handleInitiateDelete, [
image, image,
@ -461,9 +394,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
isProcessing, isProcessing,
]); ]);
const handleLightBox = () => { const handleLightBox = useCallback(() => {
dispatch(setIsLightboxOpen(!isLightboxOpen)); dispatch(setIsLightboxOpen(!isLightboxOpen));
}; }, [dispatch, isLightboxOpen]);
return ( return (
<> <>
@ -494,7 +427,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
> >
<IAIButton <IAIButton
size="sm" size="sm"
onClick={handleClickUseAsInitialImage} onClick={handleSendToImageToImage}
leftIcon={<FaShare />} leftIcon={<FaShare />}
> >
{t('parameters.sendToImg2Img')} {t('parameters.sendToImg2Img')}
@ -568,16 +501,16 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
icon={<FaQuoteRight />} icon={<FaQuoteRight />}
tooltip={`${t('parameters.usePrompt')} (P)`} tooltip={`${t('parameters.usePrompt')} (P)`}
aria-label={`${t('parameters.usePrompt')} (P)`} aria-label={`${t('parameters.usePrompt')} (P)`}
isDisabled={!image?.metadata?.sd_metadata?.prompt} isDisabled={!image?.metadata?.invokeai?.node?.prompt}
onClick={handleClickUsePrompt} onClick={handleUsePrompt}
/> />
<IAIIconButton <IAIIconButton
icon={<FaSeedling />} icon={<FaSeedling />}
tooltip={`${t('parameters.useSeed')} (S)`} tooltip={`${t('parameters.useSeed')} (S)`}
aria-label={`${t('parameters.useSeed')} (S)`} aria-label={`${t('parameters.useSeed')} (S)`}
isDisabled={!image?.metadata?.sd_metadata?.seed} isDisabled={!image?.metadata?.invokeai?.node?.seed}
onClick={handleClickUseSeed} onClick={handleUseSeed}
/> />
<IAIIconButton <IAIIconButton

View File

@ -1,8 +1,8 @@
import { Flex, Icon } from '@chakra-ui/react'; import { Flex, Icon } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { MdPhoto } from 'react-icons/md'; import { MdPhoto } from 'react-icons/md';
import { selectedImageSelector } from '../store/gallerySelectors'; import { selectedImageSelector } from '../store/gallerySelectors';

View File

@ -1,46 +1,27 @@
import { Box, Flex, Image } from '@chakra-ui/react'; import { Box, Flex, Image } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl'; import { useGetUrl } from 'common/util/getUrl';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { uiSelector } from 'features/ui/store/uiSelectors'; import { uiSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { selectedImageSelector } from '../store/gallerySelectors'; import { selectedImageSelector } from '../store/gallerySelectors';
import CurrentImageFallback from './CurrentImageFallback'; import CurrentImageFallback from './CurrentImageFallback';
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer'; import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
import NextPrevImageButtons from './NextPrevImageButtons'; import NextPrevImageButtons from './NextPrevImageButtons';
import CurrentImageHidden from './CurrentImageHidden'; import CurrentImageHidden from './CurrentImageHidden';
import { memo } from 'react';
export const imagesSelector = createSelector( export const imagesSelector = createSelector(
[uiSelector, selectedImageSelector, systemSelector], [uiSelector, selectedImageSelector, systemSelector],
(ui, selectedImage, system) => { (ui, selectedImage, system) => {
const { shouldShowImageDetails, shouldHidePreview } = ui; const { shouldShowImageDetails, shouldHidePreview } = ui;
const { progressImage } = system;
// TODO: Clean this up, this is really gross
const imageToDisplay = progressImage
? {
url: progressImage.dataURL,
width: progressImage.width,
height: progressImage.height,
isProgressImage: true,
image: progressImage,
}
: selectedImage
? {
url: selectedImage.url,
width: selectedImage.metadata.width,
height: selectedImage.metadata.height,
isProgressImage: false,
image: selectedImage,
}
: null;
return { return {
shouldShowImageDetails, shouldShowImageDetails,
shouldHidePreview, shouldHidePreview,
imageToDisplay, image: selectedImage,
}; };
}, },
{ {
@ -50,8 +31,8 @@ export const imagesSelector = createSelector(
} }
); );
export default function CurrentImagePreview() { const CurrentImagePreview = () => {
const { shouldShowImageDetails, imageToDisplay, shouldHidePreview } = const { shouldShowImageDetails, image, shouldHidePreview } =
useAppSelector(imagesSelector); useAppSelector(imagesSelector);
const { getUrl } = useGetUrl(); const { getUrl } = useGetUrl();
@ -65,41 +46,23 @@ export default function CurrentImagePreview() {
height: '100%', height: '100%',
}} }}
> >
{imageToDisplay && ( {image && (
<Image <Image
src={ src={shouldHidePreview ? undefined : getUrl(image.url)}
shouldHidePreview width={image.metadata.width}
? undefined height={image.metadata.height}
: imageToDisplay.isProgressImage fallback={shouldHidePreview ? <CurrentImageHidden /> : undefined}
? imageToDisplay.url
: getUrl(imageToDisplay.url)
}
width={imageToDisplay.width}
height={imageToDisplay.height}
fallback={
shouldHidePreview ? (
<CurrentImageHidden />
) : !imageToDisplay.isProgressImage ? (
<CurrentImageFallback />
) : undefined
}
sx={{ sx={{
objectFit: 'contain', objectFit: 'contain',
maxWidth: '100%', maxWidth: '100%',
maxHeight: '100%', maxHeight: '100%',
height: 'auto', height: 'auto',
position: 'absolute', position: 'absolute',
imageRendering: imageToDisplay.isProgressImage
? 'pixelated'
: 'initial',
borderRadius: 'base', borderRadius: 'base',
}} }}
/> />
)} )}
{!shouldShowImageDetails && <NextPrevImageButtons />} {shouldShowImageDetails && image && 'metadata' in image && (
{shouldShowImageDetails &&
imageToDisplay &&
'metadata' in imageToDisplay.image && (
<Box <Box
sx={{ sx={{
position: 'absolute', position: 'absolute',
@ -110,9 +73,12 @@ export default function CurrentImagePreview() {
overflow: 'scroll', overflow: 'scroll',
}} }}
> >
<ImageMetadataViewer image={imageToDisplay.image} /> <ImageMetadataViewer image={image} />
</Box> </Box>
)} )}
{!shouldShowImageDetails && <NextPrevImageButtons />}
</Flex> </Flex>
); );
} };
export default memo(CurrentImagePreview);

View File

@ -9,13 +9,13 @@ import {
Text, Text,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { configSelector } from 'features/system/store/configSelectors'; import { configSelector } from 'features/system/store/configSelectors';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice'; import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { ChangeEvent, memo, useCallback, useRef } from 'react'; import { ChangeEvent, memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -5,65 +5,38 @@ import {
Image, Image,
MenuItem, MenuItem,
MenuList, MenuList,
Text, Skeleton,
useDisclosure, useDisclosure,
useTheme, useTheme,
useToast, useToast,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import { imageSelected } from 'features/gallery/store/gallerySlice';
imageSelected, import { DragEvent, memo, useCallback, useState } from 'react';
setCurrentImage, import { FaCheck, FaExpand, FaImage, FaShare, FaTrash } from 'react-icons/fa';
} from 'features/gallery/store/gallerySlice';
import {
initialImageSelected,
setAllImageToImageParameters,
setAllParameters,
setSeed,
} from 'features/parameters/store/generationSlice';
import { DragEvent, memo, useState } from 'react';
import {
FaCheck,
FaExpand,
FaLink,
FaShare,
FaTrash,
FaTrashAlt,
} from 'react-icons/fa';
import DeleteImageModal from './DeleteImageModal'; import DeleteImageModal from './DeleteImageModal';
import { ContextMenu } from 'chakra-ui-contextmenu'; import { ContextMenu } from 'chakra-ui-contextmenu';
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/types/invokeai';
import { import { resizeAndScaleCanvas } from 'features/canvas/store/canvasSlice';
resizeAndScaleCanvas,
setInitialCanvasImage,
} from 'features/canvas/store/canvasSlice';
import { gallerySelector } from 'features/gallery/store/gallerySelectors'; import { gallerySelector } from 'features/gallery/store/gallerySelectors';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { useGetUrl } from 'common/util/getUrl'; import { useGetUrl } from 'common/util/getUrl';
import { ExternalLinkIcon } from '@chakra-ui/icons'; import { ExternalLinkIcon } from '@chakra-ui/icons';
import { BiZoomIn } from 'react-icons/bi';
import { IoArrowUndoCircleOutline } from 'react-icons/io5'; import { IoArrowUndoCircleOutline } from 'react-icons/io5';
import { imageDeleted } from 'services/thunks/image'; import { imageDeleted } from 'services/thunks/image';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { configSelector } from 'features/system/store/configSelectors';
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useParameters } from 'features/parameters/hooks/useParameters';
export const selector = createSelector( export const selector = createSelector(
[ [gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
gallerySelector, (gallery, system, lightbox, activeTabName) => {
systemSelector,
configSelector,
lightboxSelector,
activeTabNameSelector,
],
(gallery, system, config, lightbox, activeTabName) => {
const { const {
galleryImageObjectFit, galleryImageObjectFit,
galleryImageMinimumWidth, galleryImageMinimumWidth,
@ -71,7 +44,6 @@ export const selector = createSelector(
} = gallery; } = gallery;
const { isLightboxOpen } = lightbox; const { isLightboxOpen } = lightbox;
const { disabledFeatures } = config;
const { isConnected, isProcessing, shouldConfirmOnDelete } = system; const { isConnected, isProcessing, shouldConfirmOnDelete } = system;
return { return {
@ -82,7 +54,6 @@ export const selector = createSelector(
shouldUseSingleGalleryColumn, shouldUseSingleGalleryColumn,
activeTabName, activeTabName,
isLightboxOpen, isLightboxOpen,
disabledFeatures,
}; };
}, },
{ {
@ -113,14 +84,15 @@ const HoverableImage = memo((props: HoverableImageProps) => {
galleryImageMinimumWidth, galleryImageMinimumWidth,
canDeleteImage, canDeleteImage,
shouldUseSingleGalleryColumn, shouldUseSingleGalleryColumn,
disabledFeatures,
shouldConfirmOnDelete, shouldConfirmOnDelete,
} = useAppSelector(selector); } = useAppSelector(selector);
const { const {
isOpen: isDeleteDialogOpen, isOpen: isDeleteDialogOpen,
onOpen: onDeleteDialogOpen, onOpen: onDeleteDialogOpen,
onClose: onDeleteDialogClose, onClose: onDeleteDialogClose,
} = useDisclosure(); } = useDisclosure();
const { image, isSelected } = props; const { image, isSelected } = props;
const { url, thumbnail, name, metadata } = image; const { url, thumbnail, name, metadata } = image;
const { getUrl } = useGetUrl(); const { getUrl } = useGetUrl();
@ -130,53 +102,62 @@ const HoverableImage = memo((props: HoverableImageProps) => {
const toast = useToast(); const toast = useToast();
const { direction } = useTheme(); const { direction } = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const setBothPrompts = useSetBothPrompts(); const { isFeatureEnabled: isLightboxEnabled } = useFeatureStatus('lightbox');
const { recallSeed, recallPrompt, sendToImageToImage, recallInitialImage } =
useParameters();
const handleMouseOver = () => setIsHovered(true); const handleMouseOver = () => setIsHovered(true);
const handleMouseOut = () => setIsHovered(false); const handleMouseOut = () => setIsHovered(false);
const handleInitiateDelete = () => { // Immediately deletes an image
const handleDelete = useCallback(() => {
if (canDeleteImage && image) {
dispatch(imageDeleted({ imageType: image.type, imageName: image.name }));
}
}, [dispatch, image, canDeleteImage]);
// Opens the alert dialog to check if user is sure they want to delete
const handleInitiateDelete = useCallback(() => {
if (shouldConfirmOnDelete) { if (shouldConfirmOnDelete) {
onDeleteDialogOpen(); onDeleteDialogOpen();
} else { } else {
handleDelete(); handleDelete();
} }
}; }, [handleDelete, onDeleteDialogOpen, shouldConfirmOnDelete]);
const handleDelete = () => { const handleSelectImage = useCallback(() => {
if (canDeleteImage && image) { dispatch(imageSelected(image));
dispatch(imageDeleted({ imageType: image.type, imageName: image.name })); }, [image, dispatch]);
}
};
const handleUsePrompt = () => { const handleDragStart = useCallback(
if (image.metadata?.sd_metadata?.prompt) { (e: DragEvent<HTMLDivElement>) => {
setBothPrompts(image.metadata?.sd_metadata?.prompt); e.dataTransfer.setData('invokeai/imageName', image.name);
} e.dataTransfer.setData('invokeai/imageType', image.type);
toast({ e.dataTransfer.effectAllowed = 'move';
title: t('toast.promptSet'), },
status: 'success', [image]
duration: 2500, );
isClosable: true,
});
};
const handleUseSeed = () => { // Recall parameters handlers
image.metadata.sd_metadata && const handleRecallPrompt = useCallback(() => {
dispatch(setSeed(image.metadata.sd_metadata.image.seed)); recallPrompt(image.metadata?.invokeai?.node?.prompt);
toast({ }, [image, recallPrompt]);
title: t('toast.seedSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
};
const handleSendToImageToImage = () => { const handleRecallSeed = useCallback(() => {
dispatch(initialImageSelected(image.name)); recallSeed(image.metadata.invokeai?.node?.seed);
}; }, [image, recallSeed]);
const handleSendToImageToImage = useCallback(() => {
sendToImageToImage(image);
}, [image, sendToImageToImage]);
const handleRecallInitialImage = useCallback(() => {
recallInitialImage(image.metadata.invokeai?.node?.image);
}, [image, recallInitialImage]);
/**
* TODO: the rest of these
*/
const handleSendToCanvas = () => { const handleSendToCanvas = () => {
// dispatch(setInitialCanvasImage(image)); // dispatch(setInitialCanvasImage(image));
@ -195,48 +176,14 @@ const HoverableImage = memo((props: HoverableImageProps) => {
}; };
const handleUseAllParameters = () => { const handleUseAllParameters = () => {
metadata.sd_metadata && dispatch(setAllParameters(metadata.sd_metadata)); // metadata.invokeai?.node &&
toast({ // dispatch(setAllParameters(metadata.invokeai?.node));
title: t('toast.parametersSet'), // toast({
status: 'success', // title: t('toast.parametersSet'),
duration: 2500, // status: 'success',
isClosable: true, // duration: 2500,
}); // isClosable: true,
}; // });
const handleUseInitialImage = async () => {
if (metadata.sd_metadata?.image?.init_image_path) {
const response = await fetch(
metadata.sd_metadata?.image?.init_image_path
);
if (response.ok) {
dispatch(setAllImageToImageParameters(metadata?.sd_metadata));
toast({
title: t('toast.initialImageSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
return;
}
}
toast({
title: t('toast.initialImageNotSet'),
description: t('toast.initialImageNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
};
const handleSelectImage = () => {
dispatch(imageSelected(image.name));
};
const handleDragStart = (e: DragEvent<HTMLDivElement>) => {
e.dataTransfer.setData('invokeai/imageName', image.name);
e.dataTransfer.setData('invokeai/imageType', image.type);
e.dataTransfer.effectAllowed = 'move';
}; };
const handleLightBox = () => { const handleLightBox = () => {
@ -253,37 +200,37 @@ const HoverableImage = memo((props: HoverableImageProps) => {
<ContextMenu<HTMLDivElement> <ContextMenu<HTMLDivElement>
menuProps={{ size: 'sm', isLazy: true }} menuProps={{ size: 'sm', isLazy: true }}
renderMenu={() => ( renderMenu={() => (
<MenuList> <MenuList sx={{ visibility: 'visible !important' }}>
<MenuItem <MenuItem
icon={<ExternalLinkIcon />} icon={<ExternalLinkIcon />}
onClickCapture={handleOpenInNewTab} onClickCapture={handleOpenInNewTab}
> >
{t('common.openInNewTab')} {t('common.openInNewTab')}
</MenuItem> </MenuItem>
{!disabledFeatures.includes('lightbox') && ( {isLightboxEnabled && (
<MenuItem icon={<FaExpand />} onClickCapture={handleLightBox}> <MenuItem icon={<FaExpand />} onClickCapture={handleLightBox}>
{t('parameters.openInViewer')} {t('parameters.openInViewer')}
</MenuItem> </MenuItem>
)} )}
<MenuItem <MenuItem
icon={<IoArrowUndoCircleOutline />} icon={<IoArrowUndoCircleOutline />}
onClickCapture={handleUsePrompt} onClickCapture={handleRecallPrompt}
isDisabled={image?.metadata?.sd_metadata?.prompt === undefined} isDisabled={image?.metadata?.invokeai?.node?.prompt === undefined}
> >
{t('parameters.usePrompt')} {t('parameters.usePrompt')}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
icon={<IoArrowUndoCircleOutline />} icon={<IoArrowUndoCircleOutline />}
onClickCapture={handleUseSeed} onClickCapture={handleRecallSeed}
isDisabled={image?.metadata?.sd_metadata?.seed === undefined} isDisabled={image?.metadata?.invokeai?.node?.seed === undefined}
> >
{t('parameters.useSeed')} {t('parameters.useSeed')}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
icon={<IoArrowUndoCircleOutline />} icon={<IoArrowUndoCircleOutline />}
onClickCapture={handleUseInitialImage} onClickCapture={handleRecallInitialImage}
isDisabled={image?.metadata?.sd_metadata?.type !== 'img2img'} isDisabled={image?.metadata?.invokeai?.node?.type !== 'img2img'}
> >
{t('parameters.useInitImg')} {t('parameters.useInitImg')}
</MenuItem> </MenuItem>
@ -292,7 +239,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
onClickCapture={handleUseAllParameters} onClickCapture={handleUseAllParameters}
isDisabled={ isDisabled={
!['txt2img', 'img2img'].includes( !['txt2img', 'img2img'].includes(
image?.metadata?.sd_metadata?.type String(image?.metadata?.invokeai?.node?.type)
) )
} }
> >
@ -322,46 +269,35 @@ const HoverableImage = memo((props: HoverableImageProps) => {
userSelect="none" userSelect="none"
draggable={true} draggable={true}
onDragStart={handleDragStart} onDragStart={handleDragStart}
onClick={handleSelectImage}
ref={ref} ref={ref}
sx={{ sx={{
padding: 2,
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center',
w: 'full',
h: 'full',
transition: 'transform 0.2s ease-out', transition: 'transform 0.2s ease-out',
_hover: { aspectRatio: '1/1',
cursor: 'pointer',
zIndex: 2,
},
_before: {
content: '""',
display: 'block',
paddingBottom: '100%',
},
}} }}
> >
<Image <Image
loading="lazy"
objectFit={ objectFit={
shouldUseSingleGalleryColumn ? 'contain' : galleryImageObjectFit shouldUseSingleGalleryColumn ? 'contain' : galleryImageObjectFit
} }
rounded="md" rounded="md"
src={getUrl(thumbnail || url)} src={getUrl(thumbnail || url)}
loading="lazy" fallback={<FaImage />}
sx={{ sx={{
position: 'absolute',
width: '100%', width: '100%',
height: '100%', height: '100%',
maxWidth: '100%', maxWidth: '100%',
maxHeight: '100%', maxHeight: '100%',
top: '50%',
transform: 'translate(-50%,-50%)',
...(direction === 'rtl'
? { insetInlineEnd: '50%' }
: { insetInlineStart: '50%' }),
}} }}
/> />
{isSelected && (
<Flex <Flex
onClick={handleSelectImage}
sx={{ sx={{
position: 'absolute', position: 'absolute',
top: '0', top: '0',
@ -370,10 +306,11 @@ const HoverableImage = memo((props: HoverableImageProps) => {
height: '100%', height: '100%',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
pointerEvents: 'none',
}} }}
> >
{isSelected && (
<Icon <Icon
filter={'drop-shadow(0px 0px 1rem black)'}
as={FaCheck} as={FaCheck}
sx={{ sx={{
width: '50%', width: '50%',
@ -381,9 +318,9 @@ const HoverableImage = memo((props: HoverableImageProps) => {
fill: 'ok.500', fill: 'ok.500',
}} }}
/> />
)}
</Flex> </Flex>
{isHovered && galleryImageMinimumWidth >= 64 && ( )}
{isHovered && galleryImageMinimumWidth >= 100 && (
<Box <Box
sx={{ sx={{
position: 'absolute', position: 'absolute',

View File

@ -10,7 +10,7 @@
// useTheme, // useTheme,
// } from '@chakra-ui/react'; // } from '@chakra-ui/react';
// import { requestImages } from 'app/socketio/actions'; // import { requestImages } from 'app/socketio/actions';
// import { useAppDispatch, useAppSelector } from 'app/storeHooks'; // import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
// import IAIButton from 'common/components/IAIButton'; // import IAIButton from 'common/components/IAIButton';
// import IAICheckbox from 'common/components/IAICheckbox'; // import IAICheckbox from 'common/components/IAICheckbox';
// import IAIIconButton from 'common/components/IAIIconButton'; // import IAIIconButton from 'common/components/IAIIconButton';
@ -35,7 +35,7 @@
// } from 'features/ui/store/uiSlice'; // } from 'features/ui/store/uiSlice';
// import { InvokeTabName } from 'features/ui/store/tabMap'; // import { InvokeTabName } from 'features/ui/store/tabMap';
// import { clamp } from 'lodash'; // import { clamp } from 'lodash-es';
// import { Direction } from 're-resizable/lib/resizer'; // import { Direction } from 're-resizable/lib/resizer';
// import React, { // import React, {
// ChangeEvent, // ChangeEvent,

View File

@ -1,6 +1,14 @@
import { ButtonGroup, Flex, Grid, Icon, Image, Text } from '@chakra-ui/react'; import {
import { requestImages } from 'app/socketio/actions'; Box,
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; ButtonGroup,
Flex,
FlexProps,
Grid,
Icon,
Text,
forwardRef,
} from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAICheckbox from 'common/components/IAICheckbox'; import IAICheckbox from 'common/components/IAICheckbox';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
@ -15,28 +23,33 @@ import {
setShouldUseSingleGalleryColumn, setShouldUseSingleGalleryColumn,
} from 'features/gallery/store/gallerySlice'; } from 'features/gallery/store/gallerySlice';
import { togglePinGalleryPanel } from 'features/ui/store/uiSlice'; import { togglePinGalleryPanel } from 'features/ui/store/uiSlice';
import { useOverlayScrollbars } from 'overlayscrollbars-react';
import { ChangeEvent, useEffect, useRef, useState } from 'react'; import {
ChangeEvent,
PropsWithChildren,
memo,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs'; import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
import { FaImage, FaUser, FaWrench } from 'react-icons/fa'; import { FaImage, FaUser, FaWrench } from 'react-icons/fa';
import { MdPhotoLibrary } from 'react-icons/md'; import { MdPhotoLibrary } from 'react-icons/md';
import HoverableImage from './HoverableImage'; import HoverableImage from './HoverableImage';
import Scrollable from 'features/ui/components/common/Scrollable';
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
import { import { resultsAdapter } from '../store/resultsSlice';
resultsAdapter,
selectResultsAll,
selectResultsTotal,
} from '../store/resultsSlice';
import { import {
receivedResultImagesPage, receivedResultImagesPage,
receivedUploadImagesPage, receivedUploadImagesPage,
} from 'services/thunks/gallery'; } from 'services/thunks/gallery';
import { selectUploadsAll, uploadsAdapter } from '../store/uploadsSlice'; import { uploadsAdapter } from '../store/uploadsSlice';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store'; import { RootState } from 'app/store/store';
import { Virtuoso, VirtuosoGrid } from 'react-virtuoso';
const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 290; const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 290;
@ -49,7 +62,7 @@ const gallerySelector = createSelector(
(uploads, results, gallery) => { (uploads, results, gallery) => {
const { currentCategory } = gallery; const { currentCategory } = gallery;
return currentCategory === 'result' return currentCategory === 'results'
? { ? {
images: resultsAdapter.getSelectors().selectAll(results), images: resultsAdapter.getSelectors().selectAll(results),
isLoading: results.isLoading, isLoading: results.isLoading,
@ -68,32 +81,41 @@ const ImageGalleryContent = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const resizeObserverRef = useRef<HTMLDivElement>(null); const resizeObserverRef = useRef<HTMLDivElement>(null);
const [shouldShouldIconButtons, setShouldShouldIconButtons] = useState(true); const [shouldShouldIconButtons, setShouldShouldIconButtons] = useState(true);
const rootRef = useRef(null);
const [scroller, setScroller] = useState<HTMLElement | null>(null);
const [initialize, osInstance] = useOverlayScrollbars({
defer: true,
options: {
scrollbars: {
visibility: 'auto',
autoHide: 'leave',
autoHideDelay: 1300,
theme: 'os-theme-dark',
},
overflow: { x: 'hidden' },
},
});
const { const {
// images, // images,
currentCategory, currentCategory,
currentImageUuid,
shouldPinGallery, shouldPinGallery,
galleryImageMinimumWidth, galleryImageMinimumWidth,
galleryGridTemplateColumns,
galleryImageObjectFit, galleryImageObjectFit,
shouldAutoSwitchToNewImages, shouldAutoSwitchToNewImages,
// areMoreImagesAvailable,
shouldUseSingleGalleryColumn, shouldUseSingleGalleryColumn,
selectedImage,
} = useAppSelector(imageGallerySelector); } = useAppSelector(imageGallerySelector);
const { images, areMoreImagesAvailable, isLoading } = const { images, areMoreImagesAvailable, isLoading } =
useAppSelector(gallerySelector); useAppSelector(gallerySelector);
// const handleClickLoadMore = () => {
// dispatch(requestImages(currentCategory));
// };
const handleClickLoadMore = () => { const handleClickLoadMore = () => {
if (currentCategory === 'result') { if (currentCategory === 'results') {
dispatch(receivedResultImagesPage()); dispatch(receivedResultImagesPage());
} }
if (currentCategory === 'user') { if (currentCategory === 'uploads') {
dispatch(receivedUploadImagesPage()); dispatch(receivedUploadImagesPage());
} }
}; };
@ -129,6 +151,25 @@ const ImageGalleryContent = () => {
return () => resizeObserver.disconnect(); // clean up return () => resizeObserver.disconnect(); // clean up
}, []); }, []);
useEffect(() => {
const { current: root } = rootRef;
if (scroller && root) {
initialize({
target: root,
elements: {
viewport: scroller,
},
});
}
return () => osInstance()?.destroy();
}, [scroller, initialize, osInstance]);
const setScrollerRef = useCallback((ref: HTMLElement | Window | null) => {
if (ref instanceof HTMLElement) {
setScroller(ref);
}
}, []);
return ( return (
<Flex flexDirection="column" w="full" h="full" gap={4}> <Flex flexDirection="column" w="full" h="full" gap={4}>
<Flex <Flex
@ -147,34 +188,34 @@ const ImageGalleryContent = () => {
<IAIIconButton <IAIIconButton
aria-label={t('gallery.showGenerations')} aria-label={t('gallery.showGenerations')}
tooltip={t('gallery.showGenerations')} tooltip={t('gallery.showGenerations')}
isChecked={currentCategory === 'result'} isChecked={currentCategory === 'results'}
role="radio" role="radio"
icon={<FaImage />} icon={<FaImage />}
onClick={() => dispatch(setCurrentCategory('result'))} onClick={() => dispatch(setCurrentCategory('results'))}
/> />
<IAIIconButton <IAIIconButton
aria-label={t('gallery.showUploads')} aria-label={t('gallery.showUploads')}
tooltip={t('gallery.showUploads')} tooltip={t('gallery.showUploads')}
role="radio" role="radio"
isChecked={currentCategory === 'user'} isChecked={currentCategory === 'uploads'}
icon={<FaUser />} icon={<FaUser />}
onClick={() => dispatch(setCurrentCategory('user'))} onClick={() => dispatch(setCurrentCategory('uploads'))}
/> />
</> </>
) : ( ) : (
<> <>
<IAIButton <IAIButton
size="sm" size="sm"
isChecked={currentCategory === 'result'} isChecked={currentCategory === 'results'}
onClick={() => dispatch(setCurrentCategory('result'))} onClick={() => dispatch(setCurrentCategory('results'))}
flexGrow={1} flexGrow={1}
> >
{t('gallery.generations')} {t('gallery.generations')}
</IAIButton> </IAIButton>
<IAIButton <IAIButton
size="sm" size="sm"
isChecked={currentCategory === 'user'} isChecked={currentCategory === 'uploads'}
onClick={() => dispatch(setCurrentCategory('user'))} onClick={() => dispatch(setCurrentCategory('uploads'))}
flexGrow={1} flexGrow={1}
> >
{t('gallery.uploads')} {t('gallery.uploads')}
@ -241,17 +282,43 @@ const ImageGalleryContent = () => {
/> />
</Flex> </Flex>
</Flex> </Flex>
<Scrollable>
<Flex direction="column" gap={2} h="full"> <Flex direction="column" gap={2} h="full">
{images.length || areMoreImagesAvailable ? ( {images.length || areMoreImagesAvailable ? (
<> <>
<Grid <Box ref={rootRef} data-overlayscrollbars="" h="100%">
gap={2} {shouldUseSingleGalleryColumn ? (
style={{ gridTemplateColumns: galleryGridTemplateColumns }} <Virtuoso
> style={{ height: '100%' }}
{images.map((image) => { data={images}
scrollerRef={(ref) => setScrollerRef(ref)}
itemContent={(index, image) => {
const { name } = image; const { name } = image;
const isSelected = currentImageUuid === name; const isSelected = selectedImage?.name === name;
return (
<Flex sx={{ pb: 2 }}>
<HoverableImage
key={`${name}-${image.thumbnail}`}
image={image}
isSelected={isSelected}
/>
</Flex>
);
}}
/>
) : (
<VirtuosoGrid
style={{ height: '100%' }}
data={images}
components={{
Item: ItemContainer,
List: ListContainer,
}}
scrollerRef={setScroller}
itemContent={(index, image) => {
const { name } = image;
const isSelected = selectedImage?.name === name;
return ( return (
<HoverableImage <HoverableImage
key={`${name}-${image.thumbnail}`} key={`${name}-${image.thumbnail}`}
@ -259,8 +326,10 @@ const ImageGalleryContent = () => {
isSelected={isSelected} isSelected={isSelected}
/> />
); );
})} }}
</Grid> />
)}
</Box>
<IAIButton <IAIButton
onClick={handleClickLoadMore} onClick={handleClickLoadMore}
isDisabled={!areMoreImagesAvailable} isDisabled={!areMoreImagesAvailable}
@ -296,10 +365,36 @@ const ImageGalleryContent = () => {
</Flex> </Flex>
)} )}
</Flex> </Flex>
</Scrollable>
</Flex> </Flex>
); );
}; };
ImageGalleryContent.displayName = 'ImageGalleryContent'; type ItemContainerProps = PropsWithChildren & FlexProps;
export default ImageGalleryContent; const ItemContainer = forwardRef((props: ItemContainerProps, ref) => (
<Box className="item-container" ref={ref}>
{props.children}
</Box>
));
type ListContainerProps = PropsWithChildren & FlexProps;
const ListContainer = forwardRef((props: ListContainerProps, ref) => {
const galleryImageMinimumWidth = useAppSelector(
(state: RootState) => state.gallery.galleryImageMinimumWidth
);
return (
<Grid
{...props}
className="list-container"
ref={ref}
sx={{
gap: 2,
gridTemplateColumns: `repeat(auto-fit, minmax(${galleryImageMinimumWidth}px, 1fr));`,
}}
>
{props.children}
</Grid>
);
});
export default memo(ImageGalleryContent);

View File

@ -1,13 +1,13 @@
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { gallerySelector } from 'features/gallery/store/gallerySelectors'; import { gallerySelector } from 'features/gallery/store/gallerySelectors';
import { import {
selectNextImage, // selectNextImage,
selectPrevImage, // selectPrevImage,
setGalleryImageMinimumWidth, setGalleryImageMinimumWidth,
} from 'features/gallery/store/gallerySlice'; } from 'features/gallery/store/gallerySlice';
import { InvokeTabName } from 'features/ui/store/tabMap'; import { InvokeTabName } from 'features/ui/store/tabMap';
import { clamp, isEqual } from 'lodash'; import { clamp, isEqual } from 'lodash-es';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import './ImageGallery.css'; import './ImageGallery.css';
@ -28,6 +28,7 @@ import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvas
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
import useResolution from 'common/hooks/useResolution'; import useResolution from 'common/hooks/useResolution';
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { memo } from 'react';
const GALLERY_TAB_WIDTHS: Record< const GALLERY_TAB_WIDTHS: Record<
InvokeTabName, InvokeTabName,
@ -72,7 +73,7 @@ const galleryPanelSelector = createSelector(
} }
); );
export default function ImageGalleryPanel() { export const ImageGalleryPanel = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { const {
shouldPinGallery, shouldPinGallery,
@ -109,28 +110,6 @@ export default function ImageGalleryPanel() {
[shouldPinGallery] [shouldPinGallery]
); );
useHotkeys(
'left',
() => {
dispatch(selectPrevImage());
},
{
enabled: !isStaging || activeTabName !== 'unifiedCanvas',
},
[isStaging, activeTabName]
);
useHotkeys(
'right',
() => {
dispatch(selectNextImage());
},
{
enabled: !isStaging || activeTabName !== 'unifiedCanvas',
},
[isStaging, activeTabName]
);
useHotkeys( useHotkeys(
'shift+g', 'shift+g',
() => { () => {
@ -232,4 +211,6 @@ export default function ImageGalleryPanel() {
}; };
return renderImageGallery(); return renderImageGallery();
} };
export default memo(ImageGalleryPanel);

View File

@ -9,8 +9,8 @@ import {
Text, Text,
Tooltip, Tooltip,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/types/invokeai';
import { useAppDispatch } from 'app/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl'; import { useGetUrl } from 'common/util/getUrl';
import promptToString from 'common/util/promptToString'; import promptToString from 'common/util/promptToString';
import { seedWeightsToString } from 'common/util/seedWeightPairs'; import { seedWeightsToString } from 'common/util/seedWeightPairs';
@ -159,6 +159,7 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
_dark: { _dark: {
bg: 'blackAlpha.600', bg: 'blackAlpha.600',
}, },
overflow: 'scroll',
}} }}
> >
<Flex gap={2}> <Flex gap={2}>

View File

@ -9,8 +9,8 @@ import {
Text, Text,
Tooltip, Tooltip,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/types/invokeai';
import { useAppDispatch } from 'app/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl'; import { useGetUrl } from 'common/util/getUrl';
import promptToString from 'common/util/promptToString'; import promptToString from 'common/util/promptToString';
import { seedWeightsToString } from 'common/util/seedWeightPairs'; import { seedWeightsToString } from 'common/util/seedWeightPairs';

View File

@ -1,16 +1,14 @@
import { ChakraProps, Flex, Grid, IconButton } from '@chakra-ui/react'; import { ChakraProps, Flex, Grid, IconButton } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { isEqual } from 'lodash'; import { clamp, isEqual } from 'lodash-es';
import { useState } from 'react'; import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaAngleLeft, FaAngleRight } from 'react-icons/fa'; import { FaAngleLeft, FaAngleRight } from 'react-icons/fa';
import { gallerySelector } from '../store/gallerySelectors'; import { gallerySelector } from '../store/gallerySelectors';
import { import { RootState } from 'app/store/store';
GalleryCategory, import { imageSelected } from '../store/gallerySlice';
selectNextImage, import { useHotkeys } from 'react-hotkeys-hook';
selectPrevImage,
} from '../store/gallerySlice';
const nextPrevButtonTriggerAreaStyles: ChakraProps['sx'] = { const nextPrevButtonTriggerAreaStyles: ChakraProps['sx'] = {
height: '100%', height: '100%',
@ -23,24 +21,47 @@ const nextPrevButtonStyles: ChakraProps['sx'] = {
}; };
export const nextPrevImageButtonsSelector = createSelector( export const nextPrevImageButtonsSelector = createSelector(
gallerySelector, [(state: RootState) => state, gallerySelector],
(gallery) => { (state, gallery) => {
const { currentImage } = gallery; const { selectedImage, currentCategory } = gallery;
const tempImages = if (!selectedImage) {
gallery.categories[ return {
currentImage ? (currentImage.category as GalleryCategory) : 'result' isOnFirstImage: true,
].images; isOnLastImage: true,
};
}
const currentImageIndex = tempImages.findIndex( const currentImageIndex = state[currentCategory].ids.findIndex(
(i) => i.uuid === gallery?.currentImage?.uuid (i) => i === selectedImage.name
); );
const imagesLength = tempImages.length;
const nextImageIndex = clamp(
currentImageIndex + 1,
0,
state[currentCategory].ids.length - 1
);
const prevImageIndex = clamp(
currentImageIndex - 1,
0,
state[currentCategory].ids.length - 1
);
const nextImageId = state[currentCategory].ids[nextImageIndex];
const prevImageId = state[currentCategory].ids[prevImageIndex];
const nextImage = state[currentCategory].entities[nextImageId];
const prevImage = state[currentCategory].entities[prevImageId];
const imagesLength = state[currentCategory].ids.length;
return { return {
isOnFirstImage: currentImageIndex === 0, isOnFirstImage: currentImageIndex === 0,
isOnLastImage: isOnLastImage:
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1, !isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
nextImage,
prevImage,
}; };
}, },
{ {
@ -54,34 +75,48 @@ const NextPrevImageButtons = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const { isOnFirstImage, isOnLastImage } = useAppSelector( const { isOnFirstImage, isOnLastImage, nextImage, prevImage } =
nextPrevImageButtonsSelector useAppSelector(nextPrevImageButtonsSelector);
);
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] = const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
useState<boolean>(false); useState<boolean>(false);
const handleCurrentImagePreviewMouseOver = () => { const handleCurrentImagePreviewMouseOver = useCallback(() => {
setShouldShowNextPrevButtons(true); setShouldShowNextPrevButtons(true);
}; }, []);
const handleCurrentImagePreviewMouseOut = () => { const handleCurrentImagePreviewMouseOut = useCallback(() => {
setShouldShowNextPrevButtons(false); setShouldShowNextPrevButtons(false);
}; }, []);
const handleClickPrevButton = () => { const handlePrevImage = useCallback(() => {
dispatch(selectPrevImage()); dispatch(imageSelected(prevImage));
}; }, [dispatch, prevImage]);
const handleClickNextButton = () => { const handleNextImage = useCallback(() => {
dispatch(selectNextImage()); dispatch(imageSelected(nextImage));
}; }, [dispatch, nextImage]);
useHotkeys(
'left',
() => {
handlePrevImage();
},
[prevImage]
);
useHotkeys(
'right',
() => {
handleNextImage();
},
[nextImage]
);
return ( return (
<Flex <Flex
sx={{ sx={{
justifyContent: 'space-between', justifyContent: 'space-between',
zIndex: 1,
height: '100%', height: '100%',
width: '100%', width: '100%',
pointerEvents: 'none', pointerEvents: 'none',
@ -100,7 +135,7 @@ const NextPrevImageButtons = () => {
aria-label={t('accessibility.previousImage')} aria-label={t('accessibility.previousImage')}
icon={<FaAngleLeft size={64} />} icon={<FaAngleLeft size={64} />}
variant="unstyled" variant="unstyled"
onClick={handleClickPrevButton} onClick={handlePrevImage}
boxSize={16} boxSize={16}
sx={nextPrevButtonStyles} sx={nextPrevButtonStyles}
/> />
@ -119,7 +154,7 @@ const NextPrevImageButtons = () => {
aria-label={t('accessibility.nextImage')} aria-label={t('accessibility.nextImage')}
icon={<FaAngleRight size={64} />} icon={<FaAngleRight size={64} />}
variant="unstyled" variant="unstyled"
onClick={handleClickNextButton} onClick={handleNextImage}
boxSize={16} boxSize={16}
sx={nextPrevButtonStyles} sx={nextPrevButtonStyles}
/> />

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { ImageType } from 'services/api'; import { ImageType } from 'services/api';
import { selectResultsEntities } from '../store/resultsSlice'; import { selectResultsEntities } from '../store/resultsSlice';
import { selectUploadsEntities } from '../store/uploadsSlice'; import { selectUploadsEntities } from '../store/uploadsSlice';

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { gallerySelector } from '../store/gallerySelectors'; import { gallerySelector } from '../store/gallerySelectors';
const selector = createSelector(gallerySelector, (gallery) => ({ const selector = createSelector(gallerySelector, (gallery) => ({

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store'; import { RootState } from 'app/store/store';
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
import { configSelector } from 'features/system/store/configSelectors'; import { configSelector } from 'features/system/store/configSelectors';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
@ -7,7 +7,7 @@ import {
activeTabNameSelector, activeTabNameSelector,
uiSelector, uiSelector,
} from 'features/ui/store/uiSelectors'; } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { import {
selectResultsAll, selectResultsAll,
selectResultsById, selectResultsById,
@ -22,17 +22,22 @@ import {
export const gallerySelector = (state: RootState) => state.gallery; export const gallerySelector = (state: RootState) => state.gallery;
export const imageGallerySelector = createSelector( export const imageGallerySelector = createSelector(
[gallerySelector, uiSelector, lightboxSelector, activeTabNameSelector], [
(gallery, ui, lightbox, activeTabName) => { (state: RootState) => state,
gallerySelector,
uiSelector,
lightboxSelector,
activeTabNameSelector,
],
(state, gallery, ui, lightbox, activeTabName) => {
const { const {
categories,
currentCategory, currentCategory,
currentImageUuid,
galleryImageMinimumWidth, galleryImageMinimumWidth,
galleryImageObjectFit, galleryImageObjectFit,
shouldAutoSwitchToNewImages, shouldAutoSwitchToNewImages,
galleryWidth, galleryWidth,
shouldUseSingleGalleryColumn, shouldUseSingleGalleryColumn,
selectedImage,
} = gallery; } = gallery;
const { shouldPinGallery } = ui; const { shouldPinGallery } = ui;
@ -40,7 +45,6 @@ export const imageGallerySelector = createSelector(
const { isLightboxOpen } = lightbox; const { isLightboxOpen } = lightbox;
return { return {
currentImageUuid,
shouldPinGallery, shouldPinGallery,
galleryImageMinimumWidth, galleryImageMinimumWidth,
galleryImageObjectFit, galleryImageObjectFit,
@ -49,9 +53,7 @@ export const imageGallerySelector = createSelector(
: `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, auto))`, : `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, auto))`,
shouldAutoSwitchToNewImages, shouldAutoSwitchToNewImages,
currentCategory, currentCategory,
images: categories[currentCategory].images, images: state[currentCategory].entities,
areMoreImagesAvailable:
categories[currentCategory].areMoreImagesAvailable,
galleryWidth, galleryWidth,
shouldEnableResize: shouldEnableResize:
isLightboxOpen || isLightboxOpen ||
@ -59,6 +61,7 @@ export const imageGallerySelector = createSelector(
? false ? false
: true, : true,
shouldUseSingleGalleryColumn, shouldUseSingleGalleryColumn,
selectedImage,
}; };
}, },
{ {
@ -69,16 +72,16 @@ export const imageGallerySelector = createSelector(
); );
export const selectedImageSelector = createSelector( export const selectedImageSelector = createSelector(
[gallerySelector, selectResultsEntities, selectUploadsEntities], [(state: RootState) => state, gallerySelector],
(gallery, allResults, allUploads) => { (state, gallery) => {
const selectedImageName = gallery.selectedImageName; const selectedImage = gallery.selectedImage;
if (selectedImageName in allResults) { if (selectedImage?.type === 'results') {
return allResults[selectedImageName]; return selectResultsById(state, selectedImage.name);
} }
if (selectedImageName in allUploads) { if (selectedImage?.type === 'uploads') {
return allUploads[selectedImageName]; return selectUploadsById(state, selectedImage.name);
} }
} }
); );

View File

@ -1,259 +1,47 @@
import type { PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/invokeai';
import { invocationComplete } from 'services/events/actions'; import { invocationComplete } from 'services/events/actions';
import { InvokeTabName } from 'features/ui/store/tabMap';
import { IRect } from 'konva/lib/types';
import { clamp } from 'lodash';
import { isImageOutput } from 'services/types/guards'; import { isImageOutput } from 'services/types/guards';
import { deserializeImageResponse } from 'services/util/deserializeImageResponse'; import { deserializeImageResponse } from 'services/util/deserializeImageResponse';
import { imageUploaded } from 'services/thunks/image'; import { imageUploaded } from 'services/thunks/image';
import { SelectedImage } from 'features/parameters/store/generationSlice';
export type GalleryCategory = 'user' | 'result';
export type AddImagesPayload = {
images: Array<InvokeAI._Image>;
areMoreImagesAvailable: boolean;
category: GalleryCategory;
};
type GalleryImageObjectFitType = 'contain' | 'cover'; type GalleryImageObjectFitType = 'contain' | 'cover';
export type Gallery = {
images: InvokeAI._Image[];
latest_mtime?: number;
earliest_mtime?: number;
areMoreImagesAvailable: boolean;
};
export interface GalleryState { export interface GalleryState {
/** /**
* The selected image's unique name * The selected image
* Use `selectedImageSelector` to access the image
*/ */
selectedImageName: string; selectedImage?: SelectedImage;
/**
* The currently selected image
* @deprecated See `state.gallery.selectedImageName`
*/
currentImage?: InvokeAI._Image;
/**
* The currently selected image's uuid.
* @deprecated See `state.gallery.selectedImageName`, use `selectedImageSelector` to access the image
*/
currentImageUuid: string;
/**
* The current progress image
* @deprecated See `state.system.progressImage`
*/
intermediateImage?: InvokeAI._Image & {
boundingBox?: IRect;
generationMode?: InvokeTabName;
};
galleryImageMinimumWidth: number; galleryImageMinimumWidth: number;
galleryImageObjectFit: GalleryImageObjectFitType; galleryImageObjectFit: GalleryImageObjectFitType;
shouldAutoSwitchToNewImages: boolean; shouldAutoSwitchToNewImages: boolean;
categories: {
user: Gallery;
result: Gallery;
};
currentCategory: GalleryCategory;
galleryWidth: number; galleryWidth: number;
shouldUseSingleGalleryColumn: boolean; shouldUseSingleGalleryColumn: boolean;
currentCategory: 'results' | 'uploads';
} }
const initialState: GalleryState = { const initialState: GalleryState = {
selectedImageName: '', selectedImage: undefined,
currentImageUuid: '',
galleryImageMinimumWidth: 64, galleryImageMinimumWidth: 64,
galleryImageObjectFit: 'cover', galleryImageObjectFit: 'cover',
shouldAutoSwitchToNewImages: true, shouldAutoSwitchToNewImages: true,
currentCategory: 'result',
categories: {
user: {
images: [],
latest_mtime: undefined,
earliest_mtime: undefined,
areMoreImagesAvailable: true,
},
result: {
images: [],
latest_mtime: undefined,
earliest_mtime: undefined,
areMoreImagesAvailable: true,
},
},
galleryWidth: 300, galleryWidth: 300,
shouldUseSingleGalleryColumn: false, shouldUseSingleGalleryColumn: false,
currentCategory: 'results',
}; };
export const gallerySlice = createSlice({ export const gallerySlice = createSlice({
name: 'gallery', name: 'gallery',
initialState, initialState,
reducers: { reducers: {
imageSelected: (state, action: PayloadAction<string>) => { imageSelected: (
state.selectedImageName = action.payload;
},
setCurrentImage: (state, action: PayloadAction<InvokeAI._Image>) => {
state.currentImage = action.payload;
state.currentImageUuid = action.payload.uuid;
},
removeImage: (
state, state,
action: PayloadAction<InvokeAI.ImageDeletedResponse> action: PayloadAction<SelectedImage | undefined>
) => { ) => {
const { uuid, category } = action.payload; state.selectedImage = action.payload;
// TODO: if the user selects an image, disable the auto switch?
const tempImages = state.categories[category as GalleryCategory].images; // state.shouldAutoSwitchToNewImages = false;
const newImages = tempImages.filter((image) => image.uuid !== uuid);
if (uuid === state.currentImageUuid) {
/**
* We are deleting the currently selected image.
*
* We want the new currentl selected image to be under the cursor in the
* gallery, so we need to do some fanagling. The currently selected image
* is set by its UUID, not its index in the image list.
*
* Get the currently selected image's index.
*/
const imageToDeleteIndex = tempImages.findIndex(
(image) => image.uuid === uuid
);
/**
* New current image needs to be in the same spot, but because the gallery
* is sorted in reverse order, the new current image's index will actuall be
* one less than the deleted image's index.
*
* Clamp the new index to ensure it is valid..
*/
const newCurrentImageIndex = clamp(
imageToDeleteIndex,
0,
newImages.length - 1
);
state.currentImage = newImages.length
? newImages[newCurrentImageIndex]
: undefined;
state.currentImageUuid = newImages.length
? newImages[newCurrentImageIndex].uuid
: '';
}
state.categories[category as GalleryCategory].images = newImages;
},
addImage: (
state,
action: PayloadAction<{
image: InvokeAI._Image;
category: GalleryCategory;
}>
) => {
const { image: newImage, category } = action.payload;
const { uuid, url, mtime } = newImage;
const tempCategory = state.categories[category as GalleryCategory];
// Do not add duplicate images
if (tempCategory.images.find((i) => i.url === url && i.mtime === mtime)) {
return;
}
tempCategory.images.unshift(newImage);
if (state.shouldAutoSwitchToNewImages) {
state.currentImageUuid = uuid;
state.currentImage = newImage;
state.currentCategory = category;
}
state.intermediateImage = undefined;
tempCategory.latest_mtime = mtime;
},
setIntermediateImage: (
state,
action: PayloadAction<
InvokeAI._Image & {
boundingBox?: IRect;
generationMode?: InvokeTabName;
}
>
) => {
state.intermediateImage = action.payload;
},
clearIntermediateImage: (state) => {
state.intermediateImage = undefined;
},
selectNextImage: (state) => {
const { currentImage } = state;
if (!currentImage) return;
const tempImages =
state.categories[currentImage.category as GalleryCategory].images;
if (currentImage) {
const currentImageIndex = tempImages.findIndex(
(i) => i.uuid === currentImage.uuid
);
if (currentImageIndex < tempImages.length - 1) {
const newCurrentImage = tempImages[currentImageIndex + 1];
state.currentImage = newCurrentImage;
state.currentImageUuid = newCurrentImage.uuid;
}
}
},
selectPrevImage: (state) => {
const { currentImage } = state;
if (!currentImage) return;
const tempImages =
state.categories[currentImage.category as GalleryCategory].images;
if (currentImage) {
const currentImageIndex = tempImages.findIndex(
(i) => i.uuid === currentImage.uuid
);
if (currentImageIndex > 0) {
const newCurrentImage = tempImages[currentImageIndex - 1];
state.currentImage = newCurrentImage;
state.currentImageUuid = newCurrentImage.uuid;
}
}
},
addGalleryImages: (state, action: PayloadAction<AddImagesPayload>) => {
const { images, areMoreImagesAvailable, category } = action.payload;
const tempImages = state.categories[category].images;
// const prevImages = category === 'user' ? state.userImages : state.resultImages
if (images.length > 0) {
// Filter images that already exist in the gallery
const newImages = images.filter(
(newImage) =>
!tempImages.find(
(i) => i.url === newImage.url && i.mtime === newImage.mtime
)
);
state.categories[category].images = tempImages
.concat(newImages)
.sort((a, b) => b.mtime - a.mtime);
if (!state.currentImage) {
const newCurrentImage = images[0];
state.currentImage = newCurrentImage;
state.currentImageUuid = newCurrentImage.uuid;
}
// keep track of the timestamps of latest and earliest images received
state.categories[category].latest_mtime = images[0].mtime;
state.categories[category].earliest_mtime =
images[images.length - 1].mtime;
}
if (areMoreImagesAvailable !== undefined) {
state.categories[category].areMoreImagesAvailable =
areMoreImagesAvailable;
}
}, },
setGalleryImageMinimumWidth: (state, action: PayloadAction<number>) => { setGalleryImageMinimumWidth: (state, action: PayloadAction<number>) => {
state.galleryImageMinimumWidth = action.payload; state.galleryImageMinimumWidth = action.payload;
@ -267,7 +55,10 @@ export const gallerySlice = createSlice({
setShouldAutoSwitchToNewImages: (state, action: PayloadAction<boolean>) => { setShouldAutoSwitchToNewImages: (state, action: PayloadAction<boolean>) => {
state.shouldAutoSwitchToNewImages = action.payload; state.shouldAutoSwitchToNewImages = action.payload;
}, },
setCurrentCategory: (state, action: PayloadAction<GalleryCategory>) => { setCurrentCategory: (
state,
action: PayloadAction<'results' | 'uploads'>
) => {
state.currentCategory = action.payload; state.currentCategory = action.payload;
}, },
setGalleryWidth: (state, action: PayloadAction<number>) => { setGalleryWidth: (state, action: PayloadAction<number>) => {
@ -286,9 +77,11 @@ export const gallerySlice = createSlice({
*/ */
builder.addCase(invocationComplete, (state, action) => { builder.addCase(invocationComplete, (state, action) => {
const { data } = action.payload; const { data } = action.payload;
if (isImageOutput(data.result)) { if (isImageOutput(data.result) && state.shouldAutoSwitchToNewImages) {
state.selectedImageName = data.result.image.image_name; state.selectedImage = {
state.intermediateImage = undefined; name: data.result.image.image_name,
type: 'results',
};
} }
}); });
@ -299,27 +92,19 @@ export const gallerySlice = createSlice({
const { response } = action.payload; const { response } = action.payload;
const uploadedImage = deserializeImageResponse(response); const uploadedImage = deserializeImageResponse(response);
state.selectedImageName = uploadedImage.name; state.selectedImage = { name: uploadedImage.name, type: 'uploads' };
}); });
}, },
}); });
export const { export const {
imageSelected, imageSelected,
addImage,
clearIntermediateImage,
removeImage,
setCurrentImage,
addGalleryImages,
setIntermediateImage,
selectNextImage,
selectPrevImage,
setGalleryImageMinimumWidth, setGalleryImageMinimumWidth,
setGalleryImageObjectFit, setGalleryImageObjectFit,
setShouldAutoSwitchToNewImages, setShouldAutoSwitchToNewImages,
setCurrentCategory,
setGalleryWidth, setGalleryWidth,
setShouldUseSingleGalleryColumn, setShouldUseSingleGalleryColumn,
setCurrentCategory,
} = gallerySlice.actions; } = gallerySlice.actions;
export default gallerySlice.reducer; export default gallerySlice.reducer;

View File

@ -5,7 +5,7 @@ import { ResultsState } from './resultsSlice';
* *
* Currently denylisting results slice entirely, see persist config in store.ts * Currently denylisting results slice entirely, see persist config in store.ts
*/ */
const itemsToDenylist: (keyof ResultsState)[] = []; const itemsToDenylist: (keyof ResultsState)[] = ['isLoading'];
export const resultsDenylist = itemsToDenylist.map( export const resultsDenylist = itemsToDenylist.map(
(denylistItem) => `results.${denylistItem}` (denylistItem) => `results.${denylistItem}`

View File

@ -1,8 +1,8 @@
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
import { Image } from 'app/invokeai'; import { Image } from 'app/types/invokeai';
import { invocationComplete } from 'services/events/actions'; import { invocationComplete } from 'services/events/actions';
import { RootState } from 'app/store'; import { RootState } from 'app/store/store';
import { import {
receivedResultImagesPage, receivedResultImagesPage,
IMAGES_PER_PAGE, IMAGES_PER_PAGE,
@ -65,7 +65,7 @@ const resultsSlice = createSlice({
deserializeImageResponse(image) deserializeImageResponse(image)
); );
resultsAdapter.addMany(state, resultImages); resultsAdapter.setMany(state, resultImages);
state.page = page; state.page = page;
state.pages = pages; state.pages = pages;
@ -107,7 +107,7 @@ const resultsSlice = createSlice({
}, },
}; };
resultsAdapter.addOne(state, image); resultsAdapter.setOne(state, image);
} }
}); });

View File

@ -5,7 +5,7 @@ import { UploadsState } from './uploadsSlice';
* *
* Currently denylisting uploads slice entirely, see persist config in store.ts * Currently denylisting uploads slice entirely, see persist config in store.ts
*/ */
const itemsToDenylist: (keyof UploadsState)[] = []; const itemsToDenylist: (keyof UploadsState)[] = ['isLoading'];
export const uploadsDenylist = itemsToDenylist.map( export const uploadsDenylist = itemsToDenylist.map(
(denylistItem) => `uploads.${denylistItem}` (denylistItem) => `uploads.${denylistItem}`

View File

@ -1,7 +1,7 @@
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
import { Image } from 'app/invokeai'; import { Image } from 'app/types/invokeai';
import { RootState } from 'app/store'; import { RootState } from 'app/store/store';
import { import {
receivedUploadImagesPage, receivedUploadImagesPage,
IMAGES_PER_PAGE, IMAGES_PER_PAGE,
@ -53,7 +53,7 @@ const uploadsSlice = createSlice({
const images = items.map((image) => deserializeImageResponse(image)); const images = items.map((image) => deserializeImageResponse(image));
uploadsAdapter.addMany(state, images); uploadsAdapter.setMany(state, images);
state.page = page; state.page = page;
state.pages = pages; state.pages = pages;
@ -69,7 +69,7 @@ const uploadsSlice = createSlice({
const uploadedImage = deserializeImageResponse(response); const uploadedImage = deserializeImageResponse(response);
uploadsAdapter.addOne(state, uploadedImage); uploadsAdapter.setOne(state, uploadedImage);
}); });
/** /**

View File

@ -1,7 +1,7 @@
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store'; import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import CurrentImageButtons from 'features/gallery/components/CurrentImageButtons'; import CurrentImageButtons from 'features/gallery/components/CurrentImageButtons';
import ImageMetadataViewer from 'features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer'; import ImageMetadataViewer from 'features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer';
@ -10,7 +10,7 @@ import { gallerySelector } from 'features/gallery/store/gallerySelectors';
import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice'; import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
import { uiSelector } from 'features/ui/store/uiSelectors'; import { uiSelector } from 'features/ui/store/uiSelectors';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { BiExit } from 'react-icons/bi'; import { BiExit } from 'react-icons/bi';
import { TransformWrapper } from 'react-zoom-pan-pinch'; import { TransformWrapper } from 'react-zoom-pan-pinch';

View File

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { TransformComponent, useTransformContext } from 'react-zoom-pan-pinch'; import { TransformComponent, useTransformContext } from 'react-zoom-pan-pinch';
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/types/invokeai';
import { useGetUrl } from 'common/util/getUrl'; import { useGetUrl } from 'common/util/getUrl';
type ReactPanZoomProps = { type ReactPanZoomProps = {

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store'; import { RootState } from 'app/store/store';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
export const lightboxSelector = createSelector( export const lightboxSelector = createSelector(
(state: RootState) => state.lightbox, (state: RootState) => state.lightbox,

View File

@ -11,15 +11,15 @@ import {
IconButton, IconButton,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { FaEllipsisV, FaPlus } from 'react-icons/fa'; import { FaEllipsisV, FaPlus } from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { nodeAdded } from '../store/nodesSlice'; import { nodeAdded } from '../store/nodesSlice';
import { cloneDeep, map } from 'lodash'; import { cloneDeep, map } from 'lodash-es';
import { RootState } from 'app/store'; import { RootState } from 'app/store/store';
import { useBuildInvocation } from '../hooks/useBuildInvocation'; import { useBuildInvocation } from '../hooks/useBuildInvocation';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/hooks/useToastWatcher'; import { makeToast } from 'features/system/hooks/useToastWatcher';
import { IAIIconButton } from 'exports';
import { AnyInvocationType } from 'services/events/types'; import { AnyInvocationType } from 'services/events/types';
import IAIIconButton from 'common/components/IAIIconButton';
const AddNodeMenu = () => { const AddNodeMenu = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();

View File

@ -1,6 +1,6 @@
import 'reactflow/dist/style.css'; import 'reactflow/dist/style.css';
import { Tooltip, Badge, Flex } from '@chakra-ui/react'; import { Tooltip, Badge, Flex } from '@chakra-ui/react';
import { map } from 'lodash'; import { map } from 'lodash-es';
import { FIELDS } from '../types/constants'; import { FIELDS } from '../types/constants';
import { memo } from 'react'; import { memo } from 'react';

Some files were not shown because too many files have changed in this diff Show More