Merge branch 'main' into bbox-ar

This commit is contained in:
blessedcoolant
2023-07-13 13:45:08 +12:00
committed by GitHub
56 changed files with 1207 additions and 723 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -12,7 +12,7 @@
margin: 0;
}
</style>
<script type="module" crossorigin src="./assets/index-581af3d4.js"></script>
<script type="module" crossorigin src="./assets/index-078526aa.js"></script>
</head>
<body dir="ltr">

View File

@ -53,7 +53,7 @@
"linear": "Linear",
"nodes": "Node Editor",
"batch": "Batch Manager",
"modelmanager": "Model Manager",
"modelManager": "Model Manager",
"postprocessing": "Post Processing",
"nodesDesc": "A node based system for the generation of images is under development currently. Stay tuned for updates about this amazing feature.",
"postProcessing": "Post Processing",
@ -527,7 +527,9 @@
"showOptionsPanel": "Show Options Panel",
"hidePreview": "Hide Preview",
"showPreview": "Show Preview",
"controlNetControlMode": "Control Mode"
"controlNetControlMode": "Control Mode",
"clipSkip": "Clip Skip",
"aspectRatio": "Ratio"
},
"settings": {
"models": "Models",
@ -551,7 +553,8 @@
"generation": "Generation",
"ui": "User Interface",
"favoriteSchedulers": "Favorite Schedulers",
"favoriteSchedulersPlaceholder": "No schedulers favorited"
"favoriteSchedulersPlaceholder": "No schedulers favorited",
"showAdvancedOptions": "Show Advanced Options"
},
"toast": {
"serverError": "Server Error",
@ -669,6 +672,7 @@
},
"ui": {
"showProgressImages": "Show Progress Images",
"hideProgressImages": "Hide Progress Images"
"hideProgressImages": "Hide Progress Images",
"swapSizes": "Swap Sizes"
}
}

View File

@ -53,7 +53,7 @@
"linear": "Linear",
"nodes": "Node Editor",
"batch": "Batch Manager",
"modelmanager": "Model Manager",
"modelManager": "Model Manager",
"postprocessing": "Post Processing",
"nodesDesc": "A node based system for the generation of images is under development currently. Stay tuned for updates about this amazing feature.",
"postProcessing": "Post Processing",
@ -593,7 +593,10 @@
"metadataLoadFailed": "Failed to load metadata",
"initialImageSet": "Initial Image Set",
"initialImageNotSet": "Initial Image Not Set",
"initialImageNotSetDesc": "Could not load initial image"
"initialImageNotSetDesc": "Could not load initial image",
"nodesSaved": "Nodes Saved",
"nodesLoaded": "Nodes Loaded",
"nodesLoadedFailed": "Failed To Load Nodes"
},
"tooltip": {
"feature": {
@ -674,5 +677,10 @@
"showProgressImages": "Show Progress Images",
"hideProgressImages": "Hide Progress Images",
"swapSizes": "Swap Sizes"
},
"nodes": {
"reloadSchema": "Reload Schema",
"saveNodes": "Save Nodes",
"loadNodes": "Load Nodes"
}
}

View File

@ -102,6 +102,8 @@ export type AppFeature =
export type SDFeature =
| 'controlNet'
| 'noise'
| 'perlinNoise'
| 'noiseThreshold'
| 'variation'
| 'symmetry'
| 'seamless'

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

@ -7,6 +7,7 @@ import {
OnConnectEnd,
OnConnectStart,
OnEdgesChange,
OnInit,
OnNodesChange,
ReactFlow,
} from 'reactflow';
@ -16,6 +17,7 @@ import {
connectionStarted,
edgesChanged,
nodesChanged,
setEditorInstance,
} from '../store/nodesSlice';
import { InvocationComponent } from './InvocationComponent';
import ProgressImageNode from './ProgressImageNode';
@ -67,6 +69,14 @@ export const Flow = () => {
dispatch(connectionEnded());
}, [dispatch]);
const onInit: OnInit = useCallback(
(v) => {
dispatch(setEditorInstance(v));
if (v) v.fitView();
},
[dispatch]
);
return (
<ReactFlow
nodeTypes={nodeTypes}
@ -77,6 +87,7 @@ export const Flow = () => {
onConnectStart={onConnectStart}
onConnect={onConnect}
onConnectEnd={onConnectEnd}
onInit={onInit}
defaultEdgeOptions={{
style: { strokeWidth: 2 },
}}

View File

@ -1,25 +1,21 @@
import { HStack } from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
import { memo, useCallback } from 'react';
import { Panel } from 'reactflow';
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import NodeInvokeButton from '../ui/NodeInvokeButton';
import CancelButton from 'features/parameters/components/ProcessButtons/CancelButton';
import { memo } from 'react';
import { Panel } from 'reactflow';
import LoadNodesButton from '../ui/LoadNodesButton';
import NodeInvokeButton from '../ui/NodeInvokeButton';
import ReloadSchemaButton from '../ui/ReloadSchemaButton';
import SaveNodesButton from '../ui/SaveNodesButton';
const TopCenterPanel = () => {
const dispatch = useAppDispatch();
const handleReloadSchema = useCallback(() => {
dispatch(receivedOpenAPISchema());
}, [dispatch]);
return (
<Panel position="top-center">
<HStack>
<NodeInvokeButton />
<CancelButton />
<IAIButton onClick={handleReloadSchema}>Reload Schema</IAIButton>
<ReloadSchemaButton />
<SaveNodesButton />
<LoadNodesButton />
</HStack>
</Panel>
);

View File

@ -0,0 +1,79 @@
import { FileButton } from '@mantine/core';
import { makeToast } from 'app/components/Toaster';
import { useAppDispatch } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { loadFileEdges, loadFileNodes } from 'features/nodes/store/nodesSlice';
import { addToast } from 'features/system/store/systemSlice';
import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { FaUpload } from 'react-icons/fa';
import { useReactFlow } from 'reactflow';
const LoadNodesButton = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { fitView } = useReactFlow();
const uploadedFileRef = useRef<() => void>(null);
const restoreJSONToEditor = useCallback(
(v: File | null) => {
if (!v) return;
const reader = new FileReader();
reader.onload = async () => {
const json = reader.result;
const retrievedNodeTree = await JSON.parse(String(json));
if (!retrievedNodeTree) {
dispatch(
addToast(
makeToast({
title: t('toast.nodesLoadedFailed'),
status: 'error',
})
)
);
}
if (retrievedNodeTree) {
dispatch(loadFileNodes(retrievedNodeTree.nodes));
dispatch(loadFileEdges(retrievedNodeTree.edges));
fitView();
dispatch(
addToast(
makeToast({ title: t('toast.nodesLoaded'), status: 'success' })
)
);
}
// Cleanup
reader.abort();
};
reader.readAsText(v);
// Cleanup
uploadedFileRef.current?.();
},
[fitView, dispatch, t]
);
return (
<FileButton
resetRef={uploadedFileRef}
accept="application/json"
onChange={restoreJSONToEditor}
>
{(props) => (
<IAIIconButton
icon={<FaUpload />}
tooltip={t('nodes.loadNodes')}
aria-label={t('nodes.loadNodes')}
{...props}
/>
)}
</FileButton>
);
};
export default memo(LoadNodesButton);

View File

@ -0,0 +1,24 @@
import { useAppDispatch } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { FaSyncAlt } from 'react-icons/fa';
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
export default function ReloadSchemaButton() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const handleReloadSchema = useCallback(() => {
dispatch(receivedOpenAPISchema());
}, [dispatch]);
return (
<IAIIconButton
icon={<FaSyncAlt />}
tooltip={t('nodes.reloadSchema')}
aria-label={t('nodes.reloadSchema')}
onClick={handleReloadSchema}
/>
);
}

View File

@ -0,0 +1,45 @@
import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { map, omit } from 'lodash-es';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { FaSave } from 'react-icons/fa';
const SaveNodesButton = () => {
const { t } = useTranslation();
const editorInstance = useAppSelector(
(state: RootState) => state.nodes.editorInstance
);
const saveEditorToJSON = useCallback(() => {
if (editorInstance) {
const editorState = editorInstance.toObject();
editorState.edges = map(editorState.edges, (edge) => {
return omit(edge, ['style']);
});
const nodeSetupJSON = new Blob([JSON.stringify(editorState)]);
const nodeDownloadElement = document.createElement('a');
nodeDownloadElement.href = URL.createObjectURL(nodeSetupJSON);
nodeDownloadElement.download = 'MyNodes.json';
document.body.appendChild(nodeDownloadElement);
nodeDownloadElement.click();
// Cleanup
nodeDownloadElement.remove();
}
}, [editorInstance]);
return (
<IAIIconButton
icon={<FaSave />}
fontSize={18}
tooltip={t('nodes.saveNodes')}
aria-label={t('nodes.saveNodes')}
onClick={saveEditorToJSON}
/>
);
};
export default memo(SaveNodesButton);

View File

@ -13,6 +13,7 @@ import {
Node,
NodeChange,
OnConnectStartParams,
ReactFlowInstance,
} from 'reactflow';
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import { ImageField } from 'services/api/types';
@ -25,6 +26,7 @@ export type NodesState = {
invocationTemplates: Record<string, InvocationTemplate>;
connectionStartParams: OnConnectStartParams | null;
shouldShowGraphOverlay: boolean;
editorInstance: ReactFlowInstance | undefined;
};
export const initialNodesState: NodesState = {
@ -34,6 +36,7 @@ export const initialNodesState: NodesState = {
invocationTemplates: {},
connectionStartParams: null,
shouldShowGraphOverlay: false,
editorInstance: undefined,
};
const nodesSlice = createSlice({
@ -121,6 +124,15 @@ const nodesSlice = createSlice({
nodeEditorReset: () => {
return { ...initialNodesState };
},
setEditorInstance: (state, action) => {
state.editorInstance = action.payload;
},
loadFileNodes: (state, action: PayloadAction<Node<InvocationValue>[]>) => {
state.nodes = action.payload;
},
loadFileEdges: (state, action: PayloadAction<Edge[]>) => {
state.edges = action.payload;
},
},
extraReducers: (builder) => {
builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => {
@ -141,6 +153,9 @@ export const {
nodeTemplatesBuilt,
nodeEditorReset,
imageCollectionFieldValueChanged,
setEditorInstance,
loadFileNodes,
loadFileEdges,
} = nodesSlice.actions;
export default nodesSlice.reducer;

View File

@ -3,6 +3,7 @@ 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';
import { activeTabNameSelector } from '../../../../ui/store/uiSelectors';
const aspectRatios = [
{ name: 'Free', value: null },
@ -17,6 +18,10 @@ export default function ParamAspectRatio() {
);
const dispatch = useAppDispatch();
const shouldFitToWidthHeight = useAppSelector(
(state: RootState) => state.generation.shouldFitToWidthHeight
);
const activeTabName = useAppSelector(activeTabNameSelector);
return (
<Flex gap={2} flexGrow={1}>
@ -26,6 +31,9 @@ export default function ParamAspectRatio() {
key={ratio.name}
size="sm"
isChecked={aspectRatio === ratio.value}
isDisabled={
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
}
onClick={() => dispatch(setAspectRatio(ratio.value))}
>
{ratio.name}

View File

@ -8,6 +8,7 @@ import { MdOutlineSwapVert } from 'react-icons/md';
import ParamAspectRatio from './ParamAspectRatio';
import ParamHeight from './ParamHeight';
import ParamWidth from './ParamWidth';
import { activeTabNameSelector } from '../../../../ui/store/uiSelectors';
export default function ParamSize() {
const { t } = useTranslation();
@ -15,6 +16,7 @@ export default function ParamSize() {
const shouldFitToWidthHeight = useAppSelector(
(state: RootState) => state.generation.shouldFitToWidthHeight
);
const activeTabName = useAppSelector(activeTabNameSelector);
return (
<Flex
sx={{
@ -50,13 +52,24 @@ export default function ParamSize() {
size="sm"
icon={<MdOutlineSwapVert />}
fontSize={20}
isDisabled={
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
}
onClick={() => dispatch(toggleSize())}
/>
</Flex>
<Flex gap={2} alignItems="center">
<Flex gap={2} flexDirection="column" width="full">
<ParamWidth isDisabled={!shouldFitToWidthHeight} />
<ParamHeight isDisabled={!shouldFitToWidthHeight} />
<ParamWidth
isDisabled={
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
}
/>
<ParamHeight
isDisabled={
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
}
/>
</Flex>
</Flex>
</Flex>

View File

@ -27,6 +27,9 @@ const ParamNoiseCollapse = () => {
const { t } = useTranslation();
const isNoiseEnabled = useFeatureStatus('noise').isFeatureEnabled;
const isPerlinNoiseEnabled = useFeatureStatus('perlinNoise').isFeatureEnabled;
const isNoiseThresholdEnabled =
useFeatureStatus('noiseThreshold').isFeatureEnabled;
const { activeLabel } = useAppSelector(selector);
@ -42,8 +45,8 @@ const ParamNoiseCollapse = () => {
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
<ParamNoiseToggle />
<ParamCpuNoiseToggle />
<ParamPerlinNoise />
<ParamNoiseThreshold />
{isPerlinNoiseEnabled && <ParamPerlinNoise />}
{isNoiseThresholdEnabled && <ParamNoiseThreshold />}
</Flex>
</IAICollapse>
);

View File

@ -6,8 +6,15 @@ import { merge } from 'lodash-es';
export const initialConfigState: AppConfig = {
shouldUpdateImagesOnConnect: false,
disabledTabs: [],
disabledFeatures: [],
disabledSDFeatures: [],
disabledFeatures: ['lightbox', 'faceRestore'],
disabledSDFeatures: [
'variation',
'seamless',
'symmetry',
'hires',
'perlinNoise',
'noiseThreshold',
],
canRestoreDeletedImagesFromBin: true,
sd: {
disabledControlNetModels: [],

View File

@ -38,6 +38,7 @@ import NodesTab from './tabs/Nodes/NodesTab';
import ResizeHandle from './tabs/ResizeHandle';
import TextToImageTab from './tabs/TextToImage/TextToImageTab';
import UnifiedCanvasTab from './tabs/UnifiedCanvas/UnifiedCanvasTab';
import { useFeatureStatus } from '../../system/hooks/useFeatureStatus';
export interface InvokeTabInfo {
id: InvokeTabName;
@ -107,6 +108,7 @@ const InvokeTabs = () => {
const isLightBoxOpen = useAppSelector(
(state: RootState) => state.lightbox.isLightboxOpen
);
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
const { shouldPinGallery, shouldPinParametersPanel, shouldShowGallery } =
useAppSelector((state: RootState) => state.ui);
@ -119,7 +121,9 @@ const InvokeTabs = () => {
useHotkeys(
'z',
() => {
dispatch(setIsLightboxOpen(!isLightBoxOpen));
if (isLightboxEnabled) {
dispatch(setIsLightboxOpen(!isLightBoxOpen));
}
},
[isLightBoxOpen]
);