mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'release-candidate-2' of github.com:invoke-ai/InvokeAI into release-candidate-2
This commit is contained in:
commit
4a7f5c7469
BIN
frontend/dist/assets/image2img.dde6a9f1.png
vendored
BIN
frontend/dist/assets/image2img.dde6a9f1.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 336 KiB |
483
frontend/dist/assets/index.3a9574b7.js
vendored
483
frontend/dist/assets/index.3a9574b7.js
vendored
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index.60ca0ee5.css
vendored
1
frontend/dist/assets/index.60ca0ee5.css
vendored
File diff suppressed because one or more lines are too long
483
frontend/dist/assets/index.783ff334.js
vendored
Normal file
483
frontend/dist/assets/index.783ff334.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index.a0250964.css
vendored
Normal file
1
frontend/dist/assets/index.a0250964.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@ -6,8 +6,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
||||||
<link rel="shortcut icon" type="icon" href="/assets/favicon.0d253ced.ico" />
|
<link rel="shortcut icon" type="icon" href="/assets/favicon.0d253ced.ico" />
|
||||||
<script type="module" crossorigin src="/assets/index.3a9574b7.js"></script>
|
<script type="module" crossorigin src="/assets/index.783ff334.js"></script>
|
||||||
<link rel="stylesheet" href="/assets/index.60ca0ee5.css">
|
<link rel="stylesheet" href="/assets/index.a0250964.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
addLogEntry,
|
addLogEntry,
|
||||||
setIsProcessing,
|
setIsProcessing,
|
||||||
} from '../../features/system/systemSlice';
|
} from '../../features/system/systemSlice';
|
||||||
|
import { tabMap, tab_dict } from '../../features/tabs/InvokeTabs';
|
||||||
import * as InvokeAI from '../invokeai';
|
import * as InvokeAI from '../invokeai';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,8 +24,14 @@ const makeSocketIOEmitters = (
|
|||||||
emitGenerateImage: () => {
|
emitGenerateImage: () => {
|
||||||
dispatch(setIsProcessing(true));
|
dispatch(setIsProcessing(true));
|
||||||
|
|
||||||
|
const options = { ...getState().options };
|
||||||
|
|
||||||
|
if (tabMap[options.activeTab] === 'txt2img') {
|
||||||
|
options.shouldUseInitImage = false;
|
||||||
|
}
|
||||||
|
|
||||||
const { generationParameters, esrganParameters, gfpganParameters } =
|
const { generationParameters, esrganParameters, gfpganParameters } =
|
||||||
frontendToBackendParameters(getState().options, getState().system);
|
frontendToBackendParameters(options, getState().system);
|
||||||
|
|
||||||
socketio.emit(
|
socketio.emit(
|
||||||
'generateImage',
|
'generateImage',
|
||||||
|
65
frontend/src/common/components/InvokeImageUploader.tsx
Normal file
65
frontend/src/common/components/InvokeImageUploader.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { Button, useToast } from '@chakra-ui/react';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { FileRejection } from 'react-dropzone';
|
||||||
|
import { useAppDispatch } from '../../app/store';
|
||||||
|
import ImageUploader from '../../features/options/ImageUploader';
|
||||||
|
|
||||||
|
interface InvokeImageUploaderProps {
|
||||||
|
label?: string;
|
||||||
|
icon?: any;
|
||||||
|
onMouseOver?: any;
|
||||||
|
OnMouseout?: any;
|
||||||
|
dispatcher: any;
|
||||||
|
styleClass?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function InvokeImageUploader(props: InvokeImageUploaderProps) {
|
||||||
|
const { label, icon, dispatcher, styleClass, onMouseOver, OnMouseout } =
|
||||||
|
props;
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
// Callbacks to for handling file upload attempts
|
||||||
|
const fileAcceptedCallback = useCallback(
|
||||||
|
(file: File) => dispatch(dispatcher(file)),
|
||||||
|
[dispatch, dispatcher]
|
||||||
|
);
|
||||||
|
|
||||||
|
const fileRejectionCallback = useCallback(
|
||||||
|
(rejection: FileRejection) => {
|
||||||
|
const msg = rejection.errors.reduce(
|
||||||
|
(acc: string, cur: { message: string }) => acc + '\n' + cur.message,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: 'Upload failed',
|
||||||
|
description: msg,
|
||||||
|
status: 'error',
|
||||||
|
isClosable: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[toast]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ImageUploader
|
||||||
|
fileAcceptedCallback={fileAcceptedCallback}
|
||||||
|
fileRejectionCallback={fileRejectionCallback}
|
||||||
|
styleClass={styleClass}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size={'sm'}
|
||||||
|
fontSize={'md'}
|
||||||
|
fontWeight={'normal'}
|
||||||
|
onMouseOver={onMouseOver}
|
||||||
|
onMouseOut={OnMouseout}
|
||||||
|
leftIcon={icon}
|
||||||
|
width={'100%'}
|
||||||
|
>
|
||||||
|
{label ? label : null}
|
||||||
|
</Button>
|
||||||
|
</ImageUploader>
|
||||||
|
);
|
||||||
|
}
|
@ -18,6 +18,7 @@ export const optionsSelector = createSelector(
|
|||||||
maskPath: options.maskPath,
|
maskPath: options.maskPath,
|
||||||
initialImagePath: options.initialImagePath,
|
initialImagePath: options.initialImagePath,
|
||||||
seed: options.seed,
|
seed: options.seed,
|
||||||
|
activeTab: options.activeTab,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -55,6 +56,7 @@ const useCheckParameters = (): boolean => {
|
|||||||
maskPath,
|
maskPath,
|
||||||
initialImagePath,
|
initialImagePath,
|
||||||
seed,
|
seed,
|
||||||
|
activeTab,
|
||||||
} = useAppSelector(optionsSelector);
|
} = useAppSelector(optionsSelector);
|
||||||
|
|
||||||
const { isProcessing, isConnected } = useAppSelector(systemSelector);
|
const { isProcessing, isConnected } = useAppSelector(systemSelector);
|
||||||
@ -65,6 +67,10 @@ const useCheckParameters = (): boolean => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prompt && !initialImagePath && activeTab === 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Cannot generate with a mask without img2img
|
// Cannot generate with a mask without img2img
|
||||||
if (maskPath && !initialImagePath) {
|
if (maskPath && !initialImagePath) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -6,9 +6,11 @@ import * as InvokeAI from '../../app/invokeai';
|
|||||||
import { useAppDispatch, useAppSelector } from '../../app/store';
|
import { useAppDispatch, useAppSelector } from '../../app/store';
|
||||||
import { RootState } from '../../app/store';
|
import { RootState } from '../../app/store';
|
||||||
import {
|
import {
|
||||||
|
setActiveTab,
|
||||||
setAllParameters,
|
setAllParameters,
|
||||||
setInitialImagePath,
|
setInitialImagePath,
|
||||||
setSeed,
|
setSeed,
|
||||||
|
setShouldShowImageDetails,
|
||||||
} from '../options/optionsSlice';
|
} from '../options/optionsSlice';
|
||||||
import DeleteImageModal from './DeleteImageModal';
|
import DeleteImageModal from './DeleteImageModal';
|
||||||
import { SystemState } from '../system/systemSlice';
|
import { SystemState } from '../system/systemSlice';
|
||||||
@ -41,21 +43,19 @@ const systemSelector = createSelector(
|
|||||||
|
|
||||||
type CurrentImageButtonsProps = {
|
type CurrentImageButtonsProps = {
|
||||||
image: InvokeAI.Image;
|
image: InvokeAI.Image;
|
||||||
shouldShowImageDetails: boolean;
|
|
||||||
setShouldShowImageDetails: (b: boolean) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Row of buttons for common actions:
|
* Row of buttons for common actions:
|
||||||
* Use as init image, use all params, use seed, upscale, fix faces, details, delete.
|
* Use as init image, use all params, use seed, upscale, fix faces, details, delete.
|
||||||
*/
|
*/
|
||||||
const CurrentImageButtons = ({
|
const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
|
||||||
image,
|
|
||||||
shouldShowImageDetails,
|
|
||||||
setShouldShowImageDetails,
|
|
||||||
}: CurrentImageButtonsProps) => {
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const shouldShowImageDetails = useAppSelector(
|
||||||
|
(state: RootState) => state.options.shouldShowImageDetails
|
||||||
|
);
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
const intermediateImage = useAppSelector(
|
const intermediateImage = useAppSelector(
|
||||||
@ -73,8 +73,11 @@ const CurrentImageButtons = ({
|
|||||||
const { isProcessing, isConnected, isGFPGANAvailable, isESRGANAvailable } =
|
const { isProcessing, isConnected, isGFPGANAvailable, isESRGANAvailable } =
|
||||||
useAppSelector(systemSelector);
|
useAppSelector(systemSelector);
|
||||||
|
|
||||||
const handleClickUseAsInitialImage = () =>
|
const handleClickUseAsInitialImage = () => {
|
||||||
dispatch(setInitialImagePath(image.url));
|
dispatch(setInitialImagePath(image.url));
|
||||||
|
dispatch(setActiveTab(1));
|
||||||
|
};
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'shift+i',
|
'shift+i',
|
||||||
() => {
|
() => {
|
||||||
@ -215,7 +218,8 @@ const CurrentImageButtons = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleClickShowImageDetails = () =>
|
const handleClickShowImageDetails = () =>
|
||||||
setShouldShowImageDetails(!shouldShowImageDetails);
|
dispatch(setShouldShowImageDetails(!shouldShowImageDetails));
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'i',
|
'i',
|
||||||
() => {
|
() => {
|
||||||
@ -237,8 +241,8 @@ const CurrentImageButtons = ({
|
|||||||
<div className="current-image-options">
|
<div className="current-image-options">
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
icon={<MdImage />}
|
icon={<MdImage />}
|
||||||
tooltip="Use As Initial Image"
|
tooltip="Send To Image To Image"
|
||||||
aria-label="Use As Initial Image"
|
aria-label="Send To Image To Image"
|
||||||
onClick={handleClickUseAsInitialImage}
|
onClick={handleClickUseAsInitialImage}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.current-image-tools {
|
.current-image-tools {
|
||||||
grid-area: current-image-tools;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -58,34 +57,37 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
display: grid;
|
display: grid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
grid-template-areas: 'current-image-content';
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
grid-area: current-image-content;
|
||||||
|
background-color: var(--img2img-img-bg-color);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
height: $app-gallery-height;
|
||||||
max-height: $app-gallery-height;
|
max-height: $app-gallery-height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.current-image-metadata {
|
||||||
|
grid-area: current-image-preview;
|
||||||
|
}
|
||||||
|
|
||||||
.current-image-next-prev-buttons {
|
.current-image-next-prev-buttons {
|
||||||
position: absolute;
|
grid-area: current-image-content;
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: calc(100% - 2rem);
|
|
||||||
padding: 0.5rem;
|
|
||||||
margin-left: 1rem;
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
height: calc($app-metadata-height - 1rem);
|
height: 100%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.next-prev-button-trigger-area {
|
.next-prev-button-trigger-area {
|
||||||
width: 7rem;
|
width: 7rem;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
|
||||||
@ -99,31 +101,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.next-prev-button {
|
.next-prev-button {
|
||||||
font-size: 5rem;
|
font-size: 4rem;
|
||||||
fill: var(--text-color-secondary);
|
fill: var(--white);
|
||||||
filter: drop-shadow(0 0 1rem var(--text-color-secondary));
|
filter: drop-shadow(0 0 1rem var(--text-color-secondary));
|
||||||
opacity: 70%;
|
opacity: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-image-metadata-viewer {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: calc(100% - 2rem);
|
|
||||||
padding: 0.5rem;
|
|
||||||
margin-left: 1rem;
|
|
||||||
background-color: var(--metadata-bg-color);
|
|
||||||
z-index: 1;
|
|
||||||
overflow: scroll;
|
|
||||||
height: calc($app-metadata-height - 1rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-image-json-viewer {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
margin: 0 0.5rem 1rem 0.5rem;
|
|
||||||
padding: 1rem;
|
|
||||||
overflow-x: scroll;
|
|
||||||
word-break: break-all;
|
|
||||||
background-color: var(--metadata-json-bg-color);
|
|
||||||
}
|
|
||||||
|
@ -1,100 +1,35 @@
|
|||||||
import { IconButton, Image } from '@chakra-ui/react';
|
import { RootState, useAppSelector } from '../../app/store';
|
||||||
import { useAppDispatch, useAppSelector } from '../../app/store';
|
|
||||||
import { RootState } from '../../app/store';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import ImageMetadataViewer from './ImageMetadataViewer';
|
|
||||||
import CurrentImageButtons from './CurrentImageButtons';
|
import CurrentImageButtons from './CurrentImageButtons';
|
||||||
import { MdPhoto } from 'react-icons/md';
|
import { MdPhoto } from 'react-icons/md';
|
||||||
import { FaAngleLeft, FaAngleRight } from 'react-icons/fa';
|
import CurrentImagePreview from './CurrentImagePreview';
|
||||||
import { selectNextImage, selectPrevImage } from './gallerySlice';
|
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the current image if there is one, plus associated actions.
|
* Displays the current image if there is one, plus associated actions.
|
||||||
*/
|
*/
|
||||||
const CurrentImageDisplay = () => {
|
const CurrentImageDisplay = () => {
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
|
|
||||||
useState<boolean>(false);
|
|
||||||
|
|
||||||
const { currentImage, intermediateImage } = useAppSelector(
|
const { currentImage, intermediateImage } = useAppSelector(
|
||||||
(state: RootState) => state.gallery
|
(state: RootState) => state.gallery
|
||||||
);
|
);
|
||||||
|
|
||||||
const [shouldShowImageDetails, setShouldShowImageDetails] =
|
const shouldShowImageDetails = useAppSelector(
|
||||||
useState<boolean>(false);
|
(state: RootState) => state.options.shouldShowImageDetails
|
||||||
|
);
|
||||||
|
|
||||||
const imageToDisplay = intermediateImage || currentImage;
|
const imageToDisplay = intermediateImage || currentImage;
|
||||||
|
|
||||||
const handleCurrentImagePreviewMouseOver = () => {
|
|
||||||
setShouldShowNextPrevButtons(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCurrentImagePreviewMouseOut = () => {
|
|
||||||
setShouldShowNextPrevButtons(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClickPrevButton = () => {
|
|
||||||
dispatch(selectPrevImage());
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClickNextButton = () => {
|
|
||||||
dispatch(selectNextImage());
|
|
||||||
};
|
|
||||||
|
|
||||||
return imageToDisplay ? (
|
return imageToDisplay ? (
|
||||||
<div className="current-image-display">
|
<div className="current-image-display">
|
||||||
<div className="current-image-tools">
|
<div className="current-image-tools">
|
||||||
<CurrentImageButtons
|
<CurrentImageButtons image={imageToDisplay} />
|
||||||
|
</div>
|
||||||
|
<CurrentImagePreview imageToDisplay={imageToDisplay} />
|
||||||
|
{shouldShowImageDetails && (
|
||||||
|
<ImageMetadataViewer
|
||||||
image={imageToDisplay}
|
image={imageToDisplay}
|
||||||
shouldShowImageDetails={shouldShowImageDetails}
|
styleClass="current-image-metadata"
|
||||||
setShouldShowImageDetails={setShouldShowImageDetails}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
<div className="current-image-preview">
|
|
||||||
<Image
|
|
||||||
src={imageToDisplay.url}
|
|
||||||
fit="contain"
|
|
||||||
maxWidth={'100%'}
|
|
||||||
maxHeight={'100%'}
|
|
||||||
/>
|
|
||||||
{shouldShowImageDetails && (
|
|
||||||
<div className="current-image-metadata-viewer">
|
|
||||||
<ImageMetadataViewer image={imageToDisplay} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!shouldShowImageDetails && (
|
|
||||||
<div className="current-image-next-prev-buttons">
|
|
||||||
<div
|
|
||||||
className="next-prev-button-trigger-area prev-button-trigger-area"
|
|
||||||
onMouseOver={handleCurrentImagePreviewMouseOver}
|
|
||||||
onMouseOut={handleCurrentImagePreviewMouseOut}
|
|
||||||
>
|
|
||||||
{shouldShowNextPrevButtons && (
|
|
||||||
<IconButton
|
|
||||||
aria-label="Previous image"
|
|
||||||
icon={<FaAngleLeft className="next-prev-button" />}
|
|
||||||
variant="unstyled"
|
|
||||||
onClick={handleClickPrevButton}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="next-prev-button-trigger-area next-button-trigger-area"
|
|
||||||
onMouseOver={handleCurrentImagePreviewMouseOver}
|
|
||||||
onMouseOut={handleCurrentImagePreviewMouseOut}
|
|
||||||
>
|
|
||||||
{shouldShowNextPrevButtons && (
|
|
||||||
<IconButton
|
|
||||||
aria-label="Next image"
|
|
||||||
icon={<FaAngleRight className="next-prev-button" />}
|
|
||||||
variant="unstyled"
|
|
||||||
onClick={handleClickNextButton}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="current-image-display-placeholder">
|
<div className="current-image-display-placeholder">
|
||||||
|
105
frontend/src/features/gallery/CurrentImagePreview.tsx
Normal file
105
frontend/src/features/gallery/CurrentImagePreview.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { IconButton, Image } from '@chakra-ui/react';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { FaAngleLeft, FaAngleRight } from 'react-icons/fa';
|
||||||
|
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
||||||
|
import { GalleryState, selectNextImage, selectPrevImage } from './gallerySlice';
|
||||||
|
import * as InvokeAI from '../../app/invokeai';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const imagesSelector = createSelector(
|
||||||
|
(state: RootState) => state.gallery,
|
||||||
|
(gallery: GalleryState) => {
|
||||||
|
const currentImageIndex = gallery.images.findIndex(
|
||||||
|
(i) => i.uuid === gallery?.currentImage?.uuid
|
||||||
|
);
|
||||||
|
const imagesLength = gallery.images.length;
|
||||||
|
return {
|
||||||
|
isOnFirstImage: currentImageIndex === 0,
|
||||||
|
isOnLastImage:
|
||||||
|
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
interface CurrentImagePreviewProps {
|
||||||
|
imageToDisplay: InvokeAI.Image;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CurrentImagePreview(props: CurrentImagePreviewProps) {
|
||||||
|
const { imageToDisplay } = props;
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const { isOnFirstImage, isOnLastImage } = useAppSelector(imagesSelector);
|
||||||
|
|
||||||
|
const shouldShowImageDetails = useAppSelector(
|
||||||
|
(state: RootState) => state.options.shouldShowImageDetails
|
||||||
|
);
|
||||||
|
|
||||||
|
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
|
||||||
|
useState<boolean>(false);
|
||||||
|
|
||||||
|
const handleCurrentImagePreviewMouseOver = () => {
|
||||||
|
setShouldShowNextPrevButtons(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCurrentImagePreviewMouseOut = () => {
|
||||||
|
setShouldShowNextPrevButtons(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickPrevButton = () => {
|
||||||
|
dispatch(selectPrevImage());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickNextButton = () => {
|
||||||
|
dispatch(selectNextImage());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="current-image-preview">
|
||||||
|
<Image
|
||||||
|
src={imageToDisplay.url}
|
||||||
|
fit="contain"
|
||||||
|
maxWidth={'100%'}
|
||||||
|
maxHeight={'100%'}
|
||||||
|
/>
|
||||||
|
{!shouldShowImageDetails && (
|
||||||
|
<div className="current-image-next-prev-buttons">
|
||||||
|
<div
|
||||||
|
className="next-prev-button-trigger-area prev-button-trigger-area"
|
||||||
|
onMouseOver={handleCurrentImagePreviewMouseOver}
|
||||||
|
onMouseOut={handleCurrentImagePreviewMouseOut}
|
||||||
|
>
|
||||||
|
{shouldShowNextPrevButtons && !isOnFirstImage && (
|
||||||
|
<IconButton
|
||||||
|
aria-label="Previous image"
|
||||||
|
icon={<FaAngleLeft className="next-prev-button" />}
|
||||||
|
variant="unstyled"
|
||||||
|
onClick={handleClickPrevButton}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="next-prev-button-trigger-area next-button-trigger-area"
|
||||||
|
onMouseOver={handleCurrentImagePreviewMouseOver}
|
||||||
|
onMouseOut={handleCurrentImagePreviewMouseOut}
|
||||||
|
>
|
||||||
|
{shouldShowNextPrevButtons && !isOnLastImage && (
|
||||||
|
<IconButton
|
||||||
|
aria-label="Next image"
|
||||||
|
icon={<FaAngleRight className="next-prev-button" />}
|
||||||
|
variant="unstyled"
|
||||||
|
onClick={handleClickNextButton}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -7,12 +7,17 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useAppDispatch } from '../../app/store';
|
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
||||||
import { setCurrentImage } from './gallerySlice';
|
import { setCurrentImage } from './gallerySlice';
|
||||||
import { FaCheck, FaSeedling, FaTrashAlt } from 'react-icons/fa';
|
import { FaCheck, FaImage, FaSeedling, FaTrashAlt } from 'react-icons/fa';
|
||||||
import DeleteImageModal from './DeleteImageModal';
|
import DeleteImageModal from './DeleteImageModal';
|
||||||
import { memo, SyntheticEvent, useState } from 'react';
|
import { memo, SyntheticEvent, useState } from 'react';
|
||||||
import { setAllParameters, setSeed } from '../options/optionsSlice';
|
import {
|
||||||
|
setActiveTab,
|
||||||
|
setAllParameters,
|
||||||
|
setInitialImagePath,
|
||||||
|
setSeed,
|
||||||
|
} from '../options/optionsSlice';
|
||||||
import * as InvokeAI from '../../app/invokeai';
|
import * as InvokeAI from '../../app/invokeai';
|
||||||
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
||||||
|
|
||||||
@ -33,6 +38,10 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
const [isHovered, setIsHovered] = useState<boolean>(false);
|
const [isHovered, setIsHovered] = useState<boolean>(false);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const activeTab = useAppSelector(
|
||||||
|
(state: RootState) => state.options.activeTab
|
||||||
|
);
|
||||||
|
|
||||||
const checkColor = useColorModeValue('green.600', 'green.300');
|
const checkColor = useColorModeValue('green.600', 'green.300');
|
||||||
const bgColor = useColorModeValue('gray.200', 'gray.700');
|
const bgColor = useColorModeValue('gray.200', 'gray.700');
|
||||||
const bgGradient = useColorModeValue(
|
const bgGradient = useColorModeValue(
|
||||||
@ -56,6 +65,14 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
dispatch(setSeed(image.metadata.image.seed));
|
dispatch(setSeed(image.metadata.image.seed));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSetInitImage = (e: SyntheticEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
dispatch(setInitialImagePath(image.url));
|
||||||
|
if (activeTab !== 1) {
|
||||||
|
dispatch(setActiveTab(1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleClickImage = () => dispatch(setCurrentImage(image));
|
const handleClickImage = () => dispatch(setCurrentImage(image));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -131,6 +148,16 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
<Tooltip label="Send To Image To Image">
|
||||||
|
<IconButton
|
||||||
|
aria-label="Send To Image To Image"
|
||||||
|
icon={<FaImage />}
|
||||||
|
size="xs"
|
||||||
|
fontSize={16}
|
||||||
|
variant={'imageHoverIconButton'}
|
||||||
|
onClickCapture={handleSetInitImage}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
@use '../../../styles/Mixins/' as *;
|
||||||
|
|
||||||
|
.image-metadata-viewer {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: var(--metadata-bg-color);
|
||||||
|
overflow: scroll;
|
||||||
|
max-height: calc($app-content-height - 4rem);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-json-viewer {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin: 0 0.5rem 1rem 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
overflow-x: scroll;
|
||||||
|
word-break: break-all;
|
||||||
|
background-color: var(--metadata-json-bg-color);
|
||||||
|
}
|
@ -0,0 +1,360 @@
|
|||||||
|
import {
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
Heading,
|
||||||
|
IconButton,
|
||||||
|
Link,
|
||||||
|
Text,
|
||||||
|
Tooltip,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
||||||
|
import { useAppDispatch } from '../../../app/store';
|
||||||
|
import * as InvokeAI from '../../../app/invokeai';
|
||||||
|
import {
|
||||||
|
setCfgScale,
|
||||||
|
setGfpganStrength,
|
||||||
|
setHeight,
|
||||||
|
setImg2imgStrength,
|
||||||
|
setInitialImagePath,
|
||||||
|
setMaskPath,
|
||||||
|
setPrompt,
|
||||||
|
setSampler,
|
||||||
|
setSeed,
|
||||||
|
setSeedWeights,
|
||||||
|
setShouldFitToWidthHeight,
|
||||||
|
setSteps,
|
||||||
|
setUpscalingLevel,
|
||||||
|
setUpscalingStrength,
|
||||||
|
setWidth,
|
||||||
|
} from '../../options/optionsSlice';
|
||||||
|
import promptToString from '../../../common/util/promptToString';
|
||||||
|
import { seedWeightsToString } from '../../../common/util/seedWeightPairs';
|
||||||
|
import { FaCopy } from 'react-icons/fa';
|
||||||
|
|
||||||
|
type MetadataItemProps = {
|
||||||
|
isLink?: boolean;
|
||||||
|
label: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
value: number | string | boolean;
|
||||||
|
labelPosition?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to display an individual metadata item or parameter.
|
||||||
|
*/
|
||||||
|
const MetadataItem = ({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
onClick,
|
||||||
|
isLink,
|
||||||
|
labelPosition,
|
||||||
|
}: MetadataItemProps) => {
|
||||||
|
return (
|
||||||
|
<Flex gap={2}>
|
||||||
|
{onClick && (
|
||||||
|
<Tooltip label={`Recall ${label}`}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Use this parameter"
|
||||||
|
icon={<IoArrowUndoCircleOutline />}
|
||||||
|
size={'xs'}
|
||||||
|
variant={'ghost'}
|
||||||
|
fontSize={20}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
</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;
|
||||||
|
styleClass?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: I don't know if this is needed.
|
||||||
|
const memoEqualityCheck = (
|
||||||
|
prev: ImageMetadataViewerProps,
|
||||||
|
next: ImageMetadataViewerProps
|
||||||
|
) => prev.image.uuid === next.image.uuid;
|
||||||
|
|
||||||
|
// 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, styleClass }: ImageMetadataViewerProps) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
// const jsonBgColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.100');
|
||||||
|
|
||||||
|
const metadata = image?.metadata?.image || {};
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
postprocessing,
|
||||||
|
sampler,
|
||||||
|
prompt,
|
||||||
|
seed,
|
||||||
|
variations,
|
||||||
|
steps,
|
||||||
|
cfg_scale,
|
||||||
|
seamless,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
strength,
|
||||||
|
fit,
|
||||||
|
init_image_path,
|
||||||
|
mask_image_path,
|
||||||
|
orig_path,
|
||||||
|
scale,
|
||||||
|
} = metadata;
|
||||||
|
|
||||||
|
const metadataJSON = JSON.stringify(metadata, null, 2);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`image-metadata-viewer ${styleClass}`}>
|
||||||
|
<Flex gap={1} direction={'column'} width={'100%'}>
|
||||||
|
<Flex gap={2}>
|
||||||
|
<Text fontWeight={'semibold'}>File:</Text>
|
||||||
|
<Link href={image.url} isExternal>
|
||||||
|
{image.url}
|
||||||
|
<ExternalLinkIcon mx="2px" />
|
||||||
|
</Link>
|
||||||
|
</Flex>
|
||||||
|
{Object.keys(metadata).length > 0 ? (
|
||||||
|
<>
|
||||||
|
{type && <MetadataItem label="Generation type" value={type} />}
|
||||||
|
{['esrgan', 'gfpgan'].includes(type) && (
|
||||||
|
<MetadataItem label="Original image" value={orig_path} />
|
||||||
|
)}
|
||||||
|
{type === 'gfpgan' && strength !== undefined && (
|
||||||
|
<MetadataItem
|
||||||
|
label="Fix faces strength"
|
||||||
|
value={strength}
|
||||||
|
onClick={() => dispatch(setGfpganStrength(strength))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === 'esrgan' && scale !== undefined && (
|
||||||
|
<MetadataItem
|
||||||
|
label="Upscaling scale"
|
||||||
|
value={scale}
|
||||||
|
onClick={() => dispatch(setUpscalingLevel(scale))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === 'esrgan' && strength !== undefined && (
|
||||||
|
<MetadataItem
|
||||||
|
label="Upscaling strength"
|
||||||
|
value={strength}
|
||||||
|
onClick={() => dispatch(setUpscalingStrength(strength))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{prompt && (
|
||||||
|
<MetadataItem
|
||||||
|
label="Prompt"
|
||||||
|
labelPosition="top"
|
||||||
|
value={promptToString(prompt)}
|
||||||
|
onClick={() => dispatch(setPrompt(prompt))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{seed !== undefined && (
|
||||||
|
<MetadataItem
|
||||||
|
label="Seed"
|
||||||
|
value={seed}
|
||||||
|
onClick={() => dispatch(setSeed(seed))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{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(setWidth(seamless))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{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(setInitialImagePath(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 } = postprocess;
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={i}
|
||||||
|
pl={'2rem'}
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
} else if (postprocess.type === 'gfpgan') {
|
||||||
|
const { strength } = postprocess;
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={i}
|
||||||
|
pl={'2rem'}
|
||||||
|
gap={1}
|
||||||
|
direction={'column'}
|
||||||
|
>
|
||||||
|
<Text size={'md'}>{`${
|
||||||
|
i + 1
|
||||||
|
}: Face restoration (GFPGAN)`}</Text>
|
||||||
|
|
||||||
|
<MetadataItem
|
||||||
|
label="Strength"
|
||||||
|
value={strength}
|
||||||
|
onClick={() =>
|
||||||
|
dispatch(setGfpganStrength(strength))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Flex gap={2} direction={'column'}>
|
||||||
|
<Flex gap={2}>
|
||||||
|
<Tooltip label={`Copy metadata JSON`}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Copy metadata JSON"
|
||||||
|
icon={<FaCopy />}
|
||||||
|
size={'xs'}
|
||||||
|
variant={'ghost'}
|
||||||
|
fontSize={14}
|
||||||
|
onClick={() =>
|
||||||
|
navigator.clipboard.writeText(metadataJSON)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Text fontWeight={'semibold'}>Metadata JSON:</Text>
|
||||||
|
</Flex>
|
||||||
|
<div className={'image-json-viewer'}>
|
||||||
|
<pre>{metadataJSON}</pre>
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Center width={'100%'} pt={10}>
|
||||||
|
<Text fontSize={'lg'} fontWeight="semibold">
|
||||||
|
No metadata available
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
memoEqualityCheck
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ImageMetadataViewer;
|
@ -1,338 +0,0 @@
|
|||||||
import {
|
|
||||||
Center,
|
|
||||||
Flex,
|
|
||||||
Heading,
|
|
||||||
IconButton,
|
|
||||||
Link,
|
|
||||||
Text,
|
|
||||||
Tooltip,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
|
||||||
import { useAppDispatch } from '../../app/store';
|
|
||||||
import * as InvokeAI from '../../app/invokeai';
|
|
||||||
import {
|
|
||||||
setCfgScale,
|
|
||||||
setGfpganStrength,
|
|
||||||
setHeight,
|
|
||||||
setImg2imgStrength,
|
|
||||||
setInitialImagePath,
|
|
||||||
setMaskPath,
|
|
||||||
setPrompt,
|
|
||||||
setSampler,
|
|
||||||
setSeed,
|
|
||||||
setSeedWeights,
|
|
||||||
setShouldFitToWidthHeight,
|
|
||||||
setSteps,
|
|
||||||
setUpscalingLevel,
|
|
||||||
setUpscalingStrength,
|
|
||||||
setWidth,
|
|
||||||
} from '../options/optionsSlice';
|
|
||||||
import promptToString from '../../common/util/promptToString';
|
|
||||||
import { seedWeightsToString } from '../../common/util/seedWeightPairs';
|
|
||||||
import { FaCopy } from 'react-icons/fa';
|
|
||||||
|
|
||||||
type MetadataItemProps = {
|
|
||||||
isLink?: boolean;
|
|
||||||
label: string;
|
|
||||||
onClick?: () => void;
|
|
||||||
value: number | string | boolean;
|
|
||||||
labelPosition?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component to display an individual metadata item or parameter.
|
|
||||||
*/
|
|
||||||
const MetadataItem = ({
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
onClick,
|
|
||||||
isLink,
|
|
||||||
labelPosition,
|
|
||||||
}: MetadataItemProps) => {
|
|
||||||
return (
|
|
||||||
<Flex gap={2}>
|
|
||||||
{onClick && (
|
|
||||||
<Tooltip label={`Recall ${label}`}>
|
|
||||||
<IconButton
|
|
||||||
aria-label="Use this parameter"
|
|
||||||
icon={<IoArrowUndoCircleOutline />}
|
|
||||||
size={'xs'}
|
|
||||||
variant={'ghost'}
|
|
||||||
fontSize={20}
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
<Flex direction={labelPosition ? 'column' : 'row'}>
|
|
||||||
<Text fontWeight={'semibold'} whiteSpace={'nowrap'} 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.uuid === next.image.uuid;
|
|
||||||
|
|
||||||
// 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 jsonBgColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.100');
|
|
||||||
|
|
||||||
const metadata = image?.metadata?.image || {};
|
|
||||||
const {
|
|
||||||
type,
|
|
||||||
postprocessing,
|
|
||||||
sampler,
|
|
||||||
prompt,
|
|
||||||
seed,
|
|
||||||
variations,
|
|
||||||
steps,
|
|
||||||
cfg_scale,
|
|
||||||
seamless,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
strength,
|
|
||||||
fit,
|
|
||||||
init_image_path,
|
|
||||||
mask_image_path,
|
|
||||||
orig_path,
|
|
||||||
scale,
|
|
||||||
} = metadata;
|
|
||||||
|
|
||||||
const metadataJSON = JSON.stringify(metadata, null, 2);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex gap={1} direction={'column'} width={'100%'}>
|
|
||||||
<Flex gap={2}>
|
|
||||||
<Text fontWeight={'semibold'}>File:</Text>
|
|
||||||
<Link href={image.url} isExternal>
|
|
||||||
{image.url}
|
|
||||||
<ExternalLinkIcon mx="2px" />
|
|
||||||
</Link>
|
|
||||||
</Flex>
|
|
||||||
{Object.keys(metadata).length > 0 ? (
|
|
||||||
<>
|
|
||||||
{type && <MetadataItem label="Generation type" value={type} />}
|
|
||||||
{['esrgan', 'gfpgan'].includes(type) && (
|
|
||||||
<MetadataItem label="Original image" value={orig_path} />
|
|
||||||
)}
|
|
||||||
{type === 'gfpgan' && strength !== undefined && (
|
|
||||||
<MetadataItem
|
|
||||||
label="Fix faces strength"
|
|
||||||
value={strength}
|
|
||||||
onClick={() => dispatch(setGfpganStrength(strength))}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{type === 'esrgan' && scale !== undefined && (
|
|
||||||
<MetadataItem
|
|
||||||
label="Upscaling scale"
|
|
||||||
value={scale}
|
|
||||||
onClick={() => dispatch(setUpscalingLevel(scale))}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{type === 'esrgan' && strength !== undefined && (
|
|
||||||
<MetadataItem
|
|
||||||
label="Upscaling strength"
|
|
||||||
value={strength}
|
|
||||||
onClick={() => dispatch(setUpscalingStrength(strength))}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{prompt && (
|
|
||||||
<MetadataItem
|
|
||||||
label="Prompt"
|
|
||||||
labelPosition="top"
|
|
||||||
value={promptToString(prompt)}
|
|
||||||
onClick={() => dispatch(setPrompt(prompt))}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{seed !== undefined && (
|
|
||||||
<MetadataItem
|
|
||||||
label="Seed"
|
|
||||||
value={seed}
|
|
||||||
onClick={() => dispatch(setSeed(seed))}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{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(setWidth(seamless))}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{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(setInitialImagePath(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 } = postprocess;
|
|
||||||
return (
|
|
||||||
<Flex key={i} pl={'2rem'} 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))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
} else if (postprocess.type === 'gfpgan') {
|
|
||||||
const { strength } = postprocess;
|
|
||||||
return (
|
|
||||||
<Flex key={i} pl={'2rem'} gap={1} direction={'column'}>
|
|
||||||
<Text size={'md'}>{`${
|
|
||||||
i + 1
|
|
||||||
}: Face restoration (GFPGAN)`}</Text>
|
|
||||||
|
|
||||||
<MetadataItem
|
|
||||||
label="Strength"
|
|
||||||
value={strength}
|
|
||||||
onClick={() => dispatch(setGfpganStrength(strength))}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Flex gap={2} direction={'column'}>
|
|
||||||
<Flex gap={2}>
|
|
||||||
<Tooltip label={`Copy metadata JSON`}>
|
|
||||||
<IconButton
|
|
||||||
aria-label="Copy metadata JSON"
|
|
||||||
icon={<FaCopy />}
|
|
||||||
size={'xs'}
|
|
||||||
variant={'ghost'}
|
|
||||||
fontSize={14}
|
|
||||||
onClick={() => navigator.clipboard.writeText(metadataJSON)}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Text fontWeight={'semibold'}>Metadata JSON:</Text>
|
|
||||||
</Flex>
|
|
||||||
<div className={'current-image-json-viewer'}>
|
|
||||||
<pre>{metadataJSON}</pre>
|
|
||||||
</div>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Center width={'100%'} pt={10}>
|
|
||||||
<Text fontSize={'lg'} fontWeight="semibold">
|
|
||||||
No metadata available
|
|
||||||
</Text>
|
|
||||||
</Center>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}, memoEqualityCheck);
|
|
||||||
|
|
||||||
export default ImageMetadataViewer;
|
|
@ -8,7 +8,7 @@ import React, { ReactElement } from 'react';
|
|||||||
import { Feature } from '../../../app/features';
|
import { Feature } from '../../../app/features';
|
||||||
import GuideIcon from '../../../common/components/GuideIcon';
|
import GuideIcon from '../../../common/components/GuideIcon';
|
||||||
|
|
||||||
interface InvokeAccordionItemProps {
|
export interface InvokeAccordionItemProps {
|
||||||
header: ReactElement;
|
header: ReactElement;
|
||||||
feature: Feature;
|
feature: Feature;
|
||||||
options: ReactElement;
|
options: ReactElement;
|
||||||
|
@ -19,7 +19,7 @@ export default function ImageFit() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label="Fit initial image to output size"
|
label="Fit Initial Image To Output Size"
|
||||||
isChecked={shouldFitToWidthHeight}
|
isChecked={shouldFitToWidthHeight}
|
||||||
onChange={handleChangeFit}
|
onChange={handleChangeFit}
|
||||||
/>
|
/>
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
import IAISwitch from '../../../../common/components/IAISwitch';
|
import IAISwitch from '../../../../common/components/IAISwitch';
|
||||||
import { setShouldUseInitImage } from '../../optionsSlice';
|
import { setShouldUseInitImage } from '../../optionsSlice';
|
||||||
|
|
||||||
export default function ImageToImage() {
|
export default function ImageToImageAccordion() {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const initialImagePath = useAppSelector(
|
const initialImagePath = useAppSelector(
|
@ -7,7 +7,13 @@ import {
|
|||||||
import IAINumberInput from '../../../../common/components/IAINumberInput';
|
import IAINumberInput from '../../../../common/components/IAINumberInput';
|
||||||
import { setImg2imgStrength } from '../../optionsSlice';
|
import { setImg2imgStrength } from '../../optionsSlice';
|
||||||
|
|
||||||
export default function ImageToImageStrength() {
|
interface ImageToImageStrengthProps {
|
||||||
|
label?: string;
|
||||||
|
styleClass?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ImageToImageStrength(props: ImageToImageStrengthProps) {
|
||||||
|
const { label = 'Strength', styleClass } = props;
|
||||||
const img2imgStrength = useAppSelector(
|
const img2imgStrength = useAppSelector(
|
||||||
(state: RootState) => state.options.img2imgStrength
|
(state: RootState) => state.options.img2imgStrength
|
||||||
);
|
);
|
||||||
@ -18,7 +24,7 @@ export default function ImageToImageStrength() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAINumberInput
|
<IAINumberInput
|
||||||
label="Strength"
|
label={label}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
min={0.01}
|
min={0.01}
|
||||||
max={0.99}
|
max={0.99}
|
||||||
@ -26,6 +32,7 @@ export default function ImageToImageStrength() {
|
|||||||
value={img2imgStrength}
|
value={img2imgStrength}
|
||||||
width="90px"
|
width="90px"
|
||||||
isInteger={false}
|
isInteger={false}
|
||||||
|
styleClass={styleClass}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ type ImageUploaderProps = {
|
|||||||
* Callback to handle a file being rejected.
|
* Callback to handle a file being rejected.
|
||||||
*/
|
*/
|
||||||
fileRejectionCallback: (rejection: FileRejection) => void;
|
fileRejectionCallback: (rejection: FileRejection) => void;
|
||||||
|
// Styling
|
||||||
|
styleClass?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,6 +27,7 @@ const ImageUploader = ({
|
|||||||
children,
|
children,
|
||||||
fileAcceptedCallback,
|
fileAcceptedCallback,
|
||||||
fileRejectionCallback,
|
fileRejectionCallback,
|
||||||
|
styleClass,
|
||||||
}: ImageUploaderProps) => {
|
}: ImageUploaderProps) => {
|
||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
(acceptedFiles: Array<File>, fileRejections: Array<FileRejection>) => {
|
(acceptedFiles: Array<File>, fileRejections: Array<FileRejection>) => {
|
||||||
@ -52,7 +55,7 @@ const ImageUploader = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box {...getRootProps()} flexGrow={3}>
|
<Box {...getRootProps()} flexGrow={3} className={`${styleClass}`}>
|
||||||
<input {...getInputProps({ multiple: false })} />
|
<input {...getInputProps({ multiple: false })} />
|
||||||
{cloneElement(children, {
|
{cloneElement(children, {
|
||||||
onClick: handleClickUploadIcon,
|
onClick: handleClickUploadIcon,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import MainAdvancedOptions from './MainAdvancedOptions';
|
|
||||||
import MainCFGScale from './MainCFGScale';
|
import MainCFGScale from './MainCFGScale';
|
||||||
import MainHeight from './MainHeight';
|
import MainHeight from './MainHeight';
|
||||||
import MainIterations from './MainIterations';
|
import MainIterations from './MainIterations';
|
||||||
@ -23,7 +22,6 @@ export default function MainOptions() {
|
|||||||
<MainHeight />
|
<MainHeight />
|
||||||
<MainSampler />
|
<MainSampler />
|
||||||
</div>
|
</div>
|
||||||
<MainAdvancedOptions />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,34 +1,25 @@
|
|||||||
import {
|
import { Accordion, ExpandedIndex } from '@chakra-ui/react';
|
||||||
Box,
|
|
||||||
Accordion,
|
|
||||||
ExpandedIndex,
|
|
||||||
// ExpandedIndex,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
|
|
||||||
// import { RootState } from '../../app/store';
|
|
||||||
// import { useAppDispatch, useAppSelector } from '../../app/store';
|
|
||||||
|
|
||||||
// import { setOpenAccordions } from '../system/systemSlice';
|
|
||||||
|
|
||||||
import OutputOptions from './OutputOptions';
|
|
||||||
import ImageToImageOptions from './AdvancedOptions/ImageToImage/ImageToImageOptions';
|
|
||||||
import { Feature } from '../../app/features';
|
|
||||||
import SeedOptions from './AdvancedOptions/Seed/SeedOptions';
|
|
||||||
import Upscale from './AdvancedOptions/Upscale/Upscale';
|
|
||||||
import UpscaleOptions from './AdvancedOptions/Upscale/UpscaleOptions';
|
|
||||||
import FaceRestore from './AdvancedOptions/FaceRestore/FaceRestore';
|
|
||||||
import FaceRestoreOptions from './AdvancedOptions/FaceRestore/FaceRestoreOptions';
|
|
||||||
import ImageToImage from './AdvancedOptions/ImageToImage/ImageToImage';
|
|
||||||
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
||||||
import { setOpenAccordions } from '../system/systemSlice';
|
import { setOpenAccordions } from '../system/systemSlice';
|
||||||
import InvokeAccordionItem from './AccordionItems/InvokeAccordionItem';
|
import InvokeAccordionItem, {
|
||||||
import Variations from './AdvancedOptions/Variations/Variations';
|
InvokeAccordionItemProps,
|
||||||
import VariationsOptions from './AdvancedOptions/Variations/VariationsOptions';
|
} from './AccordionItems/InvokeAccordionItem';
|
||||||
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
|
type OptionsAccordionType = {
|
||||||
|
[optionAccordionKey: string]: InvokeAccordionItemProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OptionAccordionsType = {
|
||||||
|
accordionInfo: OptionsAccordionType;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main container for generation and processing parameters.
|
* Main container for generation and processing parameters.
|
||||||
*/
|
*/
|
||||||
const OptionsAccordion = () => {
|
const OptionsAccordion = (props: OptionAccordionsType) => {
|
||||||
|
const { accordionInfo } = props;
|
||||||
|
|
||||||
const openAccordions = useAppSelector(
|
const openAccordions = useAppSelector(
|
||||||
(state: RootState) => state.system.openAccordions
|
(state: RootState) => state.system.openAccordions
|
||||||
);
|
);
|
||||||
@ -41,6 +32,23 @@ const OptionsAccordion = () => {
|
|||||||
const handleChangeAccordionState = (openAccordions: ExpandedIndex) =>
|
const handleChangeAccordionState = (openAccordions: ExpandedIndex) =>
|
||||||
dispatch(setOpenAccordions(openAccordions));
|
dispatch(setOpenAccordions(openAccordions));
|
||||||
|
|
||||||
|
const renderAccordions = () => {
|
||||||
|
const accordionsToRender: ReactElement[] = [];
|
||||||
|
if (accordionInfo) {
|
||||||
|
Object.keys(accordionInfo).forEach((key) => {
|
||||||
|
accordionsToRender.push(
|
||||||
|
<InvokeAccordionItem
|
||||||
|
key={key}
|
||||||
|
header={accordionInfo[key as keyof typeof accordionInfo].header}
|
||||||
|
feature={accordionInfo[key as keyof typeof accordionInfo].feature}
|
||||||
|
options={accordionInfo[key as keyof typeof accordionInfo].options}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return accordionsToRender;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion
|
<Accordion
|
||||||
defaultIndex={openAccordions}
|
defaultIndex={openAccordions}
|
||||||
@ -49,49 +57,7 @@ const OptionsAccordion = () => {
|
|||||||
onChange={handleChangeAccordionState}
|
onChange={handleChangeAccordionState}
|
||||||
className="advanced-settings"
|
className="advanced-settings"
|
||||||
>
|
>
|
||||||
<InvokeAccordionItem
|
{renderAccordions()}
|
||||||
header={
|
|
||||||
<Box flex="1" textAlign="left">
|
|
||||||
Seed
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
feature={Feature.SEED}
|
|
||||||
options={<SeedOptions />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InvokeAccordionItem
|
|
||||||
header={<Variations />}
|
|
||||||
feature={Feature.VARIATIONS}
|
|
||||||
options={<VariationsOptions />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InvokeAccordionItem
|
|
||||||
header={<FaceRestore />}
|
|
||||||
feature={Feature.FACE_CORRECTION}
|
|
||||||
options={<FaceRestoreOptions />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InvokeAccordionItem
|
|
||||||
header={<Upscale />}
|
|
||||||
feature={Feature.UPSCALE}
|
|
||||||
options={<UpscaleOptions />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InvokeAccordionItem
|
|
||||||
header={<ImageToImage />}
|
|
||||||
feature={Feature.IMAGE_TO_IMAGE}
|
|
||||||
options={<ImageToImageOptions />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InvokeAccordionItem
|
|
||||||
header={
|
|
||||||
<Box flex="1" textAlign="left">
|
|
||||||
Other
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
feature={Feature.OTHER}
|
|
||||||
options={<OutputOptions />}
|
|
||||||
/>
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,7 @@ export interface OptionsState {
|
|||||||
upscalingLevel: UpscalingLevel;
|
upscalingLevel: UpscalingLevel;
|
||||||
upscalingStrength: number;
|
upscalingStrength: number;
|
||||||
shouldUseInitImage: boolean;
|
shouldUseInitImage: boolean;
|
||||||
initialImagePath: string;
|
initialImagePath: string | null;
|
||||||
maskPath: string;
|
maskPath: string;
|
||||||
seamless: boolean;
|
seamless: boolean;
|
||||||
shouldFitToWidthHeight: boolean;
|
shouldFitToWidthHeight: boolean;
|
||||||
@ -33,6 +33,8 @@ export interface OptionsState {
|
|||||||
shouldRunGFPGAN: boolean;
|
shouldRunGFPGAN: boolean;
|
||||||
shouldRandomizeSeed: boolean;
|
shouldRandomizeSeed: boolean;
|
||||||
showAdvancedOptions: boolean;
|
showAdvancedOptions: boolean;
|
||||||
|
activeTab: number;
|
||||||
|
shouldShowImageDetails: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialOptionsState: OptionsState = {
|
const initialOptionsState: OptionsState = {
|
||||||
@ -49,7 +51,7 @@ const initialOptionsState: OptionsState = {
|
|||||||
seamless: false,
|
seamless: false,
|
||||||
shouldUseInitImage: false,
|
shouldUseInitImage: false,
|
||||||
img2imgStrength: 0.75,
|
img2imgStrength: 0.75,
|
||||||
initialImagePath: '',
|
initialImagePath: null,
|
||||||
maskPath: '',
|
maskPath: '',
|
||||||
shouldFitToWidthHeight: true,
|
shouldFitToWidthHeight: true,
|
||||||
shouldGenerateVariations: false,
|
shouldGenerateVariations: false,
|
||||||
@ -62,6 +64,8 @@ const initialOptionsState: OptionsState = {
|
|||||||
gfpganStrength: 0.8,
|
gfpganStrength: 0.8,
|
||||||
shouldRandomizeSeed: true,
|
shouldRandomizeSeed: true,
|
||||||
showAdvancedOptions: true,
|
showAdvancedOptions: true,
|
||||||
|
activeTab: 0,
|
||||||
|
shouldShowImageDetails: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState: OptionsState = initialOptionsState;
|
const initialState: OptionsState = initialOptionsState;
|
||||||
@ -121,7 +125,7 @@ export const optionsSlice = createSlice({
|
|||||||
setShouldUseInitImage: (state, action: PayloadAction<boolean>) => {
|
setShouldUseInitImage: (state, action: PayloadAction<boolean>) => {
|
||||||
state.shouldUseInitImage = action.payload;
|
state.shouldUseInitImage = action.payload;
|
||||||
},
|
},
|
||||||
setInitialImagePath: (state, action: PayloadAction<string>) => {
|
setInitialImagePath: (state, action: PayloadAction<string | null>) => {
|
||||||
const newInitialImagePath = action.payload;
|
const newInitialImagePath = action.payload;
|
||||||
state.shouldUseInitImage = newInitialImagePath ? true : false;
|
state.shouldUseInitImage = newInitialImagePath ? true : false;
|
||||||
state.initialImagePath = newInitialImagePath;
|
state.initialImagePath = newInitialImagePath;
|
||||||
@ -271,6 +275,12 @@ export const optionsSlice = createSlice({
|
|||||||
setShowAdvancedOptions: (state, action: PayloadAction<boolean>) => {
|
setShowAdvancedOptions: (state, action: PayloadAction<boolean>) => {
|
||||||
state.showAdvancedOptions = action.payload;
|
state.showAdvancedOptions = action.payload;
|
||||||
},
|
},
|
||||||
|
setActiveTab: (state, action: PayloadAction<number>) => {
|
||||||
|
state.activeTab = action.payload;
|
||||||
|
},
|
||||||
|
setShouldShowImageDetails: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.shouldShowImageDetails = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -305,6 +315,8 @@ export const {
|
|||||||
setShouldRunESRGAN,
|
setShouldRunESRGAN,
|
||||||
setShouldRandomizeSeed,
|
setShouldRandomizeSeed,
|
||||||
setShowAdvancedOptions,
|
setShowAdvancedOptions,
|
||||||
|
setActiveTab,
|
||||||
|
setShouldShowImageDetails,
|
||||||
} = optionsSlice.actions;
|
} = optionsSlice.actions;
|
||||||
|
|
||||||
export default optionsSlice.reducer;
|
export default optionsSlice.reducer;
|
||||||
|
@ -61,6 +61,16 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
|
|||||||
desc: 'Display the next image in the gallery',
|
desc: 'Display the next image in the gallery',
|
||||||
hotkey: 'Arrow right',
|
hotkey: 'Arrow right',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Change Tabs',
|
||||||
|
desc: 'Switch to another workspace',
|
||||||
|
hotkey: '1-6',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Theme Toggle',
|
||||||
|
desc: 'Switch between dark and light modes',
|
||||||
|
hotkey: 'Shift+D',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const renderHotkeyModalItems = () => {
|
const renderHotkeyModalItems = () => {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { IconButton, Link, useColorMode } from '@chakra-ui/react';
|
import { IconButton, Link, useColorMode } from '@chakra-ui/react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
import { FaSun, FaMoon, FaGithub } from 'react-icons/fa';
|
import { FaSun, FaMoon, FaGithub } from 'react-icons/fa';
|
||||||
import { MdHelp, MdKeyboard, MdSettings } from 'react-icons/md';
|
import { MdHelp, MdKeyboard, MdSettings } from 'react-icons/md';
|
||||||
@ -15,6 +16,14 @@ import StatusIndicator from './StatusIndicator';
|
|||||||
const SiteHeader = () => {
|
const SiteHeader = () => {
|
||||||
const { colorMode, toggleColorMode } = useColorMode();
|
const { colorMode, toggleColorMode } = useColorMode();
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'shift+d',
|
||||||
|
() => {
|
||||||
|
toggleColorMode();
|
||||||
|
},
|
||||||
|
[colorMode, toggleColorMode]
|
||||||
|
);
|
||||||
|
|
||||||
const colorModeIcon = colorMode == 'light' ? <FaMoon /> : <FaSun />;
|
const colorModeIcon = colorMode == 'light' ? <FaMoon /> : <FaSun />;
|
||||||
|
|
||||||
// Make FaMoon and FaSun icon apparent size consistent
|
// Make FaMoon and FaSun icon apparent size consistent
|
||||||
|
132
frontend/src/features/tabs/ImageToImage/ImageToImage.scss
Normal file
132
frontend/src/features/tabs/ImageToImage/ImageToImage.scss
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
@use '../../../styles/Mixins/' as *;
|
||||||
|
|
||||||
|
.image-to-image-workarea {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content auto max-content;
|
||||||
|
column-gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-to-image-panel {
|
||||||
|
display: grid;
|
||||||
|
row-gap: 1rem;
|
||||||
|
grid-auto-rows: max-content;
|
||||||
|
width: $options-bar-max-width;
|
||||||
|
height: $app-content-height;
|
||||||
|
overflow-y: scroll;
|
||||||
|
@include HideScrollbar;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-to-image-strength-main-option {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: none !important;
|
||||||
|
|
||||||
|
.number-input-entry {
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-to-image-display {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: var(--background-color-secondary);
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
.current-image-options {
|
||||||
|
grid-auto-columns: max-content;
|
||||||
|
justify-self: center;
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-to-image-single-preview {
|
||||||
|
display: grid;
|
||||||
|
column-gap: 0.5rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
place-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-to-image-dual-preview-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: 'img2img-preview';
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-to-image-dual-preview {
|
||||||
|
grid-area: img2img-preview;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
column-gap: 0.5rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
place-content: center;
|
||||||
|
|
||||||
|
.current-image-preview {
|
||||||
|
img {
|
||||||
|
height: calc($app-gallery-height - 2rem);
|
||||||
|
max-height: calc($app-gallery-height - 2rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.img2img-metadata {
|
||||||
|
grid-area: img2img-preview;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.init-image-preview {
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: 'init-image-content';
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
|
.init-image-preview-header {
|
||||||
|
grid-area: init-image-content;
|
||||||
|
z-index: 2;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto max-content;
|
||||||
|
height: max-content;
|
||||||
|
align-items: center;
|
||||||
|
align-self: start;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
padding: 0.2rem 0.6rem;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
background-color: var(--tab-hover-color);
|
||||||
|
width: max-content;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.init-image-image {
|
||||||
|
grid-area: init-image-content;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
object-fit: contain;
|
||||||
|
background-color: var(--img2img-img-bg-color);
|
||||||
|
width: auto;
|
||||||
|
height: calc($app-gallery-height - 2rem);
|
||||||
|
max-height: calc($app-gallery-height - 2rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-to-image-upload-btn {
|
||||||
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
height: $app-content-height;
|
||||||
|
|
||||||
|
button {
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
background-color: var(--background-color-secondary);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--img2img-img-bg-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
frontend/src/features/tabs/ImageToImage/ImageToImage.tsx
Normal file
15
frontend/src/features/tabs/ImageToImage/ImageToImage.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ImageGallery from '../../gallery/ImageGallery';
|
||||||
|
import ImageToImageDisplay from './ImageToImageDisplay';
|
||||||
|
|
||||||
|
import ImageToImagePanel from './ImageToImagePanel';
|
||||||
|
|
||||||
|
export default function ImageToImage() {
|
||||||
|
return (
|
||||||
|
<div className="image-to-image-workarea">
|
||||||
|
<ImageToImagePanel />
|
||||||
|
<ImageToImageDisplay />
|
||||||
|
<ImageGallery />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FaUpload } from 'react-icons/fa';
|
||||||
|
import { uploadInitialImage } from '../../../app/socketio/actions';
|
||||||
|
import { RootState, useAppSelector } from '../../../app/store';
|
||||||
|
import InvokeImageUploader from '../../../common/components/InvokeImageUploader';
|
||||||
|
import CurrentImageButtons from '../../gallery/CurrentImageButtons';
|
||||||
|
import CurrentImagePreview from '../../gallery/CurrentImagePreview';
|
||||||
|
import ImageMetadataViewer from '../../gallery/ImageMetaDataViewer/ImageMetadataViewer';
|
||||||
|
|
||||||
|
import InitImagePreview from './InitImagePreview';
|
||||||
|
|
||||||
|
export default function ImageToImageDisplay() {
|
||||||
|
const initialImagePath = useAppSelector(
|
||||||
|
(state: RootState) => state.options.initialImagePath
|
||||||
|
);
|
||||||
|
|
||||||
|
const { currentImage, intermediateImage } = useAppSelector(
|
||||||
|
(state: RootState) => state.gallery
|
||||||
|
);
|
||||||
|
|
||||||
|
const shouldShowImageDetails = useAppSelector(
|
||||||
|
(state: RootState) => state.options.shouldShowImageDetails
|
||||||
|
);
|
||||||
|
|
||||||
|
const imageToDisplay = intermediateImage || currentImage;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="image-to-image-display"
|
||||||
|
style={
|
||||||
|
imageToDisplay
|
||||||
|
? { gridAutoRows: 'max-content auto' }
|
||||||
|
: { gridAutoRows: 'auto' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{initialImagePath ? (
|
||||||
|
<>
|
||||||
|
{imageToDisplay ? (
|
||||||
|
<>
|
||||||
|
<CurrentImageButtons image={imageToDisplay} />
|
||||||
|
<div className="image-to-image-dual-preview-container">
|
||||||
|
<div className="image-to-image-dual-preview">
|
||||||
|
<InitImagePreview />
|
||||||
|
<div className="image-to-image-current-image-display">
|
||||||
|
<CurrentImagePreview imageToDisplay={imageToDisplay} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{shouldShowImageDetails && (
|
||||||
|
<ImageMetadataViewer
|
||||||
|
image={imageToDisplay}
|
||||||
|
styleClass="img2img-metadata"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="image-to-image-single-preview">
|
||||||
|
<InitImagePreview />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="upload-image">
|
||||||
|
<InvokeImageUploader
|
||||||
|
label="Upload or Drop Image Here"
|
||||||
|
icon={<FaUpload />}
|
||||||
|
styleClass="image-to-image-upload-btn"
|
||||||
|
dispatcher={uploadInitialImage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { Feature } from '../../../app/features';
|
||||||
|
import { RootState, useAppSelector } from '../../../app/store';
|
||||||
|
import FaceRestore from '../../options/AdvancedOptions/FaceRestore/FaceRestore';
|
||||||
|
import FaceRestoreOptions from '../../options/AdvancedOptions/FaceRestore/FaceRestoreOptions';
|
||||||
|
import ImageFit from '../../options/AdvancedOptions/ImageToImage/ImageFit';
|
||||||
|
import ImageToImageStrength from '../../options/AdvancedOptions/ImageToImage/ImageToImageStrength';
|
||||||
|
import SeedOptions from '../../options/AdvancedOptions/Seed/SeedOptions';
|
||||||
|
import Upscale from '../../options/AdvancedOptions/Upscale/Upscale';
|
||||||
|
import UpscaleOptions from '../../options/AdvancedOptions/Upscale/UpscaleOptions';
|
||||||
|
import Variations from '../../options/AdvancedOptions/Variations/Variations';
|
||||||
|
import VariationsOptions from '../../options/AdvancedOptions/Variations/VariationsOptions';
|
||||||
|
import MainAdvancedOptions from '../../options/MainOptions/MainAdvancedOptions';
|
||||||
|
import MainOptions from '../../options/MainOptions/MainOptions';
|
||||||
|
import OptionsAccordion from '../../options/OptionsAccordion';
|
||||||
|
import OutputOptions from '../../options/OutputOptions';
|
||||||
|
import ProcessButtons from '../../options/ProcessButtons/ProcessButtons';
|
||||||
|
import PromptInput from '../../options/PromptInput/PromptInput';
|
||||||
|
|
||||||
|
export default function ImageToImagePanel() {
|
||||||
|
const showAdvancedOptions = useAppSelector(
|
||||||
|
(state: RootState) => state.options.showAdvancedOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
const imageToImageAccordions = {
|
||||||
|
seed: {
|
||||||
|
header: (
|
||||||
|
<Box flex="1" textAlign="left">
|
||||||
|
Seed
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
feature: Feature.SEED,
|
||||||
|
options: <SeedOptions />,
|
||||||
|
},
|
||||||
|
variations: {
|
||||||
|
header: <Variations />,
|
||||||
|
feature: Feature.VARIATIONS,
|
||||||
|
options: <VariationsOptions />,
|
||||||
|
},
|
||||||
|
face_restore: {
|
||||||
|
header: <FaceRestore />,
|
||||||
|
feature: Feature.FACE_CORRECTION,
|
||||||
|
options: <FaceRestoreOptions />,
|
||||||
|
},
|
||||||
|
upscale: {
|
||||||
|
header: <Upscale />,
|
||||||
|
feature: Feature.UPSCALE,
|
||||||
|
options: <UpscaleOptions />,
|
||||||
|
},
|
||||||
|
other: {
|
||||||
|
header: (
|
||||||
|
<Box flex="1" textAlign="left">
|
||||||
|
Other
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
feature: Feature.OTHER,
|
||||||
|
options: <OutputOptions />,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="image-to-image-panel">
|
||||||
|
<PromptInput />
|
||||||
|
<ProcessButtons />
|
||||||
|
<MainOptions />
|
||||||
|
<ImageToImageStrength
|
||||||
|
label="Image To Image Strength"
|
||||||
|
styleClass="main-option-block image-to-image-strength-main-option"
|
||||||
|
/>
|
||||||
|
<ImageFit />
|
||||||
|
<MainAdvancedOptions />
|
||||||
|
{showAdvancedOptions ? (
|
||||||
|
<OptionsAccordion accordionInfo={imageToImageAccordions} />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
37
frontend/src/features/tabs/ImageToImage/InitImagePreview.tsx
Normal file
37
frontend/src/features/tabs/ImageToImage/InitImagePreview.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { IconButton, Image } from '@chakra-ui/react';
|
||||||
|
import React, { SyntheticEvent } from 'react';
|
||||||
|
import { MdClear } from 'react-icons/md';
|
||||||
|
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
||||||
|
import { setInitialImagePath } from '../../options/optionsSlice';
|
||||||
|
|
||||||
|
export default function InitImagePreview() {
|
||||||
|
const initialImagePath = useAppSelector(
|
||||||
|
(state: RootState) => state.options.initialImagePath
|
||||||
|
);
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleClickResetInitialImage = (e: SyntheticEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
dispatch(setInitialImagePath(null));
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="init-image-preview">
|
||||||
|
<div className="init-image-preview-header">
|
||||||
|
<h1>Initial Image</h1>
|
||||||
|
<IconButton
|
||||||
|
isDisabled={!initialImagePath}
|
||||||
|
size={'sm'}
|
||||||
|
aria-label={'Reset Initial Image'}
|
||||||
|
onClick={handleClickResetInitialImage}
|
||||||
|
icon={<MdClear />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{initialImagePath && (
|
||||||
|
<div className="init-image-image">
|
||||||
|
<Image fit={'contain'} src={initialImagePath} rounded={'md'} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
import { Image } from '@chakra-ui/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { RootState, useAppSelector } from '../../../app/store';
|
||||||
|
|
||||||
|
export default function InitialImageOverlay() {
|
||||||
|
const initialImagePath = useAppSelector(
|
||||||
|
(state: RootState) => state.options.initialImagePath
|
||||||
|
);
|
||||||
|
|
||||||
|
return initialImagePath ? (
|
||||||
|
<Image
|
||||||
|
fit={'contain'}
|
||||||
|
src={initialImagePath}
|
||||||
|
rounded={'md'}
|
||||||
|
className={'checkerboard'}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
import { Tab, TabPanel, TabPanels, Tabs, Tooltip } from '@chakra-ui/react';
|
import { Tab, TabPanel, TabPanels, Tabs, Tooltip } from '@chakra-ui/react';
|
||||||
|
import _ from 'lodash';
|
||||||
import React, { ReactElement } from 'react';
|
import React, { ReactElement } from 'react';
|
||||||
import { ImageToImageWIP } from '../../common/components/WorkInProgress/ImageToImageWIP';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
||||||
import InpaintingWIP from '../../common/components/WorkInProgress/InpaintingWIP';
|
import InpaintingWIP from '../../common/components/WorkInProgress/InpaintingWIP';
|
||||||
import NodesWIP from '../../common/components/WorkInProgress/NodesWIP';
|
import NodesWIP from '../../common/components/WorkInProgress/NodesWIP';
|
||||||
import OutpaintingWIP from '../../common/components/WorkInProgress/OutpaintingWIP';
|
import OutpaintingWIP from '../../common/components/WorkInProgress/OutpaintingWIP';
|
||||||
@ -11,41 +13,74 @@ import NodesIcon from '../../common/icons/NodesIcon';
|
|||||||
import OutpaintIcon from '../../common/icons/OutpaintIcon';
|
import OutpaintIcon from '../../common/icons/OutpaintIcon';
|
||||||
import PostprocessingIcon from '../../common/icons/PostprocessingIcon';
|
import PostprocessingIcon from '../../common/icons/PostprocessingIcon';
|
||||||
import TextToImageIcon from '../../common/icons/TextToImageIcon';
|
import TextToImageIcon from '../../common/icons/TextToImageIcon';
|
||||||
|
import { setActiveTab } from '../options/optionsSlice';
|
||||||
|
import ImageToImage from './ImageToImage/ImageToImage';
|
||||||
import TextToImage from './TextToImage/TextToImage';
|
import TextToImage from './TextToImage/TextToImage';
|
||||||
|
|
||||||
|
export const tab_dict = {
|
||||||
|
txt2img: {
|
||||||
|
title: <TextToImageIcon fill={'black'} boxSize={'2.5rem'} />,
|
||||||
|
panel: <TextToImage />,
|
||||||
|
tooltip: 'Text To Image',
|
||||||
|
},
|
||||||
|
img2img: {
|
||||||
|
title: <ImageToImageIcon fill={'black'} boxSize={'2.5rem'} />,
|
||||||
|
panel: <ImageToImage />,
|
||||||
|
tooltip: 'Image To Image',
|
||||||
|
},
|
||||||
|
inpainting: {
|
||||||
|
title: <InpaintIcon fill={'black'} boxSize={'2.5rem'} />,
|
||||||
|
panel: <InpaintingWIP />,
|
||||||
|
tooltip: 'Inpainting',
|
||||||
|
},
|
||||||
|
outpainting: {
|
||||||
|
title: <OutpaintIcon fill={'black'} boxSize={'2.5rem'} />,
|
||||||
|
panel: <OutpaintingWIP />,
|
||||||
|
tooltip: 'Outpainting',
|
||||||
|
},
|
||||||
|
nodes: {
|
||||||
|
title: <NodesIcon fill={'black'} boxSize={'2.5rem'} />,
|
||||||
|
panel: <NodesWIP />,
|
||||||
|
tooltip: 'Nodes',
|
||||||
|
},
|
||||||
|
postprocess: {
|
||||||
|
title: <PostprocessingIcon fill={'black'} boxSize={'2.5rem'} />,
|
||||||
|
panel: <PostProcessingWIP />,
|
||||||
|
tooltip: 'Post Processing',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tabMap = _.map(tab_dict, (tab, key) => key);
|
||||||
|
|
||||||
export default function InvokeTabs() {
|
export default function InvokeTabs() {
|
||||||
const tab_dict = {
|
const activeTab = useAppSelector(
|
||||||
txt2img: {
|
(state: RootState) => state.options.activeTab
|
||||||
title: <TextToImageIcon fill={'black'} boxSize={'2.5rem'} />,
|
);
|
||||||
panel: <TextToImage />,
|
const dispatch = useAppDispatch();
|
||||||
tooltip: 'Text To Image',
|
|
||||||
},
|
useHotkeys('1', () => {
|
||||||
img2img: {
|
dispatch(setActiveTab(0));
|
||||||
title: <ImageToImageIcon fill={'black'} boxSize={'2.5rem'} />,
|
});
|
||||||
panel: <ImageToImageWIP />,
|
|
||||||
tooltip: 'Image To Image',
|
useHotkeys('2', () => {
|
||||||
},
|
dispatch(setActiveTab(1));
|
||||||
inpainting: {
|
});
|
||||||
title: <InpaintIcon fill={'black'} boxSize={'2.5rem'} />,
|
|
||||||
panel: <InpaintingWIP />,
|
useHotkeys('3', () => {
|
||||||
tooltip: 'Inpainting',
|
dispatch(setActiveTab(2));
|
||||||
},
|
});
|
||||||
outpainting: {
|
|
||||||
title: <OutpaintIcon fill={'black'} boxSize={'2.5rem'} />,
|
useHotkeys('4', () => {
|
||||||
panel: <OutpaintingWIP />,
|
dispatch(setActiveTab(3));
|
||||||
tooltip: 'Outpainting',
|
});
|
||||||
},
|
|
||||||
nodes: {
|
useHotkeys('5', () => {
|
||||||
title: <NodesIcon fill={'black'} boxSize={'2.5rem'} />,
|
dispatch(setActiveTab(4));
|
||||||
panel: <NodesWIP />,
|
});
|
||||||
tooltip: 'Nodes',
|
|
||||||
},
|
useHotkeys('6', () => {
|
||||||
postprocess: {
|
dispatch(setActiveTab(5));
|
||||||
title: <PostprocessingIcon fill={'black'} boxSize={'2.5rem'} />,
|
});
|
||||||
panel: <PostProcessingWIP />,
|
|
||||||
tooltip: 'Post Processing',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderTabs = () => {
|
const renderTabs = () => {
|
||||||
const tabsToRender: ReactElement[] = [];
|
const tabsToRender: ReactElement[] = [];
|
||||||
@ -76,7 +111,16 @@ export default function InvokeTabs() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs className="app-tabs" variant={'unstyled'}>
|
<Tabs
|
||||||
|
isLazy
|
||||||
|
className="app-tabs"
|
||||||
|
variant={'unstyled'}
|
||||||
|
defaultIndex={activeTab}
|
||||||
|
index={activeTab}
|
||||||
|
onChange={(index: number) => {
|
||||||
|
dispatch(setActiveTab(index));
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="app-tabs-list">{renderTabs()}</div>
|
<div className="app-tabs-list">{renderTabs()}</div>
|
||||||
<TabPanels className="app-tabs-panels">{renderTabPanels()}</TabPanels>
|
<TabPanels className="app-tabs-panels">{renderTabPanels()}</TabPanels>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
@ -1,7 +1,20 @@
|
|||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Feature } from '../../../app/features';
|
||||||
import { RootState, useAppSelector } from '../../../app/store';
|
import { RootState, useAppSelector } from '../../../app/store';
|
||||||
|
import FaceRestore from '../../options/AdvancedOptions/FaceRestore/FaceRestore';
|
||||||
|
import FaceRestoreOptions from '../../options/AdvancedOptions/FaceRestore/FaceRestoreOptions';
|
||||||
|
import ImageToImageAccordion from '../../options/AdvancedOptions/ImageToImage/ImageToImageAccordion';
|
||||||
|
import ImageToImageOptions from '../../options/AdvancedOptions/ImageToImage/ImageToImageOptions';
|
||||||
|
import SeedOptions from '../../options/AdvancedOptions/Seed/SeedOptions';
|
||||||
|
import Upscale from '../../options/AdvancedOptions/Upscale/Upscale';
|
||||||
|
import UpscaleOptions from '../../options/AdvancedOptions/Upscale/UpscaleOptions';
|
||||||
|
import Variations from '../../options/AdvancedOptions/Variations/Variations';
|
||||||
|
import VariationsOptions from '../../options/AdvancedOptions/Variations/VariationsOptions';
|
||||||
|
import MainAdvancedOptions from '../../options/MainOptions/MainAdvancedOptions';
|
||||||
import MainOptions from '../../options/MainOptions/MainOptions';
|
import MainOptions from '../../options/MainOptions/MainOptions';
|
||||||
import OptionsAccordion from '../../options/OptionsAccordion';
|
import OptionsAccordion from '../../options/OptionsAccordion';
|
||||||
|
import OutputOptions from '../../options/OutputOptions';
|
||||||
import ProcessButtons from '../../options/ProcessButtons/ProcessButtons';
|
import ProcessButtons from '../../options/ProcessButtons/ProcessButtons';
|
||||||
import PromptInput from '../../options/PromptInput/PromptInput';
|
import PromptInput from '../../options/PromptInput/PromptInput';
|
||||||
|
|
||||||
@ -9,12 +22,57 @@ export default function TextToImagePanel() {
|
|||||||
const showAdvancedOptions = useAppSelector(
|
const showAdvancedOptions = useAppSelector(
|
||||||
(state: RootState) => state.options.showAdvancedOptions
|
(state: RootState) => state.options.showAdvancedOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const textToImageAccordions = {
|
||||||
|
seed: {
|
||||||
|
header: (
|
||||||
|
<Box flex="1" textAlign="left">
|
||||||
|
Seed
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
feature: Feature.SEED,
|
||||||
|
options: <SeedOptions />,
|
||||||
|
},
|
||||||
|
variations: {
|
||||||
|
header: <Variations />,
|
||||||
|
feature: Feature.VARIATIONS,
|
||||||
|
options: <VariationsOptions />,
|
||||||
|
},
|
||||||
|
face_restore: {
|
||||||
|
header: <FaceRestore />,
|
||||||
|
feature: Feature.FACE_CORRECTION,
|
||||||
|
options: <FaceRestoreOptions />,
|
||||||
|
},
|
||||||
|
upscale: {
|
||||||
|
header: <Upscale />,
|
||||||
|
feature: Feature.UPSCALE,
|
||||||
|
options: <UpscaleOptions />,
|
||||||
|
},
|
||||||
|
// img2img: {
|
||||||
|
// header: <ImageToImageAccordion />,
|
||||||
|
// feature: Feature.IMAGE_TO_IMAGE,
|
||||||
|
// options: <ImageToImageOptions />,
|
||||||
|
// },
|
||||||
|
other: {
|
||||||
|
header: (
|
||||||
|
<Box flex="1" textAlign="left">
|
||||||
|
Other
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
feature: Feature.OTHER,
|
||||||
|
options: <OutputOptions />,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-to-image-panel">
|
<div className="text-to-image-panel">
|
||||||
<PromptInput />
|
<PromptInput />
|
||||||
<ProcessButtons />
|
<ProcessButtons />
|
||||||
<MainOptions />
|
<MainOptions />
|
||||||
{showAdvancedOptions ? <OptionsAccordion /> : null}
|
<MainAdvancedOptions />
|
||||||
|
{showAdvancedOptions ? (
|
||||||
|
<OptionsAccordion accordionInfo={textToImageAccordions} />
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -89,4 +89,7 @@
|
|||||||
|
|
||||||
--console-icon-button-bg-color: rgb(50, 53, 64);
|
--console-icon-button-bg-color: rgb(50, 53, 64);
|
||||||
--console-icon-button-bg-color-hover: rgb(70, 73, 84);
|
--console-icon-button-bg-color-hover: rgb(70, 73, 84);
|
||||||
|
|
||||||
|
// Img2Img
|
||||||
|
--img2img-img-bg-color: rgb(30, 32, 42);
|
||||||
}
|
}
|
||||||
|
@ -88,4 +88,7 @@
|
|||||||
--console-border-color: rgb(160, 162, 164);
|
--console-border-color: rgb(160, 162, 164);
|
||||||
--console-icon-button-bg-color: var(--switch-bg-color);
|
--console-icon-button-bg-color: var(--switch-bg-color);
|
||||||
--console-icon-button-bg-color-hover: var(--console-border-color);
|
--console-icon-button-bg-color-hover: var(--console-border-color);
|
||||||
|
|
||||||
|
// Img2Img
|
||||||
|
--img2img-img-bg-color: rgb(180, 182, 184);
|
||||||
}
|
}
|
||||||
|
@ -26,10 +26,12 @@
|
|||||||
@use '../features/gallery/CurrentImageDisplay.scss';
|
@use '../features/gallery/CurrentImageDisplay.scss';
|
||||||
@use '../features/gallery/ImageGallery.scss';
|
@use '../features/gallery/ImageGallery.scss';
|
||||||
@use '../features/gallery/InvokePopover.scss';
|
@use '../features/gallery/InvokePopover.scss';
|
||||||
|
@use '../features/gallery/ImageMetaDataViewer/ImageMetadataViewer.scss';
|
||||||
|
|
||||||
// Tabs
|
// Tabs
|
||||||
@use '../features/tabs/InvokeTabs.scss';
|
@use '../features/tabs/InvokeTabs.scss';
|
||||||
@use '../features/tabs/TextToImage/TextToImage.scss';
|
@use '../features/tabs/TextToImage/TextToImage.scss';
|
||||||
|
@use '../features/tabs/ImageToImage/ImageToImage.scss';
|
||||||
|
|
||||||
// Component Shared
|
// Component Shared
|
||||||
@use '../common/components/IAINumberInput.scss';
|
@use '../common/components/IAINumberInput.scss';
|
||||||
|
Loading…
Reference in New Issue
Block a user