Merge branch 'development' of https://github.com/invoke-ai/InvokeAI into development

This commit is contained in:
blessedcoolant 2022-10-07 06:29:24 +13:00
commit bf5ccfffa5
16 changed files with 882 additions and 3 deletions

File diff suppressed because one or more lines are too long

483
frontend/dist/assets/index.d6634413.js vendored Normal file

File diff suppressed because one or more lines are too long

18
frontend/dist/index.html vendored Normal file
View File

@ -0,0 +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 - A Stable Diffusion Toolkit</title>
<link rel="shortcut icon" type="icon" href="/assets/favicon.0d253ced.ico" />
<script type="module" crossorigin src="/assets/index.d6634413.js"></script>
<link rel="stylesheet" href="/assets/index.c485ac40.css">
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@ -23,6 +23,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.2",
"react-hotkeys-hook": "^3.4.7",
"react-icons": "^4.4.0",
"react-redux": "^8.0.2",
"redux-persist": "^6.0.0",

View File

@ -19,6 +19,8 @@ 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';
import { useHotkeys } from 'react-hotkeys-hook';
import { useToast } from '@chakra-ui/react';
const systemSelector = createSelector(
(state: RootState) => state.system,
@ -54,6 +56,8 @@ const CurrentImageButtons = ({
}: CurrentImageButtonsProps) => {
const dispatch = useAppDispatch();
const toast = useToast();
const intermediateImage = useAppSelector(
(state: RootState) => state.gallery.intermediateImage
);
@ -71,19 +75,163 @@ const CurrentImageButtons = ({
const handleClickUseAsInitialImage = () =>
dispatch(setInitialImagePath(image.url));
useHotkeys(
'shift+i',
() => {
if (image) {
handleClickUseAsInitialImage();
toast({
title: 'Sent To Image To Image',
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: 'No Image Loaded',
description: 'No image found to send to image to image module.',
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image]
);
const handleClickUseAllParameters = () =>
dispatch(setAllParameters(image.metadata));
useHotkeys(
'a',
() => {
if (['txt2img', 'img2img'].includes(image?.metadata?.image?.type)) {
handleClickUseAllParameters();
toast({
title: 'Parameters Set',
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: 'Parameters Not Set',
description: 'No metadata found for this image.',
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image]
);
// Non-null assertion: this button is disabled if there is no seed.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const handleClickUseSeed = () => dispatch(setSeed(image.metadata.image.seed));
useHotkeys(
's',
() => {
if (image?.metadata?.image?.seed) {
handleClickUseSeed();
toast({
title: 'Seed Set',
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: 'Seed Not Set',
description: 'Could not find seed for this image.',
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image]
);
const handleClickUpscale = () => dispatch(runESRGAN(image));
useHotkeys(
'u',
() => {
if (
isESRGANAvailable &&
Boolean(!intermediateImage) &&
isConnected &&
!isProcessing &&
upscalingLevel
) {
handleClickUpscale();
} else {
toast({
title: 'Upscaling Failed',
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[
image,
isESRGANAvailable,
intermediateImage,
isConnected,
isProcessing,
upscalingLevel,
]
);
const handleClickFixFaces = () => dispatch(runGFPGAN(image));
useHotkeys(
'r',
() => {
if (
isGFPGANAvailable &&
Boolean(!intermediateImage) &&
isConnected &&
!isProcessing &&
gfpganStrength
) {
handleClickFixFaces();
} else {
toast({
title: 'Face Restoration Failed',
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[
image,
isGFPGANAvailable,
intermediateImage,
isConnected,
isProcessing,
gfpganStrength,
]
);
const handleClickShowImageDetails = () =>
setShouldShowImageDetails(!shouldShowImageDetails);
useHotkeys(
'i',
() => {
if (image) {
handleClickShowImageDetails();
} else {
toast({
title: 'Failed to load metadata',
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image, shouldShowImageDetails]
);
return (
<div className="current-image-options">

View File

@ -27,6 +27,7 @@ import { deleteImage } from '../../app/socketio/actions';
import { RootState } from '../../app/store';
import { setShouldConfirmOnDelete, SystemState } from '../system/systemSlice';
import * as InvokeAI from '../../app/invokeai';
import { useHotkeys } from 'react-hotkeys-hook';
interface DeleteImageModalProps {
/**
@ -67,6 +68,14 @@ const DeleteImageModal = forwardRef(
onClose();
};
useHotkeys(
'del',
() => {
shouldConfirmOnDelete ? onOpen() : handleDelete();
},
[image, shouldConfirmOnDelete]
);
const handleChangeShouldConfirmOnDelete = (
e: ChangeEvent<HTMLInputElement>
) => dispatch(setShouldConfirmOnDelete(!e.target.checked));

View File

@ -4,12 +4,23 @@ import { cancelProcessing } from '../../../app/socketio/actions';
import { useAppDispatch, useAppSelector } from '../../../app/store';
import IAIIconButton from '../../../common/components/IAIIconButton';
import { systemSelector } from '../../../common/hooks/useCheckParameters';
import { useHotkeys } from 'react-hotkeys-hook';
export default function CancelButton() {
const dispatch = useAppDispatch();
const { isProcessing, isConnected } = useAppSelector(systemSelector);
const handleClickCancel = () => dispatch(cancelProcessing());
useHotkeys(
'shift+x',
() => {
if (isConnected || isProcessing) {
handleClickCancel();
}
},
[isConnected, isProcessing]
);
return (
<IAIIconButton
icon={<MdCancel />}

View File

@ -1,5 +1,5 @@
import { FormControl, Textarea } from '@chakra-ui/react';
import { ChangeEvent, KeyboardEvent } from 'react';
import { ChangeEvent, KeyboardEvent, useRef } from 'react';
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
import { generateImage } from '../../../app/socketio/actions';
@ -9,6 +9,7 @@ import { isEqual } from 'lodash';
import useCheckParameters, {
systemSelector,
} from '../../../common/hooks/useCheckParameters';
import { useHotkeys } from 'react-hotkeys-hook';
export const optionsSelector = createSelector(
(state: RootState) => state.options,
@ -28,6 +29,7 @@ export const optionsSelector = createSelector(
* Prompt input text area.
*/
const PromptInput = () => {
const promptRef = useRef<HTMLTextAreaElement>(null);
const { prompt } = useAppSelector(optionsSelector);
const { isProcessing } = useAppSelector(systemSelector);
const dispatch = useAppDispatch();
@ -37,6 +39,24 @@ const PromptInput = () => {
dispatch(setPrompt(e.target.value));
};
useHotkeys(
'ctrl+enter',
() => {
if (isReady) {
dispatch(generateImage());
}
},
[isReady]
);
useHotkeys(
'alt+a',
() => {
promptRef.current?.focus();
},
[]
);
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && e.shiftKey === false && isReady) {
e.preventDefault();
@ -60,6 +80,7 @@ const PromptInput = () => {
onKeyDown={handleKeyDown}
resize="vertical"
height={30}
ref={promptRef}
/>
</FormControl>
</div>

View File

@ -0,0 +1,53 @@
@use '../../../styles/Mixins/' as *;
.hotkeys-modal {
display: grid;
padding: 1rem;
background-color: var(--settings-modal-bg) !important;
row-gap: 1rem;
font-family: Inter;
h1 {
font-size: 1.2rem;
font-weight: bold;
}
}
.hotkeys-modal-items {
display: grid;
row-gap: 0.5rem;
max-height: 32rem;
overflow-y: scroll;
@include HideScrollbar;
}
.hotkey-modal-item {
display: grid;
grid-template-columns: auto max-content;
justify-content: space-between;
align-items: center;
background-color: var(--background-color);
padding: 0.5rem 1rem;
border-radius: 0.3rem;
.hotkey-info {
display: grid;
.hotkey-title {
font-weight: bold;
}
.hotkey-description {
font-size: 0.9rem;
color: var(--text-color-secondary);
}
}
.hotkey-key {
font-size: 0.8rem;
font-weight: bold;
border: 2px solid var(--settings-modal-bg);
padding: 0.2rem 0.5rem;
border-radius: 0.3rem;
}
}

View File

@ -0,0 +1,88 @@
import {
Modal,
ModalCloseButton,
ModalContent,
ModalOverlay,
useDisclosure,
} from '@chakra-ui/react';
import React, { cloneElement, ReactElement } from 'react';
import HotkeysModalItem from './HotkeysModalItem';
type HotkeysModalProps = {
/* The button to open the Settings Modal */
children: ReactElement;
};
export default function HotkeysModal({ children }: HotkeysModalProps) {
const {
isOpen: isHotkeyModalOpen,
onOpen: onHotkeysModalOpen,
onClose: onHotkeysModalClose,
} = useDisclosure();
const hotkeys = [
{ title: 'Invoke', desc: 'Generate an image', hotkey: 'Ctrl+Enter' },
{ title: 'Cancel', desc: 'Cancel image generation', hotkey: 'Shift+X' },
{
title: 'Set Seed',
desc: 'Use the seed of the current image',
hotkey: 'S',
},
{
title: 'Set Parameters',
desc: 'Use all parameters of the current image',
hotkey: 'A',
},
{ title: 'Restore Faces', desc: 'Restore the current image', hotkey: 'R' },
{ title: 'Upscale', desc: 'Upscale the current image', hotkey: 'U' },
{
title: 'Show Info',
desc: 'Show metadata info of the current image',
hotkey: 'I',
},
{
title: 'Send To Image To Image',
desc: 'Send the current image to Image to Image module',
hotkey: 'Shift+I',
},
{ title: 'Delete Image', desc: 'Delete the current image', hotkey: 'Del' },
{
title: 'Focus Prompt',
desc: 'Focus the prompt input area',
hotkey: 'Alt+A',
},
];
const renderHotkeyModalItems = () => {
const hotkeyModalItemsToRender: ReactElement[] = [];
hotkeys.forEach((hotkey, i) => {
hotkeyModalItemsToRender.push(
<HotkeysModalItem
key={i}
title={hotkey.title}
description={hotkey.desc}
hotkey={hotkey.hotkey}
/>
);
});
return hotkeyModalItemsToRender;
};
return (
<>
{cloneElement(children, {
onClick: onHotkeysModalOpen,
})}
<Modal isOpen={isHotkeyModalOpen} onClose={onHotkeysModalClose}>
<ModalOverlay />
<ModalContent className="hotkeys-modal">
<ModalCloseButton />
<h1>Keyboard Shorcuts</h1>
<div className="hotkeys-modal-items">{renderHotkeyModalItems()}</div>
</ModalContent>
</Modal>
</>
);
}

View File

@ -0,0 +1,20 @@
import React from 'react';
interface HotkeysModalProps {
hotkey: string;
title: string;
description?: string;
}
export default function HotkeysModalItem(props: HotkeysModalProps) {
const { title, hotkey, description } = props;
return (
<div className="hotkey-modal-item">
<div className="hotkey-info">
<p className="hotkey-title">{title}</p>
{description && <p className="hotkey-description">{description}</p>}
</div>
<div className="hotkey-key">{hotkey}</div>
</div>
);
}

View File

@ -2,6 +2,7 @@
.settings-modal {
background-color: var(--settings-modal-bg) !important;
font-family: Inter;
.settings-modal-content {
display: grid;

View File

@ -21,7 +21,7 @@
.site-header-right-side {
display: grid;
grid-template-columns: repeat(5, max-content);
grid-template-columns: repeat(6, max-content);
align-items: center;
column-gap: 0.5rem;
}

View File

@ -1,9 +1,11 @@
import { IconButton, Link, useColorMode } from '@chakra-ui/react';
import { FaSun, FaMoon, FaGithub } from 'react-icons/fa';
import { MdHelp, MdSettings } from 'react-icons/md';
import { MdHelp, MdKeyboard, MdSettings } from 'react-icons/md';
import InvokeAILogo from '../../assets/images/logo.png';
import HotkeysModal from './HotkeysModal/HotkeysModal';
import SettingsModal from './SettingsModal/SettingsModal';
import StatusIndicator from './StatusIndicator';
@ -40,6 +42,16 @@ const SiteHeader = () => {
/>
</SettingsModal>
<HotkeysModal>
<IconButton
aria-label="Hotkeys"
variant="link"
fontSize={24}
size={'sm'}
icon={<MdKeyboard />}
/>
</HotkeysModal>
<IconButton
aria-label="Link to Github Issues"
variant="link"

View File

@ -11,6 +11,7 @@
@use '../features/system/SiteHeader.scss';
@use '../features/system/StatusIndicator.scss';
@use '../features/system/SettingsModal/SettingsModal.scss';
@use '../features/system/HotkeysModal/HotkeysModal.scss';
@use '../features/system/Console.scss';
// options

View File

@ -2364,6 +2364,11 @@ hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-
dependencies:
react-is "^16.7.0"
hotkeys-js@3.9.4:
version "3.9.4"
resolved "https://registry.yarnpkg.com/hotkeys-js/-/hotkeys-js-3.9.4.tgz#ce1aa4c3a132b6a63a9dd5644fc92b8a9b9cbfb9"
integrity sha512-2zuLt85Ta+gIyvs4N88pCYskNrxf1TFv3LR9t5mdAZIX8BcgQQ48F2opUptvHa6m8zsy5v/a0i9mWzTrlNWU0Q==
ignore@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
@ -2823,6 +2828,13 @@ react-focus-lock@^2.9.1:
use-callback-ref "^1.3.0"
use-sidecar "^1.1.2"
react-hotkeys-hook@^3.4.7:
version "3.4.7"
resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-3.4.7.tgz#e16a0a85f59feed9f48d12cfaf166d7df4c96b7a"
integrity sha512-+bbPmhPAl6ns9VkXkNNyxlmCAIyDAcWbB76O4I0ntr3uWCRuIQf/aRLartUahe9chVMPj+OEzzfk3CQSjclUEQ==
dependencies:
hotkeys-js "3.9.4"
react-icons@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.4.0.tgz#a13a8a20c254854e1ec9aecef28a95cdf24ef703"