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

This commit is contained in:
Lincoln Stein
2023-05-25 19:06:09 -04:00
committed by GitHub
188 changed files with 3446 additions and 4400 deletions

View File

@ -23,8 +23,8 @@
"dev": "concurrently \"vite dev\" \"yarn run theme:watch\"",
"dev:host": "concurrently \"vite dev --host\" \"yarn run theme:watch\"",
"build": "yarn run lint && vite build",
"api:web": "openapi -i http://localhost:9090/openapi.json -o src/services/api --client axios --useOptions --useUnionTypes --exportSchemas true --indent 2 --request src/services/fixtures/request.ts",
"api:file": "openapi -i src/services/fixtures/openapi.json -o src/services/api --client axios --useOptions --useUnionTypes --exportSchemas true --indent 2 --request src/services/fixtures/request.ts",
"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",
"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",
"lint:madge": "madge --circular src/main.tsx",
"lint:eslint": "eslint --max-warnings=0 .",

View File

@ -10,7 +10,7 @@ export const readinessSelector = createSelector(
[generationSelector, systemSelector, activeTabNameSelector],
(generation, system, activeTabName) => {
const {
prompt,
positivePrompt: prompt,
shouldGenerateVariations,
seedWeights,
initialImage,

View File

@ -5,7 +5,6 @@ import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice';
import { imageUploaded } from 'services/thunks/image';
import { v4 as uuidv4 } from 'uuid';
import { deserializeImageResponse } from 'services/util/deserializeImageResponse';
import { setMergedCanvas } from 'features/canvas/store/canvasSlice';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
@ -66,7 +65,7 @@ export const addCanvasMergedListener = () => {
action.meta.arg.formData.file.name === filename
);
const mergedCanvasImage = deserializeImageResponse(payload.response);
const mergedCanvasImage = payload.response;
dispatch(
setMergedCanvas({

View File

@ -17,24 +17,24 @@ export const addRequestedImageDeletionListener = () => {
return;
}
const { name, type } = image;
const { image_name, image_type } = image;
if (type !== 'uploads' && type !== 'results') {
moduleLog.warn({ data: image }, `Invalid image type ${type}`);
if (image_type !== 'uploads' && image_type !== 'results') {
moduleLog.warn({ data: image }, `Invalid image type ${image_type}`);
return;
}
const selectedImageName = getState().gallery.selectedImage?.name;
const selectedImageName = getState().gallery.selectedImage?.image_name;
if (selectedImageName === name) {
const allIds = getState()[type].ids;
const allEntities = getState()[type].entities;
if (selectedImageName === image_name) {
const allIds = getState()[image_type].ids;
const allEntities = getState()[image_type].entities;
const deletedImageIndex = allIds.findIndex(
(result) => result.toString() === name
(result) => result.toString() === image_name
);
const filteredIds = allIds.filter((id) => id.toString() !== name);
const filteredIds = allIds.filter((id) => id.toString() !== image_name);
const newSelectedImageIndex = clamp(
deletedImageIndex,
@ -53,7 +53,7 @@ export const addRequestedImageDeletionListener = () => {
}
}
dispatch(imageDeleted({ imageName: name, imageType: type }));
dispatch(imageDeleted({ imageName: image_name, imageType: image_type }));
},
});
};

View File

@ -1,4 +1,3 @@
import { deserializeImageResponse } from 'services/util/deserializeImageResponse';
import { startAppListening } from '..';
import { uploadAdded } from 'features/gallery/store/uploadsSlice';
import { imageSelected } from 'features/gallery/store/gallerySlice';
@ -7,6 +6,7 @@ import { addToast } from 'features/system/store/systemSlice';
import { initialImageSelected } from 'features/parameters/store/actions';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { resultAdded } from 'features/gallery/store/resultsSlice';
import { isResultsImageDTO, isUploadsImageDTO } from 'services/types/guards';
export const addImageUploadedListener = () => {
startAppListening({
@ -14,13 +14,11 @@ export const addImageUploadedListener = () => {
imageUploaded.fulfilled.match(action) &&
action.payload.response.image_type !== 'intermediates',
effect: (action, { dispatch, getState }) => {
const { response } = action.payload;
const { imageType } = action.meta.arg;
const { response: image } = action.payload;
const state = getState();
const image = deserializeImageResponse(response);
if (imageType === 'uploads') {
if (isUploadsImageDTO(image)) {
dispatch(uploadAdded(image));
dispatch(addToast({ title: 'Image Uploaded', status: 'success' }));
@ -38,7 +36,7 @@ export const addImageUploadedListener = () => {
}
}
if (imageType === 'results') {
if (isResultsImageDTO(image)) {
dispatch(resultAdded(image));
}
},

View File

@ -1,12 +1,15 @@
import { initialImageChanged } from 'features/parameters/store/generationSlice';
import { Image, isInvokeAIImage } from 'app/types/invokeai';
import { selectResultsById } from 'features/gallery/store/resultsSlice';
import { selectUploadsById } from 'features/gallery/store/uploadsSlice';
import { t } from 'i18next';
import { addToast } from 'features/system/store/systemSlice';
import { startAppListening } from '..';
import { initialImageSelected } from 'features/parameters/store/actions';
import {
initialImageSelected,
isImageDTO,
} from 'features/parameters/store/actions';
import { makeToast } from 'app/components/Toaster';
import { ImageDTO } from 'services/api';
export const addInitialImageSelectedListener = () => {
startAppListening({
@ -21,21 +24,21 @@ export const addInitialImageSelectedListener = () => {
return;
}
if (isInvokeAIImage(action.payload)) {
if (isImageDTO(action.payload)) {
dispatch(initialImageChanged(action.payload));
dispatch(addToast(makeToast(t('toast.sentToImageToImage'))));
return;
}
const { name, type } = action.payload;
const { image_name, image_type } = action.payload;
let image: Image | undefined;
let image: ImageDTO | undefined;
const state = getState();
if (type === 'results') {
image = selectResultsById(state, name);
} else if (type === 'uploads') {
image = selectUploadsById(state, name);
if (image_type === 'results') {
image = selectResultsById(state, image_name);
} else if (image_type === 'uploads') {
image = selectUploadsById(state, image_name);
}
if (!image) {

View File

@ -1,14 +1,10 @@
import { invocationComplete } from 'services/events/actions';
import { isImageOutput } from 'services/types/guards';
import {
buildImageUrls,
extractTimestampFromImageName,
} from 'services/util/deserializeImageField';
import { Image } from 'app/types/invokeai';
import { resultAdded } from 'features/gallery/store/resultsSlice';
import { imageReceived, thumbnailReceived } from 'services/thunks/image';
imageMetadataReceived,
imageUrlsReceived,
} from 'services/thunks/image';
import { startAppListening } from '..';
import { imageSelected } from 'features/gallery/store/gallerySlice';
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
const nodeDenylist = ['dataURL_image'];
@ -24,62 +20,40 @@ export const addImageResultReceivedListener = () => {
}
return false;
},
effect: (action, { getState, dispatch }) => {
effect: async (action, { getState, dispatch, take }) => {
if (!invocationComplete.match(action)) {
return;
}
const { data, shouldFetchImages } = action.payload;
const { data } = action.payload;
const { result, node, graph_execution_state_id } = data;
if (isImageOutput(result) && !nodeDenylist.includes(node.type)) {
const name = result.image.image_name;
const type = result.image.image_type;
const state = getState();
const { image_name, image_type } = result.image;
// if we need to refetch, set URLs to placeholder for now
const { url, thumbnail } = shouldFetchImages
? { url: '', thumbnail: '' }
: buildImageUrls(type, name);
dispatch(
imageUrlsReceived({ imageName: image_name, imageType: image_type })
);
const timestamp = extractTimestampFromImageName(name);
const image: Image = {
name,
type,
url,
thumbnail,
metadata: {
created: timestamp,
width: result.width,
height: result.height,
invokeai: {
session_id: graph_execution_state_id,
...(node ? { node } : {}),
},
},
};
dispatch(resultAdded(image));
if (state.gallery.shouldAutoSwitchToNewImages) {
dispatch(imageSelected(image));
}
if (state.config.shouldFetchImages) {
dispatch(imageReceived({ imageName: name, imageType: type }));
dispatch(
thumbnailReceived({
thumbnailName: name,
thumbnailType: type,
})
);
}
dispatch(
imageMetadataReceived({
imageName: image_name,
imageType: image_type,
})
);
// Handle canvas image
if (
graph_execution_state_id ===
state.canvas.layerState.stagingArea.sessionId
getState().canvas.layerState.stagingArea.sessionId
) {
const [{ payload: image }] = await take(
(
action
): action is ReturnType<typeof imageMetadataReceived.fulfilled> =>
imageMetadataReceived.fulfilled.match(action) &&
action.payload.image_name === image_name
);
dispatch(addImageToStagingArea(image));
}
}

View File

@ -122,21 +122,21 @@ export type PostProcessedImageMetadata = ESRGANMetadata | FacetoolMetadata;
/**
* ResultImage
*/
export type Image = {
name: string;
type: ImageType;
url: string;
thumbnail: string;
metadata: ImageResponseMetadata;
};
// export ty`pe Image = {
// name: string;
// type: ImageType;
// url: string;
// thumbnail: string;
// metadata: ImageResponseMetadata;
// };
export const isInvokeAIImage = (obj: Image | SelectedImage): obj is Image => {
if ('url' in obj && 'thumbnail' in obj) {
return true;
}
// export const isInvokeAIImage = (obj: Image | SelectedImage): obj is Image => {
// if ('url' in obj && 'thumbnail' in obj) {
// return true;
// }
return false;
};
// return false;
// };
/**
* Types related to the system status.
@ -346,7 +346,6 @@ export type AppConfig = {
/**
* Whether or not we need to re-fetch images
*/
shouldFetchImages: boolean;
disabledTabs: InvokeTabName[];
disabledFeatures: AppFeature[];
disabledSDFeatures: SDFeature[];

View File

@ -1,10 +1,10 @@
import { Badge, Flex } from '@chakra-ui/react';
import { Image } from 'app/types/invokeai';
import { isNumber, isString } from 'lodash-es';
import { useMemo } from 'react';
import { ImageDTO } from 'services/api';
type ImageMetadataOverlayProps = {
image: Image;
image: ImageDTO;
};
const ImageMetadataOverlay = ({ image }: ImageMetadataOverlayProps) => {
@ -17,11 +17,11 @@ const ImageMetadataOverlay = ({ image }: ImageMetadataOverlayProps) => {
}, [image.metadata]);
const model = useMemo(() => {
if (!isString(image.metadata?.invokeai?.node?.model)) {
if (!isString(image.metadata?.model)) {
return;
}
return image.metadata?.invokeai?.node?.model;
return image.metadata?.model;
}, [image.metadata]);
return (

View File

@ -0,0 +1,12 @@
/**
* Comparator function for sorting dates in ascending order
*/
export const dateComparator = (a: string, b: string) => {
const dateA = new Date(a);
const dateB = new Date(b);
// sort in ascending order
if (dateA > dateB) return 1;
if (dateA < dateB) return -1;
return 0;
};

View File

@ -46,7 +46,7 @@ const IAICanvasObjectRenderer = () => {
key={i}
x={obj.x}
y={obj.y}
url={getUrl(obj.image.url)}
url={getUrl(obj.image.image_url)}
/>
);
} else if (isCanvasBaseLine(obj)) {

View File

@ -62,7 +62,7 @@ const IAICanvasStagingArea = (props: Props) => {
<Group {...rest}>
{shouldShowStagingImage && currentStagingAreaImage && (
<IAICanvasImage
url={getUrl(currentStagingAreaImage.image.url)}
url={getUrl(currentStagingAreaImage.image.image_url)}
x={x}
y={y}
/>

View File

@ -157,17 +157,19 @@ const IAICanvasStagingAreaToolbar = () => {
}
colorScheme="accent"
/>
<IAIIconButton
{/* <IAIIconButton
tooltip={t('unifiedCanvas.saveToGallery')}
aria-label={t('unifiedCanvas.saveToGallery')}
icon={<FaSave />}
onClick={() =>
dispatch(
saveStagingAreaImageToGallery(currentStagingAreaImage.image.url)
saveStagingAreaImageToGallery(
currentStagingAreaImage.image.image_url
)
)
}
colorScheme="accent"
/>
/> */}
<IAIIconButton
tooltip={t('unifiedCanvas.discardAll')}
aria-label={t('unifiedCanvas.discardAll')}

View File

@ -1,6 +1,5 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/types/invokeai';
import {
roundDownToMultiple,
roundToMultiple,
@ -29,6 +28,7 @@ import {
isCanvasBaseImage,
isCanvasMaskLine,
} from './canvasTypes';
import { ImageDTO } from 'services/api';
export const initialLayerState: CanvasLayerState = {
objects: [],
@ -157,9 +157,9 @@ export const canvasSlice = createSlice({
setCursorPosition: (state, action: PayloadAction<Vector2d | null>) => {
state.cursorPosition = action.payload;
},
setInitialCanvasImage: (state, action: PayloadAction<InvokeAI.Image>) => {
setInitialCanvasImage: (state, action: PayloadAction<ImageDTO>) => {
const image = action.payload;
const { width, height } = image.metadata;
const { width, height } = image;
const { stageDimensions } = state;
const newBoundingBoxDimensions = {
@ -302,7 +302,7 @@ export const canvasSlice = createSlice({
selectedImageIndex: -1,
};
},
addImageToStagingArea: (state, action: PayloadAction<InvokeAI.Image>) => {
addImageToStagingArea: (state, action: PayloadAction<ImageDTO>) => {
const image = action.payload;
if (!image || !state.layerState.stagingArea.boundingBox) {

View File

@ -1,6 +1,7 @@
import * as InvokeAI from 'app/types/invokeai';
import { IRect, Vector2d } from 'konva/lib/types';
import { RgbaColor } from 'react-colorful';
import { ImageDTO } from 'services/api';
export const LAYER_NAMES_DICT = [
{ key: 'Base', value: 'base' },
@ -37,7 +38,7 @@ export type CanvasImage = {
y: number;
width: number;
height: number;
image: InvokeAI.Image;
image: ImageDTO;
};
export type CanvasMaskLine = {

View File

@ -195,14 +195,14 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
}
if (shouldTransformUrls) {
return getUrl(image.url);
return getUrl(image.image_url);
}
if (image.url.startsWith('http')) {
return image.url;
if (image.image_url.startsWith('http')) {
return image.image_url;
}
return window.location.toString() + image.url;
return window.location.toString() + image.image_url;
};
const url = getImageUrl();

View File

@ -61,8 +61,8 @@ const CurrentImagePreview = () => {
if (!image) {
return;
}
e.dataTransfer.setData('invokeai/imageName', image.name);
e.dataTransfer.setData('invokeai/imageType', image.type);
e.dataTransfer.setData('invokeai/imageName', image.image_name);
e.dataTransfer.setData('invokeai/imageType', image.image_type);
e.dataTransfer.effectAllowed = 'move';
},
[image]
@ -108,7 +108,7 @@ const CurrentImagePreview = () => {
image && (
<>
<Image
src={getUrl(image.url)}
src={getUrl(image.image_url)}
fallbackStrategy="beforeLoadOrError"
fallback={<ImageFallbackSpinner />}
onDragStart={handleDragStart}

View File

@ -13,7 +13,6 @@ import { DragEvent, MouseEvent, memo, useCallback, useState } from 'react';
import { FaCheck, FaExpand, FaImage, FaShare, FaTrash } from 'react-icons/fa';
import DeleteImageModal from './DeleteImageModal';
import { ContextMenu } from 'chakra-ui-contextmenu';
import * as InvokeAI from 'app/types/invokeai';
import {
resizeAndScaleCanvas,
setInitialCanvasImage,
@ -39,6 +38,7 @@ import {
sentImageToImg2Img,
} from '../store/actions';
import { useAppToaster } from 'app/components/Toaster';
import { ImageDTO } from 'services/api';
export const selector = createSelector(
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
@ -70,14 +70,16 @@ export const selector = createSelector(
);
interface HoverableImageProps {
image: InvokeAI.Image;
image: ImageDTO;
isSelected: boolean;
}
const memoEqualityCheck = (
prev: HoverableImageProps,
next: HoverableImageProps
) => prev.image.name === next.image.name && prev.isSelected === next.isSelected;
) =>
prev.image.image_name === next.image.image_name &&
prev.isSelected === next.isSelected;
/**
* Gallery image component with delete/use all/use seed buttons on hover.
@ -100,7 +102,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
} = useDisclosure();
const { image, isSelected } = props;
const { url, thumbnail, name } = image;
const { image_url, thumbnail_url, image_name } = image;
const { getUrl } = useGetUrl();
const [isHovered, setIsHovered] = useState<boolean>(false);
@ -144,8 +146,8 @@ const HoverableImage = memo((props: HoverableImageProps) => {
const handleDragStart = useCallback(
(e: DragEvent<HTMLDivElement>) => {
e.dataTransfer.setData('invokeai/imageName', image.name);
e.dataTransfer.setData('invokeai/imageType', image.type);
e.dataTransfer.setData('invokeai/imageName', image.image_name);
e.dataTransfer.setData('invokeai/imageType', image.image_type);
e.dataTransfer.effectAllowed = 'move';
},
[image]
@ -153,11 +155,11 @@ const HoverableImage = memo((props: HoverableImageProps) => {
// Recall parameters handlers
const handleRecallPrompt = useCallback(() => {
recallPrompt(image.metadata?.invokeai?.node?.prompt);
recallPrompt(image.metadata?.positive_conditioning);
}, [image, recallPrompt]);
const handleRecallSeed = useCallback(() => {
recallSeed(image.metadata.invokeai?.node?.seed);
recallSeed(image.metadata?.seed);
}, [image, recallSeed]);
const handleSendToImageToImage = useCallback(() => {
@ -165,9 +167,9 @@ const HoverableImage = memo((props: HoverableImageProps) => {
dispatch(initialImageSelected(image));
}, [dispatch, image]);
const handleRecallInitialImage = useCallback(() => {
recallInitialImage(image.metadata.invokeai?.node?.image);
}, [image, recallInitialImage]);
// const handleRecallInitialImage = useCallback(() => {
// recallInitialImage(image.metadata.invokeai?.node?.image);
// }, [image, recallInitialImage]);
/**
* TODO: the rest of these
@ -200,7 +202,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
};
const handleOpenInNewTab = () => {
window.open(getUrl(image.url), '_blank');
window.open(getUrl(image.image_url), '_blank');
};
return (
@ -223,7 +225,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
<MenuItem
icon={<IoArrowUndoCircleOutline />}
onClickCapture={handleRecallPrompt}
isDisabled={image?.metadata?.invokeai?.node?.prompt === undefined}
isDisabled={image?.metadata?.positive_conditioning === undefined}
>
{t('parameters.usePrompt')}
</MenuItem>
@ -231,23 +233,23 @@ const HoverableImage = memo((props: HoverableImageProps) => {
<MenuItem
icon={<IoArrowUndoCircleOutline />}
onClickCapture={handleRecallSeed}
isDisabled={image?.metadata?.invokeai?.node?.seed === undefined}
isDisabled={image?.metadata?.seed === undefined}
>
{t('parameters.useSeed')}
</MenuItem>
<MenuItem
{/* <MenuItem
icon={<IoArrowUndoCircleOutline />}
onClickCapture={handleRecallInitialImage}
isDisabled={image?.metadata?.invokeai?.node?.type !== 'img2img'}
isDisabled={image?.metadata?.type !== 'img2img'}
>
{t('parameters.useInitImg')}
</MenuItem>
</MenuItem> */}
<MenuItem
icon={<IoArrowUndoCircleOutline />}
onClickCapture={handleUseAllParameters}
isDisabled={
!['txt2img', 'img2img', 'inpaint'].includes(
String(image?.metadata?.invokeai?.node?.type)
String(image?.metadata?.type)
)
}
>
@ -278,7 +280,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
{(ref) => (
<Box
position="relative"
key={name}
key={image_name}
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
userSelect="none"
@ -303,7 +305,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
shouldUseSingleGalleryColumn ? 'contain' : galleryImageObjectFit
}
rounded="md"
src={getUrl(thumbnail || url)}
src={getUrl(thumbnail_url || image_url)}
fallback={<FaImage />}
sx={{
width: '100%',

View File

@ -12,7 +12,7 @@ import { memo, useCallback } from 'react';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import DeleteImageModal from '../DeleteImageModal';
import { requestedImageDeletion } from 'features/gallery/store/actions';
import { Image } from 'app/types/invokeai';
import { ImageDTO } from 'services/api';
const selector = createSelector(
[systemSelector],
@ -30,7 +30,7 @@ const selector = createSelector(
);
type DeleteImageButtonProps = {
image: Image | undefined;
image: ImageDTO | undefined;
};
const DeleteImageButton = (props: DeleteImageButtonProps) => {

View File

@ -5,7 +5,6 @@ import {
FlexProps,
Grid,
Icon,
Image,
Text,
forwardRef,
} from '@chakra-ui/react';
@ -51,10 +50,10 @@ import { uploadsAdapter } from '../store/uploadsSlice';
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store/store';
import { Virtuoso, VirtuosoGrid } from 'react-virtuoso';
import { Image as ImageType } from 'app/types/invokeai';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import GalleryProgressImage from './GalleryProgressImage';
import { uiSelector } from 'features/ui/store/uiSelectors';
import { ImageDTO } from 'services/api';
const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 290;
const PROGRESS_IMAGE_PLACEHOLDER = 'PROGRESS_IMAGE_PLACEHOLDER';
@ -66,7 +65,7 @@ const categorySelector = createSelector(
const { currentCategory } = gallery;
if (currentCategory === 'results') {
const tempImages: (ImageType | typeof PROGRESS_IMAGE_PLACEHOLDER)[] = [];
const tempImages: (ImageDTO | typeof PROGRESS_IMAGE_PLACEHOLDER)[] = [];
if (system.progressImage) {
tempImages.push(PROGRESS_IMAGE_PLACEHOLDER);
@ -352,7 +351,7 @@ const ImageGalleryContent = () => {
const isSelected =
image === PROGRESS_IMAGE_PLACEHOLDER
? false
: selectedImage?.name === image?.name;
: selectedImage?.image_name === image?.image_name;
return (
<Flex sx={{ pb: 2 }}>
@ -362,7 +361,7 @@ const ImageGalleryContent = () => {
/>
) : (
<HoverableImage
key={`${image.name}-${image.thumbnail}`}
key={`${image.image_name}-${image.thumbnail_url}`}
image={image}
isSelected={isSelected}
/>
@ -385,13 +384,13 @@ const ImageGalleryContent = () => {
const isSelected =
image === PROGRESS_IMAGE_PLACEHOLDER
? false
: selectedImage?.name === image?.name;
: selectedImage?.image_name === image?.image_name;
return image === PROGRESS_IMAGE_PLACEHOLDER ? (
<GalleryProgressImage key={PROGRESS_IMAGE_PLACEHOLDER} />
) : (
<HoverableImage
key={`${image.name}-${image.thumbnail}`}
key={`${image.image_name}-${image.thumbnail_url}`}
image={image}
isSelected={isSelected}
/>

View File

@ -18,7 +18,9 @@ import {
setCfgScale,
setHeight,
setImg2imgStrength,
setNegativePrompt,
setPerlin,
setPositivePrompt,
setScheduler,
setSeamless,
setSeed,
@ -36,6 +38,9 @@ import { useTranslation } from 'react-i18next';
import { FaCopy } from 'react-icons/fa';
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import { ImageDTO } from 'services/api';
import { filter } from 'lodash-es';
import { Scheduler } from 'app/constants';
type MetadataItemProps = {
isLink?: boolean;
@ -58,7 +63,6 @@ const MetadataItem = ({
withCopy = false,
}: MetadataItemProps) => {
const { t } = useTranslation();
return (
<Flex gap={2}>
{onClick && (
@ -104,14 +108,14 @@ const MetadataItem = ({
};
type ImageMetadataViewerProps = {
image: InvokeAI.Image;
image: ImageDTO;
};
// TODO: I don't know if this is needed.
const memoEqualityCheck = (
prev: ImageMetadataViewerProps,
next: ImageMetadataViewerProps
) => prev.image.name === next.image.name;
) => prev.image.image_name === next.image.image_name;
// TODO: Show more interesting information in this component.
@ -128,8 +132,9 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
dispatch(setShouldShowImageDetails(false));
});
const sessionId = image.metadata.invokeai?.session_id;
const node = image.metadata.invokeai?.node as Record<string, any>;
const sessionId = image?.session_id;
const metadata = image?.metadata;
const { t } = useTranslation();
const { getUrl } = useGetUrl();
@ -154,110 +159,131 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
>
<Flex gap={2}>
<Text fontWeight="semibold">File:</Text>
<Link href={getUrl(image.url)} isExternal maxW="calc(100% - 3rem)">
{image.url.length > 64
? image.url.substring(0, 64).concat('...')
: image.url}
<Link
href={getUrl(image.image_url)}
isExternal
maxW="calc(100% - 3rem)"
>
{image.image_name}
<ExternalLinkIcon mx="2px" />
</Link>
</Flex>
{node && Object.keys(node).length > 0 ? (
{metadata && Object.keys(metadata).length > 0 ? (
<>
{node.type && (
<MetadataItem label="Invocation type" value={node.type} />
{metadata.type && (
<MetadataItem label="Invocation type" value={metadata.type} />
)}
{node.model && <MetadataItem label="Model" value={node.model} />}
{node.prompt && (
{metadata.width && (
<MetadataItem
label="Width"
value={metadata.width}
onClick={() => dispatch(setWidth(Number(metadata.width)))}
/>
)}
{metadata.height && (
<MetadataItem
label="Height"
value={metadata.height}
onClick={() => dispatch(setHeight(Number(metadata.height)))}
/>
)}
{metadata.model && (
<MetadataItem label="Model" value={metadata.model} />
)}
{metadata.positive_conditioning && (
<MetadataItem
label="Prompt"
labelPosition="top"
value={
typeof node.prompt === 'string'
? node.prompt
: promptToString(node.prompt)
typeof metadata.positive_conditioning === 'string'
? metadata.positive_conditioning
: promptToString(metadata.positive_conditioning)
}
onClick={() => setBothPrompts(node.prompt)}
onClick={() => setPositivePrompt(metadata.positive_conditioning!)}
/>
)}
{node.seed !== undefined && (
{metadata.negative_conditioning && (
<MetadataItem
label="Prompt"
labelPosition="top"
value={
typeof metadata.negative_conditioning === 'string'
? metadata.negative_conditioning
: promptToString(metadata.negative_conditioning)
}
onClick={() => setNegativePrompt(metadata.negative_conditioning!)}
/>
)}
{metadata.seed !== undefined && (
<MetadataItem
label="Seed"
value={node.seed}
onClick={() => dispatch(setSeed(Number(node.seed)))}
value={metadata.seed}
onClick={() => dispatch(setSeed(Number(metadata.seed)))}
/>
)}
{node.threshold !== undefined && (
{/* {metadata.threshold !== undefined && (
<MetadataItem
label="Noise Threshold"
value={node.threshold}
onClick={() => dispatch(setThreshold(Number(node.threshold)))}
value={metadata.threshold}
onClick={() => dispatch(setThreshold(Number(metadata.threshold)))}
/>
)}
{node.perlin !== undefined && (
{metadata.perlin !== undefined && (
<MetadataItem
label="Perlin Noise"
value={node.perlin}
onClick={() => dispatch(setPerlin(Number(node.perlin)))}
value={metadata.perlin}
onClick={() => dispatch(setPerlin(Number(metadata.perlin)))}
/>
)}
{node.scheduler && (
)} */}
{metadata.scheduler && (
<MetadataItem
label="Scheduler"
value={node.scheduler}
onClick={() => dispatch(setScheduler(node.scheduler))}
/>
)}
{node.steps && (
<MetadataItem
label="Steps"
value={node.steps}
onClick={() => dispatch(setSteps(Number(node.steps)))}
/>
)}
{node.cfg_scale !== undefined && (
<MetadataItem
label="CFG scale"
value={node.cfg_scale}
onClick={() => dispatch(setCfgScale(Number(node.cfg_scale)))}
/>
)}
{node.variations && node.variations.length > 0 && (
<MetadataItem
label="Seed-weight pairs"
value={seedWeightsToString(node.variations)}
value={metadata.scheduler}
onClick={() =>
dispatch(setSeedWeights(seedWeightsToString(node.variations)))
dispatch(setScheduler(metadata.scheduler as Scheduler))
}
/>
)}
{node.seamless && (
{metadata.steps && (
<MetadataItem
label="Steps"
value={metadata.steps}
onClick={() => dispatch(setSteps(Number(metadata.steps)))}
/>
)}
{metadata.cfg_scale !== undefined && (
<MetadataItem
label="CFG scale"
value={metadata.cfg_scale}
onClick={() => dispatch(setCfgScale(Number(metadata.cfg_scale)))}
/>
)}
{/* {metadata.variations && metadata.variations.length > 0 && (
<MetadataItem
label="Seed-weight pairs"
value={seedWeightsToString(metadata.variations)}
onClick={() =>
dispatch(
setSeedWeights(seedWeightsToString(metadata.variations))
)
}
/>
)}
{metadata.seamless && (
<MetadataItem
label="Seamless"
value={node.seamless}
onClick={() => dispatch(setSeamless(node.seamless))}
value={metadata.seamless}
onClick={() => dispatch(setSeamless(metadata.seamless))}
/>
)}
{node.hires_fix && (
{metadata.hires_fix && (
<MetadataItem
label="High Resolution Optimization"
value={node.hires_fix}
onClick={() => dispatch(setHiresFix(node.hires_fix))}
value={metadata.hires_fix}
onClick={() => dispatch(setHiresFix(metadata.hires_fix))}
/>
)}
{node.width && (
<MetadataItem
label="Width"
value={node.width}
onClick={() => dispatch(setWidth(Number(node.width)))}
/>
)}
{node.height && (
<MetadataItem
label="Height"
value={node.height}
onClick={() => dispatch(setHeight(Number(node.height)))}
/>
)}
)} */}
{/* {init_image_path && (
<MetadataItem
label="Initial image"
@ -266,22 +292,22 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
onClick={() => dispatch(setInitialImage(init_image_path))}
/>
)} */}
{node.strength && (
{metadata.strength && (
<MetadataItem
label="Image to image strength"
value={node.strength}
value={metadata.strength}
onClick={() =>
dispatch(setImg2imgStrength(Number(node.strength)))
dispatch(setImg2imgStrength(Number(metadata.strength)))
}
/>
)}
{node.fit && (
{/* {metadata.fit && (
<MetadataItem
label="Image to image fit"
value={node.fit}
onClick={() => dispatch(setShouldFitToWidthHeight(node.fit))}
value={metadata.fit}
onClick={() => dispatch(setShouldFitToWidthHeight(metadata.fit))}
/>
)}
)} */}
</>
) : (
<Center width="100%" pt={10}>

View File

@ -1,470 +0,0 @@
import { ExternalLinkIcon } from '@chakra-ui/icons';
import {
Box,
Center,
Flex,
Heading,
IconButton,
Link,
Text,
Tooltip,
} from '@chakra-ui/react';
import * as InvokeAI from 'app/types/invokeai';
import { useAppDispatch } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl';
import promptToString from 'common/util/promptToString';
import { seedWeightsToString } from 'common/util/seedWeightPairs';
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
import {
setCfgScale,
setHeight,
setImg2imgStrength,
// setInitialImage,
setMaskPath,
setPerlin,
setSampler,
setSeamless,
setSeed,
setSeedWeights,
setShouldFitToWidthHeight,
setSteps,
setThreshold,
setWidth,
} from 'features/parameters/store/generationSlice';
import {
setCodeformerFidelity,
setFacetoolStrength,
setFacetoolType,
setHiresFix,
setUpscalingDenoising,
setUpscalingLevel,
setUpscalingStrength,
} from 'features/parameters/store/postprocessingSlice';
import { setShouldShowImageDetails } from 'features/ui/store/uiSlice';
import { memo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaCopy } from 'react-icons/fa';
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
import * as png from '@stevebel/png';
type MetadataItemProps = {
isLink?: boolean;
label: string;
onClick?: () => void;
value: number | string | boolean;
labelPosition?: string;
withCopy?: boolean;
};
/**
* Component to display an individual metadata item or parameter.
*/
const MetadataItem = ({
label,
value,
onClick,
isLink,
labelPosition,
withCopy = false,
}: MetadataItemProps) => {
const { t } = useTranslation();
return (
<Flex gap={2}>
{onClick && (
<Tooltip label={`Recall ${label}`}>
<IconButton
aria-label={t('accessibility.useThisParameter')}
icon={<IoArrowUndoCircleOutline />}
size="xs"
variant="ghost"
fontSize={20}
onClick={onClick}
/>
</Tooltip>
)}
{withCopy && (
<Tooltip label={`Copy ${label}`}>
<IconButton
aria-label={`Copy ${label}`}
icon={<FaCopy />}
size="xs"
variant="ghost"
fontSize={14}
onClick={() => navigator.clipboard.writeText(value.toString())}
/>
</Tooltip>
)}
<Flex direction={labelPosition ? 'column' : 'row'}>
<Text fontWeight="semibold" whiteSpace="pre-wrap" pr={2}>
{label}:
</Text>
{isLink ? (
<Link href={value.toString()} isExternal wordBreak="break-all">
{value.toString()} <ExternalLinkIcon mx="2px" />
</Link>
) : (
<Text overflowY="scroll" wordBreak="break-all">
{value.toString()}
</Text>
)}
</Flex>
</Flex>
);
};
type ImageMetadataViewerProps = {
image: InvokeAI.Image;
};
// TODO: I don't know if this is needed.
const memoEqualityCheck = (
prev: ImageMetadataViewerProps,
next: ImageMetadataViewerProps
) => prev.image.name === next.image.name;
// TODO: Show more interesting information in this component.
/**
* Image metadata viewer overlays currently selected image and provides
* access to any of its metadata for use in processing.
*/
const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
const dispatch = useAppDispatch();
const setBothPrompts = useSetBothPrompts();
useHotkeys('esc', () => {
dispatch(setShouldShowImageDetails(false));
});
const metadata = image?.metadata.sd_metadata || {};
const dreamPrompt = image?.metadata.sd_metadata?.dreamPrompt;
const {
cfg_scale,
fit,
height,
hires_fix,
init_image_path,
mask_image_path,
orig_path,
perlin,
postprocessing,
prompt,
sampler,
seamless,
seed,
steps,
strength,
threshold,
type,
variations,
width,
model_weights,
} = metadata;
const { t } = useTranslation();
const { getUrl } = useGetUrl();
const metadataJSON = JSON.stringify(image, null, 2);
// fetch(getUrl(image.url))
// .then((r) => r.arrayBuffer())
// .then((buffer) => {
// const { text } = png.decode(buffer);
// const metadata = text?.['sd-metadata']
// ? JSON.parse(text['sd-metadata'] ?? {})
// : {};
// console.log(metadata);
// });
return (
<Flex
sx={{
padding: 4,
gap: 1,
flexDirection: 'column',
width: 'full',
height: 'full',
backdropFilter: 'blur(20px)',
bg: 'whiteAlpha.600',
_dark: {
bg: 'blackAlpha.600',
},
}}
>
<Flex gap={2}>
<Text fontWeight="semibold">File:</Text>
<Link href={getUrl(image.url)} isExternal maxW="calc(100% - 3rem)">
{image.url.length > 64
? image.url.substring(0, 64).concat('...')
: image.url}
<ExternalLinkIcon mx="2px" />
</Link>
</Flex>
<Flex gap={2} direction="column">
<Flex gap={2}>
<Tooltip label="Copy metadata JSON">
<IconButton
aria-label={t('accessibility.copyMetadataJson')}
icon={<FaCopy />}
size="xs"
variant="ghost"
fontSize={14}
onClick={() => navigator.clipboard.writeText(metadataJSON)}
/>
</Tooltip>
<Text fontWeight="semibold">Metadata JSON:</Text>
</Flex>
<Box
sx={{
mt: 0,
mr: 2,
mb: 4,
ml: 2,
padding: 4,
borderRadius: 'base',
overflowX: 'scroll',
wordBreak: 'break-all',
bg: 'whiteAlpha.500',
_dark: { bg: 'blackAlpha.500' },
}}
>
<pre>{metadataJSON}</pre>
</Box>
</Flex>
{Object.keys(metadata).length > 0 ? (
<>
{type && <MetadataItem label="Generation type" value={type} />}
{model_weights && (
<MetadataItem label="Model" value={model_weights} />
)}
{['esrgan', 'gfpgan'].includes(type) && (
<MetadataItem label="Original image" value={orig_path} />
)}
{prompt && (
<MetadataItem
label="Prompt"
labelPosition="top"
value={
typeof prompt === 'string' ? prompt : promptToString(prompt)
}
onClick={() => setBothPrompts(prompt)}
/>
)}
{seed !== undefined && (
<MetadataItem
label="Seed"
value={seed}
onClick={() => dispatch(setSeed(seed))}
/>
)}
{threshold !== undefined && (
<MetadataItem
label="Noise Threshold"
value={threshold}
onClick={() => dispatch(setThreshold(threshold))}
/>
)}
{perlin !== undefined && (
<MetadataItem
label="Perlin Noise"
value={perlin}
onClick={() => dispatch(setPerlin(perlin))}
/>
)}
{sampler && (
<MetadataItem
label="Sampler"
value={sampler}
onClick={() => dispatch(setSampler(sampler))}
/>
)}
{steps && (
<MetadataItem
label="Steps"
value={steps}
onClick={() => dispatch(setSteps(steps))}
/>
)}
{cfg_scale !== undefined && (
<MetadataItem
label="CFG scale"
value={cfg_scale}
onClick={() => dispatch(setCfgScale(cfg_scale))}
/>
)}
{variations && variations.length > 0 && (
<MetadataItem
label="Seed-weight pairs"
value={seedWeightsToString(variations)}
onClick={() =>
dispatch(setSeedWeights(seedWeightsToString(variations)))
}
/>
)}
{seamless && (
<MetadataItem
label="Seamless"
value={seamless}
onClick={() => dispatch(setSeamless(seamless))}
/>
)}
{hires_fix && (
<MetadataItem
label="High Resolution Optimization"
value={hires_fix}
onClick={() => dispatch(setHiresFix(hires_fix))}
/>
)}
{width && (
<MetadataItem
label="Width"
value={width}
onClick={() => dispatch(setWidth(width))}
/>
)}
{height && (
<MetadataItem
label="Height"
value={height}
onClick={() => dispatch(setHeight(height))}
/>
)}
{/* {init_image_path && (
<MetadataItem
label="Initial image"
value={init_image_path}
isLink
onClick={() => dispatch(setInitialImage(init_image_path))}
/>
)} */}
{mask_image_path && (
<MetadataItem
label="Mask image"
value={mask_image_path}
isLink
onClick={() => dispatch(setMaskPath(mask_image_path))}
/>
)}
{type === 'img2img' && strength && (
<MetadataItem
label="Image to image strength"
value={strength}
onClick={() => dispatch(setImg2imgStrength(strength))}
/>
)}
{fit && (
<MetadataItem
label="Image to image fit"
value={fit}
onClick={() => dispatch(setShouldFitToWidthHeight(fit))}
/>
)}
{postprocessing && postprocessing.length > 0 && (
<>
<Heading size="sm">Postprocessing</Heading>
{postprocessing.map(
(
postprocess: InvokeAI.PostProcessedImageMetadata,
i: number
) => {
if (postprocess.type === 'esrgan') {
const { scale, strength, denoise_str } = postprocess;
return (
<Flex key={i} pl={8} gap={1} direction="column">
<Text size="md">{`${i + 1}: Upscale (ESRGAN)`}</Text>
<MetadataItem
label="Scale"
value={scale}
onClick={() => dispatch(setUpscalingLevel(scale))}
/>
<MetadataItem
label="Strength"
value={strength}
onClick={() =>
dispatch(setUpscalingStrength(strength))
}
/>
{denoise_str !== undefined && (
<MetadataItem
label="Denoising strength"
value={denoise_str}
onClick={() =>
dispatch(setUpscalingDenoising(denoise_str))
}
/>
)}
</Flex>
);
} else if (postprocess.type === 'gfpgan') {
const { strength } = postprocess;
return (
<Flex key={i} pl={8} gap={1} direction="column">
<Text size="md">{`${
i + 1
}: Face restoration (GFPGAN)`}</Text>
<MetadataItem
label="Strength"
value={strength}
onClick={() => {
dispatch(setFacetoolStrength(strength));
dispatch(setFacetoolType('gfpgan'));
}}
/>
</Flex>
);
} else if (postprocess.type === 'codeformer') {
const { strength, fidelity } = postprocess;
return (
<Flex key={i} pl={8} gap={1} direction="column">
<Text size="md">{`${
i + 1
}: Face restoration (Codeformer)`}</Text>
<MetadataItem
label="Strength"
value={strength}
onClick={() => {
dispatch(setFacetoolStrength(strength));
dispatch(setFacetoolType('codeformer'));
}}
/>
{fidelity && (
<MetadataItem
label="Fidelity"
value={fidelity}
onClick={() => {
dispatch(setCodeformerFidelity(fidelity));
dispatch(setFacetoolType('codeformer'));
}}
/>
)}
</Flex>
);
}
}
)}
</>
)}
{dreamPrompt && (
<MetadataItem withCopy label="Dream Prompt" value={dreamPrompt} />
)}
</>
) : (
<Center width="100%" pt={10}>
<Text fontSize="lg" fontWeight="semibold">
No metadata available
</Text>
</Center>
)}
</Flex>
);
}, memoEqualityCheck);
ImageMetadataViewer.displayName = 'ImageMetadataViewer';
export default ImageMetadataViewer;

View File

@ -13,11 +13,9 @@ const useGetImageByNameSelector = createSelector(
const useGetImageByNameAndType = () => {
const { allResults, allUploads } = useAppSelector(useGetImageByNameSelector);
return (name: string, type: ImageType) => {
if (type === 'results') {
const resultImagesResult = allResults[name];
if (resultImagesResult) {
return resultImagesResult;
}

View File

@ -1,9 +1,9 @@
import { createAction } from '@reduxjs/toolkit';
import { Image } from 'app/types/invokeai';
import { SelectedImage } from 'features/parameters/store/actions';
import { ImageNameAndType } from 'features/parameters/store/actions';
import { ImageDTO } from 'services/api';
export const requestedImageDeletion = createAction<
Image | SelectedImage | undefined
ImageDTO | ImageNameAndType | undefined
>('gallery/requestedImageDeletion');
export const sentImageToCanvas = createAction('gallery/sentImageToCanvas');

View File

@ -1,16 +1,15 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import { Image } from 'app/types/invokeai';
import { imageReceived, thumbnailReceived } from 'services/thunks/image';
import {
receivedResultImagesPage,
receivedUploadImagesPage,
} from '../../../services/thunks/gallery';
import { ImageDTO } from 'services/api';
type GalleryImageObjectFitType = 'contain' | 'cover';
export interface GalleryState {
selectedImage?: Image;
selectedImage?: ImageDTO;
galleryImageMinimumWidth: number;
galleryImageObjectFit: GalleryImageObjectFitType;
shouldAutoSwitchToNewImages: boolean;
@ -30,7 +29,7 @@ export const gallerySlice = createSlice({
name: 'gallery',
initialState: initialGalleryState,
reducers: {
imageSelected: (state, action: PayloadAction<Image | undefined>) => {
imageSelected: (state, action: PayloadAction<ImageDTO | undefined>) => {
state.selectedImage = action.payload;
// TODO: if the user selects an image, disable the auto switch?
// state.shouldAutoSwitchToNewImages = false;
@ -61,37 +60,18 @@ export const gallerySlice = createSlice({
},
},
extraReducers(builder) {
builder.addCase(imageReceived.fulfilled, (state, action) => {
// When we get an updated URL for an image, we need to update the selectedImage in gallery,
// which is currently its own object (instead of a reference to an image in results/uploads)
const { imagePath } = action.payload;
const { imageName } = action.meta.arg;
if (state.selectedImage?.name === imageName) {
state.selectedImage.url = imagePath;
}
});
builder.addCase(thumbnailReceived.fulfilled, (state, action) => {
// When we get an updated URL for an image, we need to update the selectedImage in gallery,
// which is currently its own object (instead of a reference to an image in results/uploads)
const { thumbnailPath } = action.payload;
const { thumbnailName } = action.meta.arg;
if (state.selectedImage?.name === thumbnailName) {
state.selectedImage.thumbnail = thumbnailPath;
}
});
builder.addCase(receivedResultImagesPage.fulfilled, (state, action) => {
// rehydrate selectedImage URL when results list comes in
// solves case when outdated URL is in local storage
const selectedImage = state.selectedImage;
if (selectedImage) {
const selectedImageInResults = action.payload.items.find(
(image) => image.image_name === selectedImage.name
(image) => image.image_name === selectedImage.image_name
);
if (selectedImageInResults) {
selectedImage.url = selectedImageInResults.image_url;
selectedImage.image_url = selectedImageInResults.image_url;
selectedImage.thumbnail_url = selectedImageInResults.thumbnail_url;
state.selectedImage = selectedImage;
}
}
@ -102,10 +82,12 @@ export const gallerySlice = createSlice({
const selectedImage = state.selectedImage;
if (selectedImage) {
const selectedImageInResults = action.payload.items.find(
(image) => image.image_name === selectedImage.name
(image) => image.image_name === selectedImage.image_name
);
if (selectedImageInResults) {
selectedImage.url = selectedImageInResults.image_url;
selectedImage.image_url = selectedImageInResults.image_url;
selectedImage.thumbnail_url = selectedImageInResults.thumbnail_url;
state.selectedImage = selectedImage;
}
}

View File

@ -1,21 +1,24 @@
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
import { Image } from 'app/types/invokeai';
import { RootState } from 'app/store/store';
import {
receivedResultImagesPage,
IMAGES_PER_PAGE,
} from 'services/thunks/gallery';
import { deserializeImageResponse } from 'services/util/deserializeImageResponse';
import {
imageDeleted,
imageReceived,
thumbnailReceived,
imageMetadataReceived,
imageUrlsReceived,
} from 'services/thunks/image';
import { ImageDTO } from 'services/api';
import { dateComparator } from 'common/util/dateComparator';
export const resultsAdapter = createEntityAdapter<Image>({
selectId: (image) => image.name,
sortComparer: (a, b) => b.metadata.created - a.metadata.created,
export type ResultsImageDTO = Omit<ImageDTO, 'image_type'> & {
image_type: 'results';
};
export const resultsAdapter = createEntityAdapter<ResultsImageDTO>({
selectId: (image) => image.image_name,
sortComparer: (a, b) => dateComparator(b.created_at, a.created_at),
});
type AdditionalResultsState = {
@ -53,13 +56,12 @@ const resultsSlice = createSlice({
* Received Result Images Page - FULFILLED
*/
builder.addCase(receivedResultImagesPage.fulfilled, (state, action) => {
const { items, page, pages } = action.payload;
const { page, pages } = action.payload;
const resultImages = items.map((image) =>
deserializeImageResponse(image)
);
// We know these will all be of the results type, but it's not represented in the API types
const items = action.payload.items as ResultsImageDTO[];
resultsAdapter.setMany(state, resultImages);
resultsAdapter.setMany(state, items);
state.page = page;
state.pages = pages;
@ -68,33 +70,32 @@ const resultsSlice = createSlice({
});
/**
* Image Received - FULFILLED
* Image Metadata Received - FULFILLED
*/
builder.addCase(imageReceived.fulfilled, (state, action) => {
const { imagePath } = action.payload;
const { imageName } = action.meta.arg;
builder.addCase(imageMetadataReceived.fulfilled, (state, action) => {
const { image_type } = action.payload;
resultsAdapter.updateOne(state, {
id: imageName,
changes: {
url: imagePath,
},
});
if (image_type === 'results') {
resultsAdapter.upsertOne(state, action.payload as ResultsImageDTO);
}
});
/**
* Thumbnail Received - FULFILLED
* Image URLs Received - FULFILLED
*/
builder.addCase(thumbnailReceived.fulfilled, (state, action) => {
const { thumbnailPath } = action.payload;
const { thumbnailName } = action.meta.arg;
builder.addCase(imageUrlsReceived.fulfilled, (state, action) => {
const { image_name, image_type, image_url, thumbnail_url } =
action.payload;
resultsAdapter.updateOne(state, {
id: thumbnailName,
changes: {
thumbnail: thumbnailPath,
},
});
if (image_type === 'results') {
resultsAdapter.updateOne(state, {
id: image_name,
changes: {
image_url: image_url,
thumbnail_url: thumbnail_url,
},
});
}
});
/**

View File

@ -1,17 +1,21 @@
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
import { Image } from 'app/types/invokeai';
import { RootState } from 'app/store/store';
import {
receivedUploadImagesPage,
IMAGES_PER_PAGE,
} from 'services/thunks/gallery';
import { imageDeleted } from 'services/thunks/image';
import { deserializeImageResponse } from 'services/util/deserializeImageResponse';
import { imageDeleted, imageUrlsReceived } from 'services/thunks/image';
import { ImageDTO } from 'services/api';
import { dateComparator } from 'common/util/dateComparator';
export const uploadsAdapter = createEntityAdapter<Image>({
selectId: (image) => image.name,
sortComparer: (a, b) => b.metadata.created - a.metadata.created,
export type UploadsImageDTO = Omit<ImageDTO, 'image_type'> & {
image_type: 'uploads';
};
export const uploadsAdapter = createEntityAdapter<UploadsImageDTO>({
selectId: (image) => image.image_name,
sortComparer: (a, b) => dateComparator(b.created_at, a.created_at),
});
type AdditionalUploadsState = {
@ -49,11 +53,12 @@ const uploadsSlice = createSlice({
* Received Upload Images Page - FULFILLED
*/
builder.addCase(receivedUploadImagesPage.fulfilled, (state, action) => {
const { items, page, pages } = action.payload;
const { page, pages } = action.payload;
const images = items.map((image) => deserializeImageResponse(image));
// We know these will all be of the uploads type, but it's not represented in the API types
const items = action.payload.items as UploadsImageDTO[];
uploadsAdapter.setMany(state, images);
uploadsAdapter.setMany(state, items);
state.page = page;
state.pages = pages;
@ -61,6 +66,24 @@ const uploadsSlice = createSlice({
state.isLoading = false;
});
/**
* Image URLs Received - FULFILLED
*/
builder.addCase(imageUrlsReceived.fulfilled, (state, action) => {
const { image_name, image_type, image_url, thumbnail_url } =
action.payload;
if (image_type === 'uploads') {
uploadsAdapter.updateOne(state, {
id: image_name,
changes: {
image_url: image_url,
thumbnail_url: thumbnail_url,
},
});
}
});
/**
* Delete Image - pending
* Pre-emptively remove the image from the gallery

View File

@ -1,10 +1,10 @@
import * as React from 'react';
import { TransformComponent, useTransformContext } from 'react-zoom-pan-pinch';
import * as InvokeAI from 'app/types/invokeai';
import { useGetUrl } from 'common/util/getUrl';
import { ImageDTO } from 'services/api';
type ReactPanZoomProps = {
image: InvokeAI.Image;
image: ImageDTO;
styleClass?: string;
alt?: string;
ref?: React.Ref<HTMLImageElement>;
@ -37,7 +37,7 @@ export default function ReactPanZoomImage({
transform: `rotate(${rotation}deg) scaleX(${scaleX}) scaleY(${scaleY})`,
width: '100%',
}}
src={getUrl(image.url)}
src={getUrl(image.image_url)}
alt={alt}
ref={ref}
className={styleClass ? styleClass : ''}

View File

@ -21,7 +21,7 @@ const ImageInputFieldComponent = (
const getImageByNameAndType = useGetImageByNameAndType();
const dispatch = useAppDispatch();
const [url, setUrl] = useState<string>();
const [url, setUrl] = useState<string | undefined>(field.value?.image_url);
const { getUrl } = useGetUrl();
const handleDrop = useCallback(
@ -39,16 +39,13 @@ const ImageInputFieldComponent = (
return;
}
setUrl(image.url);
setUrl(image.image_url);
dispatch(
fieldValueChanged({
nodeId,
fieldName: field.name,
value: {
image_name: name,
image_type: type,
},
value: image,
})
);
},

View File

@ -11,7 +11,7 @@ import {
NodeChange,
OnConnectStartParams,
} from 'reactflow';
import { ImageField } from 'services/api';
import { ImageDTO } from 'services/api';
import { receivedOpenAPISchema } from 'services/thunks/schema';
import { InvocationTemplate, InvocationValue } from '../types/types';
import { parseSchema } from '../util/parseSchema';
@ -65,13 +65,7 @@ const nodesSlice = createSlice({
action: PayloadAction<{
nodeId: string;
fieldName: string;
value:
| string
| number
| boolean
| Pick<ImageField, 'image_name' | 'image_type'>
| RgbaColor
| undefined;
value: string | number | boolean | ImageDTO | RgbaColor | undefined;
}>
) => {
const { nodeId, fieldName, value } = action.payload;

View File

@ -1,7 +1,10 @@
import { OpenAPIV3 } from 'openapi-types';
import { RgbaColor } from 'react-colorful';
import { ImageField } from 'services/api';
import { Graph, ImageDTO } from 'services/api';
import { AnyInvocationType } from 'services/events/types';
import { O } from 'ts-toolbelt';
export type NonNullableGraph = O.Required<Graph, 'nodes' | 'edges'>;
export type InvocationValue = {
id: string;
@ -179,7 +182,7 @@ export type ConditioningInputFieldValue = FieldValueBase & {
export type ImageInputFieldValue = FieldValueBase & {
type: 'image';
value?: Pick<ImageField, 'image_name' | 'image_type'>;
value?: ImageDTO;
};
export type ModelInputFieldValue = FieldValueBase & {
@ -245,7 +248,7 @@ export type BooleanInputFieldTemplate = InputFieldTemplateBase & {
};
export type ImageInputFieldTemplate = InputFieldTemplateBase & {
default: Pick<ImageField, 'image_name' | 'image_type'>;
default: ImageDTO;
type: 'image';
};

View File

@ -1,35 +1,131 @@
import { RootState } from 'app/store/store';
import { Graph } from 'services/api';
import { buildImg2ImgNode } from '../nodeBuilders/buildImageToImageNode';
import { buildRangeNode } from '../nodeBuilders/buildRangeNode';
import { buildIterateNode } from '../nodeBuilders/buildIterateNode';
import { buildEdges } from '../edgeBuilders/buildEdges';
import {
CompelInvocation,
Graph,
ImageToLatentsInvocation,
LatentsToImageInvocation,
LatentsToLatentsInvocation,
} from 'services/api';
import { NonNullableGraph } from 'features/nodes/types/types';
import { addNoiseNodes } from '../nodeBuilders/addNoiseNodes';
import { log } from 'app/logging/useLogger';
const moduleLog = log.child({ namespace: 'buildImageToImageGraph' });
const POSITIVE_CONDITIONING = 'positive_conditioning';
const NEGATIVE_CONDITIONING = 'negative_conditioning';
const IMAGE_TO_LATENTS = 'image_to_latents';
const LATENTS_TO_LATENTS = 'latents_to_latents';
const LATENTS_TO_IMAGE = 'latents_to_image';
/**
* Builds the Linear workflow graph.
* Builds the Image to Image tab graph.
*/
export const buildImageToImageGraph = (state: RootState): Graph => {
const baseNode = buildImg2ImgNode(state);
const {
positivePrompt,
negativePrompt,
model,
cfgScale: cfg_scale,
scheduler,
steps,
initialImage,
img2imgStrength: strength,
} = state.generation;
// We always range and iterate nodes, no matter the iteration count
// This is required to provide the correct seeds to the backend engine
const rangeNode = buildRangeNode(state);
const iterateNode = buildIterateNode();
if (!initialImage) {
moduleLog.error('No initial image found in state');
throw new Error('No initial image found in state');
}
// Build the edges for the nodes selected.
const edges = buildEdges(baseNode, rangeNode, iterateNode);
// Assemble!
const graph = {
nodes: {
[rangeNode.id]: rangeNode,
[iterateNode.id]: iterateNode,
[baseNode.id]: baseNode,
},
edges,
let graph: NonNullableGraph = {
nodes: {},
edges: [],
};
// TODO: hires fix requires latent space upscaling; we don't have nodes for this yet
// Create the conditioning, t2l and l2i nodes
const positiveConditioningNode: CompelInvocation = {
id: POSITIVE_CONDITIONING,
type: 'compel',
prompt: positivePrompt,
model,
};
const negativeConditioningNode: CompelInvocation = {
id: NEGATIVE_CONDITIONING,
type: 'compel',
prompt: negativePrompt,
model,
};
const imageToLatentsNode: ImageToLatentsInvocation = {
id: IMAGE_TO_LATENTS,
type: 'i2l',
model,
image: {
image_name: initialImage?.image_name,
image_type: initialImage?.image_type,
},
};
const latentsToLatentsNode: LatentsToLatentsInvocation = {
id: LATENTS_TO_LATENTS,
type: 'l2l',
cfg_scale,
model,
scheduler,
steps,
strength,
};
const latentsToImageNode: LatentsToImageInvocation = {
id: LATENTS_TO_IMAGE,
type: 'l2i',
model,
};
// Add to the graph
graph.nodes[POSITIVE_CONDITIONING] = positiveConditioningNode;
graph.nodes[NEGATIVE_CONDITIONING] = negativeConditioningNode;
graph.nodes[IMAGE_TO_LATENTS] = imageToLatentsNode;
graph.nodes[LATENTS_TO_LATENTS] = latentsToLatentsNode;
graph.nodes[LATENTS_TO_IMAGE] = latentsToImageNode;
// Connect them
graph.edges.push({
source: { node_id: POSITIVE_CONDITIONING, field: 'conditioning' },
destination: {
node_id: LATENTS_TO_LATENTS,
field: 'positive_conditioning',
},
});
graph.edges.push({
source: { node_id: NEGATIVE_CONDITIONING, field: 'conditioning' },
destination: {
node_id: LATENTS_TO_LATENTS,
field: 'negative_conditioning',
},
});
graph.edges.push({
source: { node_id: IMAGE_TO_LATENTS, field: 'latents' },
destination: {
node_id: LATENTS_TO_LATENTS,
field: 'latents',
},
});
graph.edges.push({
source: { node_id: LATENTS_TO_LATENTS, field: 'latents' },
destination: {
node_id: LATENTS_TO_IMAGE,
field: 'latents',
},
});
// Create and add the noise nodes
graph = addNoiseNodes(graph, latentsToLatentsNode.id, state);
return graph;
};

View File

@ -1,35 +1,99 @@
import { RootState } from 'app/store/store';
import { Graph } from 'services/api';
import { buildTxt2ImgNode } from '../nodeBuilders/buildTextToImageNode';
import { buildRangeNode } from '../nodeBuilders/buildRangeNode';
import { buildIterateNode } from '../nodeBuilders/buildIterateNode';
import { buildEdges } from '../edgeBuilders/buildEdges';
import {
CompelInvocation,
Graph,
LatentsToImageInvocation,
TextToLatentsInvocation,
} from 'services/api';
import { NonNullableGraph } from 'features/nodes/types/types';
import { addNoiseNodes } from '../nodeBuilders/addNoiseNodes';
const POSITIVE_CONDITIONING = 'positive_conditioning';
const NEGATIVE_CONDITIONING = 'negative_conditioning';
const TEXT_TO_LATENTS = 'text_to_latents';
const LATENTS_TO_IMAGE = 'latnets_to_image';
/**
* Builds the Linear workflow graph.
* Builds the Text to Image tab graph.
*/
export const buildTextToImageGraph = (state: RootState): Graph => {
const baseNode = buildTxt2ImgNode(state);
const {
positivePrompt,
negativePrompt,
model,
cfgScale: cfg_scale,
scheduler,
steps,
} = state.generation;
// We always range and iterate nodes, no matter the iteration count
// This is required to provide the correct seeds to the backend engine
const rangeNode = buildRangeNode(state);
const iterateNode = buildIterateNode();
// Build the edges for the nodes selected.
const edges = buildEdges(baseNode, rangeNode, iterateNode);
// Assemble!
const graph = {
nodes: {
[rangeNode.id]: rangeNode,
[iterateNode.id]: iterateNode,
[baseNode.id]: baseNode,
},
edges,
let graph: NonNullableGraph = {
nodes: {},
edges: [],
};
// TODO: hires fix requires latent space upscaling; we don't have nodes for this yet
// Create the conditioning, t2l and l2i nodes
const positiveConditioningNode: CompelInvocation = {
id: POSITIVE_CONDITIONING,
type: 'compel',
prompt: positivePrompt,
model,
};
const negativeConditioningNode: CompelInvocation = {
id: NEGATIVE_CONDITIONING,
type: 'compel',
prompt: negativePrompt,
model,
};
const textToLatentsNode: TextToLatentsInvocation = {
id: TEXT_TO_LATENTS,
type: 't2l',
cfg_scale,
model,
scheduler,
steps,
};
const latentsToImageNode: LatentsToImageInvocation = {
id: LATENTS_TO_IMAGE,
type: 'l2i',
model,
};
// Add to the graph
graph.nodes[POSITIVE_CONDITIONING] = positiveConditioningNode;
graph.nodes[NEGATIVE_CONDITIONING] = negativeConditioningNode;
graph.nodes[TEXT_TO_LATENTS] = textToLatentsNode;
graph.nodes[LATENTS_TO_IMAGE] = latentsToImageNode;
// Connect them
graph.edges.push({
source: { node_id: POSITIVE_CONDITIONING, field: 'conditioning' },
destination: {
node_id: TEXT_TO_LATENTS,
field: 'positive_conditioning',
},
});
graph.edges.push({
source: { node_id: NEGATIVE_CONDITIONING, field: 'conditioning' },
destination: {
node_id: TEXT_TO_LATENTS,
field: 'negative_conditioning',
},
});
graph.edges.push({
source: { node_id: TEXT_TO_LATENTS, field: 'latents' },
destination: {
node_id: LATENTS_TO_IMAGE,
field: 'latents',
},
});
// Create and add the noise nodes
graph = addNoiseNodes(graph, TEXT_TO_LATENTS, state);
return graph;
};

View File

@ -0,0 +1,208 @@
import { RootState } from 'app/store/store';
import {
IterateInvocation,
NoiseInvocation,
RandomIntInvocation,
RangeOfSizeInvocation,
} from 'services/api';
import { NonNullableGraph } from 'features/nodes/types/types';
import { cloneDeep } from 'lodash-es';
const NOISE = 'noise';
const RANDOM_INT = 'rand_int';
const RANGE_OF_SIZE = 'range_of_size';
const ITERATE = 'iterate';
/**
* Adds the appropriate noise nodes to a linear UI t2l or l2l graph.
*
* @param graph The graph to add the noise nodes to.
* @param baseNodeId The id of the base node to connect the noise nodes to.
* @param state The app state..
*/
export const addNoiseNodes = (
graph: NonNullableGraph,
baseNodeId: string,
state: RootState
): NonNullableGraph => {
const graphClone = cloneDeep(graph);
// Create and add the noise nodes
const { width, height, seed, iterations, shouldRandomizeSeed } =
state.generation;
// Single iteration, explicit seed
if (!shouldRandomizeSeed && iterations === 1) {
const noiseNode: NoiseInvocation = {
id: NOISE,
type: 'noise',
seed: seed,
width,
height,
};
graphClone.nodes[NOISE] = noiseNode;
// Connect them
graphClone.edges.push({
source: { node_id: NOISE, field: 'noise' },
destination: {
node_id: baseNodeId,
field: 'noise',
},
});
}
// Single iteration, random seed
if (shouldRandomizeSeed && iterations === 1) {
// TODO: This assumes the `high` value is the max seed value
const randomIntNode: RandomIntInvocation = {
id: RANDOM_INT,
type: 'rand_int',
};
const noiseNode: NoiseInvocation = {
id: NOISE,
type: 'noise',
width,
height,
};
graphClone.nodes[RANDOM_INT] = randomIntNode;
graphClone.nodes[NOISE] = noiseNode;
graphClone.edges.push({
source: { node_id: RANDOM_INT, field: 'a' },
destination: {
node_id: NOISE,
field: 'seed',
},
});
graphClone.edges.push({
source: { node_id: NOISE, field: 'noise' },
destination: {
node_id: baseNodeId,
field: 'noise',
},
});
}
// Multiple iterations, explicit seed
if (!shouldRandomizeSeed && iterations > 1) {
const rangeOfSizeNode: RangeOfSizeInvocation = {
id: RANGE_OF_SIZE,
type: 'range_of_size',
start: seed,
size: iterations,
};
const iterateNode: IterateInvocation = {
id: ITERATE,
type: 'iterate',
};
const noiseNode: NoiseInvocation = {
id: NOISE,
type: 'noise',
width,
height,
};
graphClone.nodes[RANGE_OF_SIZE] = rangeOfSizeNode;
graphClone.nodes[ITERATE] = iterateNode;
graphClone.nodes[NOISE] = noiseNode;
graphClone.edges.push({
source: { node_id: RANGE_OF_SIZE, field: 'collection' },
destination: {
node_id: ITERATE,
field: 'collection',
},
});
graphClone.edges.push({
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: NOISE,
field: 'seed',
},
});
graphClone.edges.push({
source: { node_id: NOISE, field: 'noise' },
destination: {
node_id: baseNodeId,
field: 'noise',
},
});
}
// Multiple iterations, random seed
if (shouldRandomizeSeed && iterations > 1) {
// TODO: This assumes the `high` value is the max seed value
const randomIntNode: RandomIntInvocation = {
id: RANDOM_INT,
type: 'rand_int',
};
const rangeOfSizeNode: RangeOfSizeInvocation = {
id: RANGE_OF_SIZE,
type: 'range_of_size',
size: iterations,
};
const iterateNode: IterateInvocation = {
id: ITERATE,
type: 'iterate',
};
const noiseNode: NoiseInvocation = {
id: NOISE,
type: 'noise',
width,
height,
};
graphClone.nodes[RANDOM_INT] = randomIntNode;
graphClone.nodes[RANGE_OF_SIZE] = rangeOfSizeNode;
graphClone.nodes[ITERATE] = iterateNode;
graphClone.nodes[NOISE] = noiseNode;
graphClone.edges.push({
source: { node_id: RANDOM_INT, field: 'a' },
destination: { node_id: RANGE_OF_SIZE, field: 'start' },
});
graphClone.edges.push({
source: { node_id: RANGE_OF_SIZE, field: 'collection' },
destination: {
node_id: ITERATE,
field: 'collection',
},
});
graphClone.edges.push({
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: NOISE,
field: 'seed',
},
});
graphClone.edges.push({
source: { node_id: NOISE, field: 'noise' },
destination: {
node_id: baseNodeId,
field: 'noise',
},
});
}
return graphClone;
};

View File

@ -0,0 +1,26 @@
import { v4 as uuidv4 } from 'uuid';
import { RootState } from 'app/store/store';
import { CompelInvocation } from 'services/api';
import { O } from 'ts-toolbelt';
export const buildCompelNode = (
prompt: string,
state: RootState,
overrides: O.Partial<CompelInvocation, 'deep'> = {}
): CompelInvocation => {
const nodeId = uuidv4();
const { generation } = state;
const { model } = generation;
const compelNode: CompelInvocation = {
id: nodeId,
type: 'compel',
prompt,
model,
};
Object.assign(compelNode, overrides);
return compelNode;
};

View File

@ -18,8 +18,8 @@ export const buildImg2ImgNode = (
const activeTabName = activeTabNameSelector(state);
const {
prompt,
negativePrompt,
positivePrompt: prompt,
negativePrompt: negativePrompt,
seed,
steps,
width,

View File

@ -13,8 +13,8 @@ export const buildInpaintNode = (
const activeTabName = activeTabNameSelector(state);
const {
prompt,
negativePrompt,
positivePrompt: prompt,
negativePrompt: negativePrompt,
seed,
steps,
width,

View File

@ -11,8 +11,8 @@ export const buildTxt2ImgNode = (
const { generation } = state;
const {
prompt,
negativePrompt,
positivePrompt: prompt,
negativePrompt: negativePrompt,
seed,
steps,
width,

View File

@ -13,7 +13,7 @@ import {
buildOutputFieldTemplates,
} from './fieldTemplateBuilders';
const invocationDenylist = ['Graph', 'LoadImage'];
const invocationDenylist = ['Graph'];
export const parseSchema = (openAPI: OpenAPIV3.Document) => {
// filter out non-invocation schemas, plus some tricky invocations for now

View File

@ -8,7 +8,7 @@ import { readinessSelector } from 'app/selectors/readinessSelector';
import {
GenerationState,
clampSymmetrySteps,
setPrompt,
setPositivePrompt,
} from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
@ -22,7 +22,7 @@ const promptInputSelector = createSelector(
[(state: RootState) => state.generation, activeTabNameSelector],
(parameters: GenerationState, activeTabName) => {
return {
prompt: parameters.prompt,
prompt: parameters.positivePrompt,
activeTabName,
};
},
@ -46,7 +46,7 @@ const ParamPositiveConditioning = () => {
const { t } = useTranslation();
const handleChangePrompt = (e: ChangeEvent<HTMLTextAreaElement>) => {
dispatch(setPrompt(e.target.value));
dispatch(setPositivePrompt(e.target.value));
};
useHotkeys(

View File

@ -57,7 +57,7 @@ const InitialImagePreview = () => {
const name = e.dataTransfer.getData('invokeai/imageName');
const type = e.dataTransfer.getData('invokeai/imageType') as ImageType;
dispatch(initialImageSelected({ name, type }));
dispatch(initialImageSelected({ image_name: name, image_type: type }));
},
[dispatch]
);
@ -73,10 +73,10 @@ const InitialImagePreview = () => {
}}
onDrop={handleDrop}
>
{initialImage?.url && (
{initialImage?.image_url && (
<>
<Image
src={getUrl(initialImage?.url)}
src={getUrl(initialImage?.image_url)}
fallbackStrategy="beforeLoadOrError"
fallback={<ImageFallbackSpinner />}
onError={handleError}
@ -92,7 +92,7 @@ const InitialImagePreview = () => {
<ImageMetadataOverlay image={initialImage} />
</>
)}
{!initialImage?.url && (
{!initialImage?.image_url && (
<Icon
as={FaImage}
sx={{

View File

@ -7,9 +7,9 @@ import { allParametersSet, setSeed } from '../store/generationSlice';
import { isImageField } from 'services/types/guards';
import { NUMPY_RAND_MAX } from 'app/constants';
import { initialImageSelected } from '../store/actions';
import { Image } from 'app/types/invokeai';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { useAppToaster } from 'app/components/Toaster';
import { ImageDTO } from 'services/api';
export const useParameters = () => {
const dispatch = useAppDispatch();
@ -88,9 +88,7 @@ export const useParameters = () => {
return;
}
dispatch(
initialImageSelected({ name: image.image_name, type: image.image_type })
);
dispatch(initialImageSelected(image));
toaster({
title: t('toast.initialImageSet'),
status: 'info',
@ -105,21 +103,21 @@ export const useParameters = () => {
* Sets image as initial image with toast
*/
const sendToImageToImage = useCallback(
(image: Image) => {
dispatch(initialImageSelected({ name: image.name, type: image.type }));
(image: ImageDTO) => {
dispatch(initialImageSelected(image));
},
[dispatch]
);
const recallAllParameters = useCallback(
(image: Image | undefined) => {
const type = image?.metadata?.invokeai?.node?.type;
(image: ImageDTO | undefined) => {
const type = image?.metadata?.type;
if (['txt2img', 'img2img', 'inpaint'].includes(String(type))) {
dispatch(allParametersSet(image));
if (image?.metadata?.invokeai?.node?.type === 'img2img') {
if (image?.metadata?.type === 'img2img') {
dispatch(setActiveTab('img2img'));
} else if (image?.metadata?.invokeai?.node?.type === 'txt2img') {
} else if (image?.metadata?.type === 'txt2img') {
dispatch(setActiveTab('txt2img'));
}

View File

@ -3,7 +3,7 @@ import { getPromptAndNegative } from 'common/util/getPromptAndNegative';
import * as InvokeAI from 'app/types/invokeai';
import promptToString from 'common/util/promptToString';
import { useAppDispatch } from 'app/store/storeHooks';
import { setNegativePrompt, setPrompt } from '../store/generationSlice';
import { setNegativePrompt, setPositivePrompt } from '../store/generationSlice';
import { useCallback } from 'react';
// TECHDEBT: We have two metadata prompt formats and need to handle recalling either of them.
@ -20,7 +20,7 @@ const useSetBothPrompts = () => {
const [prompt, negativePrompt] = getPromptAndNegative(promptString);
dispatch(setPrompt(prompt));
dispatch(setPositivePrompt(prompt));
dispatch(setNegativePrompt(negativePrompt));
},
[dispatch]

View File

@ -1,12 +1,31 @@
import { createAction } from '@reduxjs/toolkit';
import { Image } from 'app/types/invokeai';
import { ImageType } from 'services/api';
import { isObject } from 'lodash-es';
import { ImageDTO, ImageType } from 'services/api';
export type SelectedImage = {
name: string;
type: ImageType;
export type ImageNameAndType = {
image_name: string;
image_type: ImageType;
};
export const isImageDTO = (image: any): image is ImageDTO => {
return (
image &&
isObject(image) &&
'image_name' in image &&
image?.image_name !== undefined &&
'image_type' in image &&
image?.image_type !== undefined &&
'image_url' in image &&
image?.image_url !== undefined &&
'thumbnail_url' in image &&
image?.thumbnail_url !== undefined &&
'image_category' in image &&
image?.image_category !== undefined &&
'created_at' in image &&
image?.created_at !== undefined
);
};
export const initialImageSelected = createAction<
Image | SelectedImage | undefined
ImageDTO | ImageNameAndType | undefined
>('generation/initialImageSelected');

View File

@ -6,16 +6,17 @@ import { clamp, sample } from 'lodash-es';
import { setAllParametersReducer } from './setAllParametersReducer';
import { receivedModels } from 'services/thunks/model';
import { Scheduler } from 'app/constants';
import { ImageDTO } from 'services/api';
export interface GenerationState {
cfgScale: number;
height: number;
img2imgStrength: number;
infillMethod: string;
initialImage?: InvokeAI.Image;
initialImage?: ImageDTO;
iterations: number;
perlin: number;
prompt: string;
positivePrompt: string;
negativePrompt: string;
scheduler: Scheduler;
seamBlur: number;
@ -49,7 +50,7 @@ export const initialGenerationState: GenerationState = {
infillMethod: 'patchmatch',
iterations: 1,
perlin: 0,
prompt: '',
positivePrompt: '',
negativePrompt: '',
scheduler: 'lms',
seamBlur: 16,
@ -82,12 +83,15 @@ export const generationSlice = createSlice({
name: 'generation',
initialState,
reducers: {
setPrompt: (state, action: PayloadAction<string | InvokeAI.Prompt>) => {
setPositivePrompt: (
state,
action: PayloadAction<string | InvokeAI.Prompt>
) => {
const newPrompt = action.payload;
if (typeof newPrompt === 'string') {
state.prompt = newPrompt;
state.positivePrompt = newPrompt;
} else {
state.prompt = promptToString(newPrompt);
state.positivePrompt = promptToString(newPrompt);
}
},
setNegativePrompt: (
@ -213,7 +217,7 @@ export const generationSlice = createSlice({
setShouldUseNoiseSettings: (state, action: PayloadAction<boolean>) => {
state.shouldUseNoiseSettings = action.payload;
},
initialImageChanged: (state, action: PayloadAction<InvokeAI.Image>) => {
initialImageChanged: (state, action: PayloadAction<ImageDTO>) => {
state.initialImage = action.payload;
},
modelSelected: (state, action: PayloadAction<string>) => {
@ -243,7 +247,7 @@ export const {
setInfillMethod,
setIterations,
setPerlin,
setPrompt,
setPositivePrompt,
setNegativePrompt,
setScheduler,
setSeamBlur,

View File

@ -1,12 +1,11 @@
import { Draft, PayloadAction } from '@reduxjs/toolkit';
import { Image } from 'app/types/invokeai';
import { GenerationState } from './generationSlice';
import { ImageToImageInvocation } from 'services/api';
import { ImageDTO, ImageToImageInvocation } from 'services/api';
import { isScheduler } from 'app/constants';
export const setAllParametersReducer = (
state: Draft<GenerationState>,
action: PayloadAction<Image | undefined>
action: PayloadAction<ImageDTO | undefined>
) => {
const node = action.payload?.metadata.invokeai?.node;
@ -32,7 +31,7 @@ export const setAllParametersReducer = (
state.model = String(model);
}
if (prompt !== undefined) {
state.prompt = String(prompt);
state.positivePrompt = String(prompt);
}
if (scheduler !== undefined) {
const schedulerString = String(scheduler);

View File

@ -5,7 +5,6 @@ import { merge } from 'lodash-es';
export const initialConfigState: AppConfig = {
shouldTransformUrls: false,
shouldFetchImages: false,
disabledTabs: [],
disabledFeatures: [],
disabledSDFeatures: [],

View File

@ -3,4 +3,4 @@ import { UIState } from './uiTypes';
/**
* UI slice persist denylist
*/
export const uiPersistDenylist: (keyof UIState)[] = [];
export const uiPersistDenylist: (keyof UIState)[] = ['shouldShowImageDetails'];

View File

@ -28,13 +28,15 @@ export type { GraphExecutionState } from './models/GraphExecutionState';
export type { GraphInvocation } from './models/GraphInvocation';
export type { GraphInvocationOutput } from './models/GraphInvocationOutput';
export type { HTTPValidationError } from './models/HTTPValidationError';
export type { ImageCategory } from './models/ImageCategory';
export type { ImageDTO } from './models/ImageDTO';
export type { ImageField } from './models/ImageField';
export type { ImageMetadata } from './models/ImageMetadata';
export type { ImageOutput } from './models/ImageOutput';
export type { ImageResponse } from './models/ImageResponse';
export type { ImageResponseMetadata } from './models/ImageResponseMetadata';
export type { ImageToImageInvocation } from './models/ImageToImageInvocation';
export type { ImageToLatentsInvocation } from './models/ImageToLatentsInvocation';
export type { ImageType } from './models/ImageType';
export type { ImageUrlsDTO } from './models/ImageUrlsDTO';
export type { InfillColorInvocation } from './models/InfillColorInvocation';
export type { InfillPatchMatchInvocation } from './models/InfillPatchMatchInvocation';
export type { InfillTileInvocation } from './models/InfillTileInvocation';
@ -42,7 +44,6 @@ export type { InpaintInvocation } from './models/InpaintInvocation';
export type { IntCollectionOutput } from './models/IntCollectionOutput';
export type { IntOutput } from './models/IntOutput';
export type { InverseLerpInvocation } from './models/InverseLerpInvocation';
export type { InvokeAIMetadata } from './models/InvokeAIMetadata';
export type { IterateInvocation } from './models/IterateInvocation';
export type { IterateInvocationOutput } from './models/IterateInvocationOutput';
export type { LatentsField } from './models/LatentsField';
@ -53,21 +54,19 @@ export type { LerpInvocation } from './models/LerpInvocation';
export type { LoadImageInvocation } from './models/LoadImageInvocation';
export type { MaskFromAlphaInvocation } from './models/MaskFromAlphaInvocation';
export type { MaskOutput } from './models/MaskOutput';
export type { MetadataColorField } from './models/MetadataColorField';
export type { MetadataImageField } from './models/MetadataImageField';
export type { MetadataLatentsField } from './models/MetadataLatentsField';
export type { ModelsList } from './models/ModelsList';
export type { MultiplyInvocation } from './models/MultiplyInvocation';
export type { NoiseInvocation } from './models/NoiseInvocation';
export type { NoiseOutput } from './models/NoiseOutput';
export type { PaginatedResults_GraphExecutionState_ } from './models/PaginatedResults_GraphExecutionState_';
export type { PaginatedResults_ImageResponse_ } from './models/PaginatedResults_ImageResponse_';
export type { PaginatedResults_ImageDTO_ } from './models/PaginatedResults_ImageDTO_';
export type { ParamIntInvocation } from './models/ParamIntInvocation';
export type { PasteImageInvocation } from './models/PasteImageInvocation';
export type { PromptOutput } from './models/PromptOutput';
export type { RandomIntInvocation } from './models/RandomIntInvocation';
export type { RandomRangeInvocation } from './models/RandomRangeInvocation';
export type { RangeInvocation } from './models/RangeInvocation';
export type { RangeOfSizeInvocation } from './models/RangeOfSizeInvocation';
export type { ResizeLatentsInvocation } from './models/ResizeLatentsInvocation';
export type { RestoreFaceInvocation } from './models/RestoreFaceInvocation';
export type { ScaleLatentsInvocation } from './models/ScaleLatentsInvocation';
@ -79,79 +78,6 @@ export type { UpscaleInvocation } from './models/UpscaleInvocation';
export type { VaeRepo } from './models/VaeRepo';
export type { ValidationError } from './models/ValidationError';
export { $AddInvocation } from './schemas/$AddInvocation';
export { $BlurInvocation } from './schemas/$BlurInvocation';
export { $Body_upload_image } from './schemas/$Body_upload_image';
export { $CkptModelInfo } from './schemas/$CkptModelInfo';
export { $CollectInvocation } from './schemas/$CollectInvocation';
export { $CollectInvocationOutput } from './schemas/$CollectInvocationOutput';
export { $ColorField } from './schemas/$ColorField';
export { $CompelInvocation } from './schemas/$CompelInvocation';
export { $CompelOutput } from './schemas/$CompelOutput';
export { $ConditioningField } from './schemas/$ConditioningField';
export { $CreateModelRequest } from './schemas/$CreateModelRequest';
export { $CropImageInvocation } from './schemas/$CropImageInvocation';
export { $CvInpaintInvocation } from './schemas/$CvInpaintInvocation';
export { $DiffusersModelInfo } from './schemas/$DiffusersModelInfo';
export { $DivideInvocation } from './schemas/$DivideInvocation';
export { $Edge } from './schemas/$Edge';
export { $EdgeConnection } from './schemas/$EdgeConnection';
export { $Graph } from './schemas/$Graph';
export { $GraphExecutionState } from './schemas/$GraphExecutionState';
export { $GraphInvocation } from './schemas/$GraphInvocation';
export { $GraphInvocationOutput } from './schemas/$GraphInvocationOutput';
export { $HTTPValidationError } from './schemas/$HTTPValidationError';
export { $ImageField } from './schemas/$ImageField';
export { $ImageOutput } from './schemas/$ImageOutput';
export { $ImageResponse } from './schemas/$ImageResponse';
export { $ImageResponseMetadata } from './schemas/$ImageResponseMetadata';
export { $ImageToImageInvocation } from './schemas/$ImageToImageInvocation';
export { $ImageToLatentsInvocation } from './schemas/$ImageToLatentsInvocation';
export { $ImageType } from './schemas/$ImageType';
export { $InfillColorInvocation } from './schemas/$InfillColorInvocation';
export { $InfillPatchMatchInvocation } from './schemas/$InfillPatchMatchInvocation';
export { $InfillTileInvocation } from './schemas/$InfillTileInvocation';
export { $InpaintInvocation } from './schemas/$InpaintInvocation';
export { $IntCollectionOutput } from './schemas/$IntCollectionOutput';
export { $IntOutput } from './schemas/$IntOutput';
export { $InverseLerpInvocation } from './schemas/$InverseLerpInvocation';
export { $InvokeAIMetadata } from './schemas/$InvokeAIMetadata';
export { $IterateInvocation } from './schemas/$IterateInvocation';
export { $IterateInvocationOutput } from './schemas/$IterateInvocationOutput';
export { $LatentsField } from './schemas/$LatentsField';
export { $LatentsOutput } from './schemas/$LatentsOutput';
export { $LatentsToImageInvocation } from './schemas/$LatentsToImageInvocation';
export { $LatentsToLatentsInvocation } from './schemas/$LatentsToLatentsInvocation';
export { $LerpInvocation } from './schemas/$LerpInvocation';
export { $LoadImageInvocation } from './schemas/$LoadImageInvocation';
export { $MaskFromAlphaInvocation } from './schemas/$MaskFromAlphaInvocation';
export { $MaskOutput } from './schemas/$MaskOutput';
export { $MetadataColorField } from './schemas/$MetadataColorField';
export { $MetadataImageField } from './schemas/$MetadataImageField';
export { $MetadataLatentsField } from './schemas/$MetadataLatentsField';
export { $ModelsList } from './schemas/$ModelsList';
export { $MultiplyInvocation } from './schemas/$MultiplyInvocation';
export { $NoiseInvocation } from './schemas/$NoiseInvocation';
export { $NoiseOutput } from './schemas/$NoiseOutput';
export { $PaginatedResults_GraphExecutionState_ } from './schemas/$PaginatedResults_GraphExecutionState_';
export { $PaginatedResults_ImageResponse_ } from './schemas/$PaginatedResults_ImageResponse_';
export { $ParamIntInvocation } from './schemas/$ParamIntInvocation';
export { $PasteImageInvocation } from './schemas/$PasteImageInvocation';
export { $PromptOutput } from './schemas/$PromptOutput';
export { $RandomIntInvocation } from './schemas/$RandomIntInvocation';
export { $RandomRangeInvocation } from './schemas/$RandomRangeInvocation';
export { $RangeInvocation } from './schemas/$RangeInvocation';
export { $ResizeLatentsInvocation } from './schemas/$ResizeLatentsInvocation';
export { $RestoreFaceInvocation } from './schemas/$RestoreFaceInvocation';
export { $ScaleLatentsInvocation } from './schemas/$ScaleLatentsInvocation';
export { $ShowImageInvocation } from './schemas/$ShowImageInvocation';
export { $SubtractInvocation } from './schemas/$SubtractInvocation';
export { $TextToImageInvocation } from './schemas/$TextToImageInvocation';
export { $TextToLatentsInvocation } from './schemas/$TextToLatentsInvocation';
export { $UpscaleInvocation } from './schemas/$UpscaleInvocation';
export { $VaeRepo } from './schemas/$VaeRepo';
export { $ValidationError } from './schemas/$ValidationError';
export { ImagesService } from './services/ImagesService';
export { ModelsService } from './services/ModelsService';
export { SessionsService } from './services/SessionsService';

View File

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

View File

@ -42,7 +42,7 @@ export type GraphExecutionState = {
/**
* The results of node executions
*/
results: Record<string, (ImageOutput | MaskOutput | CompelOutput | LatentsOutput | NoiseOutput | IntOutput | PromptOutput | IntCollectionOutput | GraphInvocationOutput | IterateInvocationOutput | CollectInvocationOutput)>;
results: Record<string, (ImageOutput | MaskOutput | PromptOutput | CompelOutput | IntOutput | LatentsOutput | NoiseOutput | IntCollectionOutput | GraphInvocationOutput | IterateInvocationOutput | CollectInvocationOutput)>;
/**
* Errors raised when executing nodes
*/

View File

@ -0,0 +1,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/**
* The category of an image. Use ImageCategory.OTHER for non-default categories.
*/
export type ImageCategory = 'general' | 'control' | 'other';

View File

@ -0,0 +1,66 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ImageCategory } from './ImageCategory';
import type { ImageMetadata } from './ImageMetadata';
import type { ImageType } from './ImageType';
/**
* Deserialized image record, enriched for the frontend with URLs.
*/
export type ImageDTO = {
/**
* The unique name of the image.
*/
image_name: string;
/**
* The type of the image.
*/
image_type: ImageType;
/**
* The URL of the image.
*/
image_url: string;
/**
* The URL of the image's thumbnail.
*/
thumbnail_url: string;
/**
* The category of the image.
*/
image_category: ImageCategory;
/**
* The width of the image in px.
*/
width: number;
/**
* The height of the image in px.
*/
height: number;
/**
* The created timestamp of the image.
*/
created_at: string;
/**
* The updated timestamp of the image.
*/
updated_at: string;
/**
* The deleted timestamp of the image.
*/
deleted_at?: string;
/**
* The session ID that generated this image, if it is a generated image.
*/
session_id?: string;
/**
* The node ID that generated this image, if it is a generated image.
*/
node_id?: string;
/**
* A limited subset of the image's generation metadata. Retrieve the image's session for full metadata.
*/
metadata?: ImageMetadata;
};

View File

@ -0,0 +1,81 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/**
* Core generation metadata for an image/tensor generated in InvokeAI.
*
* Also includes any metadata from the image's PNG tEXt chunks.
*
* Generated by traversing the execution graph, collecting the parameters of the nearest ancestors
* of a given node.
*
* Full metadata may be accessed by querying for the session in the `graph_executions` table.
*/
export type ImageMetadata = {
/**
* The type of the ancestor node of the image output node.
*/
type?: string;
/**
* The positive conditioning.
*/
positive_conditioning?: string;
/**
* The negative conditioning.
*/
negative_conditioning?: string;
/**
* Width of the image/latents in pixels.
*/
width?: number;
/**
* Height of the image/latents in pixels.
*/
height?: number;
/**
* The seed used for noise generation.
*/
seed?: number;
/**
* The classifier-free guidance scale.
*/
cfg_scale?: number;
/**
* The number of steps used for inference.
*/
steps?: number;
/**
* The scheduler used for inference.
*/
scheduler?: string;
/**
* The model used for inference.
*/
model?: string;
/**
* The strength used for image-to-image/latents-to-latents.
*/
strength?: number;
/**
* The ID of the initial latents.
*/
latents?: string;
/**
* The VAE used for decoding.
*/
vae?: string;
/**
* The UNet used dor inference.
*/
unet?: string;
/**
* The CLIP Encoder used for conditioning.
*/
clip?: string;
/**
* Uploaded image metadata, extracted from the PNG tEXt chunk.
*/
extra?: string;
};

View File

@ -8,7 +8,7 @@ import type { ImageField } from './ImageField';
* Base class for invocations that output an image
*/
export type ImageOutput = {
type: 'image';
type: 'image_output';
/**
* The output image
*/

View File

@ -1,33 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ImageResponseMetadata } from './ImageResponseMetadata';
import type { ImageType } from './ImageType';
/**
* The response type for images
*/
export type ImageResponse = {
/**
* The type of the image
*/
image_type: ImageType;
/**
* The name of the image
*/
image_name: string;
/**
* The url of the image
*/
image_url: string;
/**
* The url of the image's thumbnail
*/
thumbnail_url: string;
/**
* The image's metadata
*/
metadata: ImageResponseMetadata;
};

View File

@ -1,28 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { InvokeAIMetadata } from './InvokeAIMetadata';
/**
* An image's metadata. Used only in HTTP responses.
*/
export type ImageResponseMetadata = {
/**
* The creation timestamp of the image
*/
created: number;
/**
* The width of the image in pixels
*/
width: number;
/**
* The height of the image in pixels
*/
height: number;
/**
* The image's InvokeAI-specific metadata
*/
invokeai?: InvokeAIMetadata;
};

View File

@ -3,6 +3,6 @@
/* eslint-disable */
/**
* An enumeration.
* The type of an image.
*/
export type ImageType = 'results' | 'intermediates' | 'uploads';
export type ImageType = 'results' | 'uploads' | 'intermediates';

View File

@ -0,0 +1,28 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ImageType } from './ImageType';
/**
* The URLs for an image and its thumbnail.
*/
export type ImageUrlsDTO = {
/**
* The unique name of the image.
*/
image_name: string;
/**
* The type of the image.
*/
image_type: ImageType;
/**
* The URL of the image.
*/
image_url: string;
/**
* The URL of the image's thumbnail.
*/
thumbnail_url: string;
};

View File

@ -1,13 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { MetadataColorField } from './MetadataColorField';
import type { MetadataImageField } from './MetadataImageField';
import type { MetadataLatentsField } from './MetadataLatentsField';
export type InvokeAIMetadata = {
session_id?: string;
node?: Record<string, (string | number | boolean | MetadataImageField | MetadataLatentsField | MetadataColorField)>;
};

View File

@ -13,5 +13,13 @@ export type MaskOutput = {
* The output mask
*/
mask: ImageField;
/**
* The width of the mask in pixels
*/
width?: number;
/**
* The height of the mask in pixels
*/
height?: number;
};

View File

@ -1,11 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type MetadataColorField = {
'r': number;
'g': number;
'b': number;
'a': number;
};

View File

@ -1,11 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ImageType } from './ImageType';
export type MetadataImageField = {
image_type: ImageType;
image_name: string;
};

View File

@ -1,8 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type MetadataLatentsField = {
latents_name: string;
};

View File

@ -2,16 +2,16 @@
/* tslint:disable */
/* eslint-disable */
import type { ImageResponse } from './ImageResponse';
import type { ImageDTO } from './ImageDTO';
/**
* Paginated results
*/
export type PaginatedResults_ImageResponse_ = {
export type PaginatedResults_ImageDTO_ = {
/**
* Items
*/
items: Array<ImageResponse>;
items: Array<ImageDTO>;
/**
* Current Page
*/

View File

@ -11,5 +11,13 @@ export type RandomIntInvocation = {
*/
id: string;
type?: 'rand_int';
/**
* The inclusive low value
*/
low?: number;
/**
* The exclusive high value
*/
high?: number;
};

View File

@ -3,7 +3,7 @@
/* eslint-disable */
/**
* Creates a range
* Creates a range of numbers from start to stop with step
*/
export type RangeInvocation = {
/**

View File

@ -0,0 +1,27 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/**
* Creates a range from start to start + size with step
*/
export type RangeOfSizeInvocation = {
/**
* The id of this node. Must be unique among all nodes.
*/
id: string;
type?: 'range_of_size';
/**
* The start of the range
*/
start?: number;
/**
* The number of values
*/
size?: number;
/**
* The step of the range
*/
step?: number;
};

View File

@ -1,24 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $AddInvocation = {
description: `Adds two numbers`,
properties: {
id: {
type: 'string',
description: `The id of this node. Must be unique among all nodes.`,
isRequired: true,
},
type: {
type: 'Enum',
},
'a': {
type: 'number',
description: `The first number`,
},
'b': {
type: 'number',
description: `The second number`,
},
},
} as const;

View File

@ -1,30 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $BlurInvocation = {
description: `Blurs an image`,
properties: {
id: {
type: 'string',
description: `The id of this node. Must be unique among all nodes.`,
isRequired: true,
},
type: {
type: 'Enum',
},
image: {
type: 'all-of',
description: `The image to blur`,
contains: [{
type: 'ImageField',
}],
},
radius: {
type: 'number',
description: `The blur radius`,
},
blur_type: {
type: 'Enum',
},
},
} as const;

View File

@ -1,12 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $Body_upload_image = {
properties: {
file: {
type: 'binary',
isRequired: true,
format: 'binary',
},
},
} as const;

View File

@ -1,37 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $CkptModelInfo = {
properties: {
description: {
type: 'string',
description: `A description of the model`,
},
format: {
type: 'Enum',
},
config: {
type: 'string',
description: `The path to the model config`,
isRequired: true,
},
weights: {
type: 'string',
description: `The path to the model weights`,
isRequired: true,
},
vae: {
type: 'string',
description: `The path to the model VAE`,
isRequired: true,
},
width: {
type: 'number',
description: `The width of the model`,
},
height: {
type: 'number',
description: `The height of the model`,
},
},
} as const;

View File

@ -1,28 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $CollectInvocation = {
description: `Collects values into a collection`,
properties: {
id: {
type: 'string',
description: `The id of this node. Must be unique among all nodes.`,
isRequired: true,
},
type: {
type: 'Enum',
},
item: {
description: `The item to collect (all inputs must be of the same type)`,
properties: {
},
},
collection: {
type: 'array',
contains: {
properties: {
},
},
},
},
} as const;

View File

@ -1,20 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $CollectInvocationOutput = {
description: `Base class for all invocation outputs`,
properties: {
type: {
type: 'Enum',
isRequired: true,
},
collection: {
type: 'array',
contains: {
properties: {
},
},
isRequired: true,
},
},
} as const;

View File

@ -1,31 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $ColorField = {
properties: {
'r': {
type: 'number',
description: `The red component`,
isRequired: true,
maximum: 255,
},
'g': {
type: 'number',
description: `The green component`,
isRequired: true,
maximum: 255,
},
'b': {
type: 'number',
description: `The blue component`,
isRequired: true,
maximum: 255,
},
'a': {
type: 'number',
description: `The alpha component`,
isRequired: true,
maximum: 255,
},
},
} as const;

View File

@ -1,24 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $CompelInvocation = {
description: `Parse prompt using compel package to conditioning.`,
properties: {
id: {
type: 'string',
description: `The id of this node. Must be unique among all nodes.`,
isRequired: true,
},
type: {
type: 'Enum',
},
prompt: {
type: 'string',
description: `Prompt`,
},
model: {
type: 'string',
description: `Model to use`,
},
},
} as const;

View File

@ -1,18 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $CompelOutput = {
description: `Compel parser output`,
properties: {
type: {
type: 'Enum',
},
conditioning: {
type: 'all-of',
description: `Conditioning`,
contains: [{
type: 'ConditioningField',
}],
},
},
} as const;

View File

@ -1,12 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $ConditioningField = {
properties: {
conditioning_name: {
type: 'string',
description: `The name of conditioning data`,
isRequired: true,
},
},
} as const;

View File

@ -1,22 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $CreateModelRequest = {
properties: {
name: {
type: 'string',
description: `The name of the model`,
isRequired: true,
},
info: {
type: 'one-of',
description: `The model info`,
contains: [{
type: 'CkptModelInfo',
}, {
type: 'DiffusersModelInfo',
}],
isRequired: true,
},
},
} as const;

View File

@ -1,39 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $CropImageInvocation = {
description: `Crops an image to a specified box. The box can be outside of the image.`,
properties: {
id: {
type: 'string',
description: `The id of this node. Must be unique among all nodes.`,
isRequired: true,
},
type: {
type: 'Enum',
},
image: {
type: 'all-of',
description: `The image to crop`,
contains: [{
type: 'ImageField',
}],
},
'x': {
type: 'number',
description: `The left x coordinate of the crop rectangle`,
},
'y': {
type: 'number',
description: `The top y coordinate of the crop rectangle`,
},
width: {
type: 'number',
description: `The width of the crop rectangle`,
},
height: {
type: 'number',
description: `The height of the crop rectangle`,
},
},
} as const;

View File

@ -1,30 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $CvInpaintInvocation = {
description: `Simple inpaint using opencv.`,
properties: {
id: {
type: 'string',
description: `The id of this node. Must be unique among all nodes.`,
isRequired: true,
},
type: {
type: 'Enum',
},
image: {
type: 'all-of',
description: `The image to inpaint`,
contains: [{
type: 'ImageField',
}],
},
mask: {
type: 'all-of',
description: `The mask to use when inpainting`,
contains: [{
type: 'ImageField',
}],
},
},
} as const;

View File

@ -1,29 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $DiffusersModelInfo = {
properties: {
description: {
type: 'string',
description: `A description of the model`,
},
format: {
type: 'Enum',
},
vae: {
type: 'all-of',
description: `The VAE repo to use for this model`,
contains: [{
type: 'VaeRepo',
}],
},
repo_id: {
type: 'string',
description: `The repo ID to use for this model`,
},
path: {
type: 'string',
description: `The path to the model`,
},
},
} as const;

View File

@ -1,24 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $DivideInvocation = {
description: `Divides two numbers`,
properties: {
id: {
type: 'string',
description: `The id of this node. Must be unique among all nodes.`,
isRequired: true,
},
type: {
type: 'Enum',
},
'a': {
type: 'number',
description: `The first number`,
},
'b': {
type: 'number',
description: `The second number`,
},
},
} as const;

View File

@ -1,23 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $Edge = {
properties: {
source: {
type: 'all-of',
description: `The connection for the edge's from node and field`,
contains: [{
type: 'EdgeConnection',
}],
isRequired: true,
},
destination: {
type: 'all-of',
description: `The connection for the edge's to node and field`,
contains: [{
type: 'EdgeConnection',
}],
isRequired: true,
},
},
} as const;

View File

@ -1,17 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $EdgeConnection = {
properties: {
node_id: {
type: 'string',
description: `The id of the node for this edge connection`,
isRequired: true,
},
field: {
type: 'string',
description: `The field for this connection`,
isRequired: true,
},
},
} as const;

View File

@ -1,96 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $Graph = {
properties: {
id: {
type: 'string',
description: `The id of this graph`,
},
nodes: {
type: 'dictionary',
contains: {
type: 'one-of',
contains: [{
type: 'LoadImageInvocation',
}, {
type: 'ShowImageInvocation',
}, {
type: 'CropImageInvocation',
}, {
type: 'PasteImageInvocation',
}, {
type: 'MaskFromAlphaInvocation',
}, {
type: 'BlurInvocation',
}, {
type: 'LerpInvocation',
}, {
type: 'InverseLerpInvocation',
}, {
type: 'CompelInvocation',
}, {
type: 'NoiseInvocation',
}, {
type: 'TextToLatentsInvocation',
}, {
type: 'LatentsToImageInvocation',
}, {
type: 'ResizeLatentsInvocation',
}, {
type: 'ScaleLatentsInvocation',
}, {
type: 'ImageToLatentsInvocation',
}, {
type: 'AddInvocation',
}, {
type: 'SubtractInvocation',
}, {
type: 'MultiplyInvocation',
}, {
type: 'DivideInvocation',
}, {
type: 'RandomIntInvocation',
}, {
type: 'ParamIntInvocation',
}, {
type: 'CvInpaintInvocation',
}, {
type: 'RangeInvocation',
}, {
type: 'RandomRangeInvocation',
}, {
type: 'UpscaleInvocation',
}, {
type: 'RestoreFaceInvocation',
}, {
type: 'TextToImageInvocation',
}, {
type: 'InfillColorInvocation',
}, {
type: 'InfillTileInvocation',
}, {
type: 'InfillPatchMatchInvocation',
}, {
type: 'GraphInvocation',
}, {
type: 'IterateInvocation',
}, {
type: 'CollectInvocation',
}, {
type: 'LatentsToLatentsInvocation',
}, {
type: 'ImageToImageInvocation',
}, {
type: 'InpaintInvocation',
}],
},
},
edges: {
type: 'array',
contains: {
type: 'Edge',
},
},
},
} as const;

View File

@ -1,97 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $GraphExecutionState = {
description: `Tracks the state of a graph execution`,
properties: {
id: {
type: 'string',
description: `The id of the execution state`,
isRequired: true,
},
graph: {
type: 'all-of',
description: `The graph being executed`,
contains: [{
type: 'Graph',
}],
isRequired: true,
},
execution_graph: {
type: 'all-of',
description: `The expanded graph of activated and executed nodes`,
contains: [{
type: 'Graph',
}],
isRequired: true,
},
executed: {
type: 'array',
contains: {
type: 'string',
},
isRequired: true,
},
executed_history: {
type: 'array',
contains: {
type: 'string',
},
isRequired: true,
},
results: {
type: 'dictionary',
contains: {
type: 'one-of',
contains: [{
type: 'ImageOutput',
}, {
type: 'MaskOutput',
}, {
type: 'CompelOutput',
}, {
type: 'LatentsOutput',
}, {
type: 'NoiseOutput',
}, {
type: 'IntOutput',
}, {
type: 'PromptOutput',
}, {
type: 'IntCollectionOutput',
}, {
type: 'GraphInvocationOutput',
}, {
type: 'IterateInvocationOutput',
}, {
type: 'CollectInvocationOutput',
}],
},
isRequired: true,
},
errors: {
type: 'dictionary',
contains: {
type: 'string',
},
isRequired: true,
},
prepared_source_mapping: {
type: 'dictionary',
contains: {
type: 'string',
},
isRequired: true,
},
source_prepared_mapping: {
type: 'dictionary',
contains: {
type: 'array',
contains: {
type: 'string',
},
},
isRequired: true,
},
},
} as const;

View File

@ -1,24 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $GraphInvocation = {
description: `A node to process inputs and produce outputs.
May use dependency injection in __init__ to receive providers.`,
properties: {
id: {
type: 'string',
description: `The id of this node. Must be unique among all nodes.`,
isRequired: true,
},
type: {
type: 'Enum',
},
graph: {
type: 'all-of',
description: `The graph to run`,
contains: [{
type: 'Graph',
}],
},
},
} as const;

View File

@ -1,12 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $GraphInvocationOutput = {
description: `Base class for all invocation outputs`,
properties: {
type: {
type: 'Enum',
isRequired: true,
},
},
} as const;

View File

@ -1,13 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $HTTPValidationError = {
properties: {
detail: {
type: 'array',
contains: {
type: 'ValidationError',
},
},
},
} as const;

View File

@ -1,21 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $ImageField = {
description: `An image field used for passing image objects between invocations`,
properties: {
image_type: {
type: 'all-of',
description: `The type of the image`,
contains: [{
type: 'ImageType',
}],
isRequired: true,
},
image_name: {
type: 'string',
description: `The name of the image`,
isRequired: true,
},
},
} as const;

View File

@ -1,30 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $ImageOutput = {
description: `Base class for invocations that output an image`,
properties: {
type: {
type: 'Enum',
isRequired: true,
},
image: {
type: 'all-of',
description: `The output image`,
contains: [{
type: 'ImageField',
}],
isRequired: true,
},
width: {
type: 'number',
description: `The width of the image in pixels`,
isRequired: true,
},
height: {
type: 'number',
description: `The height of the image in pixels`,
isRequired: true,
},
},
} as const;

View File

@ -1,39 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $ImageResponse = {
description: `The response type for images`,
properties: {
image_type: {
type: 'all-of',
description: `The type of the image`,
contains: [{
type: 'ImageType',
}],
isRequired: true,
},
image_name: {
type: 'string',
description: `The name of the image`,
isRequired: true,
},
image_url: {
type: 'string',
description: `The url of the image`,
isRequired: true,
},
thumbnail_url: {
type: 'string',
description: `The url of the image's thumbnail`,
isRequired: true,
},
metadata: {
type: 'all-of',
description: `The image's metadata`,
contains: [{
type: 'ImageResponseMetadata',
}],
isRequired: true,
},
},
} as const;

View File

@ -1,30 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $ImageResponseMetadata = {
description: `An image's metadata. Used only in HTTP responses.`,
properties: {
created: {
type: 'number',
description: `The creation timestamp of the image`,
isRequired: true,
},
width: {
type: 'number',
description: `The width of the image in pixels`,
isRequired: true,
},
height: {
type: 'number',
description: `The height of the image in pixels`,
isRequired: true,
},
invokeai: {
type: 'all-of',
description: `The image's InvokeAI-specific metadata`,
contains: [{
type: 'InvokeAIMetadata',
}],
},
},
} as const;

View File

@ -1,67 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $ImageToImageInvocation = {
description: `Generates an image using img2img.`,
properties: {
id: {
type: 'string',
description: `The id of this node. Must be unique among all nodes.`,
isRequired: true,
},
type: {
type: 'Enum',
},
prompt: {
type: 'string',
description: `The prompt to generate an image from`,
},
seed: {
type: 'number',
description: `The seed to use (omit for random)`,
maximum: 2147483647,
},
steps: {
type: 'number',
description: `The number of steps to use to generate the image`,
},
width: {
type: 'number',
description: `The width of the resulting image`,
multipleOf: 8,
},
height: {
type: 'number',
description: `The height of the resulting image`,
multipleOf: 8,
},
cfg_scale: {
type: 'number',
description: `The Classifier-Free Guidance, higher values may result in a result closer to the prompt`,
minimum: 1,
},
scheduler: {
type: 'Enum',
},
model: {
type: 'string',
description: `The model to use (currently ignored)`,
},
image: {
type: 'all-of',
description: `The input image`,
contains: [{
type: 'ImageField',
}],
},
strength: {
type: 'number',
description: `The strength of the original image`,
maximum: 1,
},
fit: {
type: 'boolean',
description: `Whether or not the result should be fit to the aspect ratio of the input image`,
},
},
} as const;

View File

@ -1,27 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $ImageToLatentsInvocation = {
description: `Encodes an image into latents.`,
properties: {
id: {
type: 'string',
description: `The id of this node. Must be unique among all nodes.`,
isRequired: true,
},
type: {
type: 'Enum',
},
image: {
type: 'all-of',
description: `The image to encode`,
contains: [{
type: 'ImageField',
}],
},
model: {
type: 'string',
description: `The model to use`,
},
},
} as const;

View File

@ -1,6 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $ImageType = {
type: 'Enum',
} as const;

View File

@ -1,30 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $InfillColorInvocation = {
description: `Infills transparent areas of an image with a solid color`,
properties: {
id: {
type: 'string',
description: `The id of this node. Must be unique among all nodes.`,
isRequired: true,
},
type: {
type: 'Enum',
},
image: {
type: 'all-of',
description: `The image to infill`,
contains: [{
type: 'ImageField',
}],
},
color: {
type: 'all-of',
description: `The color to use to infill`,
contains: [{
type: 'ColorField',
}],
},
},
} as const;

View File

@ -1,23 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $InfillPatchMatchInvocation = {
description: `Infills transparent areas of an image using the PatchMatch algorithm`,
properties: {
id: {
type: 'string',
description: `The id of this node. Must be unique among all nodes.`,
isRequired: true,
},
type: {
type: 'Enum',
},
image: {
type: 'all-of',
description: `The image to infill`,
contains: [{
type: 'ImageField',
}],
},
},
} as const;

View File

@ -1,33 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $InfillTileInvocation = {
description: `Infills transparent areas of an image with tiles of the image`,
properties: {
id: {
type: 'string',
description: `The id of this node. Must be unique among all nodes.`,
isRequired: true,
},
type: {
type: 'Enum',
},
image: {
type: 'all-of',
description: `The image to infill`,
contains: [{
type: 'ImageField',
}],
},
tile_size: {
type: 'number',
description: `The tile size (px)`,
minimum: 1,
},
seed: {
type: 'number',
description: `The seed to use for tile generation (omit for random)`,
maximum: 2147483647,
},
},
} as const;

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