Add New WebUI and Desktop Mode
Co-Authored-By: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
BIN
frontend/dist/assets/Inter-Bold.790c108b.ttf
vendored
Normal file
BIN
frontend/dist/assets/Inter.b9a8e5e2.ttf
vendored
Normal file
BIN
frontend/dist/assets/favicon.0d253ced.ico
vendored
Normal file
After Width: | Height: | Size: 116 KiB |
BIN
frontend/dist/assets/image2img.dde6a9f1.png
vendored
Normal file
After Width: | Height: | Size: 336 KiB |
694
frontend/dist/assets/index.1332a4e9.js
vendored
483
frontend/dist/assets/index.1dc2a85b.js
vendored
Normal file
1
frontend/dist/assets/index.447eb2a9.css
vendored
@ -1 +0,0 @@
|
||||
.checkerboard{background-position:0px 0px,10px 10px;background-size:20px 20px;background-image:linear-gradient(45deg,#eee 25%,transparent 25%,transparent 75%,#eee 75%,#eee 100%),linear-gradient(45deg,#eee 25%,white 25%,white 75%,#eee 75%,#eee 100%)}
|
1
frontend/dist/assets/index.853a336f.css
vendored
Normal file
BIN
frontend/dist/assets/logo.13003d72.png
vendored
Normal file
After Width: | Height: | Size: 43 KiB |
10
frontend/dist/index.html
vendored
@ -1,14 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>InvokeAI Stable Diffusion Dream Server</title>
|
||||
<script type="module" crossorigin src="/assets/index.1332a4e9.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index.447eb2a9.css">
|
||||
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
||||
<link rel="shortcut icon" type="icon" href="/assets/favicon.0d253ced.ico" />
|
||||
<script type="module" crossorigin src="/assets/index.1dc2a85b.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index.853a336f.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
BIN
frontend/favicon.ico
Normal file
After Width: | Height: | Size: 116 KiB |
@ -1,12 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>InvokeAI Stable Diffusion Dream Server</title>
|
||||
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
||||
<link rel="shortcut icon" type="icon" href="favicon.ico" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -19,6 +19,7 @@
|
||||
"dateformat": "^5.0.3",
|
||||
"framer-motion": "^7.2.1",
|
||||
"lodash": "^4.17.21",
|
||||
"re-resizable": "^6.9.9",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.2",
|
||||
@ -38,6 +39,7 @@
|
||||
"eslint": "^8.23.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"sass": "^1.55.0",
|
||||
"tsc-watch": "^5.0.3",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.0.7",
|
||||
|
17
frontend/src/app/App.scss
Normal file
@ -0,0 +1,17 @@
|
||||
@use '../styles/Mixins/' as *;
|
||||
|
||||
.App {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.app-content {
|
||||
display: grid;
|
||||
row-gap: 1rem;
|
||||
margin: 0.6rem;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
background-color: var(--background-color);
|
||||
grid-auto-rows: max-content;
|
||||
width: $app-width;
|
||||
height: $app-height;
|
||||
}
|
@ -1,16 +1,14 @@
|
||||
import { Grid, GridItem } from '@chakra-ui/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import CurrentImageDisplay from '../features/gallery/CurrentImageDisplay';
|
||||
import ImageGallery from '../features/gallery/ImageGallery';
|
||||
import ProgressBar from '../features/system/ProgressBar';
|
||||
import SiteHeader from '../features/system/SiteHeader';
|
||||
import OptionsAccordion from '../features/options/OptionsAccordion';
|
||||
import ProcessButtons from '../features/options/ProcessButtons';
|
||||
import PromptInput from '../features/options/PromptInput';
|
||||
import LogViewer from '../features/system/LogViewer';
|
||||
import Console from '../features/system/Console';
|
||||
import Loading from '../Loading';
|
||||
import { useAppDispatch } from './store';
|
||||
import { requestSystemConfig } from './socketio/actions';
|
||||
import { keepGUIAlive } from './utils';
|
||||
import InvokeTabs from '../features/tabs/InvokeTabs';
|
||||
|
||||
keepGUIAlive();
|
||||
|
||||
const App = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
@ -22,43 +20,14 @@ const App = () => {
|
||||
}, [dispatch]);
|
||||
|
||||
return isReady ? (
|
||||
<>
|
||||
<Grid
|
||||
width="100vw"
|
||||
height="100vh"
|
||||
templateAreas={`
|
||||
"header header header header"
|
||||
"progressBar progressBar progressBar progressBar"
|
||||
"menu prompt processButtons imageRoll"
|
||||
"menu currentImage currentImage imageRoll"`}
|
||||
gridTemplateRows={'36px 10px 100px auto'}
|
||||
gridTemplateColumns={'350px auto 100px 388px'}
|
||||
gap={2}
|
||||
>
|
||||
<GridItem area={'header'} pt={1}>
|
||||
<SiteHeader />
|
||||
</GridItem>
|
||||
<GridItem area={'progressBar'}>
|
||||
<div className="App">
|
||||
<ProgressBar />
|
||||
</GridItem>
|
||||
<GridItem pl="2" area={'menu'} overflowY="scroll">
|
||||
<OptionsAccordion />
|
||||
</GridItem>
|
||||
<GridItem area={'prompt'}>
|
||||
<PromptInput />
|
||||
</GridItem>
|
||||
<GridItem area={'processButtons'}>
|
||||
<ProcessButtons />
|
||||
</GridItem>
|
||||
<GridItem area={'currentImage'}>
|
||||
<CurrentImageDisplay />
|
||||
</GridItem>
|
||||
<GridItem pr="2" area={'imageRoll'} overflowY="scroll">
|
||||
<ImageGallery />
|
||||
</GridItem>
|
||||
</Grid>
|
||||
<LogViewer />
|
||||
</>
|
||||
<div className="app-content">
|
||||
<SiteHeader />
|
||||
<InvokeTabs />
|
||||
</div>
|
||||
<Console />
|
||||
</div>
|
||||
) : (
|
||||
<Loading />
|
||||
);
|
||||
|
@ -15,13 +15,15 @@ export const SAMPLERS: Array<string> = [
|
||||
// Valid image widths
|
||||
export const WIDTHS: Array<number> = [
|
||||
64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960,
|
||||
1024,
|
||||
1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792,
|
||||
1856, 1920, 1984, 2048,
|
||||
];
|
||||
|
||||
// Valid image heights
|
||||
export const HEIGHTS: Array<number> = [
|
||||
64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960,
|
||||
1024,
|
||||
1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792,
|
||||
1856, 1920, 1984, 2048,
|
||||
];
|
||||
|
||||
// Valid upscaling levels
|
||||
|
@ -7,12 +7,12 @@ type FeatureHelpInfo = {
|
||||
export enum Feature {
|
||||
PROMPT,
|
||||
GALLERY,
|
||||
OUTPUT,
|
||||
SEED_AND_VARIATION,
|
||||
ESRGAN,
|
||||
OTHER,
|
||||
SEED,
|
||||
VARIATIONS,
|
||||
UPSCALE,
|
||||
FACE_CORRECTION,
|
||||
IMAGE_TO_IMAGE,
|
||||
SAMPLER,
|
||||
}
|
||||
|
||||
export const FEATURES: Record<Feature, FeatureHelpInfo> = {
|
||||
@ -26,18 +26,23 @@ export const FEATURES: Record<Feature, FeatureHelpInfo> = {
|
||||
href: 'link/to/docs/feature3.html',
|
||||
guideImage: 'asset/path.gif',
|
||||
},
|
||||
[Feature.OUTPUT]: {
|
||||
text: 'The Height and Width of generations can be controlled here. If you experience errors, you may be generating an image too large for your system. The seamless option will more often result in repeating patterns in outputs.',
|
||||
[Feature.OTHER]: {
|
||||
text: 'Additional Options',
|
||||
href: 'link/to/docs/feature3.html',
|
||||
guideImage: 'asset/path.gif',
|
||||
},
|
||||
[Feature.SEED_AND_VARIATION]: {
|
||||
text: 'Seed values provide an initial set of noise which guide the denoising process. Try a variation with an amount of between 0 and 1 to change the output image for that seed.',
|
||||
[Feature.SEED]: {
|
||||
text: 'Seed values provide an initial set of noise which guide the denoising process.',
|
||||
href: 'link/to/docs/feature3.html',
|
||||
guideImage: 'asset/path.gif',
|
||||
},
|
||||
[Feature.ESRGAN]: {
|
||||
text: 'The ESRGAN setting can be used to increase the output resolution without requiring a higher width/height in the initial generation.',
|
||||
[Feature.VARIATIONS]: {
|
||||
text: 'Try a variation with an amount of between 0 and 1 to change the output image for the set seed.',
|
||||
href: 'link/to/docs/feature3.html',
|
||||
guideImage: 'asset/path.gif',
|
||||
},
|
||||
[Feature.UPSCALE]: {
|
||||
text: 'Using ESRGAN you can increase the output resolution without requiring a higher width/height in the initial generation.',
|
||||
href: 'link/to/docs/feature1.html',
|
||||
guideImage: 'asset/path.gif',
|
||||
},
|
||||
@ -51,9 +56,4 @@ export const FEATURES: Record<Feature, FeatureHelpInfo> = {
|
||||
href: 'link/to/docs/feature3.html',
|
||||
guideImage: 'asset/path.gif',
|
||||
},
|
||||
[Feature.SAMPLER]: {
|
||||
text: 'This setting allows for different denoising samplers to be used, as well as the number of denoising steps used, which will change the resulting output.',
|
||||
href: 'link/to/docs/feature3.html',
|
||||
guideImage: 'asset/path.gif',
|
||||
},
|
||||
};
|
||||
|
5
frontend/src/app/invokeai.d.ts
vendored
@ -129,6 +129,7 @@ export declare type SystemStatus = {
|
||||
totalIterations: number;
|
||||
currentStatus: string;
|
||||
currentStatusHasSteps: boolean;
|
||||
hasError: boolean;
|
||||
};
|
||||
|
||||
export declare type SystemConfig = {
|
||||
@ -160,9 +161,7 @@ export declare type ErrorResponse = {
|
||||
|
||||
export declare type GalleryImagesResponse = {
|
||||
images: Array<Omit<Image, 'uuid'>>;
|
||||
nextPage: number;
|
||||
offset: number;
|
||||
onlyNewImages: boolean;
|
||||
areMoreImagesAvailable: boolean;
|
||||
};
|
||||
|
||||
export declare type ImageUrlAndUuidResponse = {
|
||||
|
@ -50,7 +50,10 @@ const makeSocketIOEmitters = (
|
||||
const esrganParameters = {
|
||||
upscale: [upscalingLevel, upscalingStrength],
|
||||
};
|
||||
socketio.emit('runESRGAN', imageToProcess, esrganParameters);
|
||||
socketio.emit('runPostprocessing', imageToProcess, {
|
||||
type: 'esrgan',
|
||||
...esrganParameters,
|
||||
});
|
||||
dispatch(
|
||||
addLogEntry({
|
||||
timestamp: dateFormat(new Date(), 'isoDateTime'),
|
||||
@ -68,7 +71,10 @@ const makeSocketIOEmitters = (
|
||||
const gfpganParameters = {
|
||||
gfpgan_strength: gfpganStrength,
|
||||
};
|
||||
socketio.emit('runGFPGAN', imageToProcess, gfpganParameters);
|
||||
socketio.emit('runPostprocessing', imageToProcess, {
|
||||
type: 'gfpgan',
|
||||
...gfpganParameters,
|
||||
});
|
||||
dispatch(
|
||||
addLogEntry({
|
||||
timestamp: dateFormat(new Date(), 'isoDateTime'),
|
||||
@ -84,16 +90,12 @@ const makeSocketIOEmitters = (
|
||||
socketio.emit('deleteImage', url, uuid);
|
||||
},
|
||||
emitRequestImages: () => {
|
||||
const { nextPage, offset } = getState().gallery;
|
||||
socketio.emit('requestImages', nextPage, offset);
|
||||
const { earliest_mtime } = getState().gallery;
|
||||
socketio.emit('requestImages', earliest_mtime);
|
||||
},
|
||||
emitRequestNewImages: () => {
|
||||
const { nextPage, offset, images } = getState().gallery;
|
||||
if (images.length > 0) {
|
||||
socketio.emit('requestImages', nextPage, offset, images[0].mtime);
|
||||
} else {
|
||||
socketio.emit('requestImages', nextPage, offset);
|
||||
}
|
||||
const { latest_mtime } = getState().gallery;
|
||||
socketio.emit('requestLatestImages', latest_mtime);
|
||||
},
|
||||
emitCancelProcessing: () => {
|
||||
socketio.emit('cancel');
|
||||
|
@ -11,6 +11,8 @@ import {
|
||||
setSystemStatus,
|
||||
setCurrentStatus,
|
||||
setSystemConfig,
|
||||
processingCanceled,
|
||||
errorOccurred,
|
||||
} from '../../features/system/systemSlice';
|
||||
|
||||
import {
|
||||
@ -25,7 +27,7 @@ import {
|
||||
setInitialImagePath,
|
||||
setMaskPath,
|
||||
} from '../../features/options/optionsSlice';
|
||||
import { requestNewImages } from './actions';
|
||||
import { requestImages, requestNewImages } from './actions';
|
||||
|
||||
/**
|
||||
* Returns an object containing listener callbacks for socketio events.
|
||||
@ -44,7 +46,11 @@ const makeSocketIOListeners = (
|
||||
try {
|
||||
dispatch(setIsConnected(true));
|
||||
dispatch(setCurrentStatus('Connected'));
|
||||
if (getState().gallery.latest_mtime) {
|
||||
dispatch(requestNewImages());
|
||||
} else {
|
||||
dispatch(requestImages());
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@ -90,7 +96,6 @@ const makeSocketIOListeners = (
|
||||
message: `Image generated: ${url}`,
|
||||
})
|
||||
);
|
||||
dispatch(setIsProcessing(false));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@ -116,7 +121,6 @@ const makeSocketIOListeners = (
|
||||
message: `Intermediate image generated: ${url}`,
|
||||
})
|
||||
);
|
||||
dispatch(setIsProcessing(false));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@ -124,7 +128,7 @@ const makeSocketIOListeners = (
|
||||
/**
|
||||
* Callback to run when we receive an 'esrganResult' event.
|
||||
*/
|
||||
onESRGANResult: (data: InvokeAI.ImageResultResponse) => {
|
||||
onPostprocessingResult: (data: InvokeAI.ImageResultResponse) => {
|
||||
try {
|
||||
const { url, metadata, mtime } = data;
|
||||
|
||||
@ -140,10 +144,9 @@ const makeSocketIOListeners = (
|
||||
dispatch(
|
||||
addLogEntry({
|
||||
timestamp: dateFormat(new Date(), 'isoDateTime'),
|
||||
message: `Upscaled: ${url}`,
|
||||
message: `Postprocessed: ${url}`,
|
||||
})
|
||||
);
|
||||
dispatch(setIsProcessing(false));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@ -204,7 +207,7 @@ const makeSocketIOListeners = (
|
||||
level: 'error',
|
||||
})
|
||||
);
|
||||
dispatch(setIsProcessing(false));
|
||||
dispatch(errorOccurred());
|
||||
dispatch(clearIntermediateImage());
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@ -214,7 +217,7 @@ const makeSocketIOListeners = (
|
||||
* Callback to run when we receive a 'galleryImages' event.
|
||||
*/
|
||||
onGalleryImages: (data: InvokeAI.GalleryImagesResponse) => {
|
||||
const { images, nextPage, offset } = data;
|
||||
const { images, areMoreImagesAvailable } = data;
|
||||
|
||||
/**
|
||||
* the logic here ideally would be in the reducer but we have a side effect:
|
||||
@ -232,7 +235,9 @@ const makeSocketIOListeners = (
|
||||
};
|
||||
});
|
||||
|
||||
dispatch(addGalleryImages({ images: preparedImages, nextPage, offset }));
|
||||
dispatch(
|
||||
addGalleryImages({ images: preparedImages, areMoreImagesAvailable })
|
||||
);
|
||||
|
||||
dispatch(
|
||||
addLogEntry({
|
||||
@ -245,7 +250,7 @@ const makeSocketIOListeners = (
|
||||
* Callback to run when we receive a 'processingCanceled' event.
|
||||
*/
|
||||
onProcessingCanceled: () => {
|
||||
dispatch(setIsProcessing(false));
|
||||
dispatch(processingCanceled());
|
||||
|
||||
const { intermediateImage } = getState().gallery;
|
||||
|
||||
@ -259,6 +264,7 @@ const makeSocketIOListeners = (
|
||||
);
|
||||
dispatch(clearIntermediateImage());
|
||||
}
|
||||
|
||||
dispatch(
|
||||
addLogEntry({
|
||||
timestamp: dateFormat(new Date(), 'isoDateTime'),
|
||||
|
@ -35,8 +35,7 @@ export const socketioMiddleware = () => {
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
onError,
|
||||
onESRGANResult,
|
||||
onGFPGANResult,
|
||||
onPostprocessingResult,
|
||||
onGenerationResult,
|
||||
onIntermediateResult,
|
||||
onProgressUpdate,
|
||||
@ -76,12 +75,9 @@ export const socketioMiddleware = () => {
|
||||
onGenerationResult(data)
|
||||
);
|
||||
|
||||
socketio.on('esrganResult', (data: InvokeAI.ImageResultResponse) =>
|
||||
onESRGANResult(data)
|
||||
);
|
||||
|
||||
socketio.on('gfpganResult', (data: InvokeAI.ImageResultResponse) =>
|
||||
onGFPGANResult(data)
|
||||
socketio.on(
|
||||
'postprocessingResult',
|
||||
(data: InvokeAI.ImageResultResponse) => onPostprocessingResult(data)
|
||||
);
|
||||
|
||||
socketio.on('intermediateResult', (data: InvokeAI.ImageResultResponse) =>
|
||||
@ -153,7 +149,6 @@ export const socketioMiddleware = () => {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 'socketio/cancelProcessing': {
|
||||
emitCancelProcessing();
|
||||
break;
|
||||
|
@ -7,6 +7,7 @@ import storage from 'redux-persist/lib/storage'; // defaults to localStorage for
|
||||
|
||||
import optionsReducer from '../features/options/optionsSlice';
|
||||
import galleryReducer from '../features/gallery/gallerySlice';
|
||||
|
||||
import systemReducer from '../features/system/systemSlice';
|
||||
import { socketioMiddleware } from './socketio/middleware';
|
||||
|
||||
|
25
frontend/src/app/utils.ts
Normal file
@ -0,0 +1,25 @@
|
||||
export function keepGUIAlive() {
|
||||
async function getRequest(url = '') {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
cache: 'no-cache',
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
const keepAliveServer = () => {
|
||||
const url = document.location;
|
||||
const route = '/flaskwebgui-keep-server-alive';
|
||||
getRequest(url + route).then((data) => {
|
||||
return data;
|
||||
});
|
||||
};
|
||||
|
||||
if (!import.meta.env.NODE_ENV || import.meta.env.NODE_ENV === 'production') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const intervalRequest = 3 * 1000;
|
||||
keepAliveServer();
|
||||
setInterval(keepAliveServer, intervalRequest);
|
||||
});
|
||||
}
|
||||
}
|
BIN
frontend/src/assets/fonts/Inter/Inter-Bold.ttf
Normal file
BIN
frontend/src/assets/fonts/Inter/Inter.ttf
Normal file
BIN
frontend/src/assets/images/image2img.png
Normal file
After Width: | Height: | Size: 336 KiB |
BIN
frontend/src/assets/images/logo.png
Normal file
After Width: | Height: | Size: 43 KiB |
20
frontend/src/common/components/GuidePopover.scss
Normal file
@ -0,0 +1,20 @@
|
||||
.guide-popover-arrow {
|
||||
background-color: var(--tab-panel-bg) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.guide-popover-content {
|
||||
background-color: var(--background-color-secondary) !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.guide-popover-guide-content {
|
||||
background: var(--tab-panel-bg);
|
||||
border: 2px solid var(--tab-hover-color);
|
||||
border-radius: 0.4rem;
|
||||
padding: 0.75rem 1rem 0.75rem 1rem;
|
||||
display: grid;
|
||||
grid-template-rows: repeat(auto-fill, 1fr);
|
||||
grid-row-gap: 0.5rem;
|
||||
justify-content: space-between;
|
||||
}
|
@ -3,8 +3,6 @@ import {
|
||||
PopoverArrow,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
PopoverHeader,
|
||||
Flex,
|
||||
Box,
|
||||
} from '@chakra-ui/react';
|
||||
import { SystemState } from '../../features/system/systemSlice';
|
||||
@ -33,14 +31,13 @@ const GuidePopover = ({ children, feature }: GuideProps) => {
|
||||
<Box>{children}</Box>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className={`guide-popover-content`}
|
||||
maxWidth="400px"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
cursor={'initial'}
|
||||
>
|
||||
<PopoverArrow />
|
||||
<Flex alignItems={'center'} gap={2} p={4}>
|
||||
{text}
|
||||
</Flex>
|
||||
<PopoverArrow className="guide-popover-arrow" />
|
||||
<div className="guide-popover-guide-content">{text}</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
) : (
|
||||
|
24
frontend/src/common/components/IAIButton.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { Button, ButtonProps, Tooltip } from '@chakra-ui/react';
|
||||
|
||||
interface Props extends ButtonProps {
|
||||
label: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable customized button component. Originally was more customized - now probably unecessary.
|
||||
*
|
||||
* TODO: Get rid of this.
|
||||
*/
|
||||
const IAIButton = (props: Props) => {
|
||||
const { label, tooltip = '', size = 'sm', ...rest } = props;
|
||||
return (
|
||||
<Tooltip label={tooltip}>
|
||||
<Button size={size} {...rest}>
|
||||
{label}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default IAIButton;
|
21
frontend/src/common/components/IAIIconButton.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { IconButtonProps, IconButton, Tooltip } from '@chakra-ui/react';
|
||||
|
||||
interface Props extends IconButtonProps {
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable customized button component. Originally was more customized - now probably unecessary.
|
||||
*
|
||||
* TODO: Get rid of this.
|
||||
*/
|
||||
const IAIIconButton = (props: Props) => {
|
||||
const { tooltip = '', onClick, ...rest } = props;
|
||||
return (
|
||||
<Tooltip label={tooltip}>
|
||||
<IconButton {...rest} cursor={onClick ? 'pointer' : 'unset'} onClick={onClick}/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default IAIIconButton;
|
34
frontend/src/common/components/IAIInput.scss
Normal file
@ -0,0 +1,34 @@
|
||||
.input {
|
||||
display: grid;
|
||||
grid-template-columns: max-content auto;
|
||||
column-gap: 1rem;
|
||||
align-items: center;
|
||||
|
||||
.input-label {
|
||||
color: var(--text-color-secondary);
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.input-entry {
|
||||
background-color: var(--background-color-secondary);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 0.2rem;
|
||||
font-weight: bold;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: 2px solid var(--prompt-border-color);
|
||||
box-shadow: 0 0 10px 0 var(--prompt-box-shadow-color);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
&[aria-invalid='true'] {
|
||||
outline: none;
|
||||
border: 2px solid var(--border-color-invalid);
|
||||
box-shadow: 0 0 10px 0 var(--box-shadow-color-invalid);
|
||||
}
|
||||
}
|
||||
}
|
41
frontend/src/common/components/IAIInput.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { FormControl, FormLabel, Input, InputProps } from '@chakra-ui/react';
|
||||
import { ChangeEvent } from 'react';
|
||||
|
||||
interface IAIInputProps extends InputProps {
|
||||
styleClass?: string;
|
||||
label?: string;
|
||||
width?: string | number;
|
||||
value: string;
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
export default function IAIInput(props: IAIInputProps) {
|
||||
const {
|
||||
label,
|
||||
styleClass,
|
||||
isDisabled = false,
|
||||
fontSize = '1rem',
|
||||
width,
|
||||
isInvalid,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
className={`input ${styleClass}`}
|
||||
isInvalid={isInvalid}
|
||||
isDisabled={isDisabled}
|
||||
flexGrow={1}
|
||||
>
|
||||
<FormLabel
|
||||
fontSize={fontSize}
|
||||
marginBottom={1}
|
||||
whiteSpace="nowrap"
|
||||
className="input-label"
|
||||
>
|
||||
{label}
|
||||
</FormLabel>
|
||||
<Input {...rest} className="input-entry" size={'sm'} width={width} />
|
||||
</FormControl>
|
||||
);
|
||||
}
|
52
frontend/src/common/components/IAINumberInput.scss
Normal file
@ -0,0 +1,52 @@
|
||||
.number-input {
|
||||
display: grid;
|
||||
grid-template-columns: max-content auto;
|
||||
column-gap: 1rem;
|
||||
align-items: center;
|
||||
|
||||
.number-input-label {
|
||||
color: var(--text-color-secondary);
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.number-input-field {
|
||||
display: grid;
|
||||
grid-template-columns: auto max-content;
|
||||
column-gap: 0.5rem;
|
||||
align-items: center;
|
||||
background-color: var(--background-color-secondary);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
.number-input-entry {
|
||||
border: none;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
padding-inline-end: 0;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: 2px solid var(--prompt-border-color);
|
||||
box-shadow: 0 0 10px 0 var(--prompt-box-shadow-color);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
.number-input-stepper {
|
||||
display: grid;
|
||||
padding-right: 0.7rem;
|
||||
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.number-input-stepper-button {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
141
frontend/src/common/components/IAINumberInput.tsx
Normal file
@ -0,0 +1,141 @@
|
||||
import {
|
||||
FormControl,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberIncrementStepper,
|
||||
NumberDecrementStepper,
|
||||
NumberInputProps,
|
||||
FormLabel,
|
||||
} from '@chakra-ui/react';
|
||||
import _ from 'lodash';
|
||||
import { FocusEvent, useEffect, useState } from 'react';
|
||||
|
||||
const numberStringRegex = /^-?(0\.)?\.?$/;
|
||||
|
||||
interface Props extends Omit<NumberInputProps, 'onChange'> {
|
||||
styleClass?: string;
|
||||
label?: string;
|
||||
width?: string | number;
|
||||
showStepper?: boolean;
|
||||
value: number;
|
||||
onChange: (v: number) => void;
|
||||
min: number;
|
||||
max: number;
|
||||
clamp?: boolean;
|
||||
isInteger?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customized Chakra FormControl + NumberInput multi-part component.
|
||||
*/
|
||||
const IAINumberInput = (props: Props) => {
|
||||
const {
|
||||
label,
|
||||
styleClass,
|
||||
isDisabled = false,
|
||||
showStepper = true,
|
||||
fontSize = '1rem',
|
||||
size = 'sm',
|
||||
width,
|
||||
textAlign,
|
||||
isInvalid,
|
||||
value,
|
||||
onChange,
|
||||
min,
|
||||
max,
|
||||
isInteger = true,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
/**
|
||||
* Using a controlled input with a value that accepts decimals needs special
|
||||
* handling. If the user starts to type in "1.5", by the time they press the
|
||||
* 5, the value has been parsed from "1." to "1" and they end up with "15".
|
||||
*
|
||||
* To resolve this, this component keeps a the value as a string internally,
|
||||
* and the UI component uses that. When a change is made, that string is parsed
|
||||
* as a number and given to the `onChange` function.
|
||||
*/
|
||||
|
||||
const [valueAsString, setValueAsString] = useState<string>(String(value));
|
||||
|
||||
/**
|
||||
* When `value` changes (e.g. from a diff source than this component), we need
|
||||
* to update the internal `valueAsString`, but only if the actual value is different
|
||||
* from the current value.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!valueAsString.match(numberStringRegex) && value !== Number(valueAsString)) {
|
||||
setValueAsString(String(value));
|
||||
}
|
||||
}, [value, valueAsString]);
|
||||
|
||||
const handleOnChange = (v: string) => {
|
||||
setValueAsString(v);
|
||||
// This allows negatives and decimals e.g. '-123', `.5`, `-0.2`, etc.
|
||||
if (!v.match(numberStringRegex)) {
|
||||
// Cast the value to number. Floor it if it should be an integer.
|
||||
onChange(isInteger ? Math.floor(Number(v)) : Number(v));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clicking the steppers allows the value to go outside bounds; we need to
|
||||
* clamp it on blur and floor it if needed.
|
||||
*/
|
||||
const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
|
||||
const clamped = _.clamp(
|
||||
isInteger ? Math.floor(Number(e.target.value)) : Number(e.target.value),
|
||||
min,
|
||||
max
|
||||
);
|
||||
setValueAsString(String(clamped));
|
||||
onChange(clamped);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
isDisabled={isDisabled}
|
||||
isInvalid={isInvalid}
|
||||
className={`number-input ${styleClass}`}
|
||||
>
|
||||
{label && (
|
||||
<FormLabel
|
||||
fontSize={fontSize}
|
||||
marginBottom={1}
|
||||
flexGrow={2}
|
||||
whiteSpace="nowrap"
|
||||
className="number-input-label"
|
||||
>
|
||||
{label}
|
||||
</FormLabel>
|
||||
)}
|
||||
<NumberInput
|
||||
size={size}
|
||||
{...rest}
|
||||
className="number-input-field"
|
||||
value={valueAsString}
|
||||
keepWithinRange={true}
|
||||
clampValueOnBlur={false}
|
||||
onChange={handleOnChange}
|
||||
onBlur={handleBlur}
|
||||
>
|
||||
<NumberInputField
|
||||
fontSize={fontSize}
|
||||
className="number-input-entry"
|
||||
width={width}
|
||||
textAlign={textAlign}
|
||||
/>
|
||||
<div
|
||||
className="number-input-stepper"
|
||||
style={showStepper ? { display: 'block' } : { display: 'none' }}
|
||||
>
|
||||
<NumberIncrementStepper className="number-input-stepper-button" />
|
||||
<NumberDecrementStepper className="number-input-stepper-button" />
|
||||
</div>
|
||||
</NumberInput>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default IAINumberInput;
|
28
frontend/src/common/components/IAISelect.scss
Normal file
@ -0,0 +1,28 @@
|
||||
.iai-select {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, max-content);
|
||||
column-gap: 1rem;
|
||||
align-items: center;
|
||||
width: max-content;
|
||||
|
||||
.iai-select-label {
|
||||
color: var(--text-color-secondary);
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.iai-select-picker {
|
||||
border: 2px solid var(--border-color);
|
||||
background-color: var(--background-color-secondary);
|
||||
font-weight: bold;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: 2px solid var(--prompt-border-color);
|
||||
box-shadow: 0 0 10px 0 var(--prompt-box-shadow-color);
|
||||
}
|
||||
}
|
||||
|
||||
.iai-select-option {
|
||||
background-color: var(--background-color-secondary);
|
||||
}
|
||||
}
|
56
frontend/src/common/components/IAISelect.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import { FormControl, FormLabel, Select, SelectProps } from '@chakra-ui/react';
|
||||
|
||||
interface Props extends SelectProps {
|
||||
label: string;
|
||||
styleClass?: string;
|
||||
validValues:
|
||||
| Array<number | string>
|
||||
| Array<{ key: string; value: string | number }>;
|
||||
}
|
||||
/**
|
||||
* Customized Chakra FormControl + Select multi-part component.
|
||||
*/
|
||||
const IAISelect = (props: Props) => {
|
||||
const {
|
||||
label,
|
||||
isDisabled,
|
||||
validValues,
|
||||
size = 'sm',
|
||||
fontSize = 'md',
|
||||
styleClass,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<FormControl isDisabled={isDisabled} className={`iai-select ${styleClass}`}>
|
||||
<FormLabel
|
||||
fontSize={fontSize}
|
||||
marginBottom={1}
|
||||
flexGrow={2}
|
||||
whiteSpace="nowrap"
|
||||
className="iai-select-label"
|
||||
>
|
||||
{label}
|
||||
</FormLabel>
|
||||
<Select
|
||||
fontSize={fontSize}
|
||||
size={size}
|
||||
{...rest}
|
||||
className="iai-select-picker"
|
||||
>
|
||||
{validValues.map((opt) => {
|
||||
return typeof opt === 'string' || typeof opt === 'number' ? (
|
||||
<option key={opt} value={opt} className="iai-select-option">
|
||||
{opt}
|
||||
</option>
|
||||
) : (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.key}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default IAISelect;
|
18
frontend/src/common/components/IAISwitch.scss
Normal file
@ -0,0 +1,18 @@
|
||||
.chakra-switch,
|
||||
.switch-button {
|
||||
span {
|
||||
background-color: var(--switch-bg-color);
|
||||
|
||||
span {
|
||||
background-color: var(--white);
|
||||
}
|
||||
}
|
||||
|
||||
span[data-checked] {
|
||||
background: var(--switch-bg-active-color);
|
||||
|
||||
span {
|
||||
background-color: var(--white);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,13 +14,13 @@ interface Props extends SwitchProps {
|
||||
/**
|
||||
* Customized Chakra FormControl + Switch multi-part component.
|
||||
*/
|
||||
const SDSwitch = (props: Props) => {
|
||||
const IAISwitch = (props: Props) => {
|
||||
const {
|
||||
label,
|
||||
isDisabled = false,
|
||||
fontSize = 'md',
|
||||
size = 'md',
|
||||
width,
|
||||
width = 'auto',
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
@ -36,10 +36,10 @@ const SDSwitch = (props: Props) => {
|
||||
{label}
|
||||
</FormLabel>
|
||||
)}
|
||||
<Switch size={size} {...rest} />
|
||||
<Switch size={size} className="switch-button" {...rest} />
|
||||
</Flex>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default SDSwitch;
|
||||
export default IAISwitch;
|
@ -1,21 +0,0 @@
|
||||
import { Button, ButtonProps } from '@chakra-ui/react';
|
||||
|
||||
interface Props extends ButtonProps {
|
||||
label: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable customized button component. Originally was more customized - now probably unecessary.
|
||||
*
|
||||
* TODO: Get rid of this.
|
||||
*/
|
||||
const SDButton = (props: Props) => {
|
||||
const { label, size = 'sm', ...rest } = props;
|
||||
return (
|
||||
<Button size={size} {...rest}>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default SDButton;
|
@ -1,59 +0,0 @@
|
||||
import {
|
||||
FormControl,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberDecrementStepper,
|
||||
Text,
|
||||
FormLabel,
|
||||
NumberInputProps,
|
||||
Flex,
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
interface Props extends NumberInputProps {
|
||||
label?: string;
|
||||
width?: string | number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customized Chakra FormControl + NumberInput multi-part component.
|
||||
*/
|
||||
const SDNumberInput = (props: Props) => {
|
||||
const {
|
||||
label,
|
||||
isDisabled = false,
|
||||
fontSize = 'md',
|
||||
size = 'sm',
|
||||
width,
|
||||
isInvalid,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<FormControl isDisabled={isDisabled} width={width} isInvalid={isInvalid}>
|
||||
<Flex gap={2} justifyContent={'space-between'} alignItems={'center'}>
|
||||
{label && (
|
||||
<FormLabel marginBottom={1}>
|
||||
<Text fontSize={fontSize} whiteSpace="nowrap">
|
||||
{label}
|
||||
</Text>
|
||||
</FormLabel>
|
||||
)}
|
||||
<NumberInput
|
||||
size={size}
|
||||
{...rest}
|
||||
keepWithinRange={false}
|
||||
clampValueOnBlur={true}
|
||||
>
|
||||
<NumberInputField fontSize={'md'} />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default SDNumberInput;
|
@ -1,56 +0,0 @@
|
||||
import {
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Select,
|
||||
SelectProps,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
interface Props extends SelectProps {
|
||||
label: string;
|
||||
validValues:
|
||||
| Array<number | string>
|
||||
| Array<{ key: string; value: string | number }>;
|
||||
}
|
||||
/**
|
||||
* Customized Chakra FormControl + Select multi-part component.
|
||||
*/
|
||||
const SDSelect = (props: Props) => {
|
||||
const {
|
||||
label,
|
||||
isDisabled,
|
||||
validValues,
|
||||
size = 'sm',
|
||||
fontSize = 'md',
|
||||
marginBottom = 1,
|
||||
whiteSpace = 'nowrap',
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<FormControl isDisabled={isDisabled}>
|
||||
<Flex justifyContent={'space-between'} alignItems={'center'}>
|
||||
<FormLabel marginBottom={marginBottom}>
|
||||
<Text fontSize={fontSize} whiteSpace={whiteSpace}>
|
||||
{label}
|
||||
</Text>
|
||||
</FormLabel>
|
||||
<Select fontSize={fontSize} size={size} {...rest}>
|
||||
{validValues.map((opt) => {
|
||||
return typeof opt === 'string' || typeof opt === 'number' ? (
|
||||
<option key={opt} value={opt}>
|
||||
{opt}
|
||||
</option>
|
||||
) : (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.key}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default SDSelect;
|
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import Img2ImgPlaceHolder from '../../../assets/images/image2img.png';
|
||||
|
||||
export const ImageToImageWIP = () => {
|
||||
return (
|
||||
<div className="work-in-progress txt2img-work-in-progress">
|
||||
<img src={Img2ImgPlaceHolder} alt="img2img_placeholder" />
|
||||
<h1>Image To Image</h1>
|
||||
<p>
|
||||
Image to Image is already available in the WebUI. You can access it from
|
||||
the Text to Image - Advanced Options menu. A dedicated UI for Image To
|
||||
Image will be released soon.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function InpaintingWIP() {
|
||||
return (
|
||||
<div className="work-in-progress inpainting-work-in-progress">
|
||||
<h1>Inpainting</h1>
|
||||
<p>
|
||||
Inpainting is available as a part of the Invoke AI Command Line
|
||||
Interface. A dedicated WebUI interface will be released in the near
|
||||
future.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
13
frontend/src/common/components/WorkInProgress/NodesWIP.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function NodesWIP() {
|
||||
return (
|
||||
<div className="work-in-progress nodes-work-in-progress">
|
||||
<h1>Nodes</h1>
|
||||
<p>
|
||||
A node based system for the generation of images is under development
|
||||
currently. Stay tuned for updates about this amazing feature.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function OutpaintingWIP() {
|
||||
return (
|
||||
<div className="work-in-progress outpainting-work-in-progress">
|
||||
<h1>Outpainting</h1>
|
||||
<p>
|
||||
Outpainting is available as a part of the Invoke AI Command Line
|
||||
Interface. A dedicated WebUI interface will be released in the near
|
||||
future.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
|
||||
export const PostProcessingWIP = () => {
|
||||
return (
|
||||
<div className="work-in-progress post-processing-work-in-progress">
|
||||
<h1>Post Processing</h1>
|
||||
<p>
|
||||
Invoke AI offers a wide variety of post processing features. Image
|
||||
Upscaling and Face Restoration are already available in the WebUI. You
|
||||
can access them from the Advanced Options menu of the Text To Image tab.
|
||||
A dedicated UI will be released soon.
|
||||
</p>
|
||||
<p>
|
||||
The Invoke AI Command Line Interface offers various other features
|
||||
including Embiggen, High Resolution Fixing and more.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
@use '../../../styles/Mixins/' as *;
|
||||
|
||||
.work-in-progress {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: $app-content-height;
|
||||
grid-auto-rows: max-content;
|
||||
background-color: var(--background-color-secondary);
|
||||
border-radius: 0.4rem;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
row-gap: 1rem;
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
max-width: 50rem;
|
||||
color: var(--subtext-color-bright);
|
||||
}
|
||||
}
|
@ -4,10 +4,11 @@ import { useMemo } from 'react';
|
||||
import { useAppSelector } from '../../app/store';
|
||||
import { RootState } from '../../app/store';
|
||||
import { OptionsState } from '../../features/options/optionsSlice';
|
||||
|
||||
import { SystemState } from '../../features/system/systemSlice';
|
||||
import { validateSeedWeights } from '../util/seedWeightPairs';
|
||||
|
||||
const optionsSelector = createSelector(
|
||||
export const optionsSelector = createSelector(
|
||||
(state: RootState) => state.options,
|
||||
(options: OptionsState) => {
|
||||
return {
|
||||
@ -26,7 +27,7 @@ const optionsSelector = createSelector(
|
||||
}
|
||||
);
|
||||
|
||||
const systemSelector = createSelector(
|
||||
export const systemSelector = createSelector(
|
||||
(state: RootState) => state.system,
|
||||
(system: SystemState) => {
|
||||
return {
|
||||
@ -46,8 +47,9 @@ const systemSelector = createSelector(
|
||||
* This is used to prevent the 'Generate' button from being clicked.
|
||||
*/
|
||||
const useCheckParameters = (): boolean => {
|
||||
const { prompt } = useAppSelector(optionsSelector);
|
||||
|
||||
const {
|
||||
prompt,
|
||||
shouldGenerateVariations,
|
||||
seedWeights,
|
||||
maskPath,
|
||||
@ -59,7 +61,7 @@ const useCheckParameters = (): boolean => {
|
||||
|
||||
return useMemo(() => {
|
||||
// Cannot generate without a prompt
|
||||
if (!prompt) {
|
||||
if (!prompt || Boolean(prompt.match(/^[\s\r\n]+$/))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
17
frontend/src/common/icons/ImageToImageIcon.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { createIcon } from "@chakra-ui/react";
|
||||
|
||||
const ImageToImageIcon = createIcon({
|
||||
displayName: 'ImageToImageIcon',
|
||||
viewBox: '0 0 3543 3543',
|
||||
path: (
|
||||
<g transform="matrix(1.10943,0,0,1.10943,-206.981,-213.533)">
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M688.533,2405.95L542.987,2405.95C349.532,2405.95 192.47,2248.89 192.47,2055.44L192.47,542.987C192.47,349.532 349.532,192.47 542.987,192.47L2527.88,192.47C2721.33,192.47 2878.4,349.532 2878.4,542.987L2878.4,1172.79L3023.94,1172.79C3217.4,1172.79 3374.46,1329.85 3374.46,1523.3C3374.46,1523.3 3374.46,3035.75 3374.46,3035.75C3374.46,3229.21 3217.4,3386.27 3023.94,3386.27L1039.05,3386.27C845.595,3386.27 688.533,3229.21 688.533,3035.75L688.533,2405.95ZM3286.96,2634.37L3286.96,1523.3C3286.96,1378.14 3169.11,1260.29 3023.94,1260.29C3023.94,1260.29 1039.05,1260.29 1039.05,1260.29C893.887,1260.29 776.033,1378.14 776.033,1523.3L776.033,2489.79L1440.94,1736.22L2385.83,2775.59L2880.71,2200.41L3286.96,2634.37ZM2622.05,1405.51C2778.5,1405.51 2905.51,1532.53 2905.51,1688.98C2905.51,1845.42 2778.5,1972.44 2622.05,1972.44C2465.6,1972.44 2338.58,1845.42 2338.58,1688.98C2338.58,1532.53 2465.6,1405.51 2622.05,1405.51ZM2790.9,1172.79L1323.86,1172.79L944.882,755.906L279.97,1509.47L279.97,542.987C279.97,397.824 397.824,279.97 542.987,279.97C542.987,279.97 2527.88,279.97 2527.88,279.97C2673.04,279.97 2790.9,397.824 2790.9,542.987L2790.9,1172.79ZM2125.98,425.197C2282.43,425.197 2409.45,552.213 2409.45,708.661C2409.45,865.11 2282.43,992.126 2125.98,992.126C1969.54,992.126 1842.52,865.11 1842.52,708.661C1842.52,552.213 1969.54,425.197 2125.98,425.197Z"
|
||||
/>
|
||||
</g>
|
||||
),
|
||||
});
|
||||
export default ImageToImageIcon;
|
16
frontend/src/common/icons/InpaintIcon.tsx
Normal file
16
frontend/src/common/icons/NodesIcon.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { createIcon } from "@chakra-ui/react";
|
||||
|
||||
const NodesIcon = createIcon({
|
||||
displayName: 'NodesIcon',
|
||||
viewBox: '0 0 3543 3543',
|
||||
path: (
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3543.31,770.787C3543.31,515.578 3336.11,308.38 3080.9,308.38L462.407,308.38C207.197,308.38 0,515.578 0,770.787L0,2766.03C0,3021.24 207.197,3228.44 462.407,3228.44L3080.9,3228.44C3336.11,3228.44 3543.31,3021.24 3543.31,2766.03C3543.31,2766.03 3543.31,770.787 3543.31,770.787ZM3427.88,770.787L3427.88,2766.03C3427.88,2957.53 3272.4,3113.01 3080.9,3113.01C3080.9,3113.01 462.407,3113.01 462.407,3113.01C270.906,3113.01 115.431,2957.53 115.431,2766.03L115.431,770.787C115.431,579.286 270.906,423.812 462.407,423.812L3080.9,423.812C3272.4,423.812 3427.88,579.286 3427.88,770.787ZM1214.23,1130.69L1321.47,1130.69C1324.01,1130.69 1326.54,1130.53 1329.05,1130.2C1329.05,1130.2 1367.3,1125.33 1397.94,1149.8C1421.63,1168.72 1437.33,1204.3 1437.33,1265.48L1437.33,2078.74L1220.99,2078.74C1146.83,2078.74 1086.61,2138.95 1086.61,2213.12L1086.61,2762.46C1086.61,2836.63 1146.83,2896.84 1220.99,2896.84L1770.34,2896.84C1844.5,2896.84 1904.71,2836.63 1904.71,2762.46L1904.71,2213.12C1904.71,2138.95 1844.5,2078.74 1770.34,2078.74L1554,2078.74L1554,1604.84C1625.84,1658.19 1703.39,1658.1 1703.39,1658.1C1703.54,1658.1 1703.69,1658.11 1703.84,1658.11L2362.2,1658.11L2362.2,1874.44C2362.2,1948.61 2422.42,2008.82 2496.58,2008.82L3045.93,2008.82C3120.09,2008.82 3180.3,1948.61 3180.3,1874.44L3180.3,1325.1C3180.3,1250.93 3120.09,1190.72 3045.93,1190.72L2496.58,1190.72C2422.42,1190.72 2362.2,1250.93 2362.2,1325.1L2362.2,1558.97L2362.2,1541.44L1704.23,1541.44C1702.2,1541.37 1650.96,1539.37 1609.51,1499.26C1577.72,1468.49 1554,1416.47 1554,1331.69L1554,1265.48C1554,1153.86 1513.98,1093.17 1470.76,1058.64C1411.24,1011.1 1338.98,1012.58 1319.15,1014.03L1214.23,1014.03L1214.23,796.992C1214.23,722.828 1154.02,662.617 1079.85,662.617L530.507,662.617C456.343,662.617 396.131,722.828 396.131,796.992L396.131,1346.34C396.131,1420.5 456.343,1480.71 530.507,1480.71L1079.85,1480.71C1154.02,1480.71 1214.23,1420.5 1214.23,1346.34L1214.23,1130.69Z"
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
export default NodesIcon;
|
16
frontend/src/common/icons/OutpaintIcon.tsx
Normal file
16
frontend/src/common/icons/PostprocessingIcon.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { createIcon } from '@chakra-ui/react';
|
||||
|
||||
const PostprocessingIcon = createIcon({
|
||||
displayName: 'PostprocessingIcon',
|
||||
viewBox: '0 0 3543 3543',
|
||||
path: (
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M709.477,1596.53L992.591,1275.66L2239.09,2646.81L2891.95,1888.03L3427.88,2460.51L3427.88,994.78C3427.88,954.66 3421.05,916.122 3408.5,880.254L3521.9,855.419C3535.8,899.386 3543.31,946.214 3543.31,994.78L3543.31,2990.02C3543.31,3245.23 3336.11,3452.43 3080.9,3452.43C3080.9,3452.43 462.407,3452.43 462.407,3452.43C207.197,3452.43 -0,3245.23 -0,2990.02L-0,994.78C-0,739.571 207.197,532.373 462.407,532.373L505.419,532.373L504.644,532.546L807.104,600.085C820.223,601.729 832.422,607.722 841.77,617.116C850.131,625.517 855.784,636.21 858.055,647.804L462.407,647.804C270.906,647.804 115.431,803.279 115.431,994.78L115.431,2075.73L-0,2101.5L115.431,2127.28L115.431,2269.78L220.47,2150.73L482.345,2209.21C503.267,2211.83 522.722,2221.39 537.63,2236.37C552.538,2251.35 562.049,2270.9 564.657,2291.93L671.84,2776.17L779.022,2291.93C781.631,2270.9 791.141,2251.35 806.05,2236.37C820.958,2221.39 840.413,2211.83 861.334,2209.21L1353.15,2101.5L861.334,1993.8C840.413,1991.18 820.958,1981.62 806.05,1966.64C791.141,1951.66 781.631,1932.11 779.022,1911.08L709.477,1596.53ZM671.84,1573.09L725.556,2006.07C726.863,2016.61 731.63,2026.4 739.101,2033.91C746.573,2041.42 756.323,2046.21 766.808,2047.53L1197.68,2101.5L766.808,2155.48C756.323,2156.8 746.573,2161.59 739.101,2169.09C731.63,2176.6 726.863,2186.4 725.556,2196.94L671.84,2629.92L618.124,2196.94C616.817,2186.4 612.05,2176.6 604.579,2169.09C597.107,2161.59 587.357,2156.8 576.872,2155.48L146.001,2101.5L576.872,2047.53C587.357,2046.21 597.107,2041.42 604.579,2033.91C612.05,2026.4 616.817,2016.61 618.124,2006.07L671.84,1573.09ZM609.035,1710.36L564.657,1911.08C562.049,1932.11 552.538,1951.66 537.63,1966.64C522.722,1981.62 503.267,1991.18 482.345,1993.8L328.665,2028.11L609.035,1710.36ZM2297.12,938.615L2451.12,973.003C2480.59,976.695 2507.99,990.158 2528.99,1011.26C2549.99,1032.37 2563.39,1059.9 2567.07,1089.52L2672.73,1566.9C2634.5,1580.11 2593.44,1587.29 2550.72,1587.29C2344.33,1587.29 2176.77,1419.73 2176.77,1213.34C2176.77,1104.78 2223.13,1006.96 2297.12,938.615ZM2718.05,76.925L2793.72,686.847C2795.56,701.69 2802.27,715.491 2812.8,726.068C2823.32,736.644 2837.06,743.391 2851.83,745.242L3458.78,821.28L2851.83,897.318C2837.06,899.168 2823.32,905.916 2812.8,916.492C2802.27,927.068 2795.56,940.87 2793.72,955.712L2718.05,1565.63L2642.38,955.712C2640.54,940.87 2633.83,927.068 2623.3,916.492C2612.78,905.916 2599.04,899.168 2584.27,897.318L1977.32,821.28L2584.27,745.242C2599.04,743.391 2612.78,736.644 2623.3,726.068C2633.83,715.491 2640.54,701.69 2642.38,686.847L2718.05,76.925ZM2883.68,1043.06C2909.88,1094.13 2924.67,1152.02 2924.67,1213.34C2924.67,1335.4 2866.06,1443.88 2775.49,1512.14L2869.03,1089.52C2871.07,1073.15 2876.07,1057.42 2883.68,1043.06ZM925.928,201.2L959.611,472.704C960.431,479.311 963.42,485.455 968.105,490.163C972.79,494.871 978.904,497.875 985.479,498.698L1255.66,532.546L985.479,566.395C978.904,567.218 972.79,570.222 968.105,574.93C963.42,579.638 960.431,585.781 959.611,592.388L925.928,863.893L892.245,592.388C891.425,585.781 888.436,579.638 883.751,574.93C879.066,570.222 872.952,567.218 866.378,566.395L596.195,532.546L866.378,498.698C872.952,497.875 879.066,494.871 883.751,490.163C888.436,485.455 891.425,479.311 892.245,472.704L925.928,201.2ZM2864.47,532.373L3080.9,532.373C3258.7,532.373 3413.2,632.945 3490.58,780.281L3319.31,742.773C3257.14,683.925 3173.2,647.804 3080.9,647.804L2927.07,647.804C2919.95,642.994 2913.25,637.473 2907.11,631.298C2886.11,610.194 2872.71,582.655 2869.03,553.04L2864.47,532.373ZM1352.36,532.373L2571.64,532.373L2567.07,553.04C2563.39,582.655 2549.99,610.194 2528.99,631.298C2522.85,637.473 2516.16,642.994 2509.03,647.804L993.801,647.804C996.072,636.21 1001.73,625.517 1010.09,617.116C1019.43,607.722 1031.63,601.729 1044.75,600.085L1353.15,532.546L1352.36,532.373Z"
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
export default PostprocessingIcon;
|
18
frontend/src/common/icons/TextToImageIcon.tsx
Normal file
BIN
frontend/src/common/icons/design_files/BaseImage.afdesign
Normal file
BIN
frontend/src/common/icons/design_files/ImageToImage.afdesign
Normal file
7
frontend/src/common/icons/design_files/ImageToImage.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 3543 3543" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1.10943,0,0,1.10943,-206.981,-213.533)">
|
||||
<path d="M688.533,2405.95L542.987,2405.95C349.532,2405.95 192.47,2248.89 192.47,2055.44L192.47,542.987C192.47,349.532 349.532,192.47 542.987,192.47L2527.88,192.47C2721.33,192.47 2878.4,349.532 2878.4,542.987L2878.4,1172.79L3023.94,1172.79C3217.4,1172.79 3374.46,1329.85 3374.46,1523.3C3374.46,1523.3 3374.46,3035.75 3374.46,3035.75C3374.46,3229.21 3217.4,3386.27 3023.94,3386.27L1039.05,3386.27C845.595,3386.27 688.533,3229.21 688.533,3035.75L688.533,2405.95ZM3286.96,2634.37L3286.96,1523.3C3286.96,1378.14 3169.11,1260.29 3023.94,1260.29C3023.94,1260.29 1039.05,1260.29 1039.05,1260.29C893.887,1260.29 776.033,1378.14 776.033,1523.3L776.033,2489.79L1440.94,1736.22L2385.83,2775.59L2880.71,2200.41L3286.96,2634.37ZM2622.05,1405.51C2778.5,1405.51 2905.51,1532.53 2905.51,1688.98C2905.51,1845.42 2778.5,1972.44 2622.05,1972.44C2465.6,1972.44 2338.58,1845.42 2338.58,1688.98C2338.58,1532.53 2465.6,1405.51 2622.05,1405.51ZM2790.9,1172.79L1323.86,1172.79L944.882,755.906L279.97,1509.47L279.97,542.987C279.97,397.824 397.824,279.97 542.987,279.97C542.987,279.97 2527.88,279.97 2527.88,279.97C2673.04,279.97 2790.9,397.824 2790.9,542.987L2790.9,1172.79ZM2125.98,425.197C2282.43,425.197 2409.45,552.213 2409.45,708.661C2409.45,865.11 2282.43,992.126 2125.98,992.126C1969.54,992.126 1842.52,865.11 1842.52,708.661C1842.52,552.213 1969.54,425.197 2125.98,425.197Z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
BIN
frontend/src/common/icons/design_files/Inpaint.afdesign
Normal file
7
frontend/src/common/icons/design_files/Inpaint.svg
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
frontend/src/common/icons/design_files/Nodes.afdesign
Normal file
5
frontend/src/common/icons/design_files/Nodes.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 3543 3543" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M3543.31,770.787C3543.31,515.578 3336.11,308.38 3080.9,308.38L462.407,308.38C207.197,308.38 0,515.578 0,770.787L0,2766.03C0,3021.24 207.197,3228.44 462.407,3228.44L3080.9,3228.44C3336.11,3228.44 3543.31,3021.24 3543.31,2766.03C3543.31,2766.03 3543.31,770.787 3543.31,770.787ZM3427.88,770.787L3427.88,2766.03C3427.88,2957.53 3272.4,3113.01 3080.9,3113.01C3080.9,3113.01 462.407,3113.01 462.407,3113.01C270.906,3113.01 115.431,2957.53 115.431,2766.03L115.431,770.787C115.431,579.286 270.906,423.812 462.407,423.812L3080.9,423.812C3272.4,423.812 3427.88,579.286 3427.88,770.787ZM1214.23,1130.69L1321.47,1130.69C1324.01,1130.69 1326.54,1130.53 1329.05,1130.2C1329.05,1130.2 1367.3,1125.33 1397.94,1149.8C1421.63,1168.72 1437.33,1204.3 1437.33,1265.48L1437.33,2078.74L1220.99,2078.74C1146.83,2078.74 1086.61,2138.95 1086.61,2213.12L1086.61,2762.46C1086.61,2836.63 1146.83,2896.84 1220.99,2896.84L1770.34,2896.84C1844.5,2896.84 1904.71,2836.63 1904.71,2762.46L1904.71,2213.12C1904.71,2138.95 1844.5,2078.74 1770.34,2078.74L1554,2078.74L1554,1604.84C1625.84,1658.19 1703.39,1658.1 1703.39,1658.1C1703.54,1658.1 1703.69,1658.11 1703.84,1658.11L2362.2,1658.11L2362.2,1874.44C2362.2,1948.61 2422.42,2008.82 2496.58,2008.82L3045.93,2008.82C3120.09,2008.82 3180.3,1948.61 3180.3,1874.44L3180.3,1325.1C3180.3,1250.93 3120.09,1190.72 3045.93,1190.72L2496.58,1190.72C2422.42,1190.72 2362.2,1250.93 2362.2,1325.1L2362.2,1558.97L2362.2,1541.44L1704.23,1541.44C1702.2,1541.37 1650.96,1539.37 1609.51,1499.26C1577.72,1468.49 1554,1416.47 1554,1331.69L1554,1265.48C1554,1153.86 1513.98,1093.17 1470.76,1058.64C1411.24,1011.1 1338.98,1012.58 1319.15,1014.03L1214.23,1014.03L1214.23,796.992C1214.23,722.828 1154.02,662.617 1079.85,662.617L530.507,662.617C456.343,662.617 396.131,722.828 396.131,796.992L396.131,1346.34C396.131,1420.5 456.343,1480.71 530.507,1480.71L1079.85,1480.71C1154.02,1480.71 1214.23,1420.5 1214.23,1346.34L1214.23,1130.69Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
BIN
frontend/src/common/icons/design_files/Outpaint.afdesign
Normal file
5
frontend/src/common/icons/design_files/Outpaint.svg
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
frontend/src/common/icons/design_files/Postprocessing.afdesign
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 3543 3543" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||
<path d="M709.477,1596.53L992.591,1275.66L2239.09,2646.81L2891.95,1888.03L3427.88,2460.51L3427.88,994.78C3427.88,954.66 3421.05,916.122 3408.5,880.254L3521.9,855.419C3535.8,899.386 3543.31,946.214 3543.31,994.78L3543.31,2990.02C3543.31,3245.23 3336.11,3452.43 3080.9,3452.43C3080.9,3452.43 462.407,3452.43 462.407,3452.43C207.197,3452.43 -0,3245.23 -0,2990.02L-0,994.78C-0,739.571 207.197,532.373 462.407,532.373L505.419,532.373L504.644,532.546L807.104,600.085C820.223,601.729 832.422,607.722 841.77,617.116C850.131,625.517 855.784,636.21 858.055,647.804L462.407,647.804C270.906,647.804 115.431,803.279 115.431,994.78L115.431,2075.73L-0,2101.5L115.431,2127.28L115.431,2269.78L220.47,2150.73L482.345,2209.21C503.267,2211.83 522.722,2221.39 537.63,2236.37C552.538,2251.35 562.049,2270.9 564.657,2291.93L671.84,2776.17L779.022,2291.93C781.631,2270.9 791.141,2251.35 806.05,2236.37C820.958,2221.39 840.413,2211.83 861.334,2209.21L1353.15,2101.5L861.334,1993.8C840.413,1991.18 820.958,1981.62 806.05,1966.64C791.141,1951.66 781.631,1932.11 779.022,1911.08L709.477,1596.53ZM671.84,1573.09L725.556,2006.07C726.863,2016.61 731.63,2026.4 739.101,2033.91C746.573,2041.42 756.323,2046.21 766.808,2047.53L1197.68,2101.5L766.808,2155.48C756.323,2156.8 746.573,2161.59 739.101,2169.09C731.63,2176.6 726.863,2186.4 725.556,2196.94L671.84,2629.92L618.124,2196.94C616.817,2186.4 612.05,2176.6 604.579,2169.09C597.107,2161.59 587.357,2156.8 576.872,2155.48L146.001,2101.5L576.872,2047.53C587.357,2046.21 597.107,2041.42 604.579,2033.91C612.05,2026.4 616.817,2016.61 618.124,2006.07L671.84,1573.09ZM609.035,1710.36L564.657,1911.08C562.049,1932.11 552.538,1951.66 537.63,1966.64C522.722,1981.62 503.267,1991.18 482.345,1993.8L328.665,2028.11L609.035,1710.36ZM2297.12,938.615L2451.12,973.003C2480.59,976.695 2507.99,990.158 2528.99,1011.26C2549.99,1032.37 2563.39,1059.9 2567.07,1089.52L2672.73,1566.9C2634.5,1580.11 2593.44,1587.29 2550.72,1587.29C2344.33,1587.29 2176.77,1419.73 2176.77,1213.34C2176.77,1104.78 2223.13,1006.96 2297.12,938.615ZM2718.05,76.925L2793.72,686.847C2795.56,701.69 2802.27,715.491 2812.8,726.068C2823.32,736.644 2837.06,743.391 2851.83,745.242L3458.78,821.28L2851.83,897.318C2837.06,899.168 2823.32,905.916 2812.8,916.492C2802.27,927.068 2795.56,940.87 2793.72,955.712L2718.05,1565.63L2642.38,955.712C2640.54,940.87 2633.83,927.068 2623.3,916.492C2612.78,905.916 2599.04,899.168 2584.27,897.318L1977.32,821.28L2584.27,745.242C2599.04,743.391 2612.78,736.644 2623.3,726.068C2633.83,715.491 2640.54,701.69 2642.38,686.847L2718.05,76.925ZM2883.68,1043.06C2909.88,1094.13 2924.67,1152.02 2924.67,1213.34C2924.67,1335.4 2866.06,1443.88 2775.49,1512.14L2869.03,1089.52C2871.07,1073.15 2876.07,1057.42 2883.68,1043.06ZM925.928,201.2L959.611,472.704C960.431,479.311 963.42,485.455 968.105,490.163C972.79,494.871 978.904,497.875 985.479,498.698L1255.66,532.546L985.479,566.395C978.904,567.218 972.79,570.222 968.105,574.93C963.42,579.638 960.431,585.781 959.611,592.388L925.928,863.893L892.245,592.388C891.425,585.781 888.436,579.638 883.751,574.93C879.066,570.222 872.952,567.218 866.378,566.395L596.195,532.546L866.378,498.698C872.952,497.875 879.066,494.871 883.751,490.163C888.436,485.455 891.425,479.311 892.245,472.704L925.928,201.2ZM2864.47,532.373L3080.9,532.373C3258.7,532.373 3413.2,632.945 3490.58,780.281L3319.31,742.773C3257.14,683.925 3173.2,647.804 3080.9,647.804L2927.07,647.804C2919.95,642.994 2913.25,637.473 2907.11,631.298C2886.11,610.194 2872.71,582.655 2869.03,553.04L2864.47,532.373ZM1352.36,532.373L2571.64,532.373L2567.07,553.04C2563.39,582.655 2549.99,610.194 2528.99,631.298C2522.85,637.473 2516.16,642.994 2509.03,647.804L993.801,647.804C996.072,636.21 1001.73,625.517 1010.09,617.116C1019.43,607.722 1031.63,601.729 1044.75,600.085L1353.15,532.546L1352.36,532.373Z" style="stroke:white;stroke-opacity:0;stroke-width:1px;"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.2 KiB |
BIN
frontend/src/common/icons/design_files/TextToImage.afdesign
Normal file
7
frontend/src/common/icons/design_files/TextToImage.svg
Normal file
After Width: | Height: | Size: 8.1 KiB |
@ -6,6 +6,7 @@
|
||||
import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from '../../app/constants';
|
||||
import { OptionsState } from '../../features/options/optionsSlice';
|
||||
import { SystemState } from '../../features/system/systemSlice';
|
||||
|
||||
import {
|
||||
seedWeightsToString,
|
||||
stringToSeedWeightsArray,
|
||||
|
@ -6,7 +6,7 @@ export const stringToSeedWeights = (
|
||||
const stringPairs = string.split(',');
|
||||
const arrPairs = stringPairs.map((p) => p.split(':'));
|
||||
const pairs = arrPairs.map((p: Array<string>): InvokeAI.SeedWeightPair => {
|
||||
return { seed: parseInt(p[0]), weight: parseFloat(p[1]) };
|
||||
return { seed: Number(p[0]), weight: Number(p[1]) };
|
||||
});
|
||||
|
||||
if (!validateSeedWeights(pairs)) {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
@ -13,8 +12,13 @@ import {
|
||||
} from '../options/optionsSlice';
|
||||
import DeleteImageModal from './DeleteImageModal';
|
||||
import { SystemState } from '../system/systemSlice';
|
||||
import SDButton from '../../common/components/SDButton';
|
||||
import IAIButton from '../../common/components/IAIButton';
|
||||
import { runESRGAN, runGFPGAN } from '../../app/socketio/actions';
|
||||
import IAIIconButton from '../../common/components/IAIIconButton';
|
||||
import { MdDelete, MdFace, MdHd, MdImage, MdInfo } from 'react-icons/md';
|
||||
import InvokePopover from './InvokePopover';
|
||||
import UpscaleOptions from '../options/AdvancedOptions/Upscale/UpscaleOptions';
|
||||
import FaceRestoreOptions from '../options/AdvancedOptions/FaceRestore/FaceRestoreOptions';
|
||||
|
||||
const systemSelector = createSelector(
|
||||
(state: RootState) => state.system,
|
||||
@ -50,12 +54,16 @@ const CurrentImageButtons = ({
|
||||
}: CurrentImageButtonsProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { intermediateImage } = useAppSelector(
|
||||
(state: RootState) => state.gallery
|
||||
const intermediateImage = useAppSelector(
|
||||
(state: RootState) => state.gallery.intermediateImage
|
||||
);
|
||||
|
||||
const { upscalingLevel, gfpganStrength } = useAppSelector(
|
||||
(state: RootState) => state.options
|
||||
const upscalingLevel = useAppSelector(
|
||||
(state: RootState) => state.options.upscalingLevel
|
||||
);
|
||||
|
||||
const gfpganStrength = useAppSelector(
|
||||
(state: RootState) => state.options.gfpganStrength
|
||||
);
|
||||
|
||||
const { isProcessing, isConnected, isGFPGANAvailable, isESRGANAvailable } =
|
||||
@ -78,51 +86,34 @@ const CurrentImageButtons = ({
|
||||
setShouldShowImageDetails(!shouldShowImageDetails);
|
||||
|
||||
return (
|
||||
<Flex gap={2}>
|
||||
<SDButton
|
||||
label="Use as initial image"
|
||||
colorScheme={'gray'}
|
||||
flexGrow={1}
|
||||
variant={'outline'}
|
||||
<div className="current-image-options">
|
||||
<IAIIconButton
|
||||
icon={<MdImage />}
|
||||
tooltip="Use As Initial Image"
|
||||
aria-label="Use As Initial Image"
|
||||
onClick={handleClickUseAsInitialImage}
|
||||
/>
|
||||
|
||||
<SDButton
|
||||
label="Use all"
|
||||
colorScheme={'gray'}
|
||||
flexGrow={1}
|
||||
variant={'outline'}
|
||||
isDisabled={!['txt2img', 'img2img'].includes(image?.metadata?.image?.type)}
|
||||
<IAIButton
|
||||
label="Use All"
|
||||
isDisabled={
|
||||
!['txt2img', 'img2img'].includes(image?.metadata?.image?.type)
|
||||
}
|
||||
onClick={handleClickUseAllParameters}
|
||||
/>
|
||||
|
||||
<SDButton
|
||||
label="Use seed"
|
||||
colorScheme={'gray'}
|
||||
flexGrow={1}
|
||||
variant={'outline'}
|
||||
<IAIButton
|
||||
label="Use Seed"
|
||||
isDisabled={!image?.metadata?.image?.seed}
|
||||
onClick={handleClickUseSeed}
|
||||
/>
|
||||
|
||||
<SDButton
|
||||
label="Upscale"
|
||||
colorScheme={'gray'}
|
||||
flexGrow={1}
|
||||
variant={'outline'}
|
||||
isDisabled={
|
||||
!isESRGANAvailable ||
|
||||
Boolean(intermediateImage) ||
|
||||
!(isConnected && !isProcessing) ||
|
||||
!upscalingLevel
|
||||
}
|
||||
onClick={handleClickUpscale}
|
||||
/>
|
||||
<SDButton
|
||||
label="Fix faces"
|
||||
colorScheme={'gray'}
|
||||
flexGrow={1}
|
||||
variant={'outline'}
|
||||
<InvokePopover
|
||||
title="Restore Faces"
|
||||
popoverOptions={<FaceRestoreOptions />}
|
||||
actionButton={
|
||||
<IAIButton
|
||||
label={'Restore Faces'}
|
||||
isDisabled={
|
||||
!isGFPGANAvailable ||
|
||||
Boolean(intermediateImage) ||
|
||||
@ -131,24 +122,47 @@ const CurrentImageButtons = ({
|
||||
}
|
||||
onClick={handleClickFixFaces}
|
||||
/>
|
||||
<SDButton
|
||||
label="Details"
|
||||
colorScheme={'gray'}
|
||||
variant={shouldShowImageDetails ? 'solid' : 'outline'}
|
||||
borderWidth={1}
|
||||
flexGrow={1}
|
||||
}
|
||||
>
|
||||
<IAIIconButton icon={<MdFace />} aria-label="Restore Faces" />
|
||||
</InvokePopover>
|
||||
|
||||
<InvokePopover
|
||||
title="Upscale"
|
||||
styleClass="upscale-popover"
|
||||
popoverOptions={<UpscaleOptions />}
|
||||
actionButton={
|
||||
<IAIButton
|
||||
label={'Upscale Image'}
|
||||
isDisabled={
|
||||
!isESRGANAvailable ||
|
||||
Boolean(intermediateImage) ||
|
||||
!(isConnected && !isProcessing) ||
|
||||
!upscalingLevel
|
||||
}
|
||||
onClick={handleClickUpscale}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<IAIIconButton icon={<MdHd />} aria-label="Upscale" />
|
||||
</InvokePopover>
|
||||
|
||||
<IAIIconButton
|
||||
icon={<MdInfo />}
|
||||
tooltip="Details"
|
||||
aria-label="Details"
|
||||
onClick={handleClickShowImageDetails}
|
||||
/>
|
||||
|
||||
<DeleteImageModal image={image}>
|
||||
<SDButton
|
||||
label="Delete"
|
||||
colorScheme={'red'}
|
||||
flexGrow={1}
|
||||
variant={'outline'}
|
||||
<IAIIconButton
|
||||
icon={<MdDelete />}
|
||||
tooltip="Delete Image"
|
||||
aria-label="Delete Image"
|
||||
isDisabled={Boolean(intermediateImage)}
|
||||
/>
|
||||
</DeleteImageModal>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
91
frontend/src/features/gallery/CurrentImageDisplay.scss
Normal file
@ -0,0 +1,91 @@
|
||||
@use '../../styles/Mixins/' as *;
|
||||
|
||||
.current-image-display {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
'current-image-tools'
|
||||
'current-image-preview';
|
||||
grid-template-rows: auto 1fr;
|
||||
justify-items: center;
|
||||
background-color: var(--background-color-secondary);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.current-image-display-placeholder {
|
||||
background-color: var(--background-color-secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
svg {
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
color: var(--svg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.current-image-tools {
|
||||
grid-area: current-image-tools;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.current-image-options {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
padding: 1rem;
|
||||
height: fit-content;
|
||||
gap: 0.5rem;
|
||||
|
||||
button {
|
||||
@include Button(
|
||||
$btn-width: 3rem,
|
||||
$icon-size: 22px,
|
||||
$btn-color: var(--btn-grey),
|
||||
$btn-color-hover: var(--btn-grey-hover)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.current-image-preview {
|
||||
grid-area: current-image-preview;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
|
||||
img {
|
||||
border-radius: 0.5rem;
|
||||
object-fit: contain;
|
||||
width: auto;
|
||||
max-height: $app-gallery-height;
|
||||
}
|
||||
}
|
||||
|
||||
.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,12 +1,10 @@
|
||||
import { Center, Flex, Image, Text, useColorModeValue } from '@chakra-ui/react';
|
||||
import { Image } from '@chakra-ui/react';
|
||||
import { useAppSelector } from '../../app/store';
|
||||
import { RootState } from '../../app/store';
|
||||
import { useState } from 'react';
|
||||
import ImageMetadataViewer from './ImageMetadataViewer';
|
||||
import CurrentImageButtons from './CurrentImageButtons';
|
||||
|
||||
// TODO: With CSS Grid I had a hard time centering the image in a grid item. This is needed for that.
|
||||
const height = 'calc(100vh - 238px)';
|
||||
import { MdPhoto } from 'react-icons/md';
|
||||
|
||||
/**
|
||||
* Displays the current image if there is one, plus associated actions.
|
||||
@ -16,24 +14,21 @@ const CurrentImageDisplay = () => {
|
||||
(state: RootState) => state.gallery
|
||||
);
|
||||
|
||||
const bgColor = useColorModeValue(
|
||||
'rgba(255, 255, 255, 0.85)',
|
||||
'rgba(0, 0, 0, 0.8)'
|
||||
);
|
||||
|
||||
const [shouldShowImageDetails, setShouldShowImageDetails] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const imageToDisplay = intermediateImage || currentImage;
|
||||
|
||||
return imageToDisplay ? (
|
||||
<Flex direction={'column'} borderWidth={1} rounded={'md'} p={2} gap={2}>
|
||||
<div className="current-image-display">
|
||||
<div className="current-image-tools">
|
||||
<CurrentImageButtons
|
||||
image={imageToDisplay}
|
||||
shouldShowImageDetails={shouldShowImageDetails}
|
||||
setShouldShowImageDetails={setShouldShowImageDetails}
|
||||
/>
|
||||
<Center height={height} position={'relative'}>
|
||||
</div>
|
||||
<div className="current-image-preview">
|
||||
<Image
|
||||
src={imageToDisplay.url}
|
||||
fit="contain"
|
||||
@ -41,26 +36,16 @@ const CurrentImageDisplay = () => {
|
||||
maxHeight={'100%'}
|
||||
/>
|
||||
{shouldShowImageDetails && (
|
||||
<Flex
|
||||
width={'100%'}
|
||||
height={'100%'}
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
left={0}
|
||||
p={3}
|
||||
boxSizing="border-box"
|
||||
backgroundColor={bgColor}
|
||||
overflow="scroll"
|
||||
>
|
||||
<div className="current-image-metadata-viewer">
|
||||
<ImageMetadataViewer image={imageToDisplay} />
|
||||
</Flex>
|
||||
</div>
|
||||
)}
|
||||
</Center>
|
||||
</Flex>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Center height={'100%'} position={'relative'}>
|
||||
<Text size={'xl'}>No image selected</Text>
|
||||
</Center>
|
||||
<div className="current-image-display-placeholder">
|
||||
<MdPhoto />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -119,7 +119,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{image?.metadata?.image?.seed && (
|
||||
{image?.metadata?.image?.seed !== undefined && (
|
||||
<Tooltip label="Use seed">
|
||||
<IconButton
|
||||
aria-label="Use seed"
|
||||
|
51
frontend/src/features/gallery/ImageGallery.scss
Normal file
@ -0,0 +1,51 @@
|
||||
@use '../../styles/Mixins/' as *;
|
||||
|
||||
.image-gallery-container {
|
||||
display: grid;
|
||||
row-gap: 1rem;
|
||||
grid-auto-rows: max-content;
|
||||
min-width: 16rem;
|
||||
}
|
||||
|
||||
.image-gallery-container-placeholder {
|
||||
display: grid;
|
||||
background-color: var(--background-color-secondary);
|
||||
border-radius: 0.5rem;
|
||||
place-items: center;
|
||||
padding: 2rem 0;
|
||||
|
||||
p {
|
||||
color: var(--subtext-color-bright);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
color: var(--svg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.image-gallery {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, max-content);
|
||||
gap: 0.6rem;
|
||||
justify-items: center;
|
||||
max-height: $app-gallery-height;
|
||||
overflow-y: scroll;
|
||||
@include HideScrollbar;
|
||||
}
|
||||
|
||||
.image-gallery-load-more-btn {
|
||||
background-color: var(--btn-load-more) !important;
|
||||
font-size: 0.85rem !important;
|
||||
|
||||
&:disabled {
|
||||
&:hover {
|
||||
background-color: var(--btn-load-more) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--btn-load-more-hover) !important;
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { Button, Center, Flex, Text } from '@chakra-ui/react';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { MdPhotoLibrary } from 'react-icons/md';
|
||||
import { requestImages } from '../../app/socketio/actions';
|
||||
import { RootState, useAppDispatch } from '../../app/store';
|
||||
import { useAppSelector } from '../../app/store';
|
||||
@ -8,7 +9,7 @@ import HoverableImage from './HoverableImage';
|
||||
* Simple image gallery.
|
||||
*/
|
||||
const ImageGallery = () => {
|
||||
const { images, currentImageUuid } = useAppSelector(
|
||||
const { images, currentImageUuid, areMoreImagesAvailable } = useAppSelector(
|
||||
(state: RootState) => state.gallery
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
@ -24,23 +25,41 @@ const ImageGallery = () => {
|
||||
dispatch(requestImages());
|
||||
};
|
||||
|
||||
return images.length ? (
|
||||
<Flex direction={'column'} gap={2} pb={2}>
|
||||
<Flex gap={2} wrap="wrap">
|
||||
return (
|
||||
<div className="image-gallery-container">
|
||||
{images.length ? (
|
||||
<>
|
||||
<p>
|
||||
<strong>Your Invocations</strong>
|
||||
</p>
|
||||
<div className="image-gallery">
|
||||
{images.map((image) => {
|
||||
const { uuid } = image;
|
||||
const isSelected = currentImageUuid === uuid;
|
||||
return (
|
||||
<HoverableImage key={uuid} image={image} isSelected={isSelected} />
|
||||
<HoverableImage
|
||||
key={uuid}
|
||||
image={image}
|
||||
isSelected={isSelected}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
<Button onClick={handleClickLoadMore}>Load more...</Button>
|
||||
</Flex>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<Center height={'100%'} position={'relative'}>
|
||||
<Text size={'xl'}>No images in gallery</Text>
|
||||
</Center>
|
||||
<div className="image-gallery-container-placeholder">
|
||||
<MdPhotoLibrary />
|
||||
<p>No Images In Gallery</p>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
onClick={handleClickLoadMore}
|
||||
isDisabled={!areMoreImagesAvailable}
|
||||
className="image-gallery-load-more-btn"
|
||||
>
|
||||
{areMoreImagesAvailable ? 'Load More' : 'All Images Loaded'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
Flex,
|
||||
Heading,
|
||||
IconButton,
|
||||
Link,
|
||||
Text,
|
||||
Tooltip,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react';
|
||||
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
||||
import { memo } from 'react';
|
||||
@ -39,12 +38,19 @@ type MetadataItemProps = {
|
||||
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 }: MetadataItemProps) => {
|
||||
const MetadataItem = ({
|
||||
label,
|
||||
value,
|
||||
onClick,
|
||||
isLink,
|
||||
labelPosition,
|
||||
}: MetadataItemProps) => {
|
||||
return (
|
||||
<Flex gap={2}>
|
||||
{onClick && (
|
||||
@ -59,7 +65,8 @@ const MetadataItem = ({ label, value, onClick, isLink }: MetadataItemProps) => {
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Text fontWeight={'semibold'} whiteSpace={'nowrap'}>
|
||||
<Flex direction={labelPosition ? 'column' : 'row'}>
|
||||
<Text fontWeight={'semibold'} whiteSpace={'nowrap'} pr={2}>
|
||||
{label}:
|
||||
</Text>
|
||||
{isLink ? (
|
||||
@ -67,11 +74,12 @@ const MetadataItem = ({ label, value, onClick, isLink }: MetadataItemProps) => {
|
||||
{value.toString()} <ExternalLinkIcon mx="2px" />
|
||||
</Link>
|
||||
) : (
|
||||
<Text maxHeight={100} overflowY={'scroll'} wordBreak={'break-all'}>
|
||||
<Text overflowY={'scroll'} wordBreak={'break-all'}>
|
||||
{value.toString()}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@ -93,7 +101,7 @@ const memoEqualityCheck = (
|
||||
*/
|
||||
const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const jsonBgColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.100');
|
||||
// const jsonBgColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.100');
|
||||
|
||||
const metadata = image?.metadata?.image || {};
|
||||
const {
|
||||
@ -119,7 +127,7 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
|
||||
const metadataJSON = JSON.stringify(metadata, null, 2);
|
||||
|
||||
return (
|
||||
<Flex gap={1} direction={'column'} overflowY={'scroll'} width={'100%'}>
|
||||
<Flex gap={1} direction={'column'} width={'100%'}>
|
||||
<Flex gap={2}>
|
||||
<Text fontWeight={'semibold'}>File:</Text>
|
||||
<Link href={image.url} isExternal>
|
||||
@ -129,25 +137,25 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
|
||||
</Flex>
|
||||
{Object.keys(metadata).length > 0 ? (
|
||||
<>
|
||||
{type && <MetadataItem label="Type" value={type} />}
|
||||
{type && <MetadataItem label="Generation type" value={type} />}
|
||||
{['esrgan', 'gfpgan'].includes(type) && (
|
||||
<MetadataItem label="Original image" value={orig_path} isLink />
|
||||
<MetadataItem label="Original image" value={orig_path} />
|
||||
)}
|
||||
{type === 'gfpgan' && strength && (
|
||||
{type === 'gfpgan' && strength !== undefined && (
|
||||
<MetadataItem
|
||||
label="Fix faces strength"
|
||||
value={strength}
|
||||
onClick={() => dispatch(setGfpganStrength(strength))}
|
||||
/>
|
||||
)}
|
||||
{type === 'esrgan' && scale && (
|
||||
{type === 'esrgan' && scale !== undefined && (
|
||||
<MetadataItem
|
||||
label="Upscaling scale"
|
||||
value={scale}
|
||||
onClick={() => dispatch(setUpscalingLevel(scale))}
|
||||
/>
|
||||
)}
|
||||
{type === 'esrgan' && strength && (
|
||||
{type === 'esrgan' && strength !== undefined && (
|
||||
<MetadataItem
|
||||
label="Upscaling strength"
|
||||
value={strength}
|
||||
@ -157,11 +165,12 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
|
||||
{prompt && (
|
||||
<MetadataItem
|
||||
label="Prompt"
|
||||
labelPosition="top"
|
||||
value={promptToString(prompt)}
|
||||
onClick={() => dispatch(setPrompt(prompt))}
|
||||
/>
|
||||
)}
|
||||
{seed && (
|
||||
{seed !== undefined && (
|
||||
<MetadataItem
|
||||
label="Seed"
|
||||
value={seed}
|
||||
@ -182,7 +191,7 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
|
||||
onClick={() => dispatch(setSteps(steps))}
|
||||
/>
|
||||
)}
|
||||
{cfg_scale && (
|
||||
{cfg_scale !== undefined && (
|
||||
<MetadataItem
|
||||
label="CFG scale"
|
||||
value={cfg_scale}
|
||||
@ -249,38 +258,53 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
|
||||
onClick={() => dispatch(setShouldFitToWidthHeight(fit))}
|
||||
/>
|
||||
)}
|
||||
{postprocessing &&
|
||||
postprocessing.length > 0 &&
|
||||
postprocessing.map(
|
||||
(postprocess: InvokeAI.PostProcessedImageMetadata) => {
|
||||
{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="Upscaling scale"
|
||||
label="Scale"
|
||||
value={scale}
|
||||
onClick={() => dispatch(setUpscalingLevel(scale))}
|
||||
/>
|
||||
<MetadataItem
|
||||
label="Upscaling strength"
|
||||
label="Strength"
|
||||
value={strength}
|
||||
onClick={() => dispatch(setUpscalingStrength(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="Fix faces strength"
|
||||
label="Strength"
|
||||
value={strength}
|
||||
onClick={() => dispatch(setGfpganStrength(strength))}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Flex gap={2} direction={'column'}>
|
||||
<Flex gap={2}>
|
||||
<Tooltip label={`Copy metadata JSON`}>
|
||||
@ -295,16 +319,9 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
|
||||
</Tooltip>
|
||||
<Text fontWeight={'semibold'}>Metadata JSON:</Text>
|
||||
</Flex>
|
||||
<Box
|
||||
// maxHeight={200}
|
||||
overflow={'scroll'}
|
||||
flexGrow={3}
|
||||
wordBreak={'break-all'}
|
||||
bgColor={jsonBgColor}
|
||||
padding={2}
|
||||
>
|
||||
<div className={'current-image-json-viewer'}>
|
||||
<pre>{metadataJSON}</pre>
|
||||
</Box>
|
||||
</div>
|
||||
</Flex>
|
||||
</>
|
||||
) : (
|
||||
|
35
frontend/src/features/gallery/InvokePopover.scss
Normal file
@ -0,0 +1,35 @@
|
||||
.popover-content {
|
||||
background-color: var(--background-color-secondary) !important;
|
||||
border: none !important;
|
||||
border-top: 0px;
|
||||
background-color: var(--tab-hover-color);
|
||||
border-radius: 0 0 0.4rem 0.4rem;
|
||||
}
|
||||
|
||||
.popover-arrow {
|
||||
background: var(--tab-hover-color) !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.popover-options {
|
||||
background: var(--tab-panel-bg);
|
||||
border-radius: 0 0 0.4rem 0.4rem;
|
||||
border: 2px solid var(--tab-hover-color);
|
||||
padding: .75rem 1rem .75rem 1rem;
|
||||
display: grid;
|
||||
grid-template-rows: repeat(auto-fill, 1fr);
|
||||
grid-row-gap: 0.5rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.popover-header {
|
||||
background: var(--tab-hover-color);
|
||||
border-radius: 0.4rem 0.4rem 0 0;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
padding-left: 1rem !important;
|
||||
}
|
||||
|
||||
.upscale-popover {
|
||||
width: 23rem !important;
|
||||
}
|
45
frontend/src/features/gallery/InvokePopover.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import {
|
||||
Box,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverContent,
|
||||
PopoverHeader,
|
||||
PopoverTrigger,
|
||||
} from '@chakra-ui/react';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
type PopoverProps = {
|
||||
title?: string;
|
||||
delay?: number;
|
||||
styleClass?: string;
|
||||
popoverOptions?: ReactNode;
|
||||
actionButton?: ReactNode;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const InvokePopover = ({
|
||||
title = 'Popup',
|
||||
styleClass,
|
||||
delay = 50,
|
||||
popoverOptions,
|
||||
actionButton,
|
||||
children,
|
||||
}: PopoverProps) => {
|
||||
return (
|
||||
<Popover trigger={'hover'} closeDelay={delay}>
|
||||
<PopoverTrigger>
|
||||
<Box>{children}</Box>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className={`popover-content ${styleClass}`}>
|
||||
<PopoverArrow className="popover-arrow" />
|
||||
<PopoverHeader className="popover-header">{title}</PopoverHeader>
|
||||
<div className="popover-options">
|
||||
{popoverOptions ? popoverOptions : null}
|
||||
{actionButton}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export default InvokePopover;
|
@ -8,15 +8,15 @@ export interface GalleryState {
|
||||
currentImageUuid: string;
|
||||
images: Array<InvokeAI.Image>;
|
||||
intermediateImage?: InvokeAI.Image;
|
||||
nextPage: number;
|
||||
offset: number;
|
||||
areMoreImagesAvailable: boolean;
|
||||
latest_mtime?: number;
|
||||
earliest_mtime?: number;
|
||||
}
|
||||
|
||||
const initialState: GalleryState = {
|
||||
currentImageUuid: '',
|
||||
images: [],
|
||||
nextPage: 1,
|
||||
offset: 0,
|
||||
areMoreImagesAvailable: true,
|
||||
};
|
||||
|
||||
export const gallerySlice = createSlice({
|
||||
@ -71,11 +71,13 @@ export const gallerySlice = createSlice({
|
||||
state.images = newImages;
|
||||
},
|
||||
addImage: (state, action: PayloadAction<InvokeAI.Image>) => {
|
||||
state.images.unshift(action.payload);
|
||||
state.currentImageUuid = action.payload.uuid;
|
||||
const newImage = action.payload;
|
||||
const { uuid, mtime } = newImage;
|
||||
state.images.unshift(newImage);
|
||||
state.currentImageUuid = uuid;
|
||||
state.intermediateImage = undefined;
|
||||
state.currentImage = action.payload;
|
||||
state.offset += 1
|
||||
state.currentImage = newImage;
|
||||
state.latest_mtime = mtime;
|
||||
},
|
||||
setIntermediateImage: (state, action: PayloadAction<InvokeAI.Image>) => {
|
||||
state.intermediateImage = action.payload;
|
||||
@ -87,20 +89,27 @@ export const gallerySlice = createSlice({
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
images: Array<InvokeAI.Image>;
|
||||
nextPage: number;
|
||||
offset: number;
|
||||
areMoreImagesAvailable: boolean;
|
||||
}>
|
||||
) => {
|
||||
const { images, nextPage, offset } = action.payload;
|
||||
if (images.length) {
|
||||
const newCurrentImage = images[0];
|
||||
const { images, areMoreImagesAvailable } = action.payload;
|
||||
if (images.length > 0) {
|
||||
state.images = state.images
|
||||
.concat(images)
|
||||
.sort((a, b) => b.mtime - a.mtime);
|
||||
|
||||
if (!state.currentImage) {
|
||||
const newCurrentImage = images[0];
|
||||
state.currentImage = newCurrentImage;
|
||||
state.currentImageUuid = newCurrentImage.uuid;
|
||||
state.nextPage = nextPage;
|
||||
state.offset = offset;
|
||||
}
|
||||
|
||||
// keep track of the timestamps of latest and earliest images received
|
||||
state.latest_mtime = images[0].mtime;
|
||||
state.earliest_mtime = images[images.length - 1].mtime;
|
||||
}
|
||||
if (areMoreImagesAvailable !== undefined) {
|
||||
state.areMoreImagesAvailable = areMoreImagesAvailable;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -0,0 +1,38 @@
|
||||
@use '../../../styles/Mixins/' as *;
|
||||
|
||||
.advanced-settings {
|
||||
display: grid;
|
||||
row-gap: 0.5rem;
|
||||
}
|
||||
|
||||
.advanced-settings-item {
|
||||
display: grid;
|
||||
max-width: $options-bar-max-width;
|
||||
border: none;
|
||||
border-top: 0px;
|
||||
border-radius: 0.4rem;
|
||||
|
||||
&[aria-expanded='true'] {
|
||||
background-color: var(--tab-hover-color);
|
||||
border-radius: 0 0 0.4rem 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.advanced-settings-panel {
|
||||
background-color: var(--tab-panel-bg);
|
||||
border-radius: 0 0 0.4rem 0.4rem;
|
||||
border: 2px solid var(--tab-hover-color);
|
||||
}
|
||||
|
||||
.advanced-settings-header {
|
||||
border-radius: 0.4rem;
|
||||
|
||||
&[aria-expanded='true'] {
|
||||
background-color: var(--tab-color);
|
||||
border-radius: 0.4rem 0.4rem 0 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--tab-hover-color) !important;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import {
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
} from '@chakra-ui/react';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { Feature } from '../../../app/features';
|
||||
import GuideIcon from '../../../common/components/GuideIcon';
|
||||
|
||||
interface InvokeAccordionItemProps {
|
||||
header: ReactElement;
|
||||
feature: Feature;
|
||||
options: ReactElement;
|
||||
}
|
||||
|
||||
export default function InvokeAccordionItem(props: InvokeAccordionItemProps) {
|
||||
const { header, feature, options } = props;
|
||||
|
||||
return (
|
||||
<AccordionItem className="advanced-settings-item">
|
||||
<h2>
|
||||
<AccordionButton className="advanced-settings-header">
|
||||
{header}
|
||||
<GuideIcon feature={feature} />
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel className="advanced-settings-panel">
|
||||
{options}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import { Flex } from '@chakra-ui/layout';
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import {
|
||||
RootState,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from '../../../../app/store';
|
||||
import IAISwitch from '../../../../common/components/IAISwitch';
|
||||
import { setShouldRunGFPGAN } from '../../optionsSlice';
|
||||
|
||||
export default function FaceRestore() {
|
||||
const isGFPGANAvailable = useAppSelector(
|
||||
(state: RootState) => state.system.isGFPGANAvailable
|
||||
);
|
||||
|
||||
const shouldRunGFPGAN = useAppSelector(
|
||||
(state: RootState) => state.options.shouldRunGFPGAN
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleChangeShouldRunGFPGAN = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldRunGFPGAN(e.target.checked));
|
||||
|
||||
return (
|
||||
<Flex
|
||||
justifyContent={'space-between'}
|
||||
alignItems={'center'}
|
||||
width={'100%'}
|
||||
mr={2}
|
||||
>
|
||||
<p>Restore Face</p>
|
||||
<IAISwitch
|
||||
isDisabled={!isGFPGANAvailable}
|
||||
isChecked={shouldRunGFPGAN}
|
||||
onChange={handleChangeShouldRunGFPGAN}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
@ -1,15 +1,14 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
|
||||
import { RootState } from '../../app/store';
|
||||
import { useAppDispatch, useAppSelector } from '../../app/store';
|
||||
|
||||
import { OptionsState, setGfpganStrength } from '../options/optionsSlice';
|
||||
import { RootState } from '../../../../app/store';
|
||||
import { useAppDispatch, useAppSelector } from '../../../../app/store';
|
||||
|
||||
import { OptionsState, setGfpganStrength } from '../../optionsSlice';
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { isEqual } from 'lodash';
|
||||
import { SystemState } from '../system/systemSlice';
|
||||
import SDNumberInput from '../../common/components/SDNumberInput';
|
||||
import { SystemState } from '../../../system/systemSlice';
|
||||
import IAINumberInput from '../../../../common/components/IAINumberInput';
|
||||
|
||||
const optionsSelector = createSelector(
|
||||
(state: RootState) => state.options,
|
||||
@ -42,17 +41,16 @@ const systemSelector = createSelector(
|
||||
/**
|
||||
* Displays face-fixing/GFPGAN options (strength).
|
||||
*/
|
||||
const GFPGANOptions = () => {
|
||||
const FaceRestoreOptions = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { gfpganStrength } = useAppSelector(optionsSelector);
|
||||
const { isGFPGANAvailable } = useAppSelector(systemSelector);
|
||||
|
||||
const handleChangeStrength = (v: string | number) =>
|
||||
dispatch(setGfpganStrength(Number(v)));
|
||||
const handleChangeStrength = (v: number) => dispatch(setGfpganStrength(v));
|
||||
|
||||
return (
|
||||
<Flex direction={'column'} gap={2}>
|
||||
<SDNumberInput
|
||||
<IAINumberInput
|
||||
isDisabled={!isGFPGANAvailable}
|
||||
label="Strength"
|
||||
step={0.05}
|
||||
@ -60,9 +58,11 @@ const GFPGANOptions = () => {
|
||||
max={1}
|
||||
onChange={handleChangeStrength}
|
||||
value={gfpganStrength}
|
||||
width="90px"
|
||||
isInteger={false}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default GFPGANOptions;
|
||||
export default FaceRestoreOptions;
|
@ -0,0 +1,27 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import {
|
||||
RootState,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from '../../../../app/store';
|
||||
import IAISwitch from '../../../../common/components/IAISwitch';
|
||||
import { setShouldFitToWidthHeight } from '../../optionsSlice';
|
||||
|
||||
export default function ImageFit() {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const shouldFitToWidthHeight = useAppSelector(
|
||||
(state: RootState) => state.options.shouldFitToWidthHeight
|
||||
);
|
||||
|
||||
const handleChangeFit = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldFitToWidthHeight(e.target.checked));
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
label="Fit initial image to output size"
|
||||
isChecked={shouldFitToWidthHeight}
|
||||
onChange={handleChangeFit}
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import { Flex } from '@chakra-ui/layout';
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import {
|
||||
RootState,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from '../../../../app/store';
|
||||
import IAISwitch from '../../../../common/components/IAISwitch';
|
||||
import { setShouldUseInitImage } from '../../optionsSlice';
|
||||
|
||||
export default function ImageToImage() {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const initialImagePath = useAppSelector(
|
||||
(state: RootState) => state.options.initialImagePath
|
||||
);
|
||||
|
||||
const shouldUseInitImage = useAppSelector(
|
||||
(state: RootState) => state.options.shouldUseInitImage
|
||||
);
|
||||
|
||||
const handleChangeShouldUseInitImage = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldUseInitImage(e.target.checked));
|
||||
return (
|
||||
<Flex
|
||||
justifyContent={'space-between'}
|
||||
alignItems={'center'}
|
||||
width={'100%'}
|
||||
mr={2}
|
||||
>
|
||||
<p>Image to Image</p>
|
||||
<IAISwitch
|
||||
isDisabled={!initialImagePath}
|
||||
isChecked={shouldUseInitImage}
|
||||
onChange={handleChangeShouldUseInitImage}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import InitAndMaskImage from '../../InitAndMaskImage';
|
||||
import ImageFit from './ImageFit';
|
||||
import ImageToImageStrength from './ImageToImageStrength';
|
||||
|
||||
/**
|
||||
* Options for img2img generation (strength, fit, init/mask upload).
|
||||
*/
|
||||
const ImageToImageOptions = () => {
|
||||
return (
|
||||
<Flex direction={'column'} gap={2}>
|
||||
<ImageToImageStrength />
|
||||
<ImageFit />
|
||||
<InitAndMaskImage />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageToImageOptions;
|
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
RootState,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from '../../../../app/store';
|
||||
import IAINumberInput from '../../../../common/components/IAINumberInput';
|
||||
import { setImg2imgStrength } from '../../optionsSlice';
|
||||
|
||||
export default function ImageToImageStrength() {
|
||||
const img2imgStrength = useAppSelector(
|
||||
(state: RootState) => state.options.img2imgStrength
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleChangeStrength = (v: number) => dispatch(setImg2imgStrength(v));
|
||||
|
||||
return (
|
||||
<IAINumberInput
|
||||
label="Strength"
|
||||
step={0.01}
|
||||
min={0.01}
|
||||
max={0.99}
|
||||
onChange={handleChangeStrength}
|
||||
value={img2imgStrength}
|
||||
width="90px"
|
||||
isInteger={false}
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
|
||||
import {
|
||||
RootState,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from '../../../../app/store';
|
||||
import IAISwitch from '../../../../common/components/IAISwitch';
|
||||
import { setShouldRandomizeSeed } from '../../optionsSlice';
|
||||
|
||||
export default function RandomizeSeed() {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const shouldRandomizeSeed = useAppSelector(
|
||||
(state: RootState) => state.options.shouldRandomizeSeed
|
||||
);
|
||||
|
||||
const handleChangeShouldRandomizeSeed = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldRandomizeSeed(e.target.checked));
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
label="Randomize Seed"
|
||||
isChecked={shouldRandomizeSeed}
|
||||
onChange={handleChangeShouldRandomizeSeed}
|
||||
/>
|
||||
);
|
||||
}
|
39
frontend/src/features/options/AdvancedOptions/Seed/Seed.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from '../../../../app/constants';
|
||||
import {
|
||||
RootState,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from '../../../../app/store';
|
||||
import IAINumberInput from '../../../../common/components/IAINumberInput';
|
||||
import { setSeed } from '../../optionsSlice';
|
||||
|
||||
export default function Seed() {
|
||||
const seed = useAppSelector((state: RootState) => state.options.seed);
|
||||
const shouldRandomizeSeed = useAppSelector(
|
||||
(state: RootState) => state.options.shouldRandomizeSeed
|
||||
);
|
||||
const shouldGenerateVariations = useAppSelector(
|
||||
(state: RootState) => state.options.shouldGenerateVariations
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleChangeSeed = (v: number) => dispatch(setSeed(v));
|
||||
|
||||
return (
|
||||
<IAINumberInput
|
||||
label="Seed"
|
||||
step={1}
|
||||
precision={0}
|
||||
flexGrow={1}
|
||||
min={NUMPY_RAND_MIN}
|
||||
max={NUMPY_RAND_MAX}
|
||||
isDisabled={shouldRandomizeSeed}
|
||||
isInvalid={seed < 0 && shouldGenerateVariations}
|
||||
onChange={handleChangeSeed}
|
||||
value={seed}
|
||||
width="10rem"
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import RandomizeSeed from './RandomizeSeed';
|
||||
import Seed from './Seed';
|
||||
import ShuffleSeed from './ShuffleSeed';
|
||||
|
||||
/**
|
||||
* Seed & variation options. Includes iteration, seed, seed randomization, variation options.
|
||||
*/
|
||||
const SeedOptions = () => {
|
||||
return (
|
||||
<Flex gap={2} direction={'column'}>
|
||||
<RandomizeSeed />
|
||||
<Flex gap={2}>
|
||||
<Seed />
|
||||
<ShuffleSeed />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default SeedOptions;
|
@ -0,0 +1,30 @@
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from '../../../../app/constants';
|
||||
import {
|
||||
RootState,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from '../../../../app/store';
|
||||
import randomInt from '../../../../common/util/randomInt';
|
||||
import { setSeed } from '../../optionsSlice';
|
||||
|
||||
export default function ShuffleSeed() {
|
||||
const dispatch = useAppDispatch();
|
||||
const shouldRandomizeSeed = useAppSelector(
|
||||
(state: RootState) => state.options.shouldRandomizeSeed
|
||||
);
|
||||
|
||||
const handleClickRandomizeSeed = () =>
|
||||
dispatch(setSeed(randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX)));
|
||||
|
||||
return (
|
||||
<Button
|
||||
size={'sm'}
|
||||
isDisabled={shouldRandomizeSeed}
|
||||
onClick={handleClickRandomizeSeed}
|
||||
>
|
||||
<p>Shuffle</p>
|
||||
</Button>
|
||||
);
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import { Flex } from '@chakra-ui/layout';
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import {
|
||||
RootState,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from '../../../../app/store';
|
||||
import IAISwitch from '../../../../common/components/IAISwitch';
|
||||
import { setShouldRunESRGAN } from '../../optionsSlice';
|
||||
|
||||
export default function Upscale() {
|
||||
const isESRGANAvailable = useAppSelector(
|
||||
(state: RootState) => state.system.isESRGANAvailable
|
||||
);
|
||||
|
||||
const shouldRunESRGAN = useAppSelector(
|
||||
(state: RootState) => state.options.shouldRunESRGAN
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleChangeShouldRunESRGAN = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldRunESRGAN(e.target.checked));
|
||||
return (
|
||||
<Flex
|
||||
justifyContent={'space-between'}
|
||||
alignItems={'center'}
|
||||
width={'100%'}
|
||||
mr={2}
|
||||
>
|
||||
<p>Upscale</p>
|
||||
<IAISwitch
|
||||
isDisabled={!isESRGANAvailable}
|
||||
isChecked={shouldRunESRGAN}
|
||||
onChange={handleChangeShouldRunESRGAN}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
.upscale-options {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
column-gap: 1rem;
|
||||
}
|
@ -1,23 +1,20 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
|
||||
import { RootState } from '../../app/store';
|
||||
import { useAppDispatch, useAppSelector } from '../../app/store';
|
||||
import { RootState } from '../../../../app/store';
|
||||
import { useAppDispatch, useAppSelector } from '../../../../app/store';
|
||||
|
||||
import {
|
||||
setUpscalingLevel,
|
||||
setUpscalingStrength,
|
||||
UpscalingLevel,
|
||||
OptionsState,
|
||||
} from '../options/optionsSlice';
|
||||
} from '../../optionsSlice';
|
||||
|
||||
|
||||
import { UPSCALING_LEVELS } from '../../app/constants';
|
||||
import { UPSCALING_LEVELS } from '../../../../app/constants';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { isEqual } from 'lodash';
|
||||
import { SystemState } from '../system/systemSlice';
|
||||
import { SystemState } from '../../../system/systemSlice';
|
||||
import { ChangeEvent } from 'react';
|
||||
import SDNumberInput from '../../common/components/SDNumberInput';
|
||||
import SDSelect from '../../common/components/SDSelect';
|
||||
import IAINumberInput from '../../../../common/components/IAINumberInput';
|
||||
import IAISelect from '../../../../common/components/IAISelect';
|
||||
|
||||
const optionsSelector = createSelector(
|
||||
(state: RootState) => state.options,
|
||||
@ -51,27 +48,27 @@ const systemSelector = createSelector(
|
||||
/**
|
||||
* Displays upscaling/ESRGAN options (level and strength).
|
||||
*/
|
||||
const ESRGANOptions = () => {
|
||||
const UpscaleOptions = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { upscalingLevel, upscalingStrength } = useAppSelector(optionsSelector);
|
||||
|
||||
const { isESRGANAvailable } = useAppSelector(systemSelector);
|
||||
|
||||
const handleChangeLevel = (e: ChangeEvent<HTMLSelectElement>) =>
|
||||
dispatch(setUpscalingLevel(Number(e.target.value) as UpscalingLevel));
|
||||
|
||||
const handleChangeStrength = (v: string | number) =>
|
||||
dispatch(setUpscalingStrength(Number(v)));
|
||||
const handleChangeStrength = (v: number) => dispatch(setUpscalingStrength(v));
|
||||
|
||||
return (
|
||||
<Flex direction={'column'} gap={2}>
|
||||
<SDSelect
|
||||
<div className='upscale-options'>
|
||||
<IAISelect
|
||||
isDisabled={!isESRGANAvailable}
|
||||
label="Scale"
|
||||
value={upscalingLevel}
|
||||
onChange={handleChangeLevel}
|
||||
validValues={UPSCALING_LEVELS}
|
||||
/>
|
||||
<SDNumberInput
|
||||
<IAINumberInput
|
||||
isDisabled={!isESRGANAvailable}
|
||||
label="Strength"
|
||||
step={0.05}
|
||||
@ -79,9 +76,10 @@ const ESRGANOptions = () => {
|
||||
max={1}
|
||||
onChange={handleChangeStrength}
|
||||
value={upscalingStrength}
|
||||
isInteger={false}
|
||||
/>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ESRGANOptions;
|
||||
export default UpscaleOptions;
|
@ -0,0 +1,28 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import {
|
||||
RootState,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from '../../../../app/store';
|
||||
import IAISwitch from '../../../../common/components/IAISwitch';
|
||||
import { setShouldGenerateVariations } from '../../optionsSlice';
|
||||
|
||||
export default function GenerateVariations() {
|
||||
const shouldGenerateVariations = useAppSelector(
|
||||
(state: RootState) => state.options.shouldGenerateVariations
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleChangeShouldGenerateVariations = (
|
||||
e: ChangeEvent<HTMLInputElement>
|
||||
) => dispatch(setShouldGenerateVariations(e.target.checked));
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
isChecked={shouldGenerateVariations}
|
||||
width={'auto'}
|
||||
onChange={handleChangeShouldGenerateVariations}
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import {
|
||||
RootState,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from '../../../../app/store';
|
||||
import IAIInput from '../../../../common/components/IAIInput';
|
||||
import { validateSeedWeights } from '../../../../common/util/seedWeightPairs';
|
||||
import { setSeedWeights } from '../../optionsSlice';
|
||||
|
||||
export default function SeedWeights() {
|
||||
const seedWeights = useAppSelector(
|
||||
(state: RootState) => state.options.seedWeights
|
||||
);
|
||||
|
||||
const shouldGenerateVariations = useAppSelector(
|
||||
(state: RootState) => state.options.shouldGenerateVariations
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleChangeSeedWeights = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setSeedWeights(e.target.value));
|
||||
|
||||
return (
|
||||
<IAIInput
|
||||
label={'Seed Weights'}
|
||||
value={seedWeights}
|
||||
isInvalid={
|
||||
shouldGenerateVariations &&
|
||||
!(validateSeedWeights(seedWeights) || seedWeights === '')
|
||||
}
|
||||
isDisabled={!shouldGenerateVariations}
|
||||
onChange={handleChangeSeedWeights}
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
RootState,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from '../../../../app/store';
|
||||
import IAINumberInput from '../../../../common/components/IAINumberInput';
|
||||
import { setVariationAmount } from '../../optionsSlice';
|
||||
|
||||
export default function VariationAmount() {
|
||||
const variationAmount = useAppSelector(
|
||||
(state: RootState) => state.options.variationAmount
|
||||
);
|
||||
|
||||
const shouldGenerateVariations = useAppSelector(
|
||||
(state: RootState) => state.options.shouldGenerateVariations
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleChangevariationAmount = (v: number) =>
|
||||
dispatch(setVariationAmount(v));
|
||||
|
||||
return (
|
||||
<IAINumberInput
|
||||
label="Variation Amount"
|
||||
value={variationAmount}
|
||||
step={0.01}
|
||||
min={0}
|
||||
max={1}
|
||||
isDisabled={!shouldGenerateVariations}
|
||||
onChange={handleChangevariationAmount}
|
||||
isInteger={false}
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import GenerateVariations from './GenerateVariations';
|
||||
|
||||
export default function Variations() {
|
||||
return (
|
||||
<Flex
|
||||
justifyContent={'space-between'}
|
||||
alignItems={'center'}
|
||||
width={'100%'}
|
||||
mr={2}
|
||||
>
|
||||
<p>Variations</p>
|
||||
<GenerateVariations />
|
||||
</Flex>
|
||||
);
|
||||
}
|