fixed merge conflicts

This commit is contained in:
Jennifer Player
2023-09-20 10:00:11 -04:00
334 changed files with 13464 additions and 4184 deletions

View File

@@ -6,6 +6,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse';
import IAIIconButton from 'common/components/IAIIconButton';
import ControlNet from 'features/controlNet/components/ControlNet';
import IPAdapterPanel from 'features/controlNet/components/ipAdapter/IPAdapterPanel';
import ParamControlNetFeatureToggle from 'features/controlNet/components/parameters/ParamControlNetFeatureToggle';
import {
controlNetAdded,
@@ -25,14 +26,23 @@ import { v4 as uuidv4 } from 'uuid';
const selector = createSelector(
[stateSelector],
({ controlNet }) => {
const { controlNets, isEnabled } = controlNet;
const { controlNets, isEnabled, isIPAdapterEnabled } = controlNet;
const validControlNets = getValidControlNets(controlNets);
const activeLabel =
isEnabled && validControlNets.length > 0
? `${validControlNets.length} Active`
: undefined;
let activeLabel = undefined;
if (isEnabled && validControlNets.length > 0) {
activeLabel = `${validControlNets.length} ControlNet`;
}
if (isIPAdapterEnabled) {
if (activeLabel) {
activeLabel = `${activeLabel}, IP Adapter`;
} else {
activeLabel = 'IP Adapter';
}
}
return { controlNetsArray: map(controlNets), activeLabel };
},
@@ -101,6 +111,7 @@ const ParamControlNetCollapse = () => {
<ControlNet controlNet={c} />
</Fragment>
))}
<IPAdapterPanel />
</Flex>
</IAICollapse>
);

View File

@@ -16,8 +16,6 @@ const selector = createSelector(
state.config.sd.iterations;
const { iterations } = state.generation;
const { shouldUseSliders } = state.ui;
const isDisabled =
state.dynamicPrompts.isEnabled && state.dynamicPrompts.combinatorial;
const step = state.hotkeys.shift ? fineStep : coarseStep;
@@ -29,13 +27,16 @@ const selector = createSelector(
inputMax,
step,
shouldUseSliders,
isDisabled,
};
},
defaultSelectorOptions
);
const ParamIterations = () => {
type Props = {
asSlider?: boolean;
};
const ParamIterations = ({ asSlider }: Props) => {
const {
iterations,
initial,
@@ -44,7 +45,6 @@ const ParamIterations = () => {
inputMax,
step,
shouldUseSliders,
isDisabled,
} = useAppSelector(selector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
@@ -60,11 +60,10 @@ const ParamIterations = () => {
dispatch(setIterations(initial));
}, [dispatch, initial]);
return shouldUseSliders ? (
return asSlider || shouldUseSliders ? (
<IAIInformationalPopover details="paramImages">
<IAISlider
isDisabled={isDisabled}
label={t('parameters.images')}
label={t('parameters.iterations')}
step={step}
min={min}
max={sliderMax}
@@ -80,8 +79,7 @@ const ParamIterations = () => {
) : (
<IAIInformationalPopover details="paramImages">
<IAINumberInput
isDisabled={isDisabled}
label={t('parameters.images')}
label={t('parameters.iterations')}
step={step}
min={min}
max={inputMax}

View File

@@ -1,21 +1,13 @@
import { Box, FormControl, useDisclosure } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { ChangeEvent, KeyboardEvent, memo, useCallback, useRef } from 'react';
import { createSelector } from '@reduxjs/toolkit';
import {
clampSymmetrySteps,
setPositivePrompt,
} from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { userInvoked } from 'app/store/actions';
import IAITextarea from 'common/components/IAITextarea';
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton';
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
import { setPositivePrompt } from 'features/parameters/store/generationSlice';
import { isEqual } from 'lodash-es';
import { ChangeEvent, KeyboardEvent, memo, useCallback, useRef } from 'react';
import { flushSync } from 'react-dom';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
@@ -23,11 +15,10 @@ import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
import IAIInformationalPopover from '../../../../../common/components/IAIInformationalPopover';
const promptInputSelector = createSelector(
[stateSelector, activeTabNameSelector],
({ generation }, activeTabName) => {
[stateSelector],
({ generation }) => {
return {
prompt: generation.positivePrompt,
activeTabName,
};
},
{
@@ -42,8 +33,7 @@ const promptInputSelector = createSelector(
*/
const ParamPositiveConditioning = () => {
const dispatch = useAppDispatch();
const { prompt, activeTabName } = useAppSelector(promptInputSelector);
const isReady = useIsReadyToInvoke();
const { prompt } = useAppSelector(promptInputSelector);
const promptRef = useRef<HTMLTextAreaElement>(null);
const { isOpen, onClose, onOpen } = useDisclosure();
const { t } = useTranslation();
@@ -105,23 +95,13 @@ const ParamPositiveConditioning = () => {
const handleKeyDown = useCallback(
(e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && e.shiftKey === false && isReady) {
e.preventDefault();
dispatch(clampSymmetrySteps());
dispatch(userInvoked(activeTabName));
}
if (isEmbeddingEnabled && e.key === '<') {
onOpen();
}
},
[isReady, dispatch, activeTabName, onOpen, isEmbeddingEnabled]
[onOpen, isEmbeddingEnabled]
);
// const handleSelect = (e: MouseEvent<HTMLTextAreaElement>) => {
// const target = e.target as HTMLTextAreaElement;
// setCaret({ start: target.selectionStart, end: target.selectionEnd });
// };
return (
<Box position="relative">
<FormControl>

View File

@@ -17,6 +17,7 @@ import ParamAspectRatio, { mappedAspectRatios } from './ParamAspectRatio';
import ParamHeight from './ParamHeight';
import ParamWidth from './ParamWidth';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
const sizeOptsSelector = createSelector(
[generationSelector, activeTabNameSelector],
@@ -31,7 +32,8 @@ const sizeOptsSelector = createSelector(
width,
height,
};
}
},
defaultSelectorOptions
);
export default function ParamSize() {

View File

@@ -1,13 +1,13 @@
import { Flex, useDisclosure } from '@chakra-ui/react';
import { upscaleRequested } from 'app/store/middleware/listenerMiddleware/listeners/upscaleRequested';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAppDispatch } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { FaExpandArrowsAlt } from 'react-icons/fa';
import { FaExpand } from 'react-icons/fa';
import { ImageDTO } from 'services/api/types';
import ParamESRGANModel from './ParamRealESRGANModel';
@@ -16,7 +16,7 @@ type Props = { imageDTO?: ImageDTO };
const ParamUpscalePopover = (props: Props) => {
const { imageDTO } = props;
const dispatch = useAppDispatch();
const isBusy = useAppSelector(selectIsBusy);
const inProgress = useIsQueueMutationInProgress();
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
@@ -34,8 +34,9 @@ const ParamUpscalePopover = (props: Props) => {
onClose={onClose}
triggerComponent={
<IAIIconButton
tooltip={t('parameters.upscale')}
onClick={onOpen}
icon={<FaExpandArrowsAlt />}
icon={<FaExpand />}
aria-label={t('parameters.upscale')}
/>
}
@@ -49,7 +50,7 @@ const ParamUpscalePopover = (props: Props) => {
<ParamESRGANModel />
<IAIButton
size="sm"
isDisabled={!imageDTO || isBusy}
isDisabled={!imageDTO || inProgress}
onClick={handleClickUpscale}
>
{t('parameters.upscaleImage')}

View File

@@ -1,184 +0,0 @@
import {
ButtonGroup,
ButtonProps,
ButtonSpinner,
Menu,
MenuButton,
MenuItemOption,
MenuList,
MenuOptionGroup,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { systemSelector } from 'features/system/store/systemSelectors';
import {
CancelStrategy,
SystemState,
cancelScheduled,
cancelTypeChanged,
} from 'features/system/store/systemSlice';
import { isEqual } from 'lodash-es';
import { memo, useCallback, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { MdCancel, MdCancelScheduleSend } from 'react-icons/md';
import { ChevronDownIcon } from '@chakra-ui/icons';
import { sessionCanceled } from 'services/api/thunks/session';
import IAIButton from 'common/components/IAIButton';
const cancelButtonSelector = createSelector(
systemSelector,
(system: SystemState) => {
return {
isProcessing: system.isProcessing,
isConnected: system.isConnected,
isCancelable: system.isCancelable,
currentIteration: system.currentIteration,
totalIterations: system.totalIterations,
sessionId: system.sessionId,
cancelType: system.cancelType,
isCancelScheduled: system.isCancelScheduled,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
type Props = Omit<ButtonProps, 'aria-label'> & {
btnGroupWidth?: string | number;
asIconButton?: boolean;
};
const CancelButton = (props: Props) => {
const dispatch = useAppDispatch();
const { btnGroupWidth = 'auto', asIconButton = false, ...rest } = props;
const {
isProcessing,
isConnected,
isCancelable,
cancelType,
isCancelScheduled,
sessionId,
} = useAppSelector(cancelButtonSelector);
const handleClickCancel = useCallback(() => {
if (!sessionId) {
return;
}
if (cancelType === 'scheduled') {
dispatch(cancelScheduled());
return;
}
dispatch(sessionCanceled({ session_id: sessionId }));
}, [dispatch, sessionId, cancelType]);
const { t } = useTranslation();
const handleCancelTypeChanged = useCallback(
(value: string | string[]) => {
const newCancelType = Array.isArray(value) ? value[0] : value;
dispatch(cancelTypeChanged(newCancelType as CancelStrategy));
},
[dispatch]
);
useHotkeys(
'shift+x',
() => {
if ((isConnected || isProcessing) && isCancelable) {
handleClickCancel();
}
},
[isConnected, isProcessing, isCancelable]
);
const cancelLabel = useMemo(() => {
if (isCancelScheduled) {
return t('parameters.cancel.isScheduled');
}
if (cancelType === 'immediate') {
return t('parameters.cancel.immediate');
}
return t('parameters.cancel.schedule');
}, [t, cancelType, isCancelScheduled]);
const cancelIcon = useMemo(() => {
if (isCancelScheduled) {
return <ButtonSpinner />;
}
if (cancelType === 'immediate') {
return <MdCancel />;
}
return <MdCancelScheduleSend />;
}, [cancelType, isCancelScheduled]);
return (
<ButtonGroup isAttached width={btnGroupWidth}>
{asIconButton ? (
<IAIIconButton
icon={cancelIcon}
tooltip={cancelLabel}
aria-label={cancelLabel}
isDisabled={!isConnected || !isProcessing || !isCancelable}
onClick={handleClickCancel}
colorScheme="error"
id="cancel-button"
{...rest}
/>
) : (
<IAIButton
leftIcon={cancelIcon}
tooltip={cancelLabel}
aria-label={cancelLabel}
isDisabled={!isConnected || !isProcessing || !isCancelable}
onClick={handleClickCancel}
colorScheme="error"
id="cancel-button"
{...rest}
>
{t('parameters.cancel.cancel')}
</IAIButton>
)}
<Menu closeOnSelect={false}>
<MenuButton
as={IAIIconButton}
tooltip={t('parameters.cancel.setType')}
aria-label={t('parameters.cancel.setType')}
icon={<ChevronDownIcon w="1em" h="1em" />}
paddingX={0}
paddingY={0}
colorScheme="error"
minWidth={5}
{...rest}
/>
<MenuList minWidth="240px">
<MenuOptionGroup
value={cancelType}
title="Cancel Type"
type="radio"
onChange={handleCancelTypeChanged}
>
<MenuItemOption value="immediate">
{t('parameters.cancel.immediate')}
</MenuItemOption>
<MenuItemOption value="scheduled">
{t('parameters.cancel.schedule')}
</MenuItemOption>
</MenuOptionGroup>
</MenuList>
</Menu>
</ButtonGroup>
);
};
export default memo(CancelButton);

View File

@@ -1,174 +0,0 @@
import {
Box,
Divider,
Flex,
ListItem,
Text,
UnorderedList,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { userInvoked } from 'app/store/actions';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIButton, { IAIButtonProps } from 'common/components/IAIButton';
import IAIIconButton, {
IAIIconButtonProps,
} from 'common/components/IAIIconButton';
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
import { clampSymmetrySteps } from 'features/parameters/store/generationSlice';
import ProgressBar from 'features/system/components/ProgressBar';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { memo, useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaPlay } from 'react-icons/fa';
import { useBoardName } from 'services/api/hooks/useBoardName';
interface InvokeButton
extends Omit<IAIButtonProps | IAIIconButtonProps, 'aria-label'> {
asIconButton?: boolean;
}
export default function InvokeButton(props: InvokeButton) {
const { asIconButton = false, sx, ...rest } = props;
const dispatch = useAppDispatch();
const { isReady, isProcessing } = useIsReadyToInvoke();
const activeTabName = useAppSelector(activeTabNameSelector);
const handleInvoke = useCallback(() => {
dispatch(clampSymmetrySteps());
dispatch(userInvoked(activeTabName));
}, [dispatch, activeTabName]);
const { t } = useTranslation();
useHotkeys(
['ctrl+enter', 'meta+enter'],
handleInvoke,
{
enabled: () => isReady,
preventDefault: true,
enableOnFormTags: ['input', 'textarea', 'select'],
},
[isReady, activeTabName]
);
return (
<Box style={{ flexGrow: 4 }} position="relative">
<Box style={{ position: 'relative' }}>
{!isReady && (
<Box
sx={{
position: 'absolute',
bottom: '0',
left: '0',
right: '0',
height: '100%',
overflow: 'clip',
borderRadius: 'base',
...sx,
}}
{...rest}
>
<ProgressBar />
</Box>
)}
{asIconButton ? (
<IAIIconButton
aria-label={t('parameters.invoke.invoke')}
type="submit"
icon={<FaPlay />}
isDisabled={!isReady}
onClick={handleInvoke}
tooltip={<InvokeButtonTooltipContent />}
colorScheme="accent"
isLoading={isProcessing}
id="invoke-button"
data-progress={isProcessing}
sx={{
w: 'full',
flexGrow: 1,
...sx,
}}
{...rest}
/>
) : (
<IAIButton
tooltip={<InvokeButtonTooltipContent />}
aria-label={t('parameters.invoke.invoke')}
type="submit"
data-progress={isProcessing}
isDisabled={!isReady}
onClick={handleInvoke}
colorScheme="accent"
id="invoke-button"
leftIcon={isProcessing ? undefined : <FaPlay />}
isLoading={isProcessing}
loadingText={t('parameters.invoke.invoke')}
sx={{
w: 'full',
flexGrow: 1,
fontWeight: 700,
...sx,
}}
{...rest}
>
Invoke
</IAIButton>
)}
</Box>
</Box>
);
}
const tooltipSelector = createSelector(
[stateSelector],
({ gallery }) => {
const { autoAddBoardId } = gallery;
return {
autoAddBoardId,
};
},
defaultSelectorOptions
);
export const InvokeButtonTooltipContent = memo(() => {
const { isReady, reasons } = useIsReadyToInvoke();
const { autoAddBoardId } = useAppSelector(tooltipSelector);
const autoAddBoardName = useBoardName(autoAddBoardId);
const { t } = useTranslation();
return (
<Flex flexDir="column" gap={1}>
<Text fontWeight={600}>
{isReady
? t('parameters.invoke.readyToInvoke')
: t('parameters.invoke.unableToInvoke')}
</Text>
{reasons.length > 0 && (
<UnorderedList>
{reasons.map((reason, i) => (
<ListItem key={`${reason}.${i}`}>
<Text fontWeight={400}>{reason}</Text>
</ListItem>
))}
</UnorderedList>
)}
<Divider
opacity={0.2}
borderColor="base.50"
_dark={{ borderColor: 'base.900' }}
/>
<Text fontWeight={400} fontStyle="oblique 10deg">
{t('parameters.invoke.addingImagesTo')}{' '}
<Text as="span" fontWeight={600}>
{autoAddBoardName || 'Uncategorized'}
</Text>
</Text>
</Flex>
);
});
InvokeButtonTooltipContent.displayName = 'InvokeButtonTooltipContent';

View File

@@ -1,18 +0,0 @@
import { Flex } from '@chakra-ui/react';
import { memo } from 'react';
import CancelButton from './CancelButton';
import InvokeButton from './InvokeButton';
/**
* Buttons to start and cancel image generation.
*/
const ProcessButtons = () => {
return (
<Flex layerStyle="first" sx={{ gap: 2, borderRadius: 'base', p: 2 }}>
<InvokeButton />
<CancelButton />
</Flex>
);
};
export default memo(ProcessButtons);

View File

@@ -1,6 +1,7 @@
import { components } from 'services/api/schema';
export const MODEL_TYPE_MAP = {
any: 'Any',
'sd-1': 'Stable Diffusion 1.x',
'sd-2': 'Stable Diffusion 2.x',
sdxl: 'Stable Diffusion XL',
@@ -8,6 +9,7 @@ export const MODEL_TYPE_MAP = {
};
export const MODEL_TYPE_SHORT_MAP = {
any: 'Any',
'sd-1': 'SD1',
'sd-2': 'SD2',
sdxl: 'SDXL',
@@ -15,6 +17,10 @@ export const MODEL_TYPE_SHORT_MAP = {
};
export const clipSkipMap = {
any: {
maxClip: 0,
markers: [],
},
'sd-1': {
maxClip: 12,
markers: [0, 1, 2, 3, 4, 8, 12],

View File

@@ -210,7 +210,13 @@ export type HeightParam = z.infer<typeof zHeight>;
export const isValidHeight = (val: unknown): val is HeightParam =>
zHeight.safeParse(val).success;
export const zBaseModel = z.enum(['sd-1', 'sd-2', 'sdxl', 'sdxl-refiner']);
export const zBaseModel = z.enum([
'any',
'sd-1',
'sd-2',
'sdxl',
'sdxl-refiner',
]);
export type BaseModelParam = z.infer<typeof zBaseModel>;
@@ -323,7 +329,17 @@ export type ControlNetModelParam = z.infer<typeof zLoRAModel>;
export const isValidControlNetModel = (
val: unknown
): val is ControlNetModelParam => zControlNetModel.safeParse(val).success;
/**
* Zod schema for IP-Adapter models
*/
export const zIPAdapterModel = z.object({
model_name: z.string().min(1),
base_model: zBaseModel,
});
/**
* Type alias for model parameter, inferred from its zod schema
*/
export type IPAdapterModelParam = z.infer<typeof zIPAdapterModel>;
/**
* Zod schema for l2l strength parameter
*/

View File

@@ -0,0 +1,29 @@
import { logger } from 'app/logging/logger';
import { zIPAdapterModel } from 'features/parameters/types/parameterSchemas';
import { IPAdapterModelField } from 'services/api/types';
export const modelIdToIPAdapterModelParam = (
ipAdapterModelId: string
): IPAdapterModelField | undefined => {
const log = logger('models');
const [base_model, _model_type, model_name] = ipAdapterModelId.split('/');
const result = zIPAdapterModel.safeParse({
base_model,
model_name,
});
if (!result.success) {
log.error(
{
ipAdapterModelId,
errors: result.error.format(),
},
'Failed to parse IP-Adapter model id'
);
return;
}
return result.data;
};

View File

@@ -0,0 +1,34 @@
import { useAppSelector } from 'app/store/storeHooks';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
export const useCoreParametersCollapseLabel = () => {
const { t } = useTranslation();
const shouldRandomizeSeed = useAppSelector(
(state) => state.generation.shouldRandomizeSeed
);
const iterations = useAppSelector((state) => state.generation.iterations);
const iterationsLabel = useMemo(() => {
if (iterations === 1) {
return t('parameters.iterationsWithCount_one', { count: 1 });
} else {
return t('parameters.iterationsWithCount_other', { count: iterations });
}
}, [iterations, t]);
const seedLabel = useMemo(() => {
if (shouldRandomizeSeed) {
return t('parameters.randomSeed');
} else {
return t('parameters.manualSeed');
}
}, [shouldRandomizeSeed, t]);
const iterationsAndSeedLabel = useMemo(
() => [iterationsLabel, seedLabel].join(', '),
[iterationsLabel, seedLabel]
);
return { iterationsAndSeedLabel, iterationsLabel, seedLabel };
};