Merge branch 'main' into lstein/installer-for-new-model-layout

This commit is contained in:
Lincoln Stein 2023-06-24 11:58:06 -04:00
commit d5f742620f
273 changed files with 6551 additions and 7152 deletions

View File

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

View File

@ -23,8 +23,7 @@
"dev": "concurrently \"vite dev\" \"yarn run theme:watch\"", "dev": "concurrently \"vite dev\" \"yarn run theme:watch\"",
"dev:host": "concurrently \"vite dev --host\" \"yarn run theme:watch\"", "dev:host": "concurrently \"vite dev --host\" \"yarn run theme:watch\"",
"build": "yarn run lint && vite build", "build": "yarn run lint && vite build",
"api:web": "openapi -i http://localhost:9090/openapi.json -o src/services/api --client axios --useOptions --useUnionTypes --indent 2 --request src/services/fixtures/request.ts", "typegen": "npx openapi-typescript http://localhost:9090/openapi.json --output src/services/schema.d.ts -t",
"api:file": "openapi -i src/services/fixtures/openapi.json -o src/services/api --client axios --useOptions --useUnionTypes --indent 2 --request src/services/fixtures/request.ts",
"preview": "vite preview", "preview": "vite preview",
"lint:madge": "madge --circular src/main.tsx", "lint:madge": "madge --circular src/main.tsx",
"lint:eslint": "eslint --max-warnings=0 .", "lint:eslint": "eslint --max-warnings=0 .",
@ -81,9 +80,12 @@
"i18next-http-backend": "^2.2.0", "i18next-http-backend": "^2.2.0",
"konva": "^9.0.1", "konva": "^9.0.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"nanostores": "^0.9.2",
"openapi-fetch": "^0.4.0",
"overlayscrollbars": "^2.1.1", "overlayscrollbars": "^2.1.1",
"overlayscrollbars-react": "^0.5.0", "overlayscrollbars-react": "^0.5.0",
"patch-package": "^7.0.0", "patch-package": "^7.0.0",
"query-string": "^8.1.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",
@ -140,6 +142,7 @@
"lint-staged": "^13.2.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": "^6.2.8",
"openapi-typescript-codegen": "^0.24.0", "openapi-typescript-codegen": "^0.24.0",
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"prettier": "^2.8.8", "prettier": "^2.8.8",

View File

@ -0,0 +1,55 @@
diff --git a/node_modules/openapi-fetch/dist/index.js b/node_modules/openapi-fetch/dist/index.js
index cd4528a..8976b51 100644
--- a/node_modules/openapi-fetch/dist/index.js
+++ b/node_modules/openapi-fetch/dist/index.js
@@ -1,5 +1,5 @@
// settings & const
-const DEFAULT_HEADERS = {
+const CONTENT_TYPE_APPLICATION_JSON = {
"Content-Type": "application/json",
};
const TRAILING_SLASH_RE = /\/*$/;
@@ -29,18 +29,29 @@ export function createFinalURL(url, options) {
}
return finalURL;
}
+function stringifyBody(body) {
+ if (body instanceof ArrayBuffer || body instanceof File || body instanceof DataView || body instanceof Blob || ArrayBuffer.isView(body) || body instanceof URLSearchParams || body instanceof FormData) {
+ return;
+ }
+
+ if (typeof body === "string") {
+ return body;
+ }
+
+ return JSON.stringify(body);
+ }
+
export default function createClient(clientOptions = {}) {
const { fetch = globalThis.fetch, ...options } = clientOptions;
- const defaultHeaders = new Headers({
- ...DEFAULT_HEADERS,
- ...(options.headers ?? {}),
- });
+ const defaultHeaders = new Headers(options.headers ?? {});
async function coreFetch(url, fetchOptions) {
const { headers, body: requestBody, params = {}, parseAs = "json", querySerializer = defaultSerializer, ...init } = fetchOptions || {};
// URL
const finalURL = createFinalURL(url, { baseUrl: options.baseUrl, params, querySerializer });
+ // Stringify body if needed
+ const stringifiedBody = stringifyBody(requestBody);
// headers
- const baseHeaders = new Headers(defaultHeaders); // clone defaults (dont overwrite!)
+ const baseHeaders = new Headers(stringifiedBody ? { ...CONTENT_TYPE_APPLICATION_JSON, ...defaultHeaders } : defaultHeaders); // clone defaults (dont overwrite!)
const headerOverrides = new Headers(headers);
for (const [k, v] of headerOverrides.entries()) {
if (v === undefined || v === null)
@@ -54,7 +65,7 @@ export default function createClient(clientOptions = {}) {
...options,
...init,
headers: baseHeaders,
- body: typeof requestBody === "string" ? requestBody : JSON.stringify(requestBody),
+ body: stringifiedBody ?? requestBody,
});
// handle empty content
// note: we return `{}` because we want user truthy checks for `.data` or `.error` to succeed

View File

@ -24,7 +24,7 @@ import Toaster from './Toaster';
import DeleteImageModal from 'features/gallery/components/DeleteImageModal'; import DeleteImageModal from 'features/gallery/components/DeleteImageModal';
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal'; import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal';
import { useListModelsQuery } from 'services/apiSlice'; import { useListModelsQuery } from 'services/api/endpoints/models';
const DEFAULT_CONFIG = {}; const DEFAULT_CONFIG = {};

View File

@ -11,8 +11,8 @@ import {
} from '@dnd-kit/core'; } from '@dnd-kit/core';
import { PropsWithChildren, memo, useCallback, useState } from 'react'; import { PropsWithChildren, memo, useCallback, useState } from 'react';
import OverlayDragImage from './OverlayDragImage'; import OverlayDragImage from './OverlayDragImage';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { isImageDTO } from 'services/types/guards'; import { isImageDTO } from 'services/api/guards';
import { snapCenterToCursor } from '@dnd-kit/modifiers'; import { snapCenterToCursor } from '@dnd-kit/modifiers';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';

View File

@ -1,6 +1,6 @@
import { Box, Image } from '@chakra-ui/react'; import { Box, Image } from '@chakra-ui/react';
import { memo } from 'react'; import { memo } from 'react';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
type OverlayDragImageProps = { type OverlayDragImageProps = {
image: ImageDTO; image: ImageDTO;

View File

@ -7,7 +7,7 @@ import React, {
} from 'react'; } from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { store } from 'app/store/store'; import { store } from 'app/store/store';
import { OpenAPI } from 'services/api'; // import { OpenAPI } from 'services/api/types';
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';
@ -23,6 +23,7 @@ import {
} from 'app/contexts/DeleteImageContext'; } from 'app/contexts/DeleteImageContext';
import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal'; import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal';
import { AddImageToBoardContextProvider } from '../contexts/AddImageToBoardContext'; import { AddImageToBoardContextProvider } from '../contexts/AddImageToBoardContext';
import { $authToken, $baseUrl } from 'services/api/client';
const App = lazy(() => import('./App')); const App = lazy(() => import('./App'));
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider')); const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
@ -47,12 +48,12 @@ const InvokeAIUI = ({
useEffect(() => { useEffect(() => {
// configure API client token // configure API client token
if (token) { if (token) {
OpenAPI.TOKEN = token; $authToken.set(token);
} }
// configure API client base url // configure API client base url
if (apiUrl) { if (apiUrl) {
OpenAPI.BASE = apiUrl; $baseUrl.set(apiUrl);
} }
// reset dynamically added middlewares // reset dynamically added middlewares
@ -69,6 +70,12 @@ const InvokeAIUI = ({
} else { } else {
addMiddleware(socketMiddleware()); addMiddleware(socketMiddleware());
} }
return () => {
// Reset the API client token and base url on unmount
$baseUrl.set(undefined);
$authToken.set(undefined);
};
}, [apiUrl, token, middleware]); }, [apiUrl, token, middleware]);
return ( return (

View File

@ -1,7 +1,7 @@
import { useDisclosure } from '@chakra-ui/react'; import { useDisclosure } from '@chakra-ui/react';
import { PropsWithChildren, createContext, useCallback, useState } from 'react'; import { PropsWithChildren, createContext, useCallback, useState } from 'react';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { useAddImageToBoardMutation } from 'services/apiSlice'; import { useAddImageToBoardMutation } from 'services/api/endpoints/boardImages';
export type ImageUsage = { export type ImageUsage = {
isInitialImage: boolean; isInitialImage: boolean;

View File

@ -11,7 +11,7 @@ import {
useEffect, useEffect,
useState, useState,
} from 'react'; } from 'react';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { controlNetSelector } from 'features/controlNet/store/controlNetSlice'; import { controlNetSelector } from 'features/controlNet/store/controlNetSlice';

View File

@ -1,7 +1,7 @@
import { AnyAction } from '@reduxjs/toolkit'; import { AnyAction } from '@reduxjs/toolkit';
import { isAnyGraphBuilt } from 'features/nodes/store/actions'; import { isAnyGraphBuilt } from 'features/nodes/store/actions';
import { forEach } from 'lodash-es'; import { forEach } from 'lodash-es';
import { Graph } from 'services/api'; import { Graph } from 'services/api/types';
export const actionSanitizer = <A extends AnyAction>(action: A): A => { export const actionSanitizer = <A extends AnyAction>(action: A): A => {
if (isAnyGraphBuilt(action)) { if (isAnyGraphBuilt(action)) {

View File

@ -1,7 +1,7 @@
import { startAppListening } from '..'; import { startAppListening } from '..';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { commitStagingAreaImage } from 'features/canvas/store/canvasSlice'; import { commitStagingAreaImage } from 'features/canvas/store/canvasSlice';
import { sessionCanceled } from 'services/thunks/session'; import { sessionCanceled } from 'services/api/thunks/session';
const moduleLog = log.child({ namespace: 'canvas' }); const moduleLog = log.child({ namespace: 'canvas' });
@ -10,7 +10,7 @@ export const addCommitStagingAreaImageListener = () => {
actionCreator: commitStagingAreaImage, actionCreator: commitStagingAreaImage,
effect: async (action, { dispatch, getState }) => { effect: async (action, { dispatch, getState }) => {
const state = getState(); const state = getState();
const { sessionId, isProcessing } = state.system; const { sessionId: session_id, isProcessing } = state.system;
const canvasSessionId = action.payload; const canvasSessionId = action.payload;
if (!isProcessing) { if (!isProcessing) {
@ -23,12 +23,12 @@ export const addCommitStagingAreaImageListener = () => {
return; return;
} }
if (canvasSessionId !== sessionId) { if (canvasSessionId !== session_id) {
moduleLog.debug( moduleLog.debug(
{ {
data: { data: {
canvasSessionId, canvasSessionId,
sessionId, session_id,
}, },
}, },
'Canvas session does not match global session, skipping cancel' 'Canvas session does not match global session, skipping cancel'
@ -36,7 +36,7 @@ export const addCommitStagingAreaImageListener = () => {
return; return;
} }
dispatch(sessionCanceled({ sessionId })); dispatch(sessionCanceled({ session_id }));
}, },
}); });
}; };

View File

@ -2,9 +2,12 @@ import { log } from 'app/logging/useLogger';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { boardIdSelected } from 'features/gallery/store/boardSlice'; import { boardIdSelected } from 'features/gallery/store/boardSlice';
import { selectImagesAll } from 'features/gallery/store/imagesSlice'; import { selectImagesAll } from 'features/gallery/store/imagesSlice';
import { IMAGES_PER_PAGE, receivedPageOfImages } from 'services/thunks/image'; import {
import { api } from 'services/apiSlice'; IMAGES_PER_PAGE,
receivedPageOfImages,
} from 'services/api/thunks/image';
import { imageSelected } from 'features/gallery/store/gallerySlice'; import { imageSelected } from 'features/gallery/store/gallerySlice';
import { boardsApi } from 'services/api/endpoints/boards';
const moduleLog = log.child({ namespace: 'boards' }); const moduleLog = log.child({ namespace: 'boards' });
@ -12,14 +15,14 @@ export const addBoardIdSelectedListener = () => {
startAppListening({ startAppListening({
actionCreator: boardIdSelected, actionCreator: boardIdSelected,
effect: (action, { getState, dispatch }) => { effect: (action, { getState, dispatch }) => {
const boardId = action.payload; const board_id = action.payload;
// we need to check if we need to fetch more images // we need to check if we need to fetch more images
const state = getState(); const state = getState();
const allImages = selectImagesAll(state); const allImages = selectImagesAll(state);
if (!boardId) { if (!board_id) {
// a board was unselected // a board was unselected
dispatch(imageSelected(allImages[0]?.image_name)); dispatch(imageSelected(allImages[0]?.image_name));
return; return;
@ -29,13 +32,14 @@ export const addBoardIdSelectedListener = () => {
const filteredImages = allImages.filter((i) => { const filteredImages = allImages.filter((i) => {
const isInCategory = categories.includes(i.image_category); const isInCategory = categories.includes(i.image_category);
const isInSelectedBoard = boardId ? i.board_id === boardId : true; const isInSelectedBoard = board_id ? i.board_id === board_id : true;
return isInCategory && isInSelectedBoard; return isInCategory && isInSelectedBoard;
}); });
// get the board from the cache // get the board from the cache
const { data: boards } = api.endpoints.listAllBoards.select()(state); const { data: boards } =
const board = boards?.find((b) => b.board_id === boardId); boardsApi.endpoints.listAllBoards.select()(state);
const board = boards?.find((b) => b.board_id === board_id);
if (!board) { if (!board) {
// can't find the board in cache... // can't find the board in cache...
@ -50,7 +54,9 @@ export const addBoardIdSelectedListener = () => {
filteredImages.length < board.image_count && filteredImages.length < board.image_count &&
filteredImages.length < IMAGES_PER_PAGE filteredImages.length < IMAGES_PER_PAGE
) { ) {
dispatch(receivedPageOfImages({ categories, boardId })); dispatch(
receivedPageOfImages({ categories, board_id, is_intermediate: false })
);
} }
}, },
}); });
@ -60,13 +66,13 @@ export const addBoardIdSelected_changeSelectedImage_listener = () => {
startAppListening({ startAppListening({
actionCreator: boardIdSelected, actionCreator: boardIdSelected,
effect: (action, { getState, dispatch }) => { effect: (action, { getState, dispatch }) => {
const boardId = action.payload; const board_id = action.payload;
const state = getState(); const state = getState();
// we need to check if we need to fetch more images // we need to check if we need to fetch more images
if (!boardId) { if (!board_id) {
// a board was unselected - we don't need to do anything // a board was unselected - we don't need to do anything
return; return;
} }
@ -75,13 +81,14 @@ export const addBoardIdSelected_changeSelectedImage_listener = () => {
const filteredImages = selectImagesAll(state).filter((i) => { const filteredImages = selectImagesAll(state).filter((i) => {
const isInCategory = categories.includes(i.image_category); const isInCategory = categories.includes(i.image_category);
const isInSelectedBoard = boardId ? i.board_id === boardId : true; const isInSelectedBoard = board_id ? i.board_id === board_id : true;
return isInCategory && isInSelectedBoard; return isInCategory && isInSelectedBoard;
}); });
// get the board from the cache // get the board from the cache
const { data: boards } = api.endpoints.listAllBoards.select()(state); const { data: boards } =
const board = boards?.find((b) => b.board_id === boardId); boardsApi.endpoints.listAllBoards.select()(state);
const board = boards?.find((b) => b.board_id === board_id);
if (!board) { if (!board) {
// can't find the board in cache... // can't find the board in cache...
return; return;
@ -92,7 +99,9 @@ export const addBoardIdSelected_changeSelectedImage_listener = () => {
filteredImages.length < board.image_count && filteredImages.length < board.image_count &&
filteredImages.length < IMAGES_PER_PAGE filteredImages.length < IMAGES_PER_PAGE
) { ) {
dispatch(receivedPageOfImages({ categories, boardId })); dispatch(
receivedPageOfImages({ categories, board_id, is_intermediate: false })
);
} }
}, },
}); });

View File

@ -2,7 +2,7 @@ import { canvasMerged } from 'features/canvas/store/actions';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { imageUploaded } from 'services/thunks/image'; import { imageUploaded } from 'services/api/thunks/image';
import { setMergedCanvas } from 'features/canvas/store/canvasSlice'; import { setMergedCanvas } from 'features/canvas/store/canvasSlice';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob'; import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob';
@ -47,13 +47,11 @@ export const addCanvasMergedListener = () => {
const imageUploadedRequest = dispatch( const imageUploadedRequest = dispatch(
imageUploaded({ imageUploaded({
formData: { file: new File([blob], 'mergedCanvas.png', {
file: new File([blob], 'mergedCanvas.png', { type: 'image/png',
type: 'image/png', }),
}), image_category: 'general',
}, is_intermediate: true,
imageCategory: 'general',
isIntermediate: true,
postUploadAction: { postUploadAction: {
type: 'TOAST_CANVAS_MERGED', type: 'TOAST_CANVAS_MERGED',
}, },
@ -68,13 +66,13 @@ export const addCanvasMergedListener = () => {
uploadedImageAction.meta.requestId === imageUploadedRequest.requestId uploadedImageAction.meta.requestId === imageUploadedRequest.requestId
); );
const mergedCanvasImage = payload; const { image_name } = payload;
dispatch( dispatch(
setMergedCanvas({ setMergedCanvas({
kind: 'image', kind: 'image',
layer: 'base', layer: 'base',
image: mergedCanvasImage, imageName: image_name,
...baseLayerRect, ...baseLayerRect,
}) })
); );

View File

@ -1,7 +1,7 @@
import { canvasSavedToGallery } from 'features/canvas/store/actions'; import { canvasSavedToGallery } from 'features/canvas/store/actions';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { imageUploaded } from 'services/thunks/image'; import { imageUploaded } from 'services/api/thunks/image';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { imageUpserted } from 'features/gallery/store/imagesSlice'; import { imageUpserted } from 'features/gallery/store/imagesSlice';
@ -30,13 +30,11 @@ export const addCanvasSavedToGalleryListener = () => {
const imageUploadedRequest = dispatch( const imageUploadedRequest = dispatch(
imageUploaded({ imageUploaded({
formData: { file: new File([blob], 'savedCanvas.png', {
file: new File([blob], 'savedCanvas.png', { type: 'image/png',
type: 'image/png', }),
}), image_category: 'general',
}, is_intermediate: false,
imageCategory: 'general',
isIntermediate: false,
postUploadAction: { postUploadAction: {
type: 'TOAST_CANVAS_SAVED_TO_GALLERY', type: 'TOAST_CANVAS_SAVED_TO_GALLERY',
}, },

View File

@ -1,14 +1,13 @@
import { startAppListening } from '..'; import { startAppListening } from '..';
import { imageMetadataReceived } from 'services/thunks/image'; import { imageMetadataReceived } from 'services/api/thunks/image';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { controlNetImageProcessed } from 'features/controlNet/store/actions'; import { controlNetImageProcessed } from 'features/controlNet/store/actions';
import { Graph } from 'services/api'; import { Graph } from 'services/api/types';
import { sessionCreated } from 'services/thunks/session'; import { sessionCreated } from 'services/api/thunks/session';
import { sessionReadyToInvoke } from 'features/system/store/actions'; import { sessionReadyToInvoke } from 'features/system/store/actions';
import { socketInvocationComplete } from 'services/events/actions'; import { socketInvocationComplete } from 'services/events/actions';
import { isImageOutput } from 'services/types/guards'; import { isImageOutput } from 'services/api/guards';
import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice'; import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice';
import { pick } from 'lodash-es';
const moduleLog = log.child({ namespace: 'controlNet' }); const moduleLog = log.child({ namespace: 'controlNet' });

View File

@ -1,13 +1,13 @@
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { imageMetadataReceived } from 'services/thunks/image'; import { imageMetadataReceived } from 'services/api/thunks/image';
import { api } from 'services/apiSlice'; import { boardImagesApi } from 'services/api/endpoints/boardImages';
const moduleLog = log.child({ namespace: 'boards' }); const moduleLog = log.child({ namespace: 'boards' });
export const addImageAddedToBoardFulfilledListener = () => { export const addImageAddedToBoardFulfilledListener = () => {
startAppListening({ startAppListening({
matcher: api.endpoints.addImageToBoard.matchFulfilled, matcher: boardImagesApi.endpoints.addImageToBoard.matchFulfilled,
effect: (action, { getState, dispatch }) => { effect: (action, { getState, dispatch }) => {
const { board_id, image_name } = action.meta.arg.originalArgs; const { board_id, image_name } = action.meta.arg.originalArgs;
@ -18,7 +18,7 @@ export const addImageAddedToBoardFulfilledListener = () => {
dispatch( dispatch(
imageMetadataReceived({ imageMetadataReceived({
imageName: image_name, image_name,
}) })
); );
}, },
@ -27,7 +27,7 @@ export const addImageAddedToBoardFulfilledListener = () => {
export const addImageAddedToBoardRejectedListener = () => { export const addImageAddedToBoardRejectedListener = () => {
startAppListening({ startAppListening({
matcher: api.endpoints.addImageToBoard.matchRejected, matcher: boardImagesApi.endpoints.addImageToBoard.matchRejected,
effect: (action, { getState, dispatch }) => { effect: (action, { getState, dispatch }) => {
const { board_id, image_name } = action.meta.arg.originalArgs; const { board_id, image_name } = action.meta.arg.originalArgs;

View File

@ -1,6 +1,6 @@
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { receivedPageOfImages } from 'services/thunks/image'; import { receivedPageOfImages } from 'services/api/thunks/image';
import { import {
imageCategoriesChanged, imageCategoriesChanged,
selectFilteredImagesAsArray, selectFilteredImagesAsArray,
@ -19,7 +19,8 @@ export const addImageCategoriesChangedListener = () => {
dispatch( dispatch(
receivedPageOfImages({ receivedPageOfImages({
categories: action.payload, categories: action.payload,
boardId: state.boards.selectedBoardId, board_id: state.boards.selectedBoardId,
is_intermediate: false,
}) })
); );
} }

View File

@ -1,6 +1,6 @@
import { requestedImageDeletion } from 'features/gallery/store/actions'; import { requestedImageDeletion } from 'features/gallery/store/actions';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { imageDeleted } from 'services/thunks/image'; import { imageDeleted } from 'services/api/thunks/image';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { clamp } from 'lodash-es'; import { clamp } from 'lodash-es';
import { imageSelected } from 'features/gallery/store/gallerySlice'; import { imageSelected } from 'features/gallery/store/gallerySlice';
@ -12,7 +12,7 @@ import { resetCanvas } from 'features/canvas/store/canvasSlice';
import { controlNetReset } from 'features/controlNet/store/controlNetSlice'; import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
import { clearInitialImage } from 'features/parameters/store/generationSlice'; import { clearInitialImage } from 'features/parameters/store/generationSlice';
import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
import { api } from 'services/apiSlice'; import { api } from 'services/api';
const moduleLog = log.child({ namespace: 'image' }); const moduleLog = log.child({ namespace: 'image' });
@ -76,7 +76,7 @@ export const addRequestedImageDeletionListener = () => {
dispatch(imageRemoved(image_name)); dispatch(imageRemoved(image_name));
// Delete from server // Delete from server
const { requestId } = dispatch(imageDeleted({ imageName: image_name })); const { requestId } = dispatch(imageDeleted({ image_name }));
// Wait for successful deletion, then trigger boards to re-fetch // Wait for successful deletion, then trigger boards to re-fetch
const wasImageDeleted = await condition( const wasImageDeleted = await condition(

View File

@ -1,6 +1,6 @@
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { imageMetadataReceived, imageUpdated } from 'services/thunks/image'; import { imageMetadataReceived, imageUpdated } from 'services/api/thunks/image';
import { imageUpserted } from 'features/gallery/store/imagesSlice'; import { imageUpserted } from 'features/gallery/store/imagesSlice';
const moduleLog = log.child({ namespace: 'image' }); const moduleLog = log.child({ namespace: 'image' });
@ -19,8 +19,8 @@ export const addImageMetadataReceivedFulfilledListener = () => {
) { ) {
dispatch( dispatch(
imageUpdated({ imageUpdated({
imageName: image.image_name, image_name: image.image_name,
requestBody: { is_intermediate: false }, is_intermediate: image.is_intermediate,
}) })
); );
} else if (image.is_intermediate) { } else if (image.is_intermediate) {

View File

@ -1,13 +1,13 @@
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { imageMetadataReceived } from 'services/thunks/image'; import { imageMetadataReceived } from 'services/api/thunks/image';
import { api } from 'services/apiSlice'; import { boardImagesApi } from 'services/api/endpoints/boardImages';
const moduleLog = log.child({ namespace: 'boards' }); const moduleLog = log.child({ namespace: 'boards' });
export const addImageRemovedFromBoardFulfilledListener = () => { export const addImageRemovedFromBoardFulfilledListener = () => {
startAppListening({ startAppListening({
matcher: api.endpoints.removeImageFromBoard.matchFulfilled, matcher: boardImagesApi.endpoints.removeImageFromBoard.matchFulfilled,
effect: (action, { getState, dispatch }) => { effect: (action, { getState, dispatch }) => {
const { board_id, image_name } = action.meta.arg.originalArgs; const { board_id, image_name } = action.meta.arg.originalArgs;
@ -18,7 +18,7 @@ export const addImageRemovedFromBoardFulfilledListener = () => {
dispatch( dispatch(
imageMetadataReceived({ imageMetadataReceived({
imageName: image_name, image_name,
}) })
); );
}, },
@ -27,7 +27,7 @@ export const addImageRemovedFromBoardFulfilledListener = () => {
export const addImageRemovedFromBoardRejectedListener = () => { export const addImageRemovedFromBoardRejectedListener = () => {
startAppListening({ startAppListening({
matcher: api.endpoints.removeImageFromBoard.matchRejected, matcher: boardImagesApi.endpoints.removeImageFromBoard.matchRejected,
effect: (action, { getState, dispatch }) => { effect: (action, { getState, dispatch }) => {
const { board_id, image_name } = action.meta.arg.originalArgs; const { board_id, image_name } = action.meta.arg.originalArgs;

View File

@ -1,5 +1,5 @@
import { startAppListening } from '..'; import { startAppListening } from '..';
import { imageUpdated } from 'services/thunks/image'; import { imageUpdated } from 'services/api/thunks/image';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
const moduleLog = log.child({ namespace: 'image' }); const moduleLog = log.child({ namespace: 'image' });

View File

@ -1,5 +1,5 @@
import { startAppListening } from '..'; import { startAppListening } from '..';
import { imageUploaded } from 'services/thunks/image'; import { imageUploaded } from 'services/api/thunks/image';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { imageUpserted } from 'features/gallery/store/imagesSlice'; import { imageUpserted } from 'features/gallery/store/imagesSlice';

View File

@ -1,6 +1,6 @@
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { imageUrlsReceived } from 'services/thunks/image'; import { imageUrlsReceived } from 'services/api/thunks/image';
import { imageUpdatedOne } from 'features/gallery/store/imagesSlice'; import { imageUpdatedOne } from 'features/gallery/store/imagesSlice';
const moduleLog = log.child({ namespace: 'image' }); const moduleLog = log.child({ namespace: 'image' });

View File

@ -5,7 +5,7 @@ import { startAppListening } from '..';
import { initialImageSelected } from 'features/parameters/store/actions'; import { initialImageSelected } from 'features/parameters/store/actions';
import { makeToast } from 'app/components/Toaster'; import { makeToast } from 'app/components/Toaster';
import { selectImagesById } from 'features/gallery/store/imagesSlice'; import { selectImagesById } from 'features/gallery/store/imagesSlice';
import { isImageDTO } from 'services/types/guards'; import { isImageDTO } from 'services/api/guards';
export const addInitialImageSelectedListener = () => { export const addInitialImageSelectedListener = () => {
startAppListening({ startAppListening({

View File

@ -1,7 +1,7 @@
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { serializeError } from 'serialize-error'; import { serializeError } from 'serialize-error';
import { receivedPageOfImages } from 'services/thunks/image'; import { receivedPageOfImages } from 'services/api/thunks/image';
const moduleLog = log.child({ namespace: 'gallery' }); const moduleLog = log.child({ namespace: 'gallery' });

View File

@ -1,6 +1,6 @@
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { sessionCanceled } from 'services/thunks/session'; import { sessionCanceled } from 'services/api/thunks/session';
import { serializeError } from 'serialize-error'; import { serializeError } from 'serialize-error';
const moduleLog = log.child({ namespace: 'session' }); const moduleLog = log.child({ namespace: 'session' });
@ -18,10 +18,10 @@ export const addSessionCanceledFulfilledListener = () => {
startAppListening({ startAppListening({
actionCreator: sessionCanceled.fulfilled, actionCreator: sessionCanceled.fulfilled,
effect: (action, { getState, dispatch }) => { effect: (action, { getState, dispatch }) => {
const { sessionId } = action.meta.arg; const { session_id } = action.meta.arg;
moduleLog.debug( moduleLog.debug(
{ data: { sessionId } }, { data: { session_id } },
`Session canceled (${sessionId})` `Session canceled (${session_id})`
); );
}, },
}); });

View File

@ -1,6 +1,6 @@
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { sessionCreated } from 'services/thunks/session'; import { sessionCreated } from 'services/api/thunks/session';
import { serializeError } from 'serialize-error'; import { serializeError } from 'serialize-error';
const moduleLog = log.child({ namespace: 'session' }); const moduleLog = log.child({ namespace: 'session' });

View File

@ -1,6 +1,6 @@
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { sessionInvoked } from 'services/thunks/session'; import { sessionInvoked } from 'services/api/thunks/session';
import { serializeError } from 'serialize-error'; import { serializeError } from 'serialize-error';
const moduleLog = log.child({ namespace: 'session' }); const moduleLog = log.child({ namespace: 'session' });
@ -18,10 +18,10 @@ export const addSessionInvokedFulfilledListener = () => {
startAppListening({ startAppListening({
actionCreator: sessionInvoked.fulfilled, actionCreator: sessionInvoked.fulfilled,
effect: (action, { getState, dispatch }) => { effect: (action, { getState, dispatch }) => {
const { sessionId } = action.meta.arg; const { session_id } = action.meta.arg;
moduleLog.debug( moduleLog.debug(
{ data: { sessionId } }, { data: { session_id } },
`Session invoked (${sessionId})` `Session invoked (${session_id})`
); );
}, },
}); });

View File

@ -1,5 +1,5 @@
import { startAppListening } from '..'; import { startAppListening } from '..';
import { sessionInvoked } from 'services/thunks/session'; import { sessionInvoked } from 'services/api/thunks/session';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { sessionReadyToInvoke } from 'features/system/store/actions'; import { sessionReadyToInvoke } from 'features/system/store/actions';
@ -9,13 +9,13 @@ export const addSessionReadyToInvokeListener = () => {
startAppListening({ startAppListening({
actionCreator: sessionReadyToInvoke, actionCreator: sessionReadyToInvoke,
effect: (action, { getState, dispatch }) => { effect: (action, { getState, dispatch }) => {
const { sessionId } = getState().system; const { sessionId: session_id } = getState().system;
if (sessionId) { if (session_id) {
moduleLog.debug( moduleLog.debug(
{ sessionId }, { session_id },
`Session ready to invoke (${sessionId})})` `Session ready to invoke (${session_id})})`
); );
dispatch(sessionInvoked({ sessionId })); dispatch(sessionInvoked({ session_id }));
} }
}, },
}); });

View File

@ -1,7 +1,7 @@
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { appSocketConnected, socketConnected } from 'services/events/actions'; import { appSocketConnected, socketConnected } from 'services/events/actions';
import { receivedPageOfImages } from 'services/thunks/image'; import { receivedPageOfImages } from 'services/api/thunks/image';
import { receivedOpenAPISchema } from 'services/thunks/schema'; import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
const moduleLog = log.child({ namespace: 'socketio' }); const moduleLog = log.child({ namespace: 'socketio' });
@ -22,7 +22,7 @@ export const addSocketConnectedEventListener = () => {
dispatch( dispatch(
receivedPageOfImages({ receivedPageOfImages({
categories: ['general'], categories: ['general'],
isIntermediate: false, is_intermediate: false,
}) })
); );
} }

View File

@ -5,11 +5,11 @@ import {
appSocketInvocationComplete, appSocketInvocationComplete,
socketInvocationComplete, socketInvocationComplete,
} from 'services/events/actions'; } from 'services/events/actions';
import { imageMetadataReceived } from 'services/thunks/image'; import { imageMetadataReceived } from 'services/api/thunks/image';
import { sessionCanceled } from 'services/thunks/session'; import { sessionCanceled } from 'services/api/thunks/session';
import { isImageOutput } from 'services/types/guards'; import { isImageOutput } from 'services/api/guards';
import { progressImageSet } from 'features/system/store/systemSlice'; import { progressImageSet } from 'features/system/store/systemSlice';
import { api } from 'services/apiSlice'; import { boardImagesApi } from 'services/api/endpoints/boardImages';
const moduleLog = log.child({ namespace: 'socketio' }); const moduleLog = log.child({ namespace: 'socketio' });
const nodeDenylist = ['dataURL_image']; const nodeDenylist = ['dataURL_image'];
@ -19,18 +19,18 @@ export const addInvocationCompleteEventListener = () => {
actionCreator: socketInvocationComplete, actionCreator: socketInvocationComplete,
effect: async (action, { dispatch, getState, take }) => { effect: async (action, { dispatch, getState, take }) => {
moduleLog.debug( moduleLog.debug(
action.payload, { data: action.payload },
`Invocation complete (${action.payload.data.node.type})` `Invocation complete (${action.payload.data.node.type})`
); );
const sessionId = action.payload.data.graph_execution_state_id; const session_id = action.payload.data.graph_execution_state_id;
const { cancelType, isCancelScheduled, boardIdToAddTo } = const { cancelType, isCancelScheduled, boardIdToAddTo } =
getState().system; getState().system;
// Handle scheduled cancelation // Handle scheduled cancelation
if (cancelType === 'scheduled' && isCancelScheduled) { if (cancelType === 'scheduled' && isCancelScheduled) {
dispatch(sessionCanceled({ sessionId })); dispatch(sessionCanceled({ session_id }));
} }
const { data } = action.payload; const { data } = action.payload;
@ -43,7 +43,7 @@ export const addInvocationCompleteEventListener = () => {
// Get its metadata // Get its metadata
dispatch( dispatch(
imageMetadataReceived({ imageMetadataReceived({
imageName: image_name, image_name,
}) })
); );
@ -61,7 +61,7 @@ export const addInvocationCompleteEventListener = () => {
if (boardIdToAddTo && !imageDTO.is_intermediate) { if (boardIdToAddTo && !imageDTO.is_intermediate) {
dispatch( dispatch(
api.endpoints.addImageToBoard.initiate({ boardImagesApi.endpoints.addImageToBoard.initiate({
board_id: boardIdToAddTo, board_id: boardIdToAddTo,
image_name, image_name,
}) })

View File

@ -1,7 +1,7 @@
import { stagingAreaImageSaved } from 'features/canvas/store/actions'; import { stagingAreaImageSaved } from 'features/canvas/store/actions';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { imageUpdated } from 'services/thunks/image'; import { imageUpdated } from 'services/api/thunks/image';
import { imageUpserted } from 'features/gallery/store/imagesSlice'; import { imageUpserted } from 'features/gallery/store/imagesSlice';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
@ -11,14 +11,12 @@ export const addStagingAreaImageSavedListener = () => {
startAppListening({ startAppListening({
actionCreator: stagingAreaImageSaved, actionCreator: stagingAreaImageSaved,
effect: async (action, { dispatch, getState, take }) => { effect: async (action, { dispatch, getState, take }) => {
const { image_name } = action.payload; const { imageName } = action.payload;
dispatch( dispatch(
imageUpdated({ imageUpdated({
imageName: image_name, image_name: imageName,
requestBody: { is_intermediate: false,
is_intermediate: false,
},
}) })
); );
@ -26,7 +24,7 @@ export const addStagingAreaImageSavedListener = () => {
(action) => (action) =>
(imageUpdated.fulfilled.match(action) || (imageUpdated.fulfilled.match(action) ||
imageUpdated.rejected.match(action)) && imageUpdated.rejected.match(action)) &&
action.meta.arg.imageName === image_name action.meta.arg.image_name === imageName
); );
if (imageUpdated.rejected.match(imageUpdatedAction)) { if (imageUpdated.rejected.match(imageUpdatedAction)) {

View File

@ -5,9 +5,8 @@ import { generationSelector } from 'features/parameters/store/generationSelector
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { nodesSelecter } from 'features/nodes/store/nodesSlice'; import { nodesSelecter } from 'features/nodes/store/nodesSlice';
import { controlNetSelector } from 'features/controlNet/store/controlNetSlice'; import { controlNetSelector } from 'features/controlNet/store/controlNetSlice';
import { ImageDTO } from 'services/api';
import { forEach, uniqBy } from 'lodash-es'; import { forEach, uniqBy } from 'lodash-es';
import { imageUrlsReceived } from 'services/thunks/image'; import { imageUrlsReceived } from 'services/api/thunks/image';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { selectImagesEntities } from 'features/gallery/store/imagesSlice'; import { selectImagesEntities } from 'features/gallery/store/imagesSlice';
@ -83,7 +82,7 @@ export const addUpdateImageUrlsOnConnectListener = () => {
allUsedImages.forEach((image_name) => { allUsedImages.forEach((image_name) => {
dispatch( dispatch(
imageUrlsReceived({ imageUrlsReceived({
imageName: image_name, image_name,
}) })
); );
}); });

View File

@ -1,10 +1,10 @@
import { startAppListening } from '..'; import { startAppListening } from '..';
import { sessionCreated } from 'services/thunks/session'; import { sessionCreated } from 'services/api/thunks/session';
import { buildCanvasGraph } from 'features/nodes/util/graphBuilders/buildCanvasGraph'; import { buildCanvasGraph } from 'features/nodes/util/graphBuilders/buildCanvasGraph';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { canvasGraphBuilt } from 'features/nodes/store/actions'; import { canvasGraphBuilt } from 'features/nodes/store/actions';
import { imageUpdated, imageUploaded } from 'services/thunks/image'; import { imageUpdated, imageUploaded } from 'services/api/thunks/image';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { import {
canvasSessionIdChanged, canvasSessionIdChanged,
stagingAreaInitialized, stagingAreaInitialized,
@ -75,13 +75,11 @@ export const addUserInvokedCanvasListener = () => {
// upload the image, saving the request id // upload the image, saving the request id
const { requestId: initImageUploadedRequestId } = dispatch( const { requestId: initImageUploadedRequestId } = dispatch(
imageUploaded({ imageUploaded({
formData: { file: new File([baseBlob], 'canvasInitImage.png', {
file: new File([baseBlob], 'canvasInitImage.png', { type: 'image/png',
type: 'image/png', }),
}), image_category: 'general',
}, is_intermediate: true,
imageCategory: 'general',
isIntermediate: true,
}) })
); );
@ -100,13 +98,11 @@ export const addUserInvokedCanvasListener = () => {
// upload the image, saving the request id // upload the image, saving the request id
const { requestId: maskImageUploadedRequestId } = dispatch( const { requestId: maskImageUploadedRequestId } = dispatch(
imageUploaded({ imageUploaded({
formData: { file: new File([maskBlob], 'canvasMaskImage.png', {
file: new File([maskBlob], 'canvasMaskImage.png', { type: 'image/png',
type: 'image/png', }),
}), image_category: 'mask',
}, is_intermediate: true,
imageCategory: 'mask',
isIntermediate: true,
}) })
); );
@ -149,8 +145,8 @@ export const addUserInvokedCanvasListener = () => {
if (['img2img', 'inpaint'].includes(generationMode) && canvasInitImage) { if (['img2img', 'inpaint'].includes(generationMode) && canvasInitImage) {
dispatch( dispatch(
imageUpdated({ imageUpdated({
imageName: canvasInitImage.image_name, image_name: canvasInitImage.image_name,
requestBody: { session_id: sessionId }, session_id: sessionId,
}) })
); );
} }
@ -159,8 +155,8 @@ export const addUserInvokedCanvasListener = () => {
if (['inpaint'].includes(generationMode) && canvasMaskImage) { if (['inpaint'].includes(generationMode) && canvasMaskImage) {
dispatch( dispatch(
imageUpdated({ imageUpdated({
imageName: canvasMaskImage.image_name, image_name: canvasMaskImage.image_name,
requestBody: { session_id: sessionId }, session_id: sessionId,
}) })
); );
} }

View File

@ -1,5 +1,5 @@
import { startAppListening } from '..'; import { startAppListening } from '..';
import { sessionCreated } from 'services/thunks/session'; import { sessionCreated } from 'services/api/thunks/session';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { imageToImageGraphBuilt } from 'features/nodes/store/actions'; import { imageToImageGraphBuilt } from 'features/nodes/store/actions';
import { userInvoked } from 'app/store/actions'; import { userInvoked } from 'app/store/actions';

View File

@ -1,5 +1,5 @@
import { startAppListening } from '..'; import { startAppListening } from '..';
import { sessionCreated } from 'services/thunks/session'; import { sessionCreated } from 'services/api/thunks/session';
import { buildNodesGraph } from 'features/nodes/util/graphBuilders/buildNodesGraph'; import { buildNodesGraph } from 'features/nodes/util/graphBuilders/buildNodesGraph';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { nodesGraphBuilt } from 'features/nodes/store/actions'; import { nodesGraphBuilt } from 'features/nodes/store/actions';

View File

@ -1,5 +1,5 @@
import { startAppListening } from '..'; import { startAppListening } from '..';
import { sessionCreated } from 'services/thunks/session'; import { sessionCreated } from 'services/api/thunks/session';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { textToImageGraphBuilt } from 'features/nodes/store/actions'; import { textToImageGraphBuilt } from 'features/nodes/store/actions';
import { userInvoked } from 'app/store/actions'; import { userInvoked } from 'app/store/actions';

View File

@ -31,7 +31,7 @@ import { stateSanitizer } from './middleware/devtools/stateSanitizer';
import { LOCALSTORAGE_PREFIX } from './constants'; import { LOCALSTORAGE_PREFIX } from './constants';
import { serialize } from './enhancers/reduxRemember/serialize'; import { serialize } from './enhancers/reduxRemember/serialize';
import { unserialize } from './enhancers/reduxRemember/unserialize'; import { unserialize } from './enhancers/reduxRemember/unserialize';
import { api } from 'services/apiSlice'; import { api } from 'services/api';
const allReducers = { const allReducers = {
canvas: canvasReducer, canvas: canvasReducer,

View File

@ -12,15 +12,14 @@ import IAIIconButton from 'common/components/IAIIconButton';
import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback';
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
import { AnimatePresence } from 'framer-motion'; import { AnimatePresence } from 'framer-motion';
import { ReactElement, SyntheticEvent, useCallback } from 'react'; import { ReactElement, SyntheticEvent } from 'react';
import { memo, useRef } from 'react'; import { memo, useRef } from 'react';
import { FaImage, FaTimes, FaUndo, FaUpload } from 'react-icons/fa'; import { FaImage, FaUndo, FaUpload } from 'react-icons/fa';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import IAIDropOverlay from './IAIDropOverlay'; import IAIDropOverlay from './IAIDropOverlay';
import { PostUploadAction, imageUploaded } from 'services/thunks/image'; import { PostUploadAction } from 'services/api/thunks/image';
import { useDropzone } from 'react-dropzone'; import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { useAppDispatch } from 'app/store/storeHooks';
type IAIDndImageProps = { type IAIDndImageProps = {
image: ImageDTO | null | undefined; image: ImageDTO | null | undefined;
@ -39,6 +38,7 @@ type IAIDndImageProps = {
minSize?: number; minSize?: number;
postUploadAction?: PostUploadAction; postUploadAction?: PostUploadAction;
imageSx?: ChakraProps['sx']; imageSx?: ChakraProps['sx'];
fitContainer?: boolean;
}; };
const IAIDndImage = (props: IAIDndImageProps) => { const IAIDndImage = (props: IAIDndImageProps) => {
@ -58,8 +58,9 @@ const IAIDndImage = (props: IAIDndImageProps) => {
minSize = 24, minSize = 24,
postUploadAction, postUploadAction,
imageSx, imageSx,
fitContainer = false,
} = props; } = props;
const dispatch = useAppDispatch();
const dndId = useRef(uuidv4()); const dndId = useRef(uuidv4());
const { const {
@ -87,31 +88,9 @@ const IAIDndImage = (props: IAIDndImageProps) => {
disabled: isDragDisabled || !image, disabled: isDragDisabled || !image,
}); });
const handleOnDropAccepted = useCallback( const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
(files: Array<File>) => { postUploadAction,
const file = files[0]; isDisabled: isUploadDisabled,
if (!file) {
return;
}
dispatch(
imageUploaded({
formData: { file },
imageCategory: 'user',
isIntermediate: false,
postUploadAction,
})
);
},
[dispatch, postUploadAction]
);
const { getRootProps, getInputProps } = useDropzone({
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
onDropAccepted: handleOnDropAccepted,
noDrag: true,
multiple: false,
disabled: isUploadDisabled,
}); });
const setNodeRef = useCombinedRefs(setDroppableRef, setDraggableRef); const setNodeRef = useCombinedRefs(setDroppableRef, setDraggableRef);
@ -149,13 +128,13 @@ const IAIDndImage = (props: IAIDndImageProps) => {
sx={{ sx={{
w: 'full', w: 'full',
h: 'full', h: 'full',
position: fitContainer ? 'absolute' : 'relative',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}} }}
> >
<Image <Image
src={image.image_url} src={image.image_url}
fallbackStrategy="beforeLoadOrError"
fallback={fallback} fallback={fallback}
onError={onError} onError={onError}
objectFit="contain" objectFit="contain"
@ -205,9 +184,9 @@ const IAIDndImage = (props: IAIDndImageProps) => {
color: 'base.500', color: 'base.500',
...uploadButtonStyles, ...uploadButtonStyles,
}} }}
{...getRootProps()} {...getUploadButtonProps()}
> >
<input {...getInputProps()} /> <input {...getUploadInputProps()} />
<Icon <Icon
as={isUploadDisabled ? FaImage : FaUpload} as={isUploadDisabled ? FaImage : FaUpload}
sx={{ sx={{

View File

@ -1,7 +1,7 @@
import { Badge, Flex } from '@chakra-ui/react'; import { Badge, Flex } from '@chakra-ui/react';
import { isString } from 'lodash-es'; import { isString } from 'lodash-es';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
type ImageMetadataOverlayProps = { type ImageMetadataOverlayProps = {
image: ImageDTO; image: ImageDTO;

View File

@ -12,7 +12,7 @@ import {
} from 'react'; } from 'react';
import { FileRejection, useDropzone } from 'react-dropzone'; import { FileRejection, useDropzone } from 'react-dropzone';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { imageUploaded } from 'services/thunks/image'; import { imageUploaded } from 'services/api/thunks/image';
import ImageUploadOverlay from './ImageUploadOverlay'; import ImageUploadOverlay from './ImageUploadOverlay';
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
@ -62,9 +62,9 @@ const ImageUploader = (props: ImageUploaderProps) => {
async (file: File) => { async (file: File) => {
dispatch( dispatch(
imageUploaded({ imageUploaded({
formData: { file }, file,
imageCategory: 'user', image_category: 'user',
isIntermediate: false, is_intermediate: false,
postUploadAction: { type: 'TOAST_UPLOADED' }, postUploadAction: { type: 'TOAST_UPLOADED' },
}) })
); );

View File

@ -0,0 +1,64 @@
import { useAppDispatch } from 'app/store/storeHooks';
import { useCallback } from 'react';
import { useDropzone } from 'react-dropzone';
import { PostUploadAction, imageUploaded } from 'services/api/thunks/image';
type UseImageUploadButtonArgs = {
postUploadAction?: PostUploadAction;
isDisabled?: boolean;
};
/**
* Provides image uploader functionality to any component.
*
* @example
* const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
* postUploadAction: {
* type: 'SET_CONTROLNET_IMAGE',
* controlNetId: '12345',
* },
* isDisabled: getIsUploadDisabled(),
* });
*
* // in the render function
* <Button {...getUploadButtonProps()} /> // will open the file dialog on click
* <input {...getUploadInputProps()} /> // hidden, handles native upload functionality
*/
export const useImageUploadButton = ({
postUploadAction,
isDisabled,
}: UseImageUploadButtonArgs) => {
const dispatch = useAppDispatch();
const onDropAccepted = useCallback(
(files: File[]) => {
const file = files[0];
if (!file) {
return;
}
dispatch(
imageUploaded({
file,
image_category: 'user',
is_intermediate: false,
postUploadAction,
})
);
},
[dispatch, postUploadAction]
);
const {
getRootProps: getUploadButtonProps,
getInputProps: getUploadInputProps,
open: openUploader,
} = useDropzone({
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
onDropAccepted,
disabled: isDisabled,
noDrag: true,
multiple: false,
});
return { getUploadButtonProps, getUploadInputProps, openUploader };
};

View File

@ -1,6 +1,6 @@
import { skipToken } from '@reduxjs/toolkit/dist/query'; import { skipToken } from '@reduxjs/toolkit/dist/query';
import { Image, Rect } from 'react-konva'; import { Image, Rect } from 'react-konva';
import { useGetImageDTOQuery } from 'services/apiSlice'; import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import useImage from 'use-image'; import useImage from 'use-image';
import { CanvasImage } from '../store/canvasTypes'; import { CanvasImage } from '../store/canvasTypes';
@ -9,7 +9,7 @@ type IAICanvasImageProps = {
}; };
const IAICanvasImage = (props: IAICanvasImageProps) => { const IAICanvasImage = (props: IAICanvasImageProps) => {
const { width, height, x, y, imageName } = props.canvasImage; const { width, height, x, y, imageName } = props.canvasImage;
const { data: imageDTO } = useGetImageDTOQuery(imageName ?? skipToken); const { currentData: imageDTO } = useGetImageDTOQuery(imageName ?? skipToken);
const [image] = useImage(imageDTO?.image_url ?? '', 'anonymous'); const [image] = useImage(imageDTO?.image_url ?? '', 'anonymous');
if (!imageDTO) { if (!imageDTO) {

View File

@ -175,7 +175,11 @@ const IAICanvasStagingAreaToolbar = () => {
aria-label={t('unifiedCanvas.saveToGallery')} aria-label={t('unifiedCanvas.saveToGallery')}
icon={<FaSave />} icon={<FaSave />}
onClick={() => onClick={() =>
dispatch(stagingAreaImageSaved(currentStagingAreaImage.image)) dispatch(
stagingAreaImageSaved({
imageName: currentStagingAreaImage.imageName,
})
)
} }
colorScheme="accent" colorScheme="accent"
/> />

View File

@ -1,5 +1,4 @@
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import { ImageDTO } from 'services/api';
export const canvasSavedToGallery = createAction('canvas/canvasSavedToGallery'); export const canvasSavedToGallery = createAction('canvas/canvasSavedToGallery');
@ -13,6 +12,6 @@ export const canvasDownloadedAsImage = createAction(
export const canvasMerged = createAction('canvas/canvasMerged'); export const canvasMerged = createAction('canvas/canvasMerged');
export const stagingAreaImageSaved = createAction<ImageDTO>( export const stagingAreaImageSaved = createAction<{ imageName: string }>(
'canvas/stagingAreaImageSaved' 'canvas/stagingAreaImageSaved'
); );

View File

@ -28,13 +28,13 @@ import {
isCanvasBaseImage, isCanvasBaseImage,
isCanvasMaskLine, isCanvasMaskLine,
} from './canvasTypes'; } from './canvasTypes';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { sessionCanceled } from 'services/thunks/session'; import { sessionCanceled } from 'services/api/thunks/session';
import { import {
setActiveTab, setActiveTab,
setShouldUseCanvasBetaLayout, setShouldUseCanvasBetaLayout,
} from 'features/ui/store/uiSlice'; } from 'features/ui/store/uiSlice';
import { imageUrlsReceived } from 'services/thunks/image'; import { imageUrlsReceived } from 'services/api/thunks/image';
export const initialLayerState: CanvasLayerState = { export const initialLayerState: CanvasLayerState = {
objects: [], objects: [],

View File

@ -1,7 +1,7 @@
import * as InvokeAI from 'app/types/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';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
export const LAYER_NAMES_DICT = [ export const LAYER_NAMES_DICT = [
{ label: 'Base', value: 'base' }, { label: 'Base', value: 'base' },

View File

@ -172,7 +172,10 @@ const ControlNet = (props: ControlNetProps) => {
aspectRatio: '1/1', aspectRatio: '1/1',
}} }}
> >
<ControlNetImagePreview controlNet={props.controlNet} /> <ControlNetImagePreview
controlNet={props.controlNet}
height={24}
/>
</Flex> </Flex>
)} )}
</Flex> </Flex>
@ -181,7 +184,7 @@ const ControlNet = (props: ControlNetProps) => {
<Box mt={2}> <Box mt={2}>
<ControlNetImagePreview <ControlNetImagePreview
controlNet={props.controlNet} controlNet={props.controlNet}
imageSx={expandedControlImageSx} height={96}
/> />
</Box> </Box>
<ParamControlNetProcessorSelect <ParamControlNetProcessorSelect

View File

@ -1,20 +1,19 @@
import { memo, useCallback, useState } from 'react'; import { memo, useCallback, useState } from 'react';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { import {
ControlNetConfig, ControlNetConfig,
controlNetImageChanged, controlNetImageChanged,
controlNetSelector, controlNetSelector,
} from '../store/controlNetSlice'; } from '../store/controlNetSlice';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { Box, ChakraProps, Flex } from '@chakra-ui/react'; import { Box, Flex, SystemStyleObject } from '@chakra-ui/react';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { AnimatePresence, motion } from 'framer-motion';
import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { FaUndo } from 'react-icons/fa'; import { FaUndo } from 'react-icons/fa';
import { useGetImageDTOQuery } from 'services/apiSlice'; import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { skipToken } from '@reduxjs/toolkit/dist/query'; import { skipToken } from '@reduxjs/toolkit/dist/query';
const selector = createSelector( const selector = createSelector(
@ -28,11 +27,11 @@ const selector = createSelector(
type Props = { type Props = {
controlNet: ControlNetConfig; controlNet: ControlNetConfig;
imageSx?: ChakraProps['sx']; height: SystemStyleObject['h'];
}; };
const ControlNetImagePreview = (props: Props) => { const ControlNetImagePreview = (props: Props) => {
const { imageSx } = props; const { height } = props;
const { const {
controlNetId, controlNetId,
controlImage: controlImageName, controlImage: controlImageName,
@ -45,14 +44,14 @@ const ControlNetImagePreview = (props: Props) => {
const [isMouseOverImage, setIsMouseOverImage] = useState(false); const [isMouseOverImage, setIsMouseOverImage] = useState(false);
const { const {
data: controlImage, currentData: controlImage,
isLoading: isLoadingControlImage, isLoading: isLoadingControlImage,
isError: isErrorControlImage, isError: isErrorControlImage,
isSuccess: isSuccessControlImage, isSuccess: isSuccessControlImage,
} = useGetImageDTOQuery(controlImageName ?? skipToken); } = useGetImageDTOQuery(controlImageName ?? skipToken);
const { const {
data: processedControlImage, currentData: processedControlImage,
isLoading: isLoadingProcessedControlImage, isLoading: isLoadingProcessedControlImage,
isError: isErrorProcessedControlImage, isError: isErrorProcessedControlImage,
isSuccess: isSuccessProcessedControlImage, isSuccess: isSuccessProcessedControlImage,
@ -85,10 +84,6 @@ const ControlNetImagePreview = (props: Props) => {
setIsMouseOverImage(false); setIsMouseOverImage(false);
}, []); }, []);
const shouldShowProcessedImageBackdrop =
Number(controlImage?.width) > Number(processedControlImage?.width) ||
Number(controlImage?.height) > Number(processedControlImage?.height);
const shouldShowProcessedImage = const shouldShowProcessedImage =
controlImage && controlImage &&
processedControlImage && processedControlImage &&
@ -97,72 +92,51 @@ const ControlNetImagePreview = (props: Props) => {
processorType !== 'none'; processorType !== 'none';
return ( return (
<Box <Flex
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
sx={{ position: 'relative', w: 'full', h: 'full' }} sx={{
position: 'relative',
w: 'full',
h: height,
alignItems: 'center',
justifyContent: 'center',
}}
> >
<IAIDndImage <IAIDndImage
image={controlImage} image={controlImage}
onDrop={handleDrop} onDrop={handleDrop}
isDropDisabled={Boolean( isDropDisabled={shouldShowProcessedImage}
processedControlImage && processorType !== 'none'
)}
isUploadDisabled={Boolean(controlImage)}
postUploadAction={{ type: 'SET_CONTROLNET_IMAGE', controlNetId }} postUploadAction={{ type: 'SET_CONTROLNET_IMAGE', controlNetId }}
imageSx={imageSx} imageSx={{
w: 'full',
h: 'full',
}}
/> />
<AnimatePresence> <Box
{shouldShowProcessedImage && ( sx={{
<motion.div position: 'absolute',
style={{ width: '100%' }} top: 0,
initial={{ insetInlineStart: 0,
opacity: 0, w: 'full',
}} h: 'full',
animate={{ opacity: shouldShowProcessedImage ? 1 : 0,
opacity: 1, transitionProperty: 'common',
transition: { duration: 0.1 }, transitionDuration: 'normal',
}} pointerEvents: 'none',
exit={{ }}
opacity: 0, >
transition: { duration: 0.1 }, <IAIDndImage
}} image={processedControlImage}
> onDrop={handleDrop}
<> payloadImage={controlImage}
{shouldShowProcessedImageBackdrop && ( isUploadDisabled={true}
<Box imageSx={{
sx={{ w: 'full',
position: 'absolute', h: 'full',
top: 0, }}
insetInlineStart: 0, />
w: 'full', </Box>
h: 'full',
bg: 'base.900',
opacity: 0.7,
}}
/>
)}
<Box
sx={{
position: 'absolute',
top: 0,
insetInlineStart: 0,
w: 'full',
h: 'full',
}}
>
<IAIDndImage
image={processedControlImage}
onDrop={handleDrop}
payloadImage={controlImage}
isUploadDisabled={true}
imageSx={imageSx}
/>
</Box>
</>
</motion.div>
)}
</AnimatePresence>
{pendingControlImages.includes(controlNetId) && ( {pendingControlImages.includes(controlNetId) && (
<Box <Box
sx={{ sx={{
@ -192,7 +166,7 @@ const ControlNetImagePreview = (props: Props) => {
/> />
</Flex> </Flex>
)} )}
</Box> </Flex>
); );
}; };

View File

@ -1,7 +1,7 @@
import { PayloadAction } from '@reduxjs/toolkit'; import { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { import {
ControlNetProcessorType, ControlNetProcessorType,
RequiredCannyImageProcessorInvocation, RequiredCannyImageProcessorInvocation,
@ -13,9 +13,9 @@ import {
ControlNetModelName, ControlNetModelName,
} from './constants'; } from './constants';
import { controlNetImageProcessed } from './actions'; import { controlNetImageProcessed } from './actions';
import { imageDeleted, imageUrlsReceived } from 'services/thunks/image'; import { imageDeleted, imageUrlsReceived } from 'services/api/thunks/image';
import { forEach } from 'lodash-es'; import { forEach } from 'lodash-es';
import { isAnySessionRejected } from 'services/thunks/session'; import { isAnySessionRejected } from 'services/api/thunks/session';
import { appSocketInvocationError } from 'services/events/actions'; import { appSocketInvocationError } from 'services/events/actions';
export const initialControlNet: Omit<ControlNetConfig, 'controlNetId'> = { export const initialControlNet: Omit<ControlNetConfig, 'controlNetId'> = {
@ -258,13 +258,13 @@ export const controlNetSlice = createSlice({
builder.addCase(imageDeleted.pending, (state, action) => { builder.addCase(imageDeleted.pending, (state, action) => {
// Preemptively remove the image from the gallery // Preemptively remove the image from the gallery
const { imageName } = action.meta.arg; const { image_name } = action.meta.arg;
forEach(state.controlNets, (c) => { forEach(state.controlNets, (c) => {
if (c.controlImage === imageName) { if (c.controlImage === image_name) {
c.controlImage = null; c.controlImage = null;
c.processedControlImage = null; c.processedControlImage = null;
} }
if (c.processedControlImage === imageName) { if (c.processedControlImage === image_name) {
c.processedControlImage = null; c.processedControlImage = null;
} }
}); });

View File

@ -12,7 +12,7 @@ import {
OpenposeImageProcessorInvocation, OpenposeImageProcessorInvocation,
PidiImageProcessorInvocation, PidiImageProcessorInvocation,
ZoeDepthImageProcessorInvocation, ZoeDepthImageProcessorInvocation,
} from 'services/api'; } from 'services/api/types';
import { O } from 'ts-toolbelt'; import { O } from 'ts-toolbelt';
/** /**

View File

@ -1,6 +1,6 @@
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useCreateBoardMutation } from 'services/apiSlice'; import { useCreateBoardMutation } from 'services/api/endpoints/boards';
const DEFAULT_BOARD_NAME = 'My Board'; const DEFAULT_BOARD_NAME = 'My Board';

View File

@ -6,8 +6,8 @@ import { IAINoImageFallback } from 'common/components/IAIImageFallback';
import { AnimatePresence } from 'framer-motion'; import { AnimatePresence } from 'framer-motion';
import { SelectedItemOverlay } from '../SelectedItemOverlay'; import { SelectedItemOverlay } from '../SelectedItemOverlay';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { useRemoveImageFromBoardMutation } from 'services/apiSlice'; import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages';
import { useDroppable } from '@dnd-kit/core'; import { useDroppable } from '@dnd-kit/core';
import IAIDropOverlay from 'common/components/IAIDropOverlay'; import IAIDropOverlay from 'common/components/IAIDropOverlay';

View File

@ -20,7 +20,7 @@ import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import AddBoardButton from './AddBoardButton'; import AddBoardButton from './AddBoardButton';
import AllImagesBoard from './AllImagesBoard'; import AllImagesBoard from './AllImagesBoard';
import { CloseIcon } from '@chakra-ui/icons'; import { CloseIcon } from '@chakra-ui/icons';
import { useListAllBoardsQuery } from 'services/apiSlice'; import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
const selector = createSelector( const selector = createSelector(
[boardsSelector], [boardsSelector],

View File

@ -14,15 +14,16 @@ import { useAppDispatch } from 'app/store/storeHooks';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { FaFolder, FaTrash } from 'react-icons/fa'; import { FaFolder, FaTrash } from 'react-icons/fa';
import { ContextMenu } from 'chakra-ui-contextmenu'; import { ContextMenu } from 'chakra-ui-contextmenu';
import { BoardDTO, ImageDTO } from 'services/api'; import { BoardDTO, ImageDTO } from 'services/api/types';
import { IAINoImageFallback } from 'common/components/IAIImageFallback'; import { IAINoImageFallback } from 'common/components/IAIImageFallback';
import { boardIdSelected } from 'features/gallery/store/boardSlice'; import { boardIdSelected } from 'features/gallery/store/boardSlice';
import { useAddImageToBoardMutation } from 'services/api/endpoints/boardImages';
import { import {
useAddImageToBoardMutation,
useDeleteBoardMutation, useDeleteBoardMutation,
useGetImageDTOQuery,
useUpdateBoardMutation, useUpdateBoardMutation,
} from 'services/apiSlice'; } from 'services/api/endpoints/boards';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { skipToken } from '@reduxjs/toolkit/dist/query'; import { skipToken } from '@reduxjs/toolkit/dist/query';
import { useDroppable } from '@dnd-kit/core'; import { useDroppable } from '@dnd-kit/core';
import { AnimatePresence } from 'framer-motion'; import { AnimatePresence } from 'framer-motion';
@ -37,7 +38,7 @@ interface HoverableBoardProps {
const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { data: coverImage } = useGetImageDTOQuery( const { currentData: coverImage } = useGetImageDTOQuery(
board.cover_image_name ?? skipToken board.cover_image_name ?? skipToken
); );

View File

@ -15,7 +15,7 @@ import IAIButton from 'common/components/IAIButton';
import { memo, useContext, useRef, useState } from 'react'; import { memo, useContext, useRef, useState } from 'react';
import { AddImageToBoardContext } from '../../../../app/contexts/AddImageToBoardContext'; import { AddImageToBoardContext } from '../../../../app/contexts/AddImageToBoardContext';
import IAIMantineSelect from 'common/components/IAIMantineSelect'; import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { useListAllBoardsQuery } from 'services/apiSlice'; import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
const UpdateImageBoardModal = () => { const UpdateImageBoardModal = () => {
// const boards = useSelector(selectBoardsAll); // const boards = useSelector(selectBoardsAll);

View File

@ -1,8 +1,7 @@
import { Box, Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/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-es';
import { gallerySelector } from '../store/gallerySelectors'; import { gallerySelector } from '../store/gallerySelectors';
import CurrentImageButtons from './CurrentImageButtons'; import CurrentImageButtons from './CurrentImageButtons';
@ -22,13 +21,8 @@ export const currentImageDisplaySelector = createSelector(
defaultSelectorOptions defaultSelectorOptions
); );
/**
* Displays the current image if there is one, plus associated actions.
*/
const CurrentImageDisplay = () => { const CurrentImageDisplay = () => {
const { hasSelectedImage, hasProgressImage } = useAppSelector( const { hasSelectedImage } = useAppSelector(currentImageDisplaySelector);
currentImageDisplaySelector
);
return ( return (
<Flex <Flex
@ -43,24 +37,8 @@ const CurrentImageDisplay = () => {
justifyContent: 'center', justifyContent: 'center',
}} }}
> >
<Flex {hasSelectedImage && <CurrentImageButtons />}
flexDirection="column" <CurrentImagePreview />
sx={{
w: 'full',
h: 'full',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
position: 'absolute',
}}
>
<CurrentImagePreview />
</Flex>
{hasSelectedImage && (
<Box sx={{ position: 'absolute', top: 0 }}>
<CurrentImageButtons />
</Box>
)}
</Flex> </Flex>
); );
}; };

View File

@ -11,9 +11,9 @@ import { memo, useCallback } from 'react';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { imageSelected } from '../store/gallerySlice'; import { imageSelected } from '../store/gallerySlice';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback';
import { useGetImageDTOQuery } from 'services/apiSlice'; import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { skipToken } from '@reduxjs/toolkit/dist/query'; import { skipToken } from '@reduxjs/toolkit/dist/query';
export const imagesSelector = createSelector( export const imagesSelector = createSelector(
@ -51,12 +51,8 @@ const CurrentImagePreview = () => {
shouldAntialiasProgressImage, shouldAntialiasProgressImage,
} = useAppSelector(imagesSelector); } = useAppSelector(imagesSelector);
// const image = useAppSelector((state: RootState) =>
// selectImagesById(state, selectedImage ?? '')
// );
const { const {
data: image, currentData: image,
isLoading, isLoading,
isError, isError,
isSuccess, isSuccess,
@ -79,9 +75,9 @@ const CurrentImagePreview = () => {
sx={{ sx={{
width: 'full', width: 'full',
height: 'full', height: 'full',
position: 'relative',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
position: 'relative',
}} }}
> >
{progressImage && shouldShowProgressInViewer ? ( {progressImage && shouldShowProgressInViewer ? (
@ -101,23 +97,15 @@ const CurrentImagePreview = () => {
}} }}
/> />
) : ( ) : (
<Flex <IAIDndImage
sx={{ image={image}
width: 'full', onDrop={handleDrop}
height: 'full', fallback={<IAIImageLoadingFallback sx={{ bg: 'none' }} />}
alignItems: 'center', isUploadDisabled={true}
justifyContent: 'center', fitContainer
}} />
>
<IAIDndImage
image={selectedImage && image ? image : undefined}
onDrop={handleDrop}
fallback={<IAIImageLoadingFallback sx={{ bg: 'none' }} />}
isUploadDisabled={true}
/>
</Flex>
)} )}
{shouldShowImageDetails && image && selectedImage && ( {shouldShowImageDetails && image && (
<Box <Box
sx={{ sx={{
position: 'absolute', position: 'absolute',
@ -131,7 +119,7 @@ const CurrentImagePreview = () => {
<ImageMetadataViewer image={image} /> <ImageMetadataViewer image={image} />
</Box> </Box>
)} )}
{!shouldShowImageDetails && image && selectedImage && ( {!shouldShowImageDetails && image && (
<Box <Box
sx={{ sx={{
position: 'absolute', position: 'absolute',

View File

@ -31,11 +31,11 @@ import { useRecallParameters } from 'features/parameters/hooks/useRecallParamete
import { initialImageSelected } from 'features/parameters/store/actions'; import { initialImageSelected } from 'features/parameters/store/actions';
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions'; import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { useDraggable } from '@dnd-kit/core'; import { useDraggable } from '@dnd-kit/core';
import { DeleteImageContext } from 'app/contexts/DeleteImageContext'; import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext'; import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext';
import { useRemoveImageFromBoardMutation } from 'services/apiSlice'; import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages';
export const selector = createSelector( export const selector = createSelector(
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector], [gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],

View File

@ -56,11 +56,11 @@ import {
imageCategoriesChanged, imageCategoriesChanged,
selectImagesAll, selectImagesAll,
} from '../store/imagesSlice'; } from '../store/imagesSlice';
import { receivedPageOfImages } from 'services/thunks/image'; import { receivedPageOfImages } from 'services/api/thunks/image';
import BoardsList from './Boards/BoardsList'; import BoardsList from './Boards/BoardsList';
import { boardsSelector } from '../store/boardSlice'; import { boardsSelector } from '../store/boardSlice';
import { ChevronUpIcon } from '@chakra-ui/icons'; import { ChevronUpIcon } from '@chakra-ui/icons';
import { useListAllBoardsQuery } from 'services/apiSlice'; import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
const itemSelector = createSelector( const itemSelector = createSelector(
[(state: RootState) => state], [(state: RootState) => state],
@ -167,7 +167,8 @@ const ImageGalleryContent = () => {
dispatch( dispatch(
receivedPageOfImages({ receivedPageOfImages({
categories, categories,
boardId: selectedBoardId, board_id: selectedBoardId,
is_intermediate: false,
}) })
); );
}, [categories, dispatch, selectedBoardId]); }, [categories, dispatch, selectedBoardId]);

View File

@ -17,7 +17,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaCopy } from 'react-icons/fa'; import { FaCopy } from 'react-icons/fa';
import { IoArrowUndoCircleOutline } from 'react-icons/io5'; import { IoArrowUndoCircleOutline } from 'react-icons/io5';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
type MetadataItemProps = { type MetadataItemProps = {
isLink?: boolean; isLink?: boolean;

View File

@ -1,6 +1,6 @@
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import { ImageUsage } from 'app/contexts/DeleteImageContext'; import { ImageUsage } from 'app/contexts/DeleteImageContext';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
export type RequestedImageDeletionArg = { export type RequestedImageDeletionArg = {
image: ImageDTO; image: ImageDTO;

View File

@ -1,6 +1,6 @@
import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { PayloadAction, createSlice } from '@reduxjs/toolkit';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { api } from 'services/apiSlice'; import { boardsApi } from 'services/api/endpoints/boards';
type BoardsState = { type BoardsState = {
searchText: string; searchText: string;
@ -29,7 +29,7 @@ const boardsSlice = createSlice({
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder.addMatcher( builder.addMatcher(
api.endpoints.deleteBoard.matchFulfilled, boardsApi.endpoints.deleteBoard.matchFulfilled,
(state, action) => { (state, action) => {
if (action.meta.arg.originalArgs === state.selectedBoardId) { if (action.meta.arg.originalArgs === state.selectedBoardId) {
state.selectedBoardId = undefined; state.selectedBoardId = undefined;

View File

@ -6,14 +6,14 @@ import {
createSlice, createSlice,
} from '@reduxjs/toolkit'; } from '@reduxjs/toolkit';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { ImageCategory, ImageDTO } from 'services/api'; import { ImageCategory, ImageDTO } from 'services/api/types';
import { dateComparator } from 'common/util/dateComparator'; import { dateComparator } from 'common/util/dateComparator';
import { keyBy } from 'lodash-es'; import { keyBy } from 'lodash-es';
import { import {
imageDeleted, imageDeleted,
imageUrlsReceived, imageUrlsReceived,
receivedPageOfImages, receivedPageOfImages,
} from 'services/thunks/image'; } from 'services/api/thunks/image';
export const imagesAdapter = createEntityAdapter<ImageDTO>({ export const imagesAdapter = createEntityAdapter<ImageDTO>({
selectId: (image) => image.image_name, selectId: (image) => image.image_name,
@ -73,13 +73,13 @@ const imagesSlice = createSlice({
}); });
builder.addCase(receivedPageOfImages.fulfilled, (state, action) => { builder.addCase(receivedPageOfImages.fulfilled, (state, action) => {
state.isLoading = false; state.isLoading = false;
const { boardId, categories, imageOrigin, isIntermediate } = const { board_id, categories, image_origin, is_intermediate } =
action.meta.arg; action.meta.arg;
const { items, offset, limit, total } = action.payload; const { items, offset, limit, total } = action.payload;
imagesAdapter.upsertMany(state, items); imagesAdapter.upsertMany(state, items);
if (!categories?.includes('general') || boardId) { if (!categories?.includes('general') || board_id) {
// need to skip updating the total images count if the images recieved were for a specific board // need to skip updating the total images count if the images recieved were for a specific board
// TODO: this doesn't work when on the Asset tab/category... // TODO: this doesn't work when on the Asset tab/category...
return; return;
@ -91,8 +91,8 @@ const imagesSlice = createSlice({
}); });
builder.addCase(imageDeleted.pending, (state, action) => { builder.addCase(imageDeleted.pending, (state, action) => {
// Image deleted // Image deleted
const { imageName } = action.meta.arg; const { image_name } = action.meta.arg;
imagesAdapter.removeOne(state, imageName); imagesAdapter.removeOne(state, image_name);
}); });
builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { builder.addCase(imageUrlsReceived.fulfilled, (state, action) => {
const { image_name, image_url, thumbnail_url } = action.payload; const { image_name, image_url, thumbnail_url } = action.payload;

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 { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
type ReactPanZoomProps = { type ReactPanZoomProps = {
image: ImageDTO; image: ImageDTO;

View File

@ -9,9 +9,9 @@ import { memo, useCallback } from 'react';
import { FieldComponentProps } from './types'; import { FieldComponentProps } from './types';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { useGetImageDTOQuery } from 'services/apiSlice'; import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { skipToken } from '@reduxjs/toolkit/dist/query'; import { skipToken } from '@reduxjs/toolkit/dist/query';
const ImageInputFieldComponent = ( const ImageInputFieldComponent = (
@ -22,15 +22,15 @@ const ImageInputFieldComponent = (
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { const {
data: image, currentData: image,
isLoading, isLoading,
isError, isError,
isSuccess, isSuccess,
} = useGetImageDTOQuery(field.value ?? skipToken); } = useGetImageDTOQuery(field.value?.image_name ?? skipToken);
const handleDrop = useCallback( const handleDrop = useCallback(
(droppedImage: ImageDTO) => { ({ image_name }: ImageDTO) => {
if (field.value === droppedImage.image_name) { if (field.value?.image_name === image_name) {
return; return;
} }
@ -38,7 +38,7 @@ const ImageInputFieldComponent = (
fieldValueChanged({ fieldValueChanged({
nodeId, nodeId,
fieldName: field.name, fieldName: field.name,
value: droppedImage.image_name, value: { image_name },
}) })
); );
}, },

View File

@ -12,7 +12,7 @@ import { forEach, isString } from 'lodash-es';
import { MODEL_TYPE_MAP as BASE_MODEL_NAME_MAP } from 'features/system/components/ModelSelect'; import { MODEL_TYPE_MAP as BASE_MODEL_NAME_MAP } from 'features/system/components/ModelSelect';
import IAIMantineSelect from 'common/components/IAIMantineSelect'; import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useListModelsQuery } from 'services/apiSlice'; import { useListModelsQuery } from 'services/api/endpoints/models';
const ModelInputFieldComponent = ( const ModelInputFieldComponent = (
props: FieldComponentProps<ModelInputFieldValue, ModelInputFieldTemplate> props: FieldComponentProps<ModelInputFieldValue, ModelInputFieldTemplate>

View File

@ -3,7 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { Panel } from 'reactflow'; import { Panel } from 'reactflow';
import { receivedOpenAPISchema } from 'services/thunks/schema'; import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import NodeInvokeButton from '../ui/NodeInvokeButton'; import NodeInvokeButton from '../ui/NodeInvokeButton';
const TopCenterPanel = () => { const TopCenterPanel = () => {

View File

@ -1,5 +1,5 @@
import { createAction, isAnyOf } from '@reduxjs/toolkit'; import { createAction, isAnyOf } from '@reduxjs/toolkit';
import { Graph } from 'services/api'; import { Graph } from 'services/api/types';
export const textToImageGraphBuilt = createAction<Graph>( export const textToImageGraphBuilt = createAction<Graph>(
'nodes/textToImageGraphBuilt' 'nodes/textToImageGraphBuilt'

View File

@ -11,14 +11,13 @@ import {
NodeChange, NodeChange,
OnConnectStartParams, OnConnectStartParams,
} from 'reactflow'; } from 'reactflow';
import { ImageDTO } from 'services/api'; import { ImageField } from 'services/api/types';
import { receivedOpenAPISchema } from 'services/thunks/schema'; import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import { InvocationTemplate, InvocationValue } from '../types/types'; import { InvocationTemplate, InvocationValue } from '../types/types';
import { parseSchema } from '../util/parseSchema'; import { parseSchema } from '../util/parseSchema';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { forEach, size } from 'lodash-es'; import { size } from 'lodash-es';
import { RgbaColor } from 'react-colorful'; import { RgbaColor } from 'react-colorful';
import { imageUrlsReceived } from 'services/thunks/image';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
export type NodesState = { export type NodesState = {
@ -66,7 +65,7 @@ const nodesSlice = createSlice({
action: PayloadAction<{ action: PayloadAction<{
nodeId: string; nodeId: string;
fieldName: string; fieldName: string;
value: string | number | boolean | ImageDTO | RgbaColor | undefined; value: string | number | boolean | ImageField | RgbaColor | undefined;
}> }>
) => { ) => {
const { nodeId, fieldName, value } = action.payload; const { nodeId, fieldName, value } = action.payload;

View File

@ -1,6 +1,6 @@
import { OpenAPIV3 } from 'openapi-types'; import { OpenAPIV3 } from 'openapi-types';
import { RgbaColor } from 'react-colorful'; import { RgbaColor } from 'react-colorful';
import { Graph, ImageDTO } from 'services/api'; import { Graph, ImageDTO, ImageField } from 'services/api/types';
import { AnyInvocationType } from 'services/events/types'; import { AnyInvocationType } from 'services/events/types';
import { O } from 'ts-toolbelt'; import { O } from 'ts-toolbelt';
@ -214,7 +214,7 @@ export type VaeInputFieldValue = FieldValueBase & {
export type ImageInputFieldValue = FieldValueBase & { export type ImageInputFieldValue = FieldValueBase & {
type: 'image'; type: 'image';
value?: string; value?: ImageField;
}; };
export type ModelInputFieldValue = FieldValueBase & { export type ModelInputFieldValue = FieldValueBase & {

View File

@ -1,6 +1,6 @@
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { filter, forEach, size } from 'lodash-es'; import { filter, forEach, size } from 'lodash-es';
import { CollectInvocation, ControlNetInvocation } from 'services/api'; import { CollectInvocation, ControlNetInvocation } from 'services/api/types';
import { NonNullableGraph } from '../types/types'; import { NonNullableGraph } from '../types/types';
import { CONTROL_NET_COLLECT } from './graphBuilders/constants'; import { CONTROL_NET_COLLECT } from './graphBuilders/constants';

View File

@ -6,7 +6,7 @@ import {
RandomRangeInvocation, RandomRangeInvocation,
RangeInvocation, RangeInvocation,
TextToImageInvocation, TextToImageInvocation,
} from 'services/api'; } from 'services/api/types';
export const buildEdges = ( export const buildEdges = (
baseNode: TextToImageInvocation | ImageToImageInvocation | InpaintInvocation, baseNode: TextToImageInvocation | ImageToImageInvocation | InpaintInvocation,

View File

@ -1,5 +1,5 @@
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { forEach } from 'lodash-es'; import { forEach } from 'lodash-es';
import { buildCanvasInpaintGraph } from './buildCanvasInpaintGraph'; import { buildCanvasInpaintGraph } from './buildCanvasInpaintGraph';

View File

@ -4,7 +4,7 @@ import {
ImageResizeInvocation, ImageResizeInvocation,
RandomIntInvocation, RandomIntInvocation,
RangeOfSizeInvocation, RangeOfSizeInvocation,
} from 'services/api'; } from 'services/api/types';
import { NonNullableGraph } from 'features/nodes/types/types'; import { NonNullableGraph } from 'features/nodes/types/types';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { import {

View File

@ -4,7 +4,7 @@ import {
InpaintInvocation, InpaintInvocation,
RandomIntInvocation, RandomIntInvocation,
RangeOfSizeInvocation, RangeOfSizeInvocation,
} from 'services/api'; } from 'services/api/types';
import { NonNullableGraph } from 'features/nodes/types/types'; import { NonNullableGraph } from 'features/nodes/types/types';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { import {

View File

@ -1,6 +1,6 @@
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { NonNullableGraph } from 'features/nodes/types/types'; import { NonNullableGraph } from 'features/nodes/types/types';
import { RandomIntInvocation, RangeOfSizeInvocation } from 'services/api'; import { RandomIntInvocation, RangeOfSizeInvocation } from 'services/api/types';
import { import {
ITERATE, ITERATE,
LATENTS_TO_IMAGE, LATENTS_TO_IMAGE,

View File

@ -3,7 +3,7 @@ import {
ImageResizeInvocation, ImageResizeInvocation,
RandomIntInvocation, RandomIntInvocation,
RangeOfSizeInvocation, RangeOfSizeInvocation,
} from 'services/api'; } from 'services/api/types';
import { NonNullableGraph } from 'features/nodes/types/types'; import { NonNullableGraph } from 'features/nodes/types/types';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { import {

View File

@ -4,7 +4,7 @@ import {
BaseModelType, BaseModelType,
RandomIntInvocation, RandomIntInvocation,
RangeOfSizeInvocation, RangeOfSizeInvocation,
} from 'services/api'; } from 'services/api/types';
import { import {
ITERATE, ITERATE,
LATENTS_TO_IMAGE, LATENTS_TO_IMAGE,

View File

@ -1,4 +1,4 @@
import { Graph } from 'services/api'; import { Graph } from 'services/api/types';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { cloneDeep, omit, reduce } from 'lodash-es'; import { cloneDeep, omit, reduce } from 'lodash-es';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';

View File

@ -1,4 +1,4 @@
import { BaseModelType, PipelineModelField } from 'services/api'; import { BaseModelType, PipelineModelField } from 'services/api/types';
/** /**
* Crudely converts a model id to a pipeline model field * Crudely converts a model id to a pipeline model field

View File

@ -1,6 +1,6 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { CompelInvocation } from 'services/api'; import { CompelInvocation } from 'services/api/types';
import { O } from 'ts-toolbelt'; import { O } from 'ts-toolbelt';
export const buildCompelNode = ( export const buildCompelNode = (

View File

@ -4,7 +4,7 @@ import {
Edge, Edge,
ImageToImageInvocation, ImageToImageInvocation,
TextToImageInvocation, TextToImageInvocation,
} from 'services/api'; } from 'services/api/types';
import { O } from 'ts-toolbelt'; import { O } from 'ts-toolbelt';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';

View File

@ -1,6 +1,6 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { InpaintInvocation } from 'services/api'; import { InpaintInvocation } from 'services/api/types';
import { O } from 'ts-toolbelt'; import { O } from 'ts-toolbelt';
export const buildInpaintNode = ( export const buildInpaintNode = (

View File

@ -1,6 +1,6 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { IterateInvocation } from 'services/api'; import { IterateInvocation } from 'services/api/types';
export const buildIterateNode = (): IterateInvocation => { export const buildIterateNode = (): IterateInvocation => {
const nodeId = uuidv4(); const nodeId = uuidv4();

View File

@ -1,7 +1,7 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { RandomRangeInvocation, RangeInvocation } from 'services/api'; import { RandomRangeInvocation, RangeInvocation } from 'services/api/types';
export const buildRangeNode = ( export const buildRangeNode = (
state: RootState state: RootState

View File

@ -1,6 +1,6 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { TextToImageInvocation } from 'services/api'; import { TextToImageInvocation } from 'services/api/types';
import { O } from 'ts-toolbelt'; import { O } from 'ts-toolbelt';
export const buildTxt2ImgNode = ( export const buildTxt2ImgNode = (

View File

@ -1,4 +1,4 @@
import { Flex } from '@chakra-ui/react'; import { Flex, Spacer, Text } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
@ -9,10 +9,14 @@ import { useCallback } from 'react';
import { generationSelector } from 'features/parameters/store/generationSelectors'; import { generationSelector } from 'features/parameters/store/generationSelectors';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback';
import { useGetImageDTOQuery } from 'services/apiSlice'; import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { skipToken } from '@reduxjs/toolkit/dist/query'; import { skipToken } from '@reduxjs/toolkit/dist/query';
import IAIIconButton from 'common/components/IAIIconButton';
import { FaUndo, FaUpload } from 'react-icons/fa';
import useImageUploader from 'common/hooks/useImageUploader';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
const selector = createSelector( const selector = createSelector(
[generationSelector], [generationSelector],
@ -28,14 +32,19 @@ const selector = createSelector(
const InitialImagePreview = () => { const InitialImagePreview = () => {
const { initialImage } = useAppSelector(selector); const { initialImage } = useAppSelector(selector);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { openUploader } = useImageUploader();
const { const {
data: image, currentData: image,
isLoading, isLoading,
isError, isError,
isSuccess, isSuccess,
} = useGetImageDTOQuery(initialImage?.imageName ?? skipToken); } = useGetImageDTOQuery(initialImage?.imageName ?? skipToken);
const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
postUploadAction: { type: 'SET_INITIAL_IMAGE' },
});
const handleDrop = useCallback( const handleDrop = useCallback(
(droppedImage: ImageDTO) => { (droppedImage: ImageDTO) => {
if (droppedImage.image_name === initialImage?.imageName) { if (droppedImage.image_name === initialImage?.imageName) {
@ -50,25 +59,66 @@ const InitialImagePreview = () => {
dispatch(clearInitialImage()); dispatch(clearInitialImage());
}, [dispatch]); }, [dispatch]);
const handleUpload = useCallback(() => {
openUploader();
}, [openUploader]);
return ( return (
<Flex <Flex
sx={{ sx={{
flexDir: 'column',
width: 'full', width: 'full',
height: 'full', height: 'full',
position: 'absolute', position: 'absolute',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
p: 4, p: 4,
gap: 4,
}} }}
> >
<Flex
sx={{
w: 'full',
flexWrap: 'wrap',
justifyContent: 'center',
alignItems: 'center',
gap: 2,
}}
>
<Text
sx={{
color: 'base.200',
fontWeight: 600,
fontSize: 'sm',
userSelect: 'none',
}}
>
Initial Image
</Text>
<Spacer />
<IAIIconButton
tooltip="Upload Initial Image"
aria-label="Upload Initial Image"
icon={<FaUpload />}
onClick={handleUpload}
{...getUploadButtonProps()}
/>
<IAIIconButton
tooltip="Reset Initial Image"
aria-label="Reset Initial Image"
icon={<FaUndo />}
onClick={handleReset}
isDisabled={!initialImage}
/>
</Flex>
<IAIDndImage <IAIDndImage
image={image} image={image}
onDrop={handleDrop} onDrop={handleDrop}
onReset={handleReset}
fallback={<IAIImageLoadingFallback sx={{ bg: 'none' }} />} fallback={<IAIImageLoadingFallback sx={{ bg: 'none' }} />}
postUploadAction={{ type: 'SET_INITIAL_IMAGE' }} isUploadDisabled={true}
withResetIcon fitContainer
/> />
<input {...getUploadInputProps()} />
</Flex> </Flex>
); );
}; };

View File

@ -26,7 +26,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { MdCancel, MdCancelScheduleSend } from 'react-icons/md'; import { MdCancel, MdCancelScheduleSend } from 'react-icons/md';
import { sessionCanceled } from 'services/thunks/session'; import { sessionCanceled } from 'services/api/thunks/session';
import { ChevronDownIcon } from '@chakra-ui/icons'; import { ChevronDownIcon } from '@chakra-ui/icons';
const cancelButtonSelector = createSelector( const cancelButtonSelector = createSelector(
@ -78,7 +78,7 @@ const CancelButton = (
return; return;
} }
dispatch(sessionCanceled({ sessionId })); dispatch(sessionCanceled({ session_id: sessionId }));
}, [dispatch, sessionId, cancelType]); }, [dispatch, sessionId, cancelType]);
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -13,10 +13,10 @@ import {
setSteps, setSteps,
setWidth, setWidth,
} from '../store/generationSlice'; } from '../store/generationSlice';
import { isImageField } from 'services/types/guards'; import { isImageField } from 'services/api/guards';
import { initialImageSelected } from '../store/actions'; import { initialImageSelected } from '../store/actions';
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { import {
isValidCfgScale, isValidCfgScale,
isValidHeight, isValidHeight,

View File

@ -1,5 +1,5 @@
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
export const initialImageSelected = createAction<ImageDTO | string | undefined>( export const initialImageSelected = createAction<ImageDTO | string | undefined>(
'generation/initialImageSelected' 'generation/initialImageSelected'

View File

@ -3,7 +3,7 @@ import { createSlice } from '@reduxjs/toolkit';
import { DEFAULT_SCHEDULER_NAME } from 'app/constants'; import { DEFAULT_SCHEDULER_NAME } from 'app/constants';
import { configChanged } from 'features/system/store/configSlice'; import { configChanged } from 'features/system/store/configSlice';
import { clamp } from 'lodash-es'; import { clamp } from 'lodash-es';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { import {
CfgScaleParam, CfgScaleParam,
HeightParam, HeightParam,

View File

@ -8,7 +8,7 @@ import { modelSelected } from 'features/parameters/store/generationSlice';
import { forEach, isString } from 'lodash-es'; import { forEach, isString } from 'lodash-es';
import { SelectItem } from '@mantine/core'; import { SelectItem } from '@mantine/core';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { useListModelsQuery } from 'services/apiSlice'; import { useListModelsQuery } from 'services/api/endpoints/models';
export const MODEL_TYPE_MAP = { export const MODEL_TYPE_MAP = {
'sd-1': 'Stable Diffusion 1.x', 'sd-1': 'Stable Diffusion 1.x',

View File

@ -19,8 +19,11 @@ import {
appSocketUnsubscribed, appSocketUnsubscribed,
} from 'services/events/actions'; } from 'services/events/actions';
import { ProgressImage } from 'services/events/types'; import { ProgressImage } from 'services/events/types';
import { imageUploaded } from 'services/thunks/image'; import { imageUploaded } from 'services/api/thunks/image';
import { isAnySessionRejected, sessionCanceled } from 'services/thunks/session'; import {
isAnySessionRejected,
sessionCanceled,
} from 'services/api/thunks/session';
import { makeToast } from '../../../app/components/Toaster'; import { makeToast } from '../../../app/components/Toaster';
import { LANGUAGES } from '../components/LanguagePicker'; import { LANGUAGES } from '../components/LanguagePicker';
@ -362,7 +365,7 @@ export const systemSlice = createSlice({
* Session Canceled - FULFILLED * Session Canceled - FULFILLED
*/ */
builder.addCase(sessionCanceled.fulfilled, (state, action) => { builder.addCase(sessionCanceled.fulfilled, (state, action) => {
state.canceledSession = action.meta.arg.sessionId; state.canceledSession = action.meta.arg.session_id;
state.isProcessing = false; state.isProcessing = false;
state.isCancelable = false; state.isCancelable = false;
state.isCancelScheduled = false; state.isCancelScheduled = false;

View File

@ -12,7 +12,7 @@ import { uiSelector } from 'features/ui/store/uiSelectors';
import { memo, useCallback, useLayoutEffect } from 'react'; import { memo, useCallback, useLayoutEffect } from 'react';
import UnifiedCanvasToolbarBeta from './UnifiedCanvasBeta/UnifiedCanvasToolbarBeta'; import UnifiedCanvasToolbarBeta from './UnifiedCanvasBeta/UnifiedCanvasToolbarBeta';
import UnifiedCanvasToolSettingsBeta from './UnifiedCanvasBeta/UnifiedCanvasToolSettingsBeta'; import UnifiedCanvasToolSettingsBeta from './UnifiedCanvasBeta/UnifiedCanvasToolSettingsBeta';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api/types';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { useDroppable } from '@dnd-kit/core'; import { useDroppable } from '@dnd-kit/core';
import IAIDropOverlay from 'common/components/IAIDropOverlay'; import IAIDropOverlay from 'common/components/IAIDropOverlay';

View File

@ -0,0 +1,33 @@
import { atom, computed } from 'nanostores';
import createClient from 'openapi-fetch';
import { paths } from 'services/api/schema';
/**
* We use nanostores to store the token and base url for very simple reactivity
*/
/**
* The user's auth token.
*/
export const $authToken = atom<string | undefined>();
/**
* The OpenAPI base url.
*/
export const $baseUrl = atom<string | undefined>();
/**
* Autogenerated, type-safe fetch client for the API. Used when RTK Query is not an option.
* Dynamically updates when the token or base url changes.
* Use `$client.get()` to get the client.
*
* @example
* const { get, post, del } = $client.get();
*/
export const $client = computed([$authToken, $baseUrl], (authToken, baseUrl) =>
createClient<paths>({
headers: authToken ? { Authorization: `Bearer ${authToken}` } : {},
// do not include `api/v1` in the base url for this client
baseUrl: `${baseUrl ?? ''}`,
})
);

View File

@ -1,24 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ApiRequestOptions } from './ApiRequestOptions';
import type { ApiResult } from './ApiResult';
export class ApiError extends Error {
public readonly url: string;
public readonly status: number;
public readonly statusText: string;
public readonly body: any;
public readonly request: ApiRequestOptions;
constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
super(message);
this.name = 'ApiError';
this.url = response.url;
this.status = response.status;
this.statusText = response.statusText;
this.body = response.body;
this.request = request;
}
}

View File

@ -1,16 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ApiRequestOptions = {
readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
readonly url: string;
readonly path?: Record<string, any>;
readonly cookies?: Record<string, any>;
readonly headers?: Record<string, any>;
readonly query?: Record<string, any>;
readonly formData?: Record<string, any>;
readonly body?: any;
readonly mediaType?: string;
readonly responseHeader?: string;
readonly errors?: Record<number, string>;
};

View File

@ -1,10 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ApiResult = {
readonly url: string;
readonly ok: boolean;
readonly status: number;
readonly statusText: string;
readonly body: any;
};

View File

@ -1,130 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export class CancelError extends Error {
constructor(message: string) {
super(message);
this.name = 'CancelError';
}
public get isCancelled(): boolean {
return true;
}
}
export interface OnCancel {
readonly isResolved: boolean;
readonly isRejected: boolean;
readonly isCancelled: boolean;
(cancelHandler: () => void): void;
}
export class CancelablePromise<T> implements Promise<T> {
#isResolved: boolean;
#isRejected: boolean;
#isCancelled: boolean;
readonly #cancelHandlers: (() => void)[];
readonly #promise: Promise<T>;
#resolve?: (value: T | PromiseLike<T>) => void;
#reject?: (reason?: any) => void;
constructor(
executor: (
resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: any) => void,
onCancel: OnCancel
) => void
) {
this.#isResolved = false;
this.#isRejected = false;
this.#isCancelled = false;
this.#cancelHandlers = [];
this.#promise = new Promise<T>((resolve, reject) => {
this.#resolve = resolve;
this.#reject = reject;
const onResolve = (value: T | PromiseLike<T>): void => {
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
return;
}
this.#isResolved = true;
this.#resolve?.(value);
};
const onReject = (reason?: any): void => {
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
return;
}
this.#isRejected = true;
this.#reject?.(reason);
};
const onCancel = (cancelHandler: () => void): void => {
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
return;
}
this.#cancelHandlers.push(cancelHandler);
};
Object.defineProperty(onCancel, 'isResolved', {
get: (): boolean => this.#isResolved,
});
Object.defineProperty(onCancel, 'isRejected', {
get: (): boolean => this.#isRejected,
});
Object.defineProperty(onCancel, 'isCancelled', {
get: (): boolean => this.#isCancelled,
});
return executor(onResolve, onReject, onCancel as OnCancel);
});
}
get [Symbol.toStringTag]() {
return "Cancellable Promise";
}
public then<TResult1 = T, TResult2 = never>(
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
): Promise<TResult1 | TResult2> {
return this.#promise.then(onFulfilled, onRejected);
}
public catch<TResult = never>(
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
): Promise<T | TResult> {
return this.#promise.catch(onRejected);
}
public finally(onFinally?: (() => void) | null): Promise<T> {
return this.#promise.finally(onFinally);
}
public cancel(): void {
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
return;
}
this.#isCancelled = true;
if (this.#cancelHandlers.length) {
try {
for (const cancelHandler of this.#cancelHandlers) {
cancelHandler();
}
} catch (error) {
console.warn('Cancellation threw an error', error);
return;
}
}
this.#cancelHandlers.length = 0;
this.#reject?.(new CancelError('Request aborted'));
}
public get isCancelled(): boolean {
return this.#isCancelled;
}
}

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