Merge branch 'main' into sdxl-support

This commit is contained in:
Lincoln Stein 2023-07-10 18:51:03 -04:00 committed by GitHub
commit d8ebbd258a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 233 additions and 99 deletions

View File

@ -1,22 +1,16 @@
import sqlite3
import threading
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Generic, Optional, TypeVar, cast
import sqlite3
import threading
from pydantic import BaseModel, Field
from pydantic.generics import GenericModel
from invokeai.app.models.image import ImageCategory, ResourceOrigin
from invokeai.app.models.metadata import ImageMetadata
from invokeai.app.models.image import (
ImageCategory,
ResourceOrigin,
)
from invokeai.app.services.models.image_record import (
ImageRecord,
ImageRecordChanges,
deserialize_image_record,
)
ImageRecord, ImageRecordChanges, deserialize_image_record)
T = TypeVar("T", bound=BaseModel)
@ -162,7 +156,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
node_id TEXT,
metadata TEXT,
is_intermediate BOOLEAN DEFAULT FALSE,
board_id TEXT,
created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),
-- Updated via trigger
updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),

View File

@ -528,7 +528,8 @@
"hidePreview": "Hide Preview",
"showPreview": "Show Preview",
"controlNetControlMode": "Control Mode",
"clipSkip": "Clip Skip"
"clipSkip": "Clip Skip",
"aspectRatio": "Ratio"
},
"settings": {
"models": "Models",
@ -671,6 +672,7 @@
},
"ui": {
"showProgressImages": "Show Progress Images",
"hideProgressImages": "Hide Progress Images"
"hideProgressImages": "Hide Progress Images",
"swapSizes": "Swap Sizes"
}
}

View File

@ -53,13 +53,15 @@ const GalleryImage = (props: HoverableImageProps) => {
const handleClick = useCallback(
(e: MouseEvent<HTMLDivElement>) => {
if (e.shiftKey) {
dispatch(imageRangeEndSelected(props.imageDTO.image_name));
} else if (e.ctrlKey || e.metaKey) {
dispatch(imageSelectionToggled(props.imageDTO.image_name));
} else {
dispatch(imageSelected(props.imageDTO.image_name));
}
// multiselect disabled for now
// if (e.shiftKey) {
// dispatch(imageRangeEndSelected(props.imageDTO.image_name));
// } else if (e.ctrlKey || e.metaKey) {
// dispatch(imageSelectionToggled(props.imageDTO.image_name));
// } else {
// dispatch(imageSelected(props.imageDTO.image_name));
// }
dispatch(imageSelected(props.imageDTO.image_name));
},
[dispatch, props.imageDTO.image_name]
);
@ -121,6 +123,7 @@ const GalleryImage = (props: HoverableImageProps) => {
// withResetIcon // removed bc it's too easy to accidentally delete images
isDropDisabled={true}
isUploadDisabled={true}
thumbnail={true}
/>
</Box>
)}

View File

@ -1,49 +1,32 @@
import { MenuItem, MenuList } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { memo, useCallback, useContext } from 'react';
import {
FaExpand,
FaFolder,
FaFolderPlus,
FaShare,
FaTrash,
} from 'react-icons/fa';
import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu';
import {
resizeAndScaleCanvas,
setInitialCanvasImage,
} from 'features/canvas/store/canvasSlice';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { useTranslation } from 'react-i18next';
import { ExternalLinkIcon } from '@chakra-ui/icons';
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
import { MenuItem, MenuList } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
import { initialImageSelected } from 'features/parameters/store/actions';
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
import { useAppToaster } from 'app/components/Toaster';
import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext';
import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages';
import { ImageDTO } from 'services/api/types';
import { RootState, stateSelector } from 'app/store/store';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu';
import {
imagesAddedToBatch,
selectionAddedToBatch,
} from 'features/batch/store/batchSlice';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import {
resizeAndScaleCanvas,
setInitialCanvasImage,
} from 'features/canvas/store/canvasSlice';
import { imageToDeleteSelected } from 'features/imageDeletion/store/imageDeletionSlice';
const selector = createSelector(
[stateSelector, (state: RootState, imageDTO: ImageDTO) => imageDTO],
({ gallery, batch }, imageDTO) => {
const selectionCount = gallery.selection.length;
const isInBatch = batch.imageNames.includes(imageDTO.image_name);
return { selectionCount, isInBatch };
},
defaultSelectorOptions
);
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
import { initialImageSelected } from 'features/parameters/store/actions';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { memo, useCallback, useContext, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaExpand, FaFolder, FaShare, FaTrash } from 'react-icons/fa';
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages';
import { ImageDTO } from 'services/api/types';
import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext';
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
type Props = {
image: ImageDTO;
@ -51,9 +34,21 @@ type Props = {
};
const ImageContextMenu = ({ image, children }: Props) => {
const { selectionCount, isInBatch } = useAppSelector((state) =>
selector(state, image)
const selector = useMemo(
() =>
createSelector(
[stateSelector],
({ gallery, batch }) => {
const selectionCount = gallery.selection.length;
const isInBatch = batch.imageNames.includes(image.image_name);
return { selectionCount, isInBatch };
},
defaultSelectorOptions
),
[image.image_name]
);
const { selectionCount, isInBatch } = useAppSelector(selector);
const dispatch = useAppDispatch();
const { t } = useTranslation();

View File

@ -8,7 +8,7 @@ import {
selectImagesById,
} from 'features/gallery/store/gallerySlice';
import { clamp, isEqual } from 'lodash-es';
import { useCallback, useState } from 'react';
import { memo, useCallback, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaAngleDoubleRight, FaAngleLeft, FaAngleRight } from 'react-icons/fa';
@ -227,4 +227,4 @@ const NextPrevImageButtons = () => {
);
};
export default NextPrevImageButtons;
export default memo(NextPrevImageButtons);

View File

@ -0,0 +1,37 @@
import { ButtonGroup, Flex } from '@chakra-ui/react';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
import { setAspectRatio } from 'features/ui/store/uiSlice';
const aspectRatios = [
{ name: 'Free', value: null },
{ name: 'Portrait', value: 0.67 / 1 },
{ name: 'Wide', value: 16 / 9 },
{ name: 'Square', value: 1 / 1 },
];
export default function ParamAspectRatio() {
const aspectRatio = useAppSelector(
(state: RootState) => state.ui.aspectRatio
);
const dispatch = useAppDispatch();
return (
<Flex gap={2} flexGrow={1}>
<ButtonGroup isAttached>
{aspectRatios.map((ratio) => (
<IAIButton
key={ratio.name}
size="sm"
isChecked={aspectRatio === ratio.value}
onClick={() => dispatch(setAspectRatio(ratio.value))}
>
{ratio.name}
</IAIButton>
))}
</ButtonGroup>
</Flex>
);
}

View File

@ -2,19 +2,22 @@ import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISlider, { IAIFullSliderProps } from 'common/components/IAISlider';
import { roundToMultiple } from 'common/util/roundDownToMultiple';
import { generationSelector } from 'features/parameters/store/generationSelectors';
import { setHeight } from 'features/parameters/store/generationSlice';
import { setHeight, setWidth } from 'features/parameters/store/generationSlice';
import { configSelector } from 'features/system/store/configSelectors';
import { hotkeysSelector } from 'features/ui/store/hotkeysSlice';
import { uiSelector } from 'features/ui/store/uiSelectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
[generationSelector, hotkeysSelector, configSelector],
(generation, hotkeys, config) => {
[generationSelector, hotkeysSelector, configSelector, uiSelector],
(generation, hotkeys, config, ui) => {
const { initial, min, sliderMax, inputMax, fineStep, coarseStep } =
config.sd.height;
const { height } = generation;
const { aspectRatio } = ui;
const step = hotkeys.shift ? fineStep : coarseStep;
@ -25,6 +28,7 @@ const selector = createSelector(
sliderMax,
inputMax,
step,
aspectRatio,
};
},
defaultSelectorOptions
@ -36,7 +40,7 @@ type ParamHeightProps = Omit<
>;
const ParamHeight = (props: ParamHeightProps) => {
const { height, initial, min, sliderMax, inputMax, step } =
const { height, initial, min, sliderMax, inputMax, step, aspectRatio } =
useAppSelector(selector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
@ -44,13 +48,21 @@ const ParamHeight = (props: ParamHeightProps) => {
const handleChange = useCallback(
(v: number) => {
dispatch(setHeight(v));
if (aspectRatio) {
const newWidth = roundToMultiple(v * aspectRatio, 8);
dispatch(setWidth(newWidth));
}
},
[dispatch]
[dispatch, aspectRatio]
);
const handleReset = useCallback(() => {
dispatch(setHeight(initial));
}, [dispatch, initial]);
if (aspectRatio) {
const newWidth = roundToMultiple(initial * aspectRatio, 8);
dispatch(setWidth(newWidth));
}
}, [dispatch, initial, aspectRatio]);
return (
<IAISlider

View File

@ -0,0 +1,64 @@
import { Flex, Spacer, Text } from '@chakra-ui/react';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { toggleSize } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
import { MdOutlineSwapVert } from 'react-icons/md';
import ParamAspectRatio from './ParamAspectRatio';
import ParamHeight from './ParamHeight';
import ParamWidth from './ParamWidth';
export default function ParamSize() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const shouldFitToWidthHeight = useAppSelector(
(state: RootState) => state.generation.shouldFitToWidthHeight
);
return (
<Flex
sx={{
gap: 2,
p: 4,
borderRadius: 4,
flexDirection: 'column',
w: 'full',
bg: 'base.150',
_dark: {
bg: 'base.750',
},
}}
>
<Flex alignItems="center" gap={2}>
<Text
sx={{
fontSize: 'sm',
width: 'full',
color: 'base.700',
_dark: {
color: 'base.300',
},
}}
>
{t('parameters.aspectRatio')}
</Text>
<Spacer />
<ParamAspectRatio />
<IAIIconButton
tooltip={t('ui.swapSizes')}
aria-label={t('ui.swapSizes')}
size="sm"
icon={<MdOutlineSwapVert />}
fontSize={20}
onClick={() => dispatch(toggleSize())}
/>
</Flex>
<Flex gap={2} alignItems="center">
<Flex gap={2} flexDirection="column" width="full">
<ParamWidth isDisabled={!shouldFitToWidthHeight} />
<ParamHeight isDisabled={!shouldFitToWidthHeight} />
</Flex>
</Flex>
</Flex>
);
}

View File

@ -2,19 +2,22 @@ import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISlider, { IAIFullSliderProps } from 'common/components/IAISlider';
import { roundToMultiple } from 'common/util/roundDownToMultiple';
import { generationSelector } from 'features/parameters/store/generationSelectors';
import { setWidth } from 'features/parameters/store/generationSlice';
import { setHeight, setWidth } from 'features/parameters/store/generationSlice';
import { configSelector } from 'features/system/store/configSelectors';
import { hotkeysSelector } from 'features/ui/store/hotkeysSlice';
import { uiSelector } from 'features/ui/store/uiSelectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
[generationSelector, hotkeysSelector, configSelector],
(generation, hotkeys, config) => {
[generationSelector, hotkeysSelector, configSelector, uiSelector],
(generation, hotkeys, config, ui) => {
const { initial, min, sliderMax, inputMax, fineStep, coarseStep } =
config.sd.width;
const { width } = generation;
const { aspectRatio } = ui;
const step = hotkeys.shift ? fineStep : coarseStep;
@ -25,6 +28,7 @@ const selector = createSelector(
sliderMax,
inputMax,
step,
aspectRatio,
};
},
defaultSelectorOptions
@ -33,7 +37,7 @@ const selector = createSelector(
type ParamWidthProps = Omit<IAIFullSliderProps, 'label' | 'value' | 'onChange'>;
const ParamWidth = (props: ParamWidthProps) => {
const { width, initial, min, sliderMax, inputMax, step } =
const { width, initial, min, sliderMax, inputMax, step, aspectRatio } =
useAppSelector(selector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
@ -41,13 +45,21 @@ const ParamWidth = (props: ParamWidthProps) => {
const handleChange = useCallback(
(v: number) => {
dispatch(setWidth(v));
if (aspectRatio) {
const newHeight = roundToMultiple(v / aspectRatio, 8);
dispatch(setHeight(newHeight));
}
},
[dispatch]
[dispatch, aspectRatio]
);
const handleReset = useCallback(() => {
dispatch(setWidth(initial));
}, [dispatch, initial]);
if (aspectRatio) {
const newHeight = roundToMultiple(initial / aspectRatio, 8);
dispatch(setHeight(newHeight));
}
}, [dispatch, initial, aspectRatio]);
return (
<IAISlider

View File

@ -78,7 +78,7 @@ export default function InvokeButton(props: InvokeButton) {
aria-label={t('parameters.invoke')}
type="submit"
icon={<FaPlay />}
isDisabled={!isReady}
isDisabled={!isReady || isProcessing}
onClick={handleInvoke}
tooltip={t('parameters.invoke')}
tooltipProps={{ placement: 'top' }}
@ -95,7 +95,7 @@ export default function InvokeButton(props: InvokeButton) {
<IAIButton
aria-label={t('parameters.invoke')}
type="submit"
isDisabled={!isReady}
isDisabled={!isReady || isProcessing}
onClick={handleInvoke}
colorScheme="accent"
id="invoke-button"

View File

@ -1,8 +1,12 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import { DEFAULT_SCHEDULER_NAME } from 'app/constants';
import { roundToMultiple } from 'common/util/roundDownToMultiple';
import { configChanged } from 'features/system/store/configSlice';
import { setShouldShowAdvancedOptions } from 'features/ui/store/uiSlice';
import {
setAspectRatio,
setShouldShowAdvancedOptions,
} from 'features/ui/store/uiSlice';
import { clamp } from 'lodash-es';
import { ImageDTO } from 'services/api/types';
import { clipSkipMap } from '../components/Parameters/Advanced/ParamClipSkip';
@ -139,6 +143,11 @@ export const generationSlice = createSlice({
setWidth: (state, action: PayloadAction<number>) => {
state.width = action.payload;
},
toggleSize: (state) => {
const [width, height] = [state.width, state.height];
state.width = height;
state.height = width;
},
setScheduler: (state, action: PayloadAction<SchedulerParam>) => {
state.scheduler = action.payload;
},
@ -262,6 +271,12 @@ export const generationSlice = createSlice({
const advancedOptionsStatus = action.payload;
if (!advancedOptionsStatus) state.clipSkip = 0;
});
builder.addCase(setAspectRatio, (state, action) => {
const ratio = action.payload;
if (ratio) {
state.height = roundToMultiple(state.width / ratio, 8);
}
});
},
});
@ -271,7 +286,9 @@ export const {
resetParametersState,
resetSeed,
setCfgScale,
setWidth,
setHeight,
toggleSize,
setImg2imgStrength,
setInfillMethod,
setIterations,
@ -292,7 +309,6 @@ export const {
setThreshold,
setTileSize,
setVariationAmount,
setWidth,
setShouldUseSymmetry,
setHorizontalSymmetrySteps,
setVerticalSymmetrySteps,

View File

@ -4,11 +4,10 @@ import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse';
import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale';
import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight';
import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations';
import ParamModelandVAEandScheduler from 'features/parameters/components/Parameters/Core/ParamModelandVAEandScheduler';
import ParamSize from 'features/parameters/components/Parameters/Core/ParamSize';
import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps';
import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth';
import ImageToImageFit from 'features/parameters/components/Parameters/ImageToImage/ImageToImageFit';
import ImageToImageStrength from 'features/parameters/components/Parameters/ImageToImage/ImageToImageStrength';
import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull';
@ -47,15 +46,14 @@ const ImageToImageTabCoreParameters = () => {
>
{shouldUseSliders ? (
<>
<ParamIterations />
<ParamSteps />
<ParamCFGScale />
<ParamModelandVAEandScheduler />
<Box pt={2}>
<ParamSeedFull />
</Box>
<ParamIterations />
<ParamSteps />
<ParamCFGScale />
<ParamWidth isDisabled={!shouldFitToWidthHeight} />
<ParamHeight isDisabled={!shouldFitToWidthHeight} />
<ParamSize />
</>
) : (
<>
@ -68,8 +66,7 @@ const ImageToImageTabCoreParameters = () => {
<Box pt={2}>
<ParamSeedFull />
</Box>
<ParamWidth isDisabled={!shouldFitToWidthHeight} />
<ParamHeight isDisabled={!shouldFitToWidthHeight} />
<ParamSize />
</>
)}
<ImageToImageStrength />

View File

@ -5,11 +5,10 @@ import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse';
import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale';
import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight';
import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations';
import ParamModelandVAEandScheduler from 'features/parameters/components/Parameters/Core/ParamModelandVAEandScheduler';
import ParamSize from 'features/parameters/components/Parameters/Core/ParamSize';
import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps';
import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth';
import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull';
import { memo } from 'react';
@ -43,15 +42,14 @@ const TextToImageTabCoreParameters = () => {
>
{shouldUseSliders ? (
<>
<ParamIterations />
<ParamSteps />
<ParamCFGScale />
<ParamModelandVAEandScheduler />
<Box pt={2}>
<ParamSeedFull />
</Box>
<ParamIterations />
<ParamSteps />
<ParamCFGScale />
<ParamWidth />
<ParamHeight />
<ParamSize />
</>
) : (
<>
@ -64,8 +62,7 @@ const TextToImageTabCoreParameters = () => {
<Box pt={2}>
<ParamSeedFull />
</Box>
<ParamWidth />
<ParamHeight />
<ParamSize />
</>
)}
</Flex>

View File

@ -44,13 +44,13 @@ const UnifiedCanvasCoreParameters = () => {
>
{shouldUseSliders ? (
<>
<ParamIterations />
<ParamSteps />
<ParamCFGScale />
<ParamModelandVAEandScheduler />
<Box pt={2}>
<ParamSeedFull />
</Box>
<ParamIterations />
<ParamSteps />
<ParamCFGScale />
<ParamBoundingBoxWidth />
<ParamBoundingBoxHeight />
</>

View File

@ -21,6 +21,7 @@ export const initialUIState: UIState = {
shouldShowProgressInViewer: true,
shouldShowEmbeddingPicker: false,
shouldShowAdvancedOptions: false,
aspectRatio: null,
favoriteSchedulers: [],
};
@ -104,6 +105,9 @@ export const uiSlice = createSlice({
setShouldShowAdvancedOptions: (state, action: PayloadAction<boolean>) => {
state.shouldShowAdvancedOptions = action.payload;
},
setAspectRatio: (state, action: PayloadAction<number | null>) => {
state.aspectRatio = action.payload;
},
},
extraReducers(builder) {
builder.addCase(initialImageChanged, (state) => {
@ -132,6 +136,7 @@ export const {
favoriteSchedulersChanged,
toggleEmbeddingPicker,
setShouldShowAdvancedOptions,
setAspectRatio,
} = uiSlice.actions;
export default uiSlice.reducer;

View File

@ -29,5 +29,6 @@ export interface UIState {
shouldShowProgressInViewer: boolean;
shouldShowEmbeddingPicker: boolean;
shouldShowAdvancedOptions: boolean;
aspectRatio: number | null;
favoriteSchedulers: SchedulerParam[];
}