mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-17 14:17:42 -05:00
Merge branch 'main' into psyche/fix/ui/cl-listening-layers
This commit is contained in:
@@ -21,7 +21,6 @@ import {
|
||||
setShouldShowBoundingBox,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import type { CanvasLayer } from 'features/canvas/store/canvasTypes';
|
||||
import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -216,13 +215,20 @@ const IAICanvasToolbar = () => {
|
||||
[dispatch, isMaskEnabled]
|
||||
);
|
||||
|
||||
const value = useMemo(() => LAYER_NAMES_DICT.filter((o) => o.value === layer)[0], [layer]);
|
||||
const layerOptions = useMemo<{ label: string; value: CanvasLayer }[]>(
|
||||
() => [
|
||||
{ label: t('unifiedCanvas.base'), value: 'base' },
|
||||
{ label: t('unifiedCanvas.mask'), value: 'mask' },
|
||||
],
|
||||
[t]
|
||||
);
|
||||
const layerValue = useMemo(() => layerOptions.filter((o) => o.value === layer)[0] ?? null, [layer, layerOptions]);
|
||||
|
||||
return (
|
||||
<Flex alignItems="center" gap={2} flexWrap="wrap">
|
||||
<Tooltip label={`${t('unifiedCanvas.layer')} (Q)`}>
|
||||
<FormControl isDisabled={isStaging} w="5rem">
|
||||
<Combobox value={value} options={LAYER_NAMES_DICT} onChange={handleChangeLayer} />
|
||||
<Combobox value={layerValue} options={layerOptions} onChange={handleChangeLayer} />
|
||||
</FormControl>
|
||||
</Tooltip>
|
||||
|
||||
|
||||
@@ -5,11 +5,6 @@ import { z } from 'zod';
|
||||
|
||||
export type CanvasLayer = 'base' | 'mask';
|
||||
|
||||
export const LAYER_NAMES_DICT: { label: string; value: CanvasLayer }[] = [
|
||||
{ label: 'Base', value: 'base' },
|
||||
{ label: 'Mask', value: 'mask' },
|
||||
];
|
||||
|
||||
const zBoundingBoxScaleMethod = z.enum(['none', 'auto', 'manual']);
|
||||
export type BoundingBoxScaleMethod = z.infer<typeof zBoundingBoxScaleMethod>;
|
||||
export const isBoundingBoxScaleMethod = (v: unknown): v is BoundingBoxScaleMethod =>
|
||||
|
||||
@@ -124,7 +124,7 @@ export const ControlAdapterImagePreview = memo(
|
||||
controlImage &&
|
||||
processedControlImage &&
|
||||
!isMouseOverImage &&
|
||||
!controlAdapter.isProcessingImage &&
|
||||
!controlAdapter.processorPendingBatchId &&
|
||||
controlAdapter.processorConfig !== null;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -190,7 +190,7 @@ export const ControlAdapterImagePreview = memo(
|
||||
/>
|
||||
</>
|
||||
|
||||
{controlAdapter.isProcessingImage && (
|
||||
{controlAdapter.processorPendingBatchId !== null && (
|
||||
<Flex
|
||||
position="absolute"
|
||||
top={0}
|
||||
|
||||
@@ -42,6 +42,7 @@ export const ControlAdapterModelCombobox = memo(({ modelKey, onChange: onChangeM
|
||||
selectedModel,
|
||||
getIsDisabled,
|
||||
isLoading,
|
||||
groupByType: true,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,14 +2,13 @@ import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { DepthAnythingModelSize, DepthAnythingProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { CA_PROCESSOR_DATA, isDepthAnythingModelSize } from 'features/controlLayers/util/controlAdapters';
|
||||
import { isDepthAnythingModelSize } from 'features/controlLayers/util/controlAdapters';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<DepthAnythingProcessorConfig>;
|
||||
const DEFAULTS = CA_PROCESSOR_DATA['depth_anything_image_processor'].buildDefaults();
|
||||
|
||||
export const DepthAnythingProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -38,12 +37,7 @@ export const DepthAnythingProcessor = memo(({ onChange, config }: Props) => {
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.modelSize')}</FormLabel>
|
||||
<Combobox
|
||||
value={value}
|
||||
defaultInputValue={DEFAULTS.model_size}
|
||||
options={options}
|
||||
onChange={handleModelSizeChange}
|
||||
/>
|
||||
<Combobox value={value} options={options} onChange={handleModelSizeChange} isSearchable={false} />
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ import { modelChanged } from 'features/parameters/store/generationSlice';
|
||||
import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas';
|
||||
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||
import { isEqual, partition } from 'lodash-es';
|
||||
import { isEqual, partition, unset } from 'lodash-es';
|
||||
import { atom } from 'nanostores';
|
||||
import type { RgbColor } from 'react-colorful';
|
||||
import type { UndoableOptions } from 'redux-undo';
|
||||
@@ -49,7 +49,7 @@ import type {
|
||||
} from './types';
|
||||
|
||||
export const initialControlLayersState: ControlLayersState = {
|
||||
_version: 2,
|
||||
_version: 3,
|
||||
selectedLayerId: null,
|
||||
brushSize: 100,
|
||||
layers: [],
|
||||
@@ -334,13 +334,13 @@ export const controlLayersSlice = createSlice({
|
||||
const layer = selectCALayerOrThrow(state, layerId);
|
||||
layer.opacity = opacity;
|
||||
},
|
||||
caLayerIsProcessingImageChanged: (
|
||||
caLayerProcessorPendingBatchIdChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ layerId: string; isProcessingImage: boolean }>
|
||||
action: PayloadAction<{ layerId: string; batchId: string | null }>
|
||||
) => {
|
||||
const { layerId, isProcessingImage } = action.payload;
|
||||
const { layerId, batchId } = action.payload;
|
||||
const layer = selectCALayerOrThrow(state, layerId);
|
||||
layer.controlAdapter.isProcessingImage = isProcessingImage;
|
||||
layer.controlAdapter.processorPendingBatchId = batchId;
|
||||
},
|
||||
//#endregion
|
||||
|
||||
@@ -800,7 +800,7 @@ export const {
|
||||
caLayerProcessorConfigChanged,
|
||||
caLayerIsFilterEnabledChanged,
|
||||
caLayerOpacityChanged,
|
||||
caLayerIsProcessingImageChanged,
|
||||
caLayerProcessorPendingBatchIdChanged,
|
||||
// IPA Layers
|
||||
ipaLayerAdded,
|
||||
ipaLayerRecalled,
|
||||
@@ -857,7 +857,16 @@ export const selectControlLayersSlice = (state: RootState) => state.controlLayer
|
||||
const migrateControlLayersState = (state: any): any => {
|
||||
if (state._version === 1) {
|
||||
// Reset state for users on v1 (e.g. beta users), some changes could cause
|
||||
return deepClone(initialControlLayersState);
|
||||
state = deepClone(initialControlLayersState);
|
||||
}
|
||||
if (state._version === 2) {
|
||||
// The CA `isProcessingImage` flag was replaced with a `processorPendingBatchId` property, fix up CA layers
|
||||
for (const layer of (state as ControlLayersState).layers) {
|
||||
if (layer.type === 'control_adapter_layer') {
|
||||
layer.controlAdapter.processorPendingBatchId = null;
|
||||
unset(layer.controlAdapter, 'isProcessingImage');
|
||||
}
|
||||
}
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
@@ -113,7 +113,7 @@ export const zLayer = z.discriminatedUnion('type', [
|
||||
export type Layer = z.infer<typeof zLayer>;
|
||||
|
||||
export type ControlLayersState = {
|
||||
_version: 2;
|
||||
_version: 3;
|
||||
selectedLayerId: string | null;
|
||||
layers: Layer[];
|
||||
brushSize: number;
|
||||
|
||||
@@ -198,8 +198,8 @@ const zControlAdapterBase = z.object({
|
||||
weight: z.number().gte(0).lte(1),
|
||||
image: zImageWithDims.nullable(),
|
||||
processedImage: zImageWithDims.nullable(),
|
||||
isProcessingImage: z.boolean(),
|
||||
processorConfig: zProcessorConfig.nullable(),
|
||||
processorPendingBatchId: z.string().nullable().default(null),
|
||||
beginEndStepPct: zBeginEndStepPct,
|
||||
});
|
||||
|
||||
@@ -521,8 +521,8 @@ export const initialControlNetV2: Omit<ControlNetConfigV2, 'id'> = {
|
||||
controlMode: 'balanced',
|
||||
image: null,
|
||||
processedImage: null,
|
||||
isProcessingImage: false,
|
||||
processorConfig: CA_PROCESSOR_DATA.canny_image_processor.buildDefaults(),
|
||||
processorPendingBatchId: null,
|
||||
};
|
||||
|
||||
export const initialT2IAdapterV2: Omit<T2IAdapterConfigV2, 'id'> = {
|
||||
@@ -532,8 +532,8 @@ export const initialT2IAdapterV2: Omit<T2IAdapterConfigV2, 'id'> = {
|
||||
beginEndStepPct: [0, 1],
|
||||
image: null,
|
||||
processedImage: null,
|
||||
isProcessingImage: false,
|
||||
processorConfig: CA_PROCESSOR_DATA.canny_image_processor.buildDefaults(),
|
||||
processorPendingBatchId: null,
|
||||
};
|
||||
|
||||
export const initialIPAdapterV2: Omit<IPAdapterConfigV2, 'id'> = {
|
||||
|
||||
@@ -587,7 +587,7 @@ const parseControlNetToControlAdapterLayer: MetadataParseFunc<ControlAdapterLaye
|
||||
image: imageDTO ? imageDTOToImageWithDims(imageDTO) : null,
|
||||
processedImage: processedImageDTO ? imageDTOToImageWithDims(processedImageDTO) : null,
|
||||
processorConfig,
|
||||
isProcessingImage: false,
|
||||
processorPendingBatchId: null,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -651,7 +651,7 @@ const parseT2IAdapterToControlAdapterLayer: MetadataParseFunc<ControlAdapterLaye
|
||||
image: imageDTO ? imageDTOToImageWithDims(imageDTO) : null,
|
||||
processedImage: processedImageDTO ? imageDTOToImageWithDims(processedImageDTO) : null,
|
||||
processorConfig,
|
||||
isProcessingImage: false,
|
||||
processorPendingBatchId: null,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -16,25 +16,26 @@ export const InvokeQueueBackButton = memo(() => {
|
||||
return (
|
||||
<Flex pos="relative" flexGrow={1} minW="240px">
|
||||
<QueueIterationsNumberInput />
|
||||
<Button
|
||||
onClick={queueBack}
|
||||
isLoading={isLoading || isLoadingDynamicPrompts}
|
||||
loadingText={invoke}
|
||||
isDisabled={isDisabled}
|
||||
rightIcon={<RiSparkling2Fill />}
|
||||
tooltip={<QueueButtonTooltip />}
|
||||
variant="solid"
|
||||
zIndex={1}
|
||||
colorScheme="invokeYellow"
|
||||
size="lg"
|
||||
w="calc(100% - 60px)"
|
||||
flexShrink={0}
|
||||
justifyContent="space-between"
|
||||
spinnerPlacement="end"
|
||||
>
|
||||
<span>{invoke}</span>
|
||||
<Spacer />
|
||||
</Button>
|
||||
<QueueButtonTooltip>
|
||||
<Button
|
||||
onClick={queueBack}
|
||||
isLoading={isLoading || isLoadingDynamicPrompts}
|
||||
loadingText={invoke}
|
||||
isDisabled={isDisabled}
|
||||
rightIcon={<RiSparkling2Fill />}
|
||||
variant="solid"
|
||||
zIndex={1}
|
||||
colorScheme="invokeYellow"
|
||||
size="lg"
|
||||
w="calc(100% - 60px)"
|
||||
flexShrink={0}
|
||||
justifyContent="space-between"
|
||||
spinnerPlacement="end"
|
||||
>
|
||||
<span>{invoke}</span>
|
||||
<Spacer />
|
||||
</Button>
|
||||
</QueueButtonTooltip>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Divider, Flex, ListItem, Text, UnorderedList } from '@invoke-ai/ui-library';
|
||||
import { Divider, Flex, ListItem, Text, Tooltip, UnorderedList } from '@invoke-ai/ui-library';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue';
|
||||
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useEnqueueBatchMutation } from 'services/api/endpoints/queue';
|
||||
@@ -21,17 +22,32 @@ type Props = {
|
||||
prepend?: boolean;
|
||||
};
|
||||
|
||||
export const QueueButtonTooltip = memo(({ prepend = false }: Props) => {
|
||||
export const QueueButtonTooltip = (props: PropsWithChildren<Props>) => {
|
||||
return (
|
||||
<Tooltip label={<TooltipContent prepend={props.prepend} />} maxW={512}>
|
||||
{props.children}
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const TooltipContent = memo(({ prepend = false }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { isReady, reasons } = useIsReadyToEnqueue();
|
||||
const isLoadingDynamicPrompts = useAppSelector((s) => s.dynamicPrompts.isLoading);
|
||||
const promptsCount = useAppSelector(selectPromptsCount);
|
||||
const iterations = useAppSelector((s) => s.generation.iterations);
|
||||
const iterationsCount = useAppSelector((s) => s.generation.iterations);
|
||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||
const autoAddBoardName = useBoardName(autoAddBoardId);
|
||||
const [_, { isLoading }] = useEnqueueBatchMutation({
|
||||
fixedCacheKey: 'enqueueBatch',
|
||||
});
|
||||
const queueCountPredictionLabel = useMemo(() => {
|
||||
const generationCount = Math.min(promptsCount * iterationsCount, 10000);
|
||||
const prompts = t('queue.prompts', { count: promptsCount });
|
||||
const iterations = t('queue.iterations', { count: iterationsCount });
|
||||
const generations = t('queue.generations', { count: generationCount });
|
||||
return `${promptsCount} ${prompts} \u00d7 ${iterationsCount} ${iterations} -> ${generationCount} ${generations}`.toLowerCase();
|
||||
}, [iterationsCount, promptsCount, t]);
|
||||
|
||||
const label = useMemo(() => {
|
||||
if (isLoading) {
|
||||
@@ -52,20 +68,21 @@ export const QueueButtonTooltip = memo(({ prepend = false }: Props) => {
|
||||
return (
|
||||
<Flex flexDir="column" gap={1}>
|
||||
<Text fontWeight="semibold">{label}</Text>
|
||||
<Text>
|
||||
{t('queue.queueCountPrediction', {
|
||||
promptsCount,
|
||||
iterations,
|
||||
count: Math.min(promptsCount * iterations, 10000),
|
||||
})}
|
||||
</Text>
|
||||
<Text>{queueCountPredictionLabel}</Text>
|
||||
{reasons.length > 0 && (
|
||||
<>
|
||||
<Divider opacity={0.2} borderColor="base.900" />
|
||||
<UnorderedList>
|
||||
{reasons.map((reason, i) => (
|
||||
<ListItem key={`${reason}.${i}`}>
|
||||
<Text>{reason}</Text>
|
||||
<ListItem key={`${reason.content}.${i}`}>
|
||||
<span>
|
||||
{reason.prefix && (
|
||||
<Text as="span" fontWeight="semibold">
|
||||
{reason.prefix}:{' '}
|
||||
</Text>
|
||||
)}
|
||||
<Text as="span">{reason.content}</Text>
|
||||
</span>
|
||||
</ListItem>
|
||||
))}
|
||||
</UnorderedList>
|
||||
@@ -82,4 +99,4 @@ export const QueueButtonTooltip = memo(({ prepend = false }: Props) => {
|
||||
);
|
||||
});
|
||||
|
||||
QueueButtonTooltip.displayName = 'QueueButtonTooltip';
|
||||
TooltipContent.displayName = 'QueueButtonTooltipContent';
|
||||
|
||||
@@ -10,15 +10,16 @@ const QueueFrontButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const { queueFront, isLoading, isDisabled } = useQueueFront();
|
||||
return (
|
||||
<IconButton
|
||||
aria-label={t('queue.queueFront')}
|
||||
isDisabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
onClick={queueFront}
|
||||
tooltip={<QueueButtonTooltip prepend />}
|
||||
icon={<AiFillThunderbolt />}
|
||||
size="lg"
|
||||
/>
|
||||
<QueueButtonTooltip prepend>
|
||||
<IconButton
|
||||
aria-label={t('queue.queueFront')}
|
||||
isDisabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
onClick={queueFront}
|
||||
icon={<AiFillThunderbolt />}
|
||||
size="lg"
|
||||
/>
|
||||
</QueueButtonTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -63,16 +63,17 @@ const FloatingSidePanelButtons = (props: Props) => {
|
||||
sx={floatingButtonStyles}
|
||||
icon={<PiSlidersHorizontalBold size="16px" />}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={t('queue.queueBack')}
|
||||
onClick={queueBack}
|
||||
isLoading={isLoading}
|
||||
isDisabled={isDisabled}
|
||||
icon={queueButtonIcon}
|
||||
colorScheme="invokeYellow"
|
||||
tooltip={<QueueButtonTooltip />}
|
||||
sx={floatingButtonStyles}
|
||||
/>
|
||||
<QueueButtonTooltip>
|
||||
<IconButton
|
||||
aria-label={t('queue.queueBack')}
|
||||
onClick={queueBack}
|
||||
isLoading={isLoading}
|
||||
isDisabled={isDisabled}
|
||||
icon={queueButtonIcon}
|
||||
colorScheme="invokeYellow"
|
||||
sx={floatingButtonStyles}
|
||||
/>
|
||||
</QueueButtonTooltip>
|
||||
<CancelCurrentQueueItemIconButton sx={floatingButtonStyles} />
|
||||
</ButtonGroup>
|
||||
<ClearAllQueueIconButton sx={floatingButtonStyles} onOpen={disclosure.onOpen} />
|
||||
|
||||
Reference in New Issue
Block a user