Merge branch 'main' into dev/diffusers

This commit is contained in:
Kevin Turner 2022-12-12 23:09:04 -08:00 committed by GitHub
commit 07ac88baed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 2158 additions and 771 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

623
frontend/dist/assets/index.d8f54146.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -8,8 +8,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.d864890e.js"></script>
<link rel="stylesheet" href="./assets/index.81f1c71c.css">
<script type="module" crossorigin src="./assets/index.d8f54146.js"></script>
<link rel="stylesheet" href="./assets/index.8ee30fa0.css">
<script type="module">try{import.meta.url;import("_").catch(()=>1);}catch(e){}window.__vite_is_modern_browser=true;</script>
<script type="module">!function(){if(window.__vite_is_modern_browser)return;console.warn("vite: loading legacy build because dynamic import or import.meta.url is unsupported, syntax error above should be ignored");var e=document.getElementById("vite-legacy-polyfill"),n=document.createElement("script");n.src=e.src,n.onload=function(){System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))},document.body.appendChild(n)}();</script>
</head>
@ -19,7 +19,7 @@
<script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
<script nomodule crossorigin id="vite-legacy-polyfill" src="./assets/polyfills-legacy-dde3a68a.js"></script>
<script nomodule crossorigin id="vite-legacy-entry" data-src="./assets/index-legacy-8e84772c.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
<script nomodule crossorigin id="vite-legacy-entry" data-src="./assets/index-legacy-f27062cd.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
</body>
</html>

View File

@ -40,6 +40,7 @@ export type IAIFullSliderProps = {
sliderMarkRightOffset?: number;
withInput?: boolean;
isInteger?: boolean;
width?: string | number;
inputWidth?: string | number;
inputReadOnly?: boolean;
withReset?: boolean;
@ -71,6 +72,7 @@ export default function IAISlider(props: IAIFullSliderProps) {
max = 100,
step = 1,
onChange,
width = '100%',
tooltipSuffix = '',
withSliderMarks = false,
sliderMarkLeftOffset = 0,
@ -161,6 +163,7 @@ export default function IAISlider(props: IAIFullSliderProps) {
onMouseLeave={() => setShowTooltip(false)}
focusThumbOnChange={false}
isDisabled={isSliderDisabled}
width={width}
{...rest}
>
{withSliderMarks && (

View File

@ -3,8 +3,11 @@ import _ from 'lodash';
import { useHotkeys } from 'react-hotkeys-hook';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import {
clearMask,
resetCanvasInteractionState,
setIsMaskEnabled,
setShouldShowBoundingBox,
setShouldSnapToGrid,
setTool,
} from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
@ -24,6 +27,8 @@ const selector = createSelector(
shouldLockBoundingBox,
shouldShowBoundingBox,
tool,
isMaskEnabled,
shouldSnapToGrid,
} = canvas;
return {
@ -33,6 +38,8 @@ const selector = createSelector(
shouldShowBoundingBox,
tool,
isStaging,
isMaskEnabled,
shouldSnapToGrid,
};
},
{
@ -44,13 +51,62 @@ const selector = createSelector(
const useInpaintingCanvasHotkeys = () => {
const dispatch = useAppDispatch();
const { activeTabName, shouldShowBoundingBox, tool, isStaging } =
useAppSelector(selector);
const {
activeTabName,
shouldShowBoundingBox,
tool,
isStaging,
isMaskEnabled,
shouldSnapToGrid,
} = useAppSelector(selector);
const previousToolRef = useRef<CanvasTool | null>(null);
const canvasStage = getCanvasStage();
// Beta Keys
const handleClearMask = () => dispatch(clearMask());
useHotkeys(
['shift+c'],
() => {
handleClearMask();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[]
);
const handleToggleEnableMask = () =>
dispatch(setIsMaskEnabled(!isMaskEnabled));
useHotkeys(
['h'],
() => {
handleToggleEnableMask();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[isMaskEnabled]
);
useHotkeys(
['n'],
() => {
dispatch(setShouldSnapToGrid(!shouldSnapToGrid));
},
{
enabled: true,
preventDefault: true,
},
[shouldSnapToGrid]
);
//
useHotkeys(
'esc',
() => {

View File

@ -70,6 +70,9 @@ const PromptInput = () => {
resize="vertical"
height={30}
ref={promptRef}
_placeholder={{
color: 'var(--text-color-secondary)',
}}
/>
</FormControl>
</div>

View File

@ -55,6 +55,7 @@ export interface OptionsState {
upscalingStrength: number;
variationAmount: number;
width: number;
shouldUseCanvasBetaLayout: boolean;
}
const initialOptionsState: OptionsState = {
@ -101,6 +102,7 @@ const initialOptionsState: OptionsState = {
upscalingStrength: 0.75,
variationAmount: 0.1,
width: 512,
shouldUseCanvasBetaLayout: false,
};
const initialState: OptionsState = initialOptionsState;
@ -396,6 +398,9 @@ export const optionsSlice = createSlice({
setInfillMethod: (state, action: PayloadAction<string>) => {
state.infillMethod = action.payload;
},
setShouldUseCanvasBetaLayout: (state, action: PayloadAction<boolean>) => {
state.shouldUseCanvasBetaLayout = action.payload;
},
},
});
@ -451,6 +456,7 @@ export const {
setUpscalingStrength,
setVariationAmount,
setWidth,
setShouldUseCanvasBetaLayout,
} = optionsSlice.actions;
export default optionsSlice.reducer;

View File

@ -23,8 +23,9 @@ const selector = createSelector(
},
''
);
const activeDesc = model_list[activeModel].description;
return { models, activeModel, isProcessing };
return { models, activeModel, isProcessing, activeDesc };
},
{
memoizeOptions: {
@ -35,7 +36,8 @@ const selector = createSelector(
const ModelSelect = () => {
const dispatch = useAppDispatch();
const { models, activeModel, isProcessing } = useAppSelector(selector);
const { models, activeModel, isProcessing, activeDesc } =
useAppSelector(selector);
const handleChangeModel = (e: ChangeEvent<HTMLSelectElement>) => {
dispatch(requestModelChange(e.target.value));
};
@ -48,6 +50,7 @@ const ModelSelect = () => {
>
<IAISelect
style={{ fontSize: '0.8rem' }}
tooltip={activeDesc}
isDisabled={isProcessing}
value={activeModel}
validValues={models}

View File

@ -32,10 +32,11 @@ import IAISelect from 'common/components/IAISelect';
import IAINumberInput from 'common/components/IAINumberInput';
import { systemSelector } from 'features/system/store/systemSelectors';
import { optionsSelector } from 'features/options/store/optionsSelectors';
import { setShouldUseCanvasBetaLayout } from 'features/options/store/optionsSlice';
const selector = createSelector(
[systemSelector, optionsSelector],
(system) => {
(system, options) => {
const {
shouldDisplayInProgressType,
shouldConfirmOnDelete,
@ -45,6 +46,8 @@ const selector = createSelector(
enableImageDebugging,
} = system;
const { shouldUseCanvasBetaLayout } = options;
return {
shouldDisplayInProgressType,
shouldConfirmOnDelete,
@ -52,6 +55,7 @@ const selector = createSelector(
models: _.map(model_list, (_model, key) => key),
saveIntermediatesInterval,
enableImageDebugging,
shouldUseCanvasBetaLayout,
};
},
{
@ -93,6 +97,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
shouldDisplayGuides,
saveIntermediatesInterval,
enableImageDebugging,
shouldUseCanvasBetaLayout,
} = useAppSelector(selector);
/**
@ -173,6 +178,14 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
dispatch(setShouldDisplayGuides(e.target.checked))
}
/>
<IAISwitch
styleClass="settings-modal-item"
label={'Use Canvas Beta Layout'}
isChecked={shouldUseCanvasBetaLayout}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldUseCanvasBetaLayout(e.target.checked))
}
/>
</div>
<div className="settings-modal-items">

View File

@ -28,25 +28,34 @@ export const floatingSelector = createSelector(
shouldPinOptionsPanel,
shouldShowOptionsPanel,
shouldHoldOptionsPanelOpen,
shouldUseCanvasBetaLayout,
} = options;
const { shouldShowGallery, shouldPinGallery, shouldHoldGalleryOpen } =
gallery;
const canvasBetaLayoutCheck =
shouldUseCanvasBetaLayout && activeTabName === 'unifiedCanvas';
const shouldShowOptionsPanelButton =
!canvasBetaLayoutCheck &&
!(
shouldShowOptionsPanel ||
(shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel)
) && ['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName);
) &&
['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName);
const shouldShowGalleryButton =
!(shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)) &&
['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName);
const shouldShowProcessButtons =
!canvasBetaLayoutCheck &&
(!shouldPinOptionsPanel || !shouldShowOptionsPanel);
return {
shouldPinOptionsPanel,
shouldShowProcessButtons:
!shouldPinOptionsPanel || !shouldShowOptionsPanel,
shouldShowProcessButtons,
shouldShowOptionsPanelButton,
shouldShowOptionsPanel,
shouldShowGallery,

View File

@ -39,8 +39,9 @@
column-gap: 1rem;
}
}
}
.inpainting-canvas-area {
.inpainting-canvas-area {
display: flex;
flex-direction: column;
align-items: center;
@ -48,28 +49,28 @@
row-gap: 1rem;
width: 100%;
height: 100%;
}
}
.inpainting-canvas-spiner {
.inpainting-canvas-spiner {
display: flex;
align-items: center;
width: 100%;
height: 100%;
}
}
.inpainting-canvas-container {
.inpainting-canvas-container {
display: flex;
align-items: center;
justify-content: center;
position: relative;
height: 100%;
width: 100%;
border-radius: 0.5rem;
}
.inpainting-canvas-wrapper {
.inpainting-canvas-wrapper {
position: relative;
}
}
.inpainting-canvas-stage {
.inpainting-canvas-stage {
outline: none;
border-radius: 0.5rem;
box-shadow: 0px 0px 0px 1px var(--border-color-light);
@ -79,8 +80,6 @@
outline: none;
border-radius: 0.5rem;
}
}
}
}
.inpainting-options-btn {

View File

@ -0,0 +1,71 @@
import { createSelector } from '@reduxjs/toolkit';
// import IAICanvas from 'features/canvas/components/IAICanvas';
import IAICanvasResizer from 'features/canvas/components/IAICanvasResizer';
import _ from 'lodash';
import { useLayoutEffect } from 'react';
import { useAppDispatch, useAppSelector } from 'app/store';
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
import IAICanvas from 'features/canvas/components/IAICanvas';
import IAICanvasOutpaintingControls from 'features/canvas/components/IAICanvasToolbar/IAICanvasToolbar';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { Flex } from '@chakra-ui/react';
import UnifiedCanvasToolbarBeta from './UnifiedCanvasToolbarBeta';
import UnifiedCanvasToolSettingsBeta from './UnifiedCanvasToolSettingsBeta';
const selector = createSelector(
[canvasSelector],
(canvas) => {
const { doesCanvasNeedScaling } = canvas;
return {
doesCanvasNeedScaling,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
const UnifiedCanvasDisplayBeta = () => {
const dispatch = useAppDispatch();
const { doesCanvasNeedScaling } = useAppSelector(selector);
useLayoutEffect(() => {
dispatch(setDoesCanvasNeedScaling(true));
const resizeCallback = _.debounce(() => {
dispatch(setDoesCanvasNeedScaling(true));
}, 250);
window.addEventListener('resize', resizeCallback);
return () => window.removeEventListener('resize', resizeCallback);
}, [dispatch]);
return (
<div className={'workarea-single-view'}>
<Flex
flexDirection={'row'}
width="100%"
height="100%"
columnGap={'1rem'}
padding="1rem"
>
<UnifiedCanvasToolbarBeta />
<Flex
width="100%"
height="100%"
flexDirection={'column'}
rowGap={'1rem'}
>
<UnifiedCanvasToolSettingsBeta />
{doesCanvasNeedScaling ? <IAICanvasResizer /> : <IAICanvas />}
</Flex>
</Flex>
</div>
);
};
export default UnifiedCanvasDisplayBeta;

View File

@ -0,0 +1,13 @@
import { Flex } from '@chakra-ui/react';
import React from 'react';
import UnifiedCanvasBrushSettings from './UnifiedCanvasBrushSettings';
import UnifiedCanvasLimitStrokesToBox from './UnifiedCanvasLimitStrokesToBox';
export default function UnifiedCanvasBaseBrushSettings() {
return (
<Flex gap={'1rem'} alignItems="center">
<UnifiedCanvasBrushSettings />
<UnifiedCanvasLimitStrokesToBox />
</Flex>
);
}

View File

@ -0,0 +1,13 @@
import { Flex } from '@chakra-ui/react';
import React from 'react';
import UnifiedCanvasBrushSize from './UnifiedCanvasBrushSize';
import UnifiedCanvasColorPicker from './UnifiedCanvasColorPicker';
export default function UnifiedCanvasBrushSettings() {
return (
<Flex columnGap={'1rem'} alignItems="center">
<UnifiedCanvasBrushSize />
<UnifiedCanvasColorPicker />
</Flex>
);
}

View File

@ -0,0 +1,52 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAISlider from 'common/components/IAISlider';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { setBrushSize } from 'features/canvas/store/canvasSlice';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
export default function UnifiedCanvasBrushSize() {
const dispatch = useAppDispatch();
const brushSize = useAppSelector(
(state: RootState) => state.canvas.brushSize
);
const isStaging = useAppSelector(isStagingSelector);
useHotkeys(
['BracketLeft'],
() => {
dispatch(setBrushSize(Math.max(brushSize - 5, 5)));
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[brushSize]
);
useHotkeys(
['BracketRight'],
() => {
dispatch(setBrushSize(Math.min(brushSize + 5, 500)));
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[brushSize]
);
return (
<IAISlider
label="Size"
value={brushSize}
withInput
onChange={(newSize) => dispatch(setBrushSize(newSize))}
sliderNumberInputProps={{ max: 500 }}
inputReadOnly={false}
width={'100px'}
/>
);
}

View File

@ -0,0 +1,24 @@
import { useAppDispatch } from 'app/store';
import IAIButton from 'common/components/IAIButton';
import { clearMask } from 'features/canvas/store/canvasSlice';
import React from 'react';
import { FaTrash } from 'react-icons/fa';
export default function UnifiedCanvasClearMask() {
const dispatch = useAppDispatch();
const handleClearMask = () => dispatch(clearMask());
return (
<IAIButton
size={'sm'}
leftIcon={<FaTrash />}
onClick={handleClearMask}
tooltip="Clear Mask (Shift+C)"
>
Clear
</IAIButton>
);
}

View File

@ -0,0 +1,121 @@
import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIColorPicker from 'common/components/IAIColorPicker';
import IAIPopover from 'common/components/IAIPopover';
import {
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { setBrushColor, setMaskColor } from 'features/canvas/store/canvasSlice';
import React from 'react';
import _ from 'lodash';
import { useHotkeys } from 'react-hotkeys-hook';
const selector = createSelector(
[canvasSelector, isStagingSelector],
(canvas, isStaging) => {
const { brushColor, maskColor, layer } = canvas;
return {
brushColor,
maskColor,
layer,
isStaging,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function UnifiedCanvasColorPicker() {
const dispatch = useAppDispatch();
const { brushColor, maskColor, layer, isStaging } = useAppSelector(selector);
const currentColorDisplay = () => {
if (layer === 'base')
return `rgba(${brushColor.r},${brushColor.g},${brushColor.b},${brushColor.a})`;
if (layer === 'mask')
return `rgba(${maskColor.r},${maskColor.g},${maskColor.b},${maskColor.a})`;
};
useHotkeys(
['shift+BracketLeft'],
() => {
dispatch(
setBrushColor({
...brushColor,
a: _.clamp(brushColor.a - 0.05, 0.05, 1),
})
);
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[brushColor]
);
useHotkeys(
['shift+BracketRight'],
() => {
dispatch(
setBrushColor({
...brushColor,
a: _.clamp(brushColor.a + 0.05, 0.05, 1),
})
);
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[brushColor]
);
return (
<IAIPopover
trigger="hover"
triggerComponent={
<Box
style={{
width: '30px',
height: '30px',
minWidth: '30px',
minHeight: '30px',
borderRadius: '99999999px',
backgroundColor: currentColorDisplay(),
cursor: 'pointer',
}}
/>
}
>
<Flex minWidth={'15rem'} direction={'column'} gap={'1rem'} width={'100%'}>
{layer === 'base' && (
<IAIColorPicker
style={{
width: '100%',
paddingTop: '0.5rem',
paddingBottom: '0.5rem',
}}
color={brushColor}
onChange={(newColor) => dispatch(setBrushColor(newColor))}
/>
)}
{layer === 'mask' && (
<IAIColorPicker
style={{
width: '100%',
paddingTop: '0.5rem',
paddingBottom: '0.5rem',
}}
color={maskColor}
onChange={(newColor) => dispatch(setMaskColor(newColor))}
/>
)}
</Flex>
</IAIPopover>
);
}

View File

@ -0,0 +1,22 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldDarkenOutsideBoundingBox } from 'features/canvas/store/canvasSlice';
import React from 'react';
export default function UnifiedCanvasDarkenOutsideSelection() {
const shouldDarkenOutsideBoundingBox = useAppSelector(
(state: RootState) => state.canvas.shouldDarkenOutsideBoundingBox
);
const dispatch = useAppDispatch();
return (
<IAICheckbox
label="Darken Outside"
isChecked={shouldDarkenOutsideBoundingBox}
onChange={(e) =>
dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked))
}
/>
);
}

View File

@ -0,0 +1,23 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import { setIsMaskEnabled } from 'features/canvas/store/canvasSlice';
import React from 'react';
export default function UnifiedCanvasEnableMask() {
const isMaskEnabled = useAppSelector(
(state: RootState) => state.canvas.isMaskEnabled
);
const dispatch = useAppDispatch();
const handleToggleEnableMask = () =>
dispatch(setIsMaskEnabled(!isMaskEnabled));
return (
<IAICheckbox
label="Enable Mask (H)"
isChecked={isMaskEnabled}
onChange={handleToggleEnableMask}
/>
);
}

View File

@ -0,0 +1,22 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldRestrictStrokesToBox } from 'features/canvas/store/canvasSlice';
import React from 'react';
export default function UnifiedCanvasLimitStrokesToBox() {
const dispatch = useAppDispatch();
const shouldRestrictStrokesToBox = useAppSelector(
(state: RootState) => state.canvas.shouldRestrictStrokesToBox
);
return (
<IAICheckbox
label="Limit To Box"
isChecked={shouldRestrictStrokesToBox}
onChange={(e) =>
dispatch(setShouldRestrictStrokesToBox(e.target.checked))
}
/>
);
}

View File

@ -0,0 +1,17 @@
import { Flex } from '@chakra-ui/react';
import React from 'react';
import UnifiedCanvasBrushSettings from './UnifiedCanvasBrushSettings';
import UnifiedCanvasClearMask from './UnifiedCanvasClearMask';
import UnifiedCanvasEnableMask from './UnifiedCanvasEnableMask';
import UnifiedCanvasPreserveMask from './UnifiedCanvasPreserveMask';
export default function UnifiedCanvasMaskBrushSettings() {
return (
<Flex gap={'1rem'} alignItems="center">
<UnifiedCanvasBrushSettings />
<UnifiedCanvasEnableMask />
<UnifiedCanvasPreserveMask />
<UnifiedCanvasClearMask />
</Flex>
);
}

View File

@ -0,0 +1,15 @@
import { Flex } from '@chakra-ui/layout';
import React from 'react';
import UnifiedCanvasDarkenOutsideSelection from './UnifiedCanvasDarkenOutsideSelection';
import UnifiedCanvasShowGrid from './UnifiedCanvasShowGrid';
import UnifiedCanvasSnapToGrid from './UnifiedCanvasSnapToGrid';
export default function UnifiedCanvasMoveSettings() {
return (
<Flex alignItems={'center'} gap="1rem">
<UnifiedCanvasShowGrid />
<UnifiedCanvasSnapToGrid />
<UnifiedCanvasDarkenOutsideSelection />
</Flex>
);
}

View File

@ -0,0 +1,20 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldPreserveMaskedArea } from 'features/canvas/store/canvasSlice';
import React from 'react';
export default function UnifiedCanvasPreserveMask() {
const dispatch = useAppDispatch();
const shouldPreserveMaskedArea = useAppSelector(
(state: RootState) => state.canvas.shouldPreserveMaskedArea
);
return (
<IAICheckbox
label="Preserve Masked"
isChecked={shouldPreserveMaskedArea}
onChange={(e) => dispatch(setShouldPreserveMaskedArea(e.target.checked))}
/>
);
}

View File

@ -0,0 +1,101 @@
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import {
setShouldAutoSave,
setShouldCropToBoundingBoxOnSave,
setShouldShowCanvasDebugInfo,
setShouldShowIntermediates,
} from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
import _ from 'lodash';
import IAIIconButton from 'common/components/IAIIconButton';
import { FaWrench } from 'react-icons/fa';
import IAIPopover from 'common/components/IAIPopover';
import IAICheckbox from 'common/components/IAICheckbox';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import EmptyTempFolderButtonModal from 'features/system/components/ClearTempFolderButtonModal';
import ClearCanvasHistoryButtonModal from 'features/canvas/components/ClearCanvasHistoryButtonModal';
export const canvasControlsSelector = createSelector(
[canvasSelector],
(canvas) => {
const {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldShowCanvasDebugInfo,
shouldShowIntermediates,
} = canvas;
return {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldShowCanvasDebugInfo,
shouldShowIntermediates,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
const UnifiedCanvasSettings = () => {
const dispatch = useAppDispatch();
const {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldShowCanvasDebugInfo,
shouldShowIntermediates,
} = useAppSelector(canvasControlsSelector);
return (
<IAIPopover
trigger="hover"
triggerComponent={
<IAIIconButton
tooltip="Canvas Settings"
tooltipProps={{
placement: 'bottom',
}}
aria-label="Canvas Settings"
icon={<FaWrench />}
/>
}
>
<Flex direction={'column'} gap={'0.5rem'}>
<IAICheckbox
label="Show Intermediates"
isChecked={shouldShowIntermediates}
onChange={(e) =>
dispatch(setShouldShowIntermediates(e.target.checked))
}
/>
<IAICheckbox
label="Auto Save to Gallery"
isChecked={shouldAutoSave}
onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))}
/>
<IAICheckbox
label="Save Box Region Only"
isChecked={shouldCropToBoundingBoxOnSave}
onChange={(e) =>
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked))
}
/>
<IAICheckbox
label="Show Canvas Debug Info"
isChecked={shouldShowCanvasDebugInfo}
onChange={(e) =>
dispatch(setShouldShowCanvasDebugInfo(e.target.checked))
}
/>
<ClearCanvasHistoryButtonModal />
<EmptyTempFolderButtonModal />
</Flex>
</IAIPopover>
);
};
export default UnifiedCanvasSettings;

View File

@ -0,0 +1,20 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldShowGrid } from 'features/canvas/store/canvasSlice';
import React from 'react';
export default function UnifiedCanvasShowGrid() {
const shouldShowGrid = useAppSelector(
(state: RootState) => state.canvas.shouldShowGrid
);
const dispatch = useAppDispatch();
return (
<IAICheckbox
label="Show Grid"
isChecked={shouldShowGrid}
onChange={(e) => dispatch(setShouldShowGrid(e.target.checked))}
/>
);
}

View File

@ -0,0 +1,23 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldSnapToGrid } from 'features/canvas/store/canvasSlice';
import React, { ChangeEvent } from 'react';
export default function UnifiedCanvasSnapToGrid() {
const shouldSnapToGrid = useAppSelector(
(state: RootState) => state.canvas.shouldSnapToGrid
);
const dispatch = useAppDispatch();
const handleChangeShouldSnapToGrid = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldSnapToGrid(e.target.checked));
return (
<IAICheckbox
label="Snap to Grid (N)"
isChecked={shouldSnapToGrid}
onChange={handleChangeShouldSnapToGrid}
/>
);
}

View File

@ -0,0 +1,42 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import React from 'react';
import _ from 'lodash';
import UnifiedCanvasBaseBrushSettings from './UnifiedCanvasToolSettings/UnifiedCanvasBaseBrushSettings';
import UnifiedCanvasMaskBrushSettings from './UnifiedCanvasToolSettings/UnifiedCanvasMaskBrushSettings';
import { Flex } from '@chakra-ui/react';
import UnifiedCanvasMoveSettings from './UnifiedCanvasToolSettings/UnifiedCanvasMoveSettings';
const selector = createSelector(
[canvasSelector],
(canvas) => {
const { tool, layer } = canvas;
return {
tool,
layer,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function UnifiedCanvasToolSettingsBeta() {
const { tool, layer } = useAppSelector(selector);
return (
<Flex height="2rem" minHeight="2rem" maxHeight="2rem" alignItems={'center'}>
{layer == 'base' && ['brush', 'eraser', 'colorPicker'].includes(tool) && (
<UnifiedCanvasBaseBrushSettings />
)}
{layer == 'mask' && ['brush', 'eraser', 'colorPicker'].includes(tool) && (
<UnifiedCanvasMaskBrushSettings />
)}
{tool == 'move' && <UnifiedCanvasMoveSettings />}
</Flex>
);
}

View File

@ -0,0 +1,55 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaCopy } from 'react-icons/fa';
export default function UnifiedCanvasCopyToClipboard() {
const isStaging = useAppSelector(isStagingSelector);
const canvasBaseLayer = getCanvasBaseLayer();
const isProcessing = useAppSelector(
(state: RootState) => state.system.isProcessing
);
const shouldCropToBoundingBoxOnSave = useAppSelector(
(state: RootState) => state.canvas.shouldCropToBoundingBoxOnSave
);
const dispatch = useAppDispatch();
useHotkeys(
['meta+c', 'ctrl+c'],
() => {
handleCopyImageToClipboard();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[canvasBaseLayer, isProcessing]
);
const handleCopyImageToClipboard = () => {
dispatch(
mergeAndUploadCanvas({
cropVisible: shouldCropToBoundingBoxOnSave ? false : true,
cropToBoundingBox: shouldCropToBoundingBoxOnSave,
shouldCopy: true,
})
);
};
return (
<IAIIconButton
aria-label="Copy to Clipboard (Cmd/Ctrl+C)"
tooltip="Copy to Clipboard (Cmd/Ctrl+C)"
icon={<FaCopy />}
onClick={handleCopyImageToClipboard}
isDisabled={isStaging}
/>
);
}

View File

@ -0,0 +1,54 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaDownload } from 'react-icons/fa';
export default function UnifiedCanvasDownloadImage() {
const dispatch = useAppDispatch();
const canvasBaseLayer = getCanvasBaseLayer();
const isStaging = useAppSelector(isStagingSelector);
const isProcessing = useAppSelector(
(state: RootState) => state.system.isProcessing
);
const shouldCropToBoundingBoxOnSave = useAppSelector(
(state: RootState) => state.canvas.shouldCropToBoundingBoxOnSave
);
useHotkeys(
['shift+d'],
() => {
handleDownloadAsImage();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[canvasBaseLayer, isProcessing]
);
const handleDownloadAsImage = () => {
dispatch(
mergeAndUploadCanvas({
cropVisible: shouldCropToBoundingBoxOnSave ? false : true,
cropToBoundingBox: shouldCropToBoundingBoxOnSave,
shouldDownload: true,
})
);
};
return (
<IAIIconButton
aria-label="Download as Image (Shift+D)"
tooltip="Download as Image (Shift+D)"
icon={<FaDownload />}
onClick={handleDownloadAsImage}
isDisabled={isStaging}
/>
);
}

View File

@ -0,0 +1,21 @@
import { useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import useImageUploader from 'common/hooks/useImageUploader';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import React from 'react';
import { FaUpload } from 'react-icons/fa';
export default function UnifiedCanvasFileUploader() {
const isStaging = useAppSelector(isStagingSelector);
const { openUploader } = useImageUploader();
return (
<IAIIconButton
aria-label="Upload"
tooltip="Upload"
icon={<FaUpload />}
onClick={openUploader}
isDisabled={isStaging}
/>
);
}

View File

@ -0,0 +1,68 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAISelect from 'common/components/IAISelect';
import {
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { setIsMaskEnabled, setLayer } from 'features/canvas/store/canvasSlice';
import React, { ChangeEvent } from 'react';
import _ from 'lodash';
import {
CanvasLayer,
LAYER_NAMES_DICT,
} from 'features/canvas/store/canvasTypes';
import { useHotkeys } from 'react-hotkeys-hook';
const selector = createSelector(
[canvasSelector, isStagingSelector],
(canvas, isStaging) => {
const { layer, isMaskEnabled } = canvas;
return { layer, isMaskEnabled, isStaging };
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function UnifiedCanvasLayerSelect() {
const dispatch = useAppDispatch();
const { layer, isMaskEnabled, isStaging } = useAppSelector(selector);
const handleToggleMaskLayer = () => {
dispatch(setLayer(layer === 'mask' ? 'base' : 'mask'));
};
useHotkeys(
['q'],
() => {
handleToggleMaskLayer();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[layer]
);
const handleChangeLayer = (e: ChangeEvent<HTMLSelectElement>) => {
const newLayer = e.target.value as CanvasLayer;
dispatch(setLayer(newLayer));
if (newLayer === 'mask' && !isMaskEnabled) {
dispatch(setIsMaskEnabled(true));
}
};
return (
<IAISelect
tooltip={'Layer (Q)'}
tooltipProps={{ hasArrow: true, placement: 'top' }}
value={layer}
validValues={LAYER_NAMES_DICT}
onChange={handleChangeLayer}
isDisabled={isStaging}
/>
);
}

View File

@ -0,0 +1,47 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaLayerGroup } from 'react-icons/fa';
export default function UnifiedCanvasMergeVisible() {
const dispatch = useAppDispatch();
const canvasBaseLayer = getCanvasBaseLayer();
const isStaging = useAppSelector(isStagingSelector);
const isProcessing = useAppSelector(
(state: RootState) => state.system.isProcessing
);
useHotkeys(
['shift+m'],
() => {
handleMergeVisible();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[canvasBaseLayer, isProcessing]
);
const handleMergeVisible = () => {
dispatch(
mergeAndUploadCanvas({
cropVisible: false,
shouldSetAsInitialImage: true,
})
);
};
return (
<IAIIconButton
aria-label="Merge Visible (Shift+M)"
tooltip="Merge Visible (Shift+M)"
icon={<FaLayerGroup />}
onClick={handleMergeVisible}
isDisabled={isStaging}
/>
);
}

View File

@ -0,0 +1,37 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { setTool } from 'features/canvas/store/canvasSlice';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaArrowsAlt } from 'react-icons/fa';
export default function UnifiedCanvasMoveTool() {
const tool = useAppSelector((state: RootState) => state.canvas.tool);
const isStaging = useAppSelector(isStagingSelector);
const dispatch = useAppDispatch();
useHotkeys(
['v'],
() => {
handleSelectMoveTool();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[]
);
const handleSelectMoveTool = () => dispatch(setTool('move'));
return (
<IAIIconButton
aria-label="Move Tool (V)"
tooltip="Move Tool (V)"
icon={<FaArrowsAlt />}
data-selected={tool === 'move' || isStaging}
onClick={handleSelectMoveTool}
/>
);
}

View File

@ -0,0 +1,43 @@
import { Flex } from '@chakra-ui/layout';
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
import CancelButton from 'features/options/components/ProcessButtons/CancelButton';
import InvokeButton from 'features/options/components/ProcessButtons/InvokeButton';
import { setShouldShowOptionsPanel } from 'features/options/store/optionsSlice';
import React from 'react';
import { FaSlidersH } from 'react-icons/fa';
export default function UnifiedCanvasProcessingButtons() {
const shouldPinOptionsPanel = useAppSelector(
(state: RootState) => state.options.shouldPinOptionsPanel
);
const dispatch = useAppDispatch();
const handleShowOptionsPanel = () => {
dispatch(setShouldShowOptionsPanel(true));
if (shouldPinOptionsPanel) {
setTimeout(() => dispatch(setDoesCanvasNeedScaling(true)), 400);
}
};
return (
<Flex flexDirection={'column'} gap="0.5rem">
<IAIIconButton
tooltip="Show Options Panel (O)"
tooltipProps={{ placement: 'top' }}
aria-label="Show Options Panel"
onClick={handleShowOptionsPanel}
>
<FaSlidersH />
</IAIIconButton>
<Flex>
<InvokeButton iconButton />
</Flex>
<Flex>
<CancelButton width={'100%'} height={'40px'} />
</Flex>
</Flex>
);
}

View File

@ -0,0 +1,29 @@
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import {
resetCanvas,
resizeAndScaleCanvas,
} from 'features/canvas/store/canvasSlice';
import React from 'react';
import { FaTrash } from 'react-icons/fa';
export default function UnifiedCanvasResetCanvas() {
const dispatch = useAppDispatch();
const isStaging = useAppSelector(isStagingSelector);
const handleResetCanvas = () => {
dispatch(resetCanvas());
dispatch(resizeAndScaleCanvas());
};
return (
<IAIIconButton
aria-label="Clear Canvas"
tooltip="Clear Canvas"
icon={<FaTrash />}
onClick={handleResetCanvas}
style={{ backgroundColor: 'var(--btn-delete-image)' }}
isDisabled={isStaging}
/>
);
}

View File

@ -0,0 +1,45 @@
import { useAppDispatch } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { resetCanvasView } from 'features/canvas/store/canvasSlice';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaCrosshairs } from 'react-icons/fa';
export default function UnifiedCanvasResetView() {
const canvasBaseLayer = getCanvasBaseLayer();
const dispatch = useAppDispatch();
useHotkeys(
['r'],
() => {
handleResetCanvasView();
},
{
enabled: () => true,
preventDefault: true,
},
[canvasBaseLayer]
);
const handleResetCanvasView = () => {
const canvasBaseLayer = getCanvasBaseLayer();
if (!canvasBaseLayer) return;
const clientRect = canvasBaseLayer.getClientRect({
skipTransform: true,
});
dispatch(
resetCanvasView({
contentRect: clientRect,
})
);
};
return (
<IAIIconButton
aria-label="Reset View (R)"
tooltip="Reset View (R)"
icon={<FaCrosshairs />}
onClick={handleResetCanvasView}
/>
);
}

View File

@ -0,0 +1,52 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaSave } from 'react-icons/fa';
export default function UnifiedCanvasSaveToGallery() {
const isStaging = useAppSelector(isStagingSelector);
const canvasBaseLayer = getCanvasBaseLayer();
const isProcessing = useAppSelector(
(state: RootState) => state.system.isProcessing
);
const shouldCropToBoundingBoxOnSave = useAppSelector(
(state: RootState) => state.canvas.shouldCropToBoundingBoxOnSave
);
const dispatch = useAppDispatch();
useHotkeys(
['shift+s'],
() => {
handleSaveToGallery();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[canvasBaseLayer, isProcessing]
);
const handleSaveToGallery = () => {
dispatch(
mergeAndUploadCanvas({
cropVisible: shouldCropToBoundingBoxOnSave ? false : true,
cropToBoundingBox: shouldCropToBoundingBoxOnSave,
shouldSaveToGallery: true,
})
);
};
return (
<IAIIconButton
aria-label="Save to Gallery (Shift+S)"
tooltip="Save to Gallery (Shift+S)"
icon={<FaSave />}
onClick={handleSaveToGallery}
isDisabled={isStaging}
/>
);
}

View File

@ -0,0 +1,162 @@
import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import {
addEraseRect,
addFillRect,
setBrushColor,
setTool,
} from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
import _ from 'lodash';
import IAIIconButton from 'common/components/IAIIconButton';
import {
FaEraser,
FaEyeDropper,
FaFillDrip,
FaPaintBrush,
FaPlus,
} from 'react-icons/fa';
import {
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
import { useHotkeys } from 'react-hotkeys-hook';
export const selector = createSelector(
[canvasSelector, isStagingSelector, systemSelector],
(canvas, isStaging, system) => {
const { isProcessing } = system;
const { tool } = canvas;
return {
tool,
isStaging,
isProcessing,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
const UnifiedCanvasToolSelect = () => {
const dispatch = useAppDispatch();
const { tool, isStaging } = useAppSelector(selector);
useHotkeys(
['b'],
() => {
handleSelectBrushTool();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[]
);
useHotkeys(
['e'],
() => {
handleSelectEraserTool();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[tool]
);
useHotkeys(
['c'],
() => {
handleSelectColorPickerTool();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[tool]
);
useHotkeys(
['shift+f'],
() => {
handleFillRect();
},
{
enabled: () => !isStaging,
preventDefault: true,
}
);
useHotkeys(
['delete', 'backspace'],
() => {
handleEraseBoundingBox();
},
{
enabled: () => !isStaging,
preventDefault: true,
}
);
const handleSelectBrushTool = () => dispatch(setTool('brush'));
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
const handleSelectColorPickerTool = () => dispatch(setTool('colorPicker'));
const handleFillRect = () => dispatch(addFillRect());
const handleEraseBoundingBox = () => dispatch(addEraseRect());
return (
<Flex flexDirection={'column'} gap={'0.5rem'}>
<ButtonGroup>
<IAIIconButton
aria-label="Brush Tool (B)"
tooltip="Brush Tool (B)"
icon={<FaPaintBrush />}
data-selected={tool === 'brush' && !isStaging}
onClick={handleSelectBrushTool}
isDisabled={isStaging}
/>
<IAIIconButton
aria-label="Eraser Tool (E)"
tooltip="Eraser Tool (E)"
icon={<FaEraser />}
data-selected={tool === 'eraser' && !isStaging}
isDisabled={isStaging}
onClick={handleSelectEraserTool}
/>
</ButtonGroup>
<ButtonGroup>
<IAIIconButton
aria-label="Fill Bounding Box (Shift+F)"
tooltip="Fill Bounding Box (Shift+F)"
icon={<FaFillDrip />}
isDisabled={isStaging}
onClick={handleFillRect}
/>
<IAIIconButton
aria-label="Erase Bounding Box Area (Delete/Backspace)"
tooltip="Erase Bounding Box Area (Delete/Backspace)"
icon={<FaPlus style={{ transform: 'rotate(45deg)' }} />}
isDisabled={isStaging}
onClick={handleEraseBoundingBox}
/>
</ButtonGroup>
<IAIIconButton
aria-label="Color Picker (C)"
tooltip="Color Picker (C)"
icon={<FaEyeDropper />}
data-selected={tool === 'colorPicker' && !isStaging}
isDisabled={isStaging}
onClick={handleSelectColorPickerTool}
width={'max-content'}
/>
</Flex>
);
};
export default UnifiedCanvasToolSelect;

View File

@ -0,0 +1,59 @@
import { Flex } from '@chakra-ui/react';
import IAICanvasUndoButton from 'features/canvas/components/IAICanvasToolbar/IAICanvasUndoButton';
import IAICanvasRedoButton from 'features/canvas/components/IAICanvasToolbar/IAICanvasRedoButton';
import UnifiedCanvasLayerSelect from './UnifiedCanvasToolbar/UnifiedCanvasLayerSelect';
import UnifiedCanvasToolSelect from './UnifiedCanvasToolbar/UnifiedCanvasToolSelect';
import UnifiedCanvasSettings from './UnifiedCanvasToolSettings/UnifiedCanvasSettings';
import UnifiedCanvasMoveTool from './UnifiedCanvasToolbar/UnifiedCanvasMoveTool';
import UnifiedCanvasResetView from './UnifiedCanvasToolbar/UnifiedCanvasResetView';
import UnifiedCanvasMergeVisible from './UnifiedCanvasToolbar/UnifiedCanvasMergeVisible';
import UnifiedCanvasSaveToGallery from './UnifiedCanvasToolbar/UnifiedCanvasSaveToGallery';
import UnifiedCanvasCopyToClipboard from './UnifiedCanvasToolbar/UnifiedCanvasCopyToClipboard';
import UnifiedCanvasDownloadImage from './UnifiedCanvasToolbar/UnifiedCanvasDownloadImage';
import UnifiedCanvasFileUploader from './UnifiedCanvasToolbar/UnifiedCanvasFileUploader';
import UnifiedCanvasResetCanvas from './UnifiedCanvasToolbar/UnifiedCanvasResetCanvas';
import UnifiedCanvasProcessingButtons from './UnifiedCanvasToolbar/UnifiedCanvasProcessingButtons';
import { RootState, useAppSelector } from 'app/store';
const UnifiedCanvasToolbarBeta = () => {
const shouldShowOptionsPanel = useAppSelector(
(state: RootState) => state.options.shouldShowOptionsPanel
);
return (
<Flex flexDirection={'column'} rowGap="0.5rem" width="6rem">
<UnifiedCanvasLayerSelect />
<UnifiedCanvasToolSelect />
<Flex gap={'0.5rem'}>
<UnifiedCanvasMoveTool />
<UnifiedCanvasResetView />
</Flex>
<Flex columnGap={'0.5rem'}>
<UnifiedCanvasMergeVisible />
<UnifiedCanvasSaveToGallery />
</Flex>
<Flex columnGap={'0.5rem'}>
<UnifiedCanvasCopyToClipboard />
<UnifiedCanvasDownloadImage />
</Flex>
<Flex gap={'0.5rem'}>
<IAICanvasUndoButton />
<IAICanvasRedoButton />
</Flex>
<Flex gap={'0.5rem'}>
<UnifiedCanvasFileUploader />
<UnifiedCanvasResetCanvas />
</Flex>
<UnifiedCanvasSettings />
{!shouldShowOptionsPanel && <UnifiedCanvasProcessingButtons />}
</Flex>
);
};
export default UnifiedCanvasToolbarBeta;

View File

@ -1,14 +1,23 @@
import UnifiedCanvasPanel from './UnifiedCanvasPanel';
import UnifiedCanvasDisplay from './UnifiedCanvasDisplay';
import InvokeWorkarea from 'features/tabs/components/InvokeWorkarea';
import { RootState, useAppSelector } from 'app/store';
import UnifiedCanvasDisplayBeta from './UnifiedCanvasBeta/UnifiedCanvasDisplayBeta';
export default function UnifiedCanvasWorkarea() {
const shouldUseCanvasBetaLayout = useAppSelector(
(state: RootState) => state.options.shouldUseCanvasBetaLayout
);
return (
<InvokeWorkarea
optionsPanel={<UnifiedCanvasPanel />}
styleClass="inpainting-workarea-overrides"
>
{shouldUseCanvasBetaLayout ? (
<UnifiedCanvasDisplayBeta />
) : (
<UnifiedCanvasDisplay />
)}
</InvokeWorkarea>
);
}

View File

@ -3,6 +3,7 @@
cd "$(dirname "$0")"
VERSION=$(grep ^VERSION ../setup.py | awk '{ print $3 }' | sed "s/'//g" )
VERSION="$VERSION-p1"
echo "Be certain that you're in the 'installer' directory before continuing."
read -p "Press any key to continue, or CTRL-C to exit..."

View File

@ -22,7 +22,6 @@ set PYTHON_URL=https://www.python.org/downloads/windows/
set MINIMUM_PYTHON_VERSION=3.9.0
set PYTHON_URL=https://www.python.org/downloads/release/python-3109/
set err_msg=An error has occurred and the script could not continue.
@rem --------------------------- Intro -------------------------------
@ -65,23 +64,21 @@ if %errorlevel% == 1 (
@rem Cleanup
del /q .tmp1 .tmp2
echo Updating PIP...
call python -m pip install --no-warn-script-location -q --upgrade pip
@rem --------------------- Get the requirements file ------------
echo.
echo Setting up requirements file for your system.
copy /y environments-and-requirements\requirements-win-colab-cuda.txt .\requirements.txt
@rem --------------------- Get the root directory for installation ------------
set rootdir=""
set response=""
set selection=""
set "rootdir="
set "response="
set "selection="
:pick_rootdir
if %rootdir% neq "" goto :done
if defined rootdir goto :done
set /p selection=Select the path to install InvokeAI's directory into [%UserProfile%]:
if %selection% == "" set selection=%UserProfile%
set dest=%selection%\invokeai
if not defined selection set selection=%UserProfile%
set selection=%selection:"=%
set dest="%selection%\invokeai"
if exist %dest% (
set response=y
set /p response=The directory %dest% exists. Do you wish to resume install from a previous attempt? [Y/n]:
@ -93,16 +90,18 @@ set selection=""
set response=y
set /p response="You have chosen to install InvokeAI into %rootdir%. OK? [Y/n]: "
if !response! == "" set response=y
if /I !response! neq y set rootdir=""
if /I !response! neq y set "rootdir="
goto :pick_rootdir
:done
set rootdir=%rootdir:"=%
@rem ---------------------- Initialize the runtime directory ---------------------
echo.
echo *** Creating Runtime Directory %rootdir% ***
if not exist %rootdir% mkdir %rootdir%
if not exist "%rootdir%" mkdir "%rootdir%"
@rem for unknown reasons the mkdir works but returns an error code
if not exist %rootdir% (
if not exist "%rootdir%" (
set err_msg=Could not create the directory %rootdir%. Please check the directory's permissions and try again.
goto :err_exit
)
@ -111,7 +110,7 @@ echo Successful.
@rem --------------------------- Create and populate .venv ---------------------------
echo.
echo ** Creating Virtual Environment for InvokeAI **
call python -mvenv %rootdir%\.venv
call python -mvenv "%rootdir%\.venv"
if %errorlevel% neq 0 (
set err_msg=Could not create virtual environment %rootdir%\.venv. Please check the directory's permissions and try again.
goto :err_exit
@ -120,18 +119,24 @@ echo Successful.
echo.
echo *** Installing InvokeAI Requirements ***
call %rootdir%\.venv\Scripts\activate.bat
echo Activating environment
call "%rootdir%\.venv\Scripts\activate.bat"
set PYTHON=%rootdir%\.venv\Scripts\python
echo updating pip with "%PYTHON%"
call "%PYTHON%" -mensurepip --upgrade
copy environments-and-requirements\requirements-win-colab-cuda.txt .\requirements.txt
call python -mpip install -r requirements.txt
call "%PYTHON%" -mpip install --prefer-binary -r requirements.txt
if %errorlevel% neq 0 (
set err_msg=Installation of requirements failed. See above for errors and check %TROUBLESHOOTING% for potential solutions.
set err_msg=Requirements installation failed. See above for errors and check %TROUBLESHOOTING% for potential solutions.
goto :err_exit
)
echo Installation successful.
echo.
echo *** Installing InvokeAI Modules and Executables ***
call python -mpip install %INVOKE_AI_SRC%
call "%PYTHON%" -mpip install %INVOKE_AI_SRC%
if %errorlevel% neq 0 (
set err_msg=Installation of InvokeAI failed. See above for errors and check %TROUBLESHOOTING% for potential solutions.
goto :err_exit
@ -139,21 +144,21 @@ if %errorlevel% neq 0 (
echo Installation successful.
@rem --------------------------- Set up the root directory ---------------------------
xcopy /E /Y .\templates\rootdir %rootdir%
xcopy /E /Y .\templates\rootdir "%rootdir%"
PUSHD "%rootdir%"
call .venv\Scripts\python .venv\Scripts\configure_invokeai.py --root="%rootdir%"
call "%PYTHON%" .venv\Scripts\configure_invokeai.py "--root=%rootdir%"
if %errorlevel% neq 0 (
set err_msg=Configuration failed. See above for error messages and check %TROUBLESHOOTING% for potential solutions.
goto :err_exit
)
POPD
copy .\templates\invoke.bat.in %rootdir%\invoke.bat
copy .\templates\update.bat.in %rootdir%\update.bat
copy .\templates\invoke.bat.in "%rootdir%\invoke.bat"
copy .\templates\update.bat.in "%rootdir%\update.bat"
@rem so that update.bat works
mkdir %rootdir%\environments-and-requirements
xcopy /I /Y .\environments-and-requirements %rootdir%\environments-and-requirements
copy .\requirements.txt %rootdir%\requirements.txt
mkdir "%rootdir%\environments-and-requirements"
xcopy /I /Y .\environments-and-requirements "%rootdir%\environments-and-requirements"
copy .\requirements.txt "%rootdir%\requirements.txt"
echo.
@ -213,3 +218,4 @@ echo The installer will exit now.
pause
exit /b
pause

View File

@ -126,17 +126,17 @@ do
read -e -p "InvokeAI will be installed into $ROOTDIR. OK? [y]: " input
RESPONSE=${input:='y'}
if [ "$RESPONSE" == 'y' ]; then
if [ -e $ROOTDIR ]; then
if [ -e "$ROOTDIR" ]; then
echo
read -e -p "Directory $ROOTDIR already exists. Do you want to resume an interrupted install? [y]: " input
read -e -p "Directory "$ROOTDIR" already exists. Do you want to resume an interrupted install? [y]: " input
RESPONSE=${input:='y'}
if [ "$RESPONSE" != 'y' ]; then
ROOTDIR=""
fi
else
mkdir -p $ROOTDIR
mkdir -p "$ROOTDIR"
if [ $? -ne 0 ]; then
echo "Could not create $ROOTDIR. Try again with a different install location."
echo "Could not create "$ROOTDIR". Try again with a different install location."
ROOTDIR=""
fi
fi
@ -149,18 +149,19 @@ done
echo
echo "** Creating Virtual Environment for InvokeAI **"
$PYTHON -mpip install --upgrade pip
$PYTHON -mvenv $ROOTDIR/.venv
_err_exit $? "Python failed to create virtual environment $ROOTDIR/.venv. Please see $TROUBLESHOOTING for help."
$PYTHON -mvenv "$ROOTDIR"/.venv
_err_exit $? "Python failed to create virtual environment "$ROOTDIR"/.venv. Please see $TROUBLESHOOTING for help."
#--------------------------------------------------------------------------------
echo
echo "** Activating Virtual Environment for InvokeAI **"
source $ROOTDIR/.venv/bin/activate
_err_exit $? "Failed to activate virtual evironment $ROOTDIR/.venv. Please see $TROUBLESHOOTING for help."
source "$ROOTDIR"/.venv/bin/activate
_err_exit $? "Failed to activate virtual evironment "$ROOTDIR"/.venv. Please see $TROUBLESHOOTING for help."
PYTHON=$ROOTDIR/.venv/bin/python
PYTHON="$ROOTDIR"/.venv/bin/python
$PYTHON -mensurepip --upgrade
$PYTHON -mpip install --upgrade pip
#--------------------------------------------------------------------------------
echo
@ -179,7 +180,7 @@ else
fi
fi
$PYTHON -mpip install -r requirements.txt
$PYTHON -mpip install --prefer-binary -r requirements.txt
_err_exit $? "Failed to install InvokeAI's dependencies."
#--------------------------------------------------------------------------------
@ -189,29 +190,29 @@ $PYTHON -mpip install $INVOKE_AI_SRC
_err_exit $? "Installation of InvokeAI failed."
#--------------------------------------------------------------------------------
echo " *** Setting Up Root Directory $ROOTDIR *** "
cp -pr templates/rootdir/* $ROOTDIR/
cp templates/invoke.sh.in $ROOTDIR/invoke.sh
chmod a+rx $ROOTDIR/invoke.sh
cp templates/update.sh.in $ROOTDIR/update.sh
chmod a+rx $ROOTDIR/update.sh
echo " *** Setting Up Root Directory "$ROOTDIR" *** "
cp -pr templates/rootdir/* "$ROOTDIR"/
cp templates/invoke.sh.in "$ROOTDIR"/invoke.sh
chmod a+rx "$ROOTDIR"/invoke.sh
cp templates/update.sh.in "$ROOTDIR"/update.sh
chmod a+rx "$ROOTDIR"/update.sh
# This allows the updater to work!
cp -pr environments-and-requirements requirements.txt $ROOTDIR/
cp -pr environments-and-requirements requirements.txt "$ROOTDIR/"
#--------------------------------------------------------------------------------
echo
echo "*** Confguring InvokeAI ***"
pushd $ROOTDIR
./.venv/bin/configure_invokeai.py --root=$ROOTDIR
pushd "$ROOTDIR" >/dev/null
$PYTHON ./.venv/bin/configure_invokeai.py --root="$ROOTDIR"
_err_exit $? "Initial configuration failed. Please see above error messages and $TROUBLESHOOTING for help."
#--------------------------------------------------------------------------------
popd
cp templates/invoke.sh.in $ROOTDIR/invoke.sh
chmod a+rx $ROOTDIR/invoke.sh
cp templates/invoke.sh.in "$ROOTDIR"/invoke.sh
chmod a+rx "$ROOTDIR"/invoke.sh
cp templates/update.sh.in $ROOTDIR/update.sh
chmod a+rx $ROOTDIR/update.sh
cp templates/update.sh.in "$ROOTDIR"/update.sh
chmod a+rx "$ROOTDIR"/update.sh
echo "You may now run InvokeAI by entering the directory $ROOTDIR and running invoke.sh"

View File

@ -84,6 +84,8 @@ class Concepts(object):
better to store the concept name (unique) than the concept trigger
(not necessarily unique!)
'''
if not prompt:
return prompt
triggers = self.match_trigger.findall(prompt)
if not triggers:
return prompt

View File

@ -18,7 +18,12 @@ from argparse import Namespace
Globals = Namespace()
# This is usually overwritten by the command line and/or environment variables
Globals.root = osp.abspath(os.environ.get('INVOKEAI_ROOT') or osp.abspath(osp.join(os.environ.get('VIRTUAL_ENV'),'..')) or osp.expanduser('~/invokeai'))
if os.environ.get('INVOKEAI_ROOT'):
Globals.root = osp.abspath(os.environ.get('INVOKEAI_ROOT'))
elif os.environ.get('VIRTUAL_ENV'):
Globals.root = osp.abspath(osp.join(os.environ.get('VIRTUAL_ENV'), '..'))
else:
Globals.root = osp.abspath(osp.expanduser('~/invokeai'))
# Where to look for the initialization file
Globals.initfile = 'invokeai.init'