mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Adds hints when unable to invoke
- Popover on Invoke button indicates why exactly it is disabled, e.g. prompt is empty, something else is processing, etc. - There may be more than one reason; all are displayed.
This commit is contained in:
parent
0eef74bc00
commit
871a8a5375
517
frontend/dist/assets/index.3a0c23f9.js
vendored
Normal file
517
frontend/dist/assets/index.3a0c23f9.js
vendored
Normal file
File diff suppressed because one or more lines are too long
517
frontend/dist/assets/index.784f40b4.js
vendored
517
frontend/dist/assets/index.784f40b4.js
vendored
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index.c1f2c49a.css
vendored
Normal file
1
frontend/dist/assets/index.c1f2c49a.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index.e29f3ddc.css
vendored
1
frontend/dist/assets/index.e29f3ddc.css
vendored
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@ -6,8 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<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.784f40b4.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.e29f3ddc.css">
|
||||
<script type="module" crossorigin src="./assets/index.3a0c23f9.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.c1f2c49a.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import ProgressBar from '../features/system/ProgressBar';
|
||||
import SiteHeader from '../features/system/SiteHeader';
|
||||
import Console from '../features/system/Console';
|
||||
import Loading from '../Loading';
|
||||
import { useAppDispatch } from './store';
|
||||
import { requestSystemConfig } from './socketio/actions';
|
||||
import { keepGUIAlive } from './utils';
|
||||
@ -16,9 +15,10 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import { GalleryState } from '../features/gallery/gallerySlice';
|
||||
import { OptionsState } from '../features/options/optionsSlice';
|
||||
import { activeTabNameSelector } from '../features/options/optionsSelectors';
|
||||
import { SystemState } from '../features/system/systemSlice';
|
||||
import { readinessChanged, SystemState } from '../features/system/systemSlice';
|
||||
import _ from 'lodash';
|
||||
import { Model } from './invokeai';
|
||||
import { readinessSelector } from './selectors/readinessSelector';
|
||||
|
||||
keepGUIAlive();
|
||||
|
||||
@ -79,17 +79,20 @@ const appSelector = createSelector(
|
||||
const App = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [isReady, setIsReady] = useState<boolean>(false);
|
||||
|
||||
// const [isReady, setIsReady] = useState<boolean>(false);
|
||||
const { isReady, reasonsWhyNotReady } = useAppSelector(readinessSelector);
|
||||
const { shouldShowGalleryButton, shouldShowOptionsPanelButton } =
|
||||
useAppSelector(appSelector);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(requestSystemConfig());
|
||||
setIsReady(true);
|
||||
}, [dispatch]);
|
||||
|
||||
return isReady ? (
|
||||
useEffect(() => {
|
||||
dispatch(readinessChanged({ isReady, reasonsWhyNotReady }));
|
||||
}, [dispatch, isReady, reasonsWhyNotReady]);
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<ImageUploader>
|
||||
<ProgressBar />
|
||||
@ -104,8 +107,6 @@ const App = () => {
|
||||
{shouldShowOptionsPanelButton && <FloatingOptionsPanelButtons />}
|
||||
</ImageUploader>
|
||||
</div>
|
||||
) : (
|
||||
<Loading />
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import _ from 'lodash';
|
||||
import { RootState } from '../../app/store';
|
||||
import { RootState } from '../store';
|
||||
import { activeTabNameSelector } from '../../features/options/optionsSelectors';
|
||||
import { OptionsState } from '../../features/options/optionsSlice';
|
||||
|
||||
@ -25,7 +25,7 @@ export const readinessSelector = createSelector(
|
||||
prompt,
|
||||
shouldGenerateVariations,
|
||||
seedWeights,
|
||||
maskPath,
|
||||
// maskPath,
|
||||
initialImage,
|
||||
seed,
|
||||
} = options;
|
||||
@ -34,33 +34,49 @@ export const readinessSelector = createSelector(
|
||||
|
||||
const { imageToInpaint } = inpainting;
|
||||
|
||||
let isReady = true;
|
||||
const reasonsWhyNotReady: string[] = [];
|
||||
|
||||
// Cannot generate without a prompt
|
||||
if (!prompt || Boolean(prompt.match(/^[\s\r\n]+$/))) {
|
||||
return false;
|
||||
isReady = false;
|
||||
reasonsWhyNotReady.push('Missing a prompt.');
|
||||
}
|
||||
|
||||
if (activeTabName === 'img2img' && !initialImage) {
|
||||
return false;
|
||||
isReady = false;
|
||||
reasonsWhyNotReady.push(
|
||||
'On ImageToImage tab, but no initial image is selected.'
|
||||
);
|
||||
}
|
||||
|
||||
if (activeTabName === 'inpainting' && !imageToInpaint) {
|
||||
return false;
|
||||
isReady = false;
|
||||
reasonsWhyNotReady.push(
|
||||
'On Inpainting tab, but no initial image is selected.'
|
||||
);
|
||||
}
|
||||
|
||||
// Cannot generate with a mask without img2img
|
||||
if (maskPath && !initialImage) {
|
||||
return false;
|
||||
}
|
||||
// // We don't use mask paths now.
|
||||
// // Cannot generate with a mask without img2img
|
||||
// if (maskPath && !initialImage) {
|
||||
// isReady = false;
|
||||
// reasonsWhyNotReady.push(
|
||||
// 'On ImageToImage tab, but no mask is provided.'
|
||||
// );
|
||||
// }
|
||||
|
||||
// TODO: job queue
|
||||
// Cannot generate if already processing an image
|
||||
if (isProcessing) {
|
||||
return false;
|
||||
isReady = false;
|
||||
reasonsWhyNotReady.push('System is already processing something.');
|
||||
}
|
||||
|
||||
// Cannot generate if not connected
|
||||
if (!isConnected) {
|
||||
return false;
|
||||
isReady = false;
|
||||
reasonsWhyNotReady.push('System is disconnected.');
|
||||
}
|
||||
|
||||
// Cannot generate variations without valid seed weights
|
||||
@ -68,11 +84,12 @@ export const readinessSelector = createSelector(
|
||||
shouldGenerateVariations &&
|
||||
(!(validateSeedWeights(seedWeights) || seedWeights === '') || seed === -1)
|
||||
) {
|
||||
return false;
|
||||
isReady = false;
|
||||
reasonsWhyNotReady.push('Seed-weight pairs are badly formatted.');
|
||||
}
|
||||
|
||||
// All good
|
||||
return true;
|
||||
return { isReady, reasonsWhyNotReady };
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
|
@ -4,12 +4,14 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Box,
|
||||
BoxProps,
|
||||
} from '@chakra-ui/react';
|
||||
import { PopoverProps } from '@chakra-ui/react';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type IAIPopoverProps = PopoverProps & {
|
||||
triggerComponent: ReactNode;
|
||||
triggerContainerProps?: BoxProps;
|
||||
children: ReactNode;
|
||||
styleClass?: string;
|
||||
hasArrow?: boolean;
|
||||
@ -18,15 +20,17 @@ type IAIPopoverProps = PopoverProps & {
|
||||
const IAIPopover = (props: IAIPopoverProps) => {
|
||||
const {
|
||||
triggerComponent,
|
||||
triggerContainerProps,
|
||||
children,
|
||||
styleClass,
|
||||
hasArrow = true,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Popover {...rest}>
|
||||
<PopoverTrigger>
|
||||
<Box>{triggerComponent}</Box>
|
||||
<Box {...triggerContainerProps}>{triggerComponent}</Box>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className={`invokeai__popover-content ${styleClass}`}>
|
||||
{hasArrow && <PopoverArrow className={'invokeai__popover-arrow'} />}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ListItem, UnorderedList } from '@chakra-ui/react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { FaPlay } from 'react-icons/fa';
|
||||
import { readinessSelector } from '../../../app/selectors/readinessSelector';
|
||||
@ -7,6 +8,7 @@ import IAIButton, {
|
||||
IAIButtonProps,
|
||||
} from '../../../common/components/IAIButton';
|
||||
import IAIIconButton from '../../../common/components/IAIIconButton';
|
||||
import IAIPopover from '../../../common/components/IAIPopover';
|
||||
import { activeTabNameSelector } from '../optionsSelectors';
|
||||
|
||||
interface InvokeButton extends Omit<IAIButtonProps, 'label'> {
|
||||
@ -16,7 +18,7 @@ interface InvokeButton extends Omit<IAIButtonProps, 'label'> {
|
||||
export default function InvokeButton(props: InvokeButton) {
|
||||
const { iconButton = false, ...rest } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const isReady = useAppSelector(readinessSelector);
|
||||
const { isReady, reasonsWhyNotReady } = useAppSelector(readinessSelector);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
|
||||
const handleClickGenerate = () => {
|
||||
@ -33,7 +35,7 @@ export default function InvokeButton(props: InvokeButton) {
|
||||
[isReady, activeTabName]
|
||||
);
|
||||
|
||||
return iconButton ? (
|
||||
const buttonComponent = iconButton ? (
|
||||
<IAIIconButton
|
||||
aria-label="Invoke"
|
||||
type="submit"
|
||||
@ -56,4 +58,22 @@ export default function InvokeButton(props: InvokeButton) {
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
return isReady ? (
|
||||
buttonComponent
|
||||
) : (
|
||||
<IAIPopover
|
||||
trigger="hover"
|
||||
triggerContainerProps={{ style: { flexGrow: 4 } }}
|
||||
triggerComponent={buttonComponent}
|
||||
>
|
||||
{reasonsWhyNotReady && (
|
||||
<UnorderedList>
|
||||
{reasonsWhyNotReady.map((reason, i) => (
|
||||
<ListItem key={i}>{reason}</ListItem>
|
||||
))}
|
||||
</UnorderedList>
|
||||
)}
|
||||
</IAIPopover>
|
||||
);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
.invoke-btn {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
svg {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
|
@ -31,7 +31,7 @@ const promptInputSelector = createSelector(
|
||||
const PromptInput = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { prompt, activeTabName } = useAppSelector(promptInputSelector);
|
||||
const isReady = useAppSelector(readinessSelector);
|
||||
const { isReady } = useAppSelector(readinessSelector);
|
||||
|
||||
const promptRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
|
@ -15,6 +15,11 @@ export interface Log {
|
||||
[index: number]: LogEntry;
|
||||
}
|
||||
|
||||
export type ReadinessPayload = {
|
||||
isReady: boolean;
|
||||
reasonsWhyNotReady: string[];
|
||||
};
|
||||
|
||||
export interface SystemState
|
||||
extends InvokeAI.SystemStatus,
|
||||
InvokeAI.SystemConfig {
|
||||
@ -36,6 +41,8 @@ export interface SystemState
|
||||
shouldDisplayGuides: boolean;
|
||||
wasErrorSeen: boolean;
|
||||
isCancelable: boolean;
|
||||
isReady: boolean;
|
||||
reasonsWhyNotReady: string[];
|
||||
}
|
||||
|
||||
const initialSystemState = {
|
||||
@ -65,6 +72,8 @@ const initialSystemState = {
|
||||
hasError: false,
|
||||
wasErrorSeen: true,
|
||||
isCancelable: true,
|
||||
isReady: false,
|
||||
reasonsWhyNotReady: [],
|
||||
};
|
||||
|
||||
const initialState: SystemState = initialSystemState;
|
||||
@ -178,6 +187,11 @@ export const systemSlice = createSlice({
|
||||
state.isProcessing = true;
|
||||
state.currentStatusHasSteps = false;
|
||||
},
|
||||
readinessChanged: (state, action: PayloadAction<ReadinessPayload>) => {
|
||||
const { isReady, reasonsWhyNotReady } = action.payload;
|
||||
state.isReady = isReady;
|
||||
state.reasonsWhyNotReady = reasonsWhyNotReady;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -200,6 +214,7 @@ export const {
|
||||
setModelList,
|
||||
setIsCancelable,
|
||||
modelChangeRequested,
|
||||
readinessChanged,
|
||||
} = systemSlice.actions;
|
||||
|
||||
export default systemSlice.reducer;
|
||||
|
@ -34,10 +34,7 @@ import InpaintingBoundingBoxPreview, {
|
||||
} from './components/InpaintingBoundingBoxPreview';
|
||||
import { KonvaEventObject } from 'konva/lib/Node';
|
||||
import KeyboardEventManager from './components/KeyboardEventManager';
|
||||
import { Icon, IconButton, Tooltip, useToast } from '@chakra-ui/react';
|
||||
import { FaLock, FaUnlock } from 'react-icons/fa';
|
||||
import { MdInvertColors, MdInvertColorsOff } from 'react-icons/md';
|
||||
import { BiHide, BiShow } from 'react-icons/bi';
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
import InpaintingCanvasStatusIcons from './InpaintingCanvasStatusIcons';
|
||||
|
||||
// Use a closure allow other components to use these things... not ideal...
|
||||
@ -60,9 +57,6 @@ const InpaintingCanvas = () => {
|
||||
shouldShowBoundingBox,
|
||||
shouldShowBoundingBoxFill,
|
||||
isDrawing,
|
||||
shouldLockBoundingBox,
|
||||
boundingBoxDimensions,
|
||||
// isTransformingBoundingBox,
|
||||
isMouseOverBoundingBox,
|
||||
isModifyingBoundingBox,
|
||||
stageCursor,
|
||||
|
@ -5,7 +5,7 @@ import { KonvaEventObject } from 'konva/lib/Node';
|
||||
import { Box } from 'konva/lib/shapes/Transformer';
|
||||
import { Vector2d } from 'konva/lib/types';
|
||||
import _ from 'lodash';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { Group, Rect, Transformer } from 'react-konva';
|
||||
import {
|
||||
RootState,
|
||||
@ -13,7 +13,6 @@ import {
|
||||
useAppSelector,
|
||||
} from '../../../../app/store';
|
||||
import { roundToMultiple } from '../../../../common/util/roundDownToMultiple';
|
||||
import { stageRef } from '../InpaintingCanvas';
|
||||
import {
|
||||
InpaintingState,
|
||||
setBoundingBoxCoordinate,
|
||||
|
@ -10,9 +10,7 @@ import { activeTabNameSelector } from '../../../options/optionsSelectors';
|
||||
import { OptionsState } from '../../../options/optionsSlice';
|
||||
import {
|
||||
InpaintingState,
|
||||
setIsDrawing,
|
||||
setIsSpacebarHeld,
|
||||
setNeedsCache,
|
||||
setShouldLockBoundingBox,
|
||||
toggleTool,
|
||||
} from '../inpaintingSlice';
|
||||
|
Loading…
Reference in New Issue
Block a user