mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into unipc-sched
This commit is contained in:
commit
805f9f8f4a
@ -0,0 +1,54 @@
|
|||||||
|
import { Badge, Flex } from '@chakra-ui/react';
|
||||||
|
import { Image } from 'app/types/invokeai';
|
||||||
|
import { isNumber, isString } from 'lodash-es';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
type ImageMetadataOverlayProps = {
|
||||||
|
image: Image;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ImageMetadataOverlay = ({ image }: ImageMetadataOverlayProps) => {
|
||||||
|
const dimensions = useMemo(() => {
|
||||||
|
if (!isNumber(image.metadata?.width) || isNumber(!image.metadata?.height)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${image.metadata?.width} × ${image.metadata?.height}`;
|
||||||
|
}, [image.metadata]);
|
||||||
|
|
||||||
|
const model = useMemo(() => {
|
||||||
|
if (!isString(image.metadata?.invokeai?.node?.model)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return image.metadata?.invokeai?.node?.model;
|
||||||
|
}, [image.metadata]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
pointerEvents: 'none',
|
||||||
|
flexDirection: 'column',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
p: 2,
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
gap: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{dimensions && (
|
||||||
|
<Badge variant="solid" colorScheme="base">
|
||||||
|
{dimensions}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{model && (
|
||||||
|
<Badge variant="solid" colorScheme="base">
|
||||||
|
{model}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageMetadataOverlay;
|
@ -1,37 +0,0 @@
|
|||||||
import { Badge, Box, Flex } from '@chakra-ui/react';
|
|
||||||
import { Image } from 'app/types/invokeai';
|
|
||||||
|
|
||||||
type ImageToImageOverlayProps = {
|
|
||||||
image: Image;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ImageToImageOverlay = ({ image }: ImageToImageOverlayProps) => {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
w: 'full',
|
|
||||||
h: 'full',
|
|
||||||
position: 'absolute',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
p: 2,
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Badge variant="solid" colorScheme="base">
|
|
||||||
{image.metadata?.width} × {image.metadata?.height}
|
|
||||||
</Badge>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ImageToImageOverlay;
|
|
@ -1,4 +1,4 @@
|
|||||||
import { Box, Flex, Image, Skeleton, useBoolean } from '@chakra-ui/react';
|
import { Box, Flex, Image } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useGetUrl } from 'common/util/getUrl';
|
import { useGetUrl } from 'common/util/getUrl';
|
||||||
@ -11,7 +11,8 @@ import NextPrevImageButtons from './NextPrevImageButtons';
|
|||||||
import CurrentImageHidden from './CurrentImageHidden';
|
import CurrentImageHidden from './CurrentImageHidden';
|
||||||
import { DragEvent, memo, useCallback } from 'react';
|
import { DragEvent, memo, useCallback } from 'react';
|
||||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
import CurrentImageFallback from './CurrentImageFallback';
|
import ImageFallbackSpinner from './ImageFallbackSpinner';
|
||||||
|
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
||||||
|
|
||||||
export const imagesSelector = createSelector(
|
export const imagesSelector = createSelector(
|
||||||
[uiSelector, gallerySelector, systemSelector],
|
[uiSelector, gallerySelector, systemSelector],
|
||||||
@ -50,8 +51,6 @@ const CurrentImagePreview = () => {
|
|||||||
} = useAppSelector(imagesSelector);
|
} = useAppSelector(imagesSelector);
|
||||||
const { getUrl } = useGetUrl();
|
const { getUrl } = useGetUrl();
|
||||||
|
|
||||||
const [isLoaded, { on, off }] = useBoolean();
|
|
||||||
|
|
||||||
const handleDragStart = useCallback(
|
const handleDragStart = useCallback(
|
||||||
(e: DragEvent<HTMLDivElement>) => {
|
(e: DragEvent<HTMLDivElement>) => {
|
||||||
if (!image) {
|
if (!image) {
|
||||||
@ -67,11 +66,11 @@ const CurrentImagePreview = () => {
|
|||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
position: 'relative',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
position: 'relative',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{progressImage && shouldShowProgressInViewer ? (
|
{progressImage && shouldShowProgressInViewer ? (
|
||||||
@ -91,28 +90,23 @@ const CurrentImagePreview = () => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
image && (
|
image && (
|
||||||
<Image
|
<>
|
||||||
onDragStart={handleDragStart}
|
<Image
|
||||||
fallbackStrategy="beforeLoadOrError"
|
src={getUrl(image.url)}
|
||||||
src={shouldHidePreview ? undefined : getUrl(image.url)}
|
fallbackStrategy="beforeLoadOrError"
|
||||||
width={image.metadata.width || 'auto'}
|
fallback={<ImageFallbackSpinner />}
|
||||||
height={image.metadata.height || 'auto'}
|
onDragStart={handleDragStart}
|
||||||
fallback={
|
sx={{
|
||||||
shouldHidePreview ? (
|
objectFit: 'contain',
|
||||||
<CurrentImageHidden />
|
maxWidth: '100%',
|
||||||
) : (
|
maxHeight: '100%',
|
||||||
<CurrentImageFallback />
|
height: 'auto',
|
||||||
)
|
position: 'absolute',
|
||||||
}
|
borderRadius: 'base',
|
||||||
sx={{
|
}}
|
||||||
objectFit: 'contain',
|
/>
|
||||||
maxWidth: '100%',
|
<ImageMetadataOverlay image={image} />
|
||||||
maxHeight: '100%',
|
</>
|
||||||
height: 'auto',
|
|
||||||
position: 'absolute',
|
|
||||||
borderRadius: 'base',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
{shouldShowImageDetails && image && 'metadata' in image && (
|
{shouldShowImageDetails && image && 'metadata' in image && (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Box, Flex, Image } from '@chakra-ui/react';
|
import { Flex, Image, Spinner } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
@ -42,6 +42,7 @@ const GalleryProgressImage = () => {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
aspectRatio: '1/1',
|
aspectRatio: '1/1',
|
||||||
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
@ -61,6 +62,7 @@ const GalleryProgressImage = () => {
|
|||||||
imageRendering: shouldAntialiasProgressImage ? 'auto' : 'pixelated',
|
imageRendering: shouldAntialiasProgressImage ? 'auto' : 'pixelated',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Spinner sx={{ position: 'absolute', top: 1, right: 1, opacity: 0.7 }} />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -278,6 +278,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
h: 'full',
|
h: 'full',
|
||||||
transition: 'transform 0.2s ease-out',
|
transition: 'transform 0.2s ease-out',
|
||||||
aspectRatio: '1/1',
|
aspectRatio: '1/1',
|
||||||
|
cursor: 'pointer',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Flex, Spinner, SpinnerProps } from '@chakra-ui/react';
|
import { Flex, Spinner, SpinnerProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
type CurrentImageFallbackProps = SpinnerProps;
|
type ImageFallbackSpinnerProps = SpinnerProps;
|
||||||
|
|
||||||
const CurrentImageFallback = (props: CurrentImageFallbackProps) => {
|
const ImageFallbackSpinner = (props: ImageFallbackSpinnerProps) => {
|
||||||
const { size = 'xl', ...rest } = props;
|
const { size = 'xl', ...rest } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -21,4 +21,4 @@ const CurrentImageFallback = (props: CurrentImageFallbackProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CurrentImageFallback;
|
export default ImageFallbackSpinner;
|
@ -1,6 +1,7 @@
|
|||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import { Image } from 'app/types/invokeai';
|
import { Image } from 'app/types/invokeai';
|
||||||
|
import { imageReceived, thumbnailReceived } from 'services/thunks/image';
|
||||||
|
|
||||||
type GalleryImageObjectFitType = 'contain' | 'cover';
|
type GalleryImageObjectFitType = 'contain' | 'cover';
|
||||||
|
|
||||||
@ -63,6 +64,29 @@ export const gallerySlice = createSlice({
|
|||||||
state.shouldUseSingleGalleryColumn = action.payload;
|
state.shouldUseSingleGalleryColumn = action.payload;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
extraReducers(builder) {
|
||||||
|
builder.addCase(imageReceived.fulfilled, (state, action) => {
|
||||||
|
// When we get an updated URL for an image, we need to update the selectedImage in gallery,
|
||||||
|
// which is currently its own object (instead of a reference to an image in results/uploads)
|
||||||
|
const { imagePath } = action.payload;
|
||||||
|
const { imageName } = action.meta.arg;
|
||||||
|
|
||||||
|
if (state.selectedImage?.name === imageName) {
|
||||||
|
state.selectedImage.url = imagePath;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.addCase(thumbnailReceived.fulfilled, (state, action) => {
|
||||||
|
// When we get an updated URL for an image, we need to update the selectedImage in gallery,
|
||||||
|
// which is currently its own object (instead of a reference to an image in results/uploads)
|
||||||
|
const { thumbnailPath } = action.payload;
|
||||||
|
const { thumbnailName } = action.meta.arg;
|
||||||
|
|
||||||
|
if (state.selectedImage?.name === thumbnailName) {
|
||||||
|
state.selectedImage.thumbnail = thumbnailPath;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import { Flex, Image, Spinner } from '@chakra-ui/react';
|
import { Flex, Image } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import SelectImagePlaceholder from 'common/components/SelectImagePlaceholder';
|
import SelectImagePlaceholder from 'common/components/SelectImagePlaceholder';
|
||||||
import { useGetUrl } from 'common/util/getUrl';
|
import { useGetUrl } from 'common/util/getUrl';
|
||||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { DragEvent, useCallback, useState } from 'react';
|
import { DragEvent, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ImageType } from 'services/api';
|
import { ImageType } from 'services/api';
|
||||||
import ImageToImageOverlay from 'common/components/ImageToImageOverlay';
|
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
||||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
|
import ImageFallbackSpinner from 'features/gallery/components/ImageFallbackSpinner';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[generationSelector],
|
[generationSelector],
|
||||||
@ -30,8 +31,6 @@ const InitialImagePreview = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [isLoaded, setIsLoaded] = useState(false);
|
|
||||||
|
|
||||||
const onError = () => {
|
const onError = () => {
|
||||||
dispatch(
|
dispatch(
|
||||||
addToast({
|
addToast({
|
||||||
@ -42,13 +41,10 @@ const InitialImagePreview = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
dispatch(clearInitialImage());
|
dispatch(clearInitialImage());
|
||||||
setIsLoaded(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDrop = useCallback(
|
const handleDrop = useCallback(
|
||||||
(e: DragEvent<HTMLDivElement>) => {
|
(e: DragEvent<HTMLDivElement>) => {
|
||||||
setIsLoaded(false);
|
|
||||||
|
|
||||||
const name = e.dataTransfer.getData('invokeai/imageName');
|
const name = e.dataTransfer.getData('invokeai/imageName');
|
||||||
const type = e.dataTransfer.getData('invokeai/imageType') as ImageType;
|
const type = e.dataTransfer.getData('invokeai/imageType') as ImageType;
|
||||||
|
|
||||||
@ -62,48 +58,32 @@ const InitialImagePreview = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
width: 'full',
|
width: 'full',
|
||||||
height: 'full',
|
height: 'full',
|
||||||
|
position: 'relative',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
position: 'relative',
|
|
||||||
}}
|
}}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
>
|
>
|
||||||
<Flex
|
{initialImage?.url && (
|
||||||
sx={{
|
<>
|
||||||
height: 'full',
|
<Image
|
||||||
width: 'full',
|
src={getUrl(initialImage?.url)}
|
||||||
blur: '5px',
|
fallbackStrategy="beforeLoadOrError"
|
||||||
position: 'relative',
|
fallback={<ImageFallbackSpinner />}
|
||||||
alignItems: 'center',
|
onError={onError}
|
||||||
justifyContent: 'center',
|
sx={{
|
||||||
}}
|
objectFit: 'contain',
|
||||||
>
|
maxWidth: '100%',
|
||||||
{initialImage?.url && (
|
maxHeight: '100%',
|
||||||
<>
|
height: 'auto',
|
||||||
<Image
|
position: 'absolute',
|
||||||
sx={{
|
borderRadius: 'base',
|
||||||
objectFit: 'contain',
|
}}
|
||||||
borderRadius: 'base',
|
/>
|
||||||
maxHeight: 'full',
|
<ImageMetadataOverlay image={initialImage} />
|
||||||
}}
|
</>
|
||||||
src={getUrl(initialImage?.url)}
|
)}
|
||||||
onError={onError}
|
{!initialImage?.url && <SelectImagePlaceholder />}
|
||||||
onLoad={() => {
|
|
||||||
setIsLoaded(true);
|
|
||||||
}}
|
|
||||||
fallback={
|
|
||||||
<Flex
|
|
||||||
sx={{ h: 36, alignItems: 'center', justifyContent: 'center' }}
|
|
||||||
>
|
|
||||||
<Spinner color="grey" w="5rem" h="5rem" />
|
|
||||||
</Flex>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{isLoaded && <ImageToImageOverlay image={initialImage} />}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{!initialImage?.url && <SelectImagePlaceholder />}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -418,6 +418,7 @@ export const systemSlice = createSlice({
|
|||||||
state.currentStep = 0;
|
state.currentStep = 0;
|
||||||
state.totalSteps = 0;
|
state.totalSteps = 0;
|
||||||
state.statusTranslationKey = 'common.statusConnected';
|
state.statusTranslationKey = 'common.statusConnected';
|
||||||
|
state.progressImage = null;
|
||||||
|
|
||||||
state.toastQueue.push(
|
state.toastQueue.push(
|
||||||
makeToast({ title: t('toast.canceled'), status: 'warning' })
|
makeToast({ title: t('toast.canceled'), status: 'warning' })
|
||||||
|
Loading…
Reference in New Issue
Block a user