mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-12 22:35:27 -05:00
feat(ui): add on first progress autoswitch mode
This commit is contained in:
@@ -42,10 +42,6 @@ export const QueueItemPreviewMini = memo(({ item, isSelected, number }: Props) =
|
||||
ctx.$selectedItemId.set(item.item_id);
|
||||
}, [ctx.$selectedItemId, item.item_id]);
|
||||
|
||||
const onDoubleClick = useCallback(() => {
|
||||
ctx.$autoSwitch.set(item.status === 'in_progress');
|
||||
}, [ctx.$autoSwitch, item.status]);
|
||||
|
||||
const onLoad = useCallback(() => {
|
||||
setImageLoaded(true);
|
||||
if (ctx.$progressData.get()[item.item_id]) {
|
||||
@@ -54,13 +50,7 @@ export const QueueItemPreviewMini = memo(({ item, isSelected, number }: Props) =
|
||||
}, [ctx.$lastLoadedItemId, ctx.$progressData, item.item_id]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
id={getQueueItemElementId(item.item_id)}
|
||||
sx={sx}
|
||||
data-selected={isSelected}
|
||||
onClick={onClick}
|
||||
onDoubleClick={onDoubleClick}
|
||||
>
|
||||
<Flex id={getQueueItemElementId(item.item_id)} sx={sx} data-selected={isSelected} onClick={onClick}>
|
||||
<QueueItemStatusLabel status={item.status} position="absolute" margin="auto" />
|
||||
{imageDTO && <DndImage imageDTO={imageDTO} onLoad={onLoad} asThumbnail />}
|
||||
{!imageLoaded && <QueueItemProgressImage itemId={item.item_id} position="absolute" />}
|
||||
|
||||
@@ -1,31 +1,13 @@
|
||||
/* eslint-disable i18next/no-literal-string */
|
||||
import { Divider, Flex, FormControl, FormLabel, Heading, Spacer, Switch } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useCanvasSessionContext } from 'features/controlLayers/components/SimpleSession/context';
|
||||
import { Flex, Heading, Spacer } from '@invoke-ai/ui-library';
|
||||
import { StartOverButton } from 'features/controlLayers/components/StartOverButton';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const StagingAreaHeader = memo(() => {
|
||||
const ctx = useCanvasSessionContext();
|
||||
const autoSwitch = useStore(ctx.$autoSwitch);
|
||||
|
||||
const onChangeAutoSwitch = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
ctx.$autoSwitch.set(e.target.checked);
|
||||
},
|
||||
[ctx.$autoSwitch]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex gap={2} w="full" alignItems="center" px={2}>
|
||||
<Heading size="sm">Review Session</Heading>
|
||||
<Spacer />
|
||||
<FormControl w="min-content" me={2}>
|
||||
<FormLabel m={0}>Auto-switch</FormLabel>
|
||||
<Switch size="sm" isChecked={autoSwitch} onChange={onChangeAutoSwitch} />
|
||||
</FormControl>
|
||||
<Divider orientation="vertical" />
|
||||
<StartOverButton />
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,10 @@ import { queueApi } from 'services/api/endpoints/queue';
|
||||
import type { S } from 'services/api/types';
|
||||
import { $socket } from 'services/events/stores';
|
||||
import { assert } from 'tsafe';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const zAutoSwitchMode = z.enum(['off', 'first_progress', 'completed']);
|
||||
export type AutoSwitchMode = z.infer<typeof zAutoSwitchMode>;
|
||||
|
||||
export type ProgressData = {
|
||||
itemId: number;
|
||||
@@ -86,7 +90,7 @@ type CanvasSessionContextValue = {
|
||||
$selectedItem: Atom<S['SessionQueueItem'] | null>;
|
||||
$selectedItemIndex: Atom<number | null>;
|
||||
$selectedItemOutputImageName: Atom<string | null>;
|
||||
$autoSwitch: WritableAtom<boolean>;
|
||||
$autoSwitch: WritableAtom<AutoSwitchMode>;
|
||||
$lastLoadedItemId: WritableAtom<number | null>;
|
||||
selectNext: () => void;
|
||||
selectPrev: () => void;
|
||||
@@ -121,7 +125,7 @@ export const CanvasSessionContextProvider = memo(
|
||||
/**
|
||||
* Whether auto-switch is enabled.
|
||||
*/
|
||||
const $autoSwitch = useState(() => atom(true))[0];
|
||||
const $autoSwitch = useState(() => atom<AutoSwitchMode>('first_progress'))[0];
|
||||
|
||||
/**
|
||||
* An internal flag used to work around race conditions with auto-switch switching to queue items before their
|
||||
@@ -184,15 +188,15 @@ export const CanvasSessionContextProvider = memo(
|
||||
* image recorded.
|
||||
*/
|
||||
const $selectedItemOutputImageName = useState(() =>
|
||||
computed([$selectedItem], (selectedItem) => {
|
||||
if (selectedItem === null) {
|
||||
computed([$selectedItemId, $progressData], (selectedItemId, progressData) => {
|
||||
if (selectedItemId === null) {
|
||||
return null;
|
||||
}
|
||||
const outputImageName = getOutputImageName(selectedItem);
|
||||
if (outputImageName === null) {
|
||||
const datum = progressData[selectedItemId];
|
||||
if (!datum) {
|
||||
return null;
|
||||
}
|
||||
return outputImageName;
|
||||
return datum.outputImageName;
|
||||
})
|
||||
)[0];
|
||||
|
||||
@@ -269,6 +273,9 @@ export const CanvasSessionContextProvider = memo(
|
||||
return;
|
||||
}
|
||||
setProgress($progressData, data);
|
||||
if ($autoSwitch.get() === 'first_progress') {
|
||||
$selectedItemId.set(data.item_id);
|
||||
}
|
||||
};
|
||||
|
||||
socket.on('invocation_progress', onProgress);
|
||||
@@ -391,7 +398,7 @@ export const CanvasSessionContextProvider = memo(
|
||||
if (lastLoadedItemId === null) {
|
||||
return;
|
||||
}
|
||||
if ($autoSwitch.get()) {
|
||||
if ($autoSwitch.get() === 'completed') {
|
||||
$selectedItemId.set(lastLoadedItemId);
|
||||
}
|
||||
$lastLoadedItemId.set(null);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ButtonGroup } from '@invoke-ai/ui-library';
|
||||
import { SimpleStagingAreaToolbarMenu } from 'features/controlLayers/components/StagingArea/SimpleStagingAreaToolbarMenu';
|
||||
import { StagingAreaToolbarDiscardAllButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardAllButton';
|
||||
import { StagingAreaToolbarDiscardSelectedButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardSelectedButton';
|
||||
import { StagingAreaToolbarImageCountButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarImageCountButton';
|
||||
@@ -16,6 +17,7 @@ export const SimpleStagingAreaToolbar = memo(() => {
|
||||
</ButtonGroup>
|
||||
<ButtonGroup borderRadius="base" shadow="dark-lg">
|
||||
<StagingAreaToolbarDiscardSelectedButton />
|
||||
<SimpleStagingAreaToolbarMenu />
|
||||
<StagingAreaToolbarDiscardAllButton />
|
||||
</ButtonGroup>
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library';
|
||||
import { StagingAreaToolbarMenuAutoSwitch } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarMenuAutoSwitch';
|
||||
import { memo } from 'react';
|
||||
import { PiDotsThreeBold } from 'react-icons/pi';
|
||||
|
||||
export const SimpleStagingAreaToolbarMenu = memo(() => {
|
||||
return (
|
||||
<Menu>
|
||||
<MenuButton as={IconButton} icon={<PiDotsThreeBold />} colorScheme="invokeBlue" />
|
||||
<MenuList>
|
||||
<StagingAreaToolbarMenuAutoSwitch />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
});
|
||||
|
||||
SimpleStagingAreaToolbarMenu.displayName = 'SimpleStagingAreaToolbarMenu';
|
||||
@@ -6,9 +6,9 @@ import { StagingAreaToolbarAcceptButton } from 'features/controlLayers/component
|
||||
import { StagingAreaToolbarDiscardAllButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardAllButton';
|
||||
import { StagingAreaToolbarDiscardSelectedButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardSelectedButton';
|
||||
import { StagingAreaToolbarImageCountButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarImageCountButton';
|
||||
import { StagingAreaToolbarMenu } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarMenu';
|
||||
import { StagingAreaToolbarNextButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarNextButton';
|
||||
import { StagingAreaToolbarPrevButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarPrevButton';
|
||||
import { StagingAreaToolbarSaveAsMenu } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarSaveAsMenu';
|
||||
import { StagingAreaToolbarSaveSelectedToGalleryButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarSaveSelectedToGalleryButton';
|
||||
import { StagingAreaToolbarToggleShowResultsButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarToggleShowResultsButton';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
@@ -42,7 +42,7 @@ export const StagingAreaToolbar = memo(() => {
|
||||
<StagingAreaToolbarAcceptButton />
|
||||
<StagingAreaToolbarToggleShowResultsButton />
|
||||
<StagingAreaToolbarSaveSelectedToGalleryButton />
|
||||
<StagingAreaToolbarSaveAsMenu />
|
||||
<StagingAreaToolbarMenu />
|
||||
<StagingAreaToolbarDiscardSelectedButton isDisabled={!shouldShowStagedImage} />
|
||||
<StagingAreaToolbarDiscardAllButton isDisabled={!shouldShowStagedImage} />
|
||||
</ButtonGroup>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { IconButton, Menu, MenuButton, MenuDivider, MenuList } from '@invoke-ai/ui-library';
|
||||
import { StagingAreaToolbarMenuAutoSwitch } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarMenuAutoSwitch';
|
||||
import { StagingAreaToolbarNewLayerFromImageMenuItems } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarMenuNewLayerFromImage';
|
||||
import { memo } from 'react';
|
||||
import { PiDotsThreeBold } from 'react-icons/pi';
|
||||
|
||||
export const StagingAreaToolbarMenu = memo(() => {
|
||||
return (
|
||||
<Menu>
|
||||
<MenuButton as={IconButton} icon={<PiDotsThreeBold />} colorScheme="invokeBlue" />
|
||||
<MenuList>
|
||||
<StagingAreaToolbarMenuAutoSwitch />
|
||||
<MenuDivider />
|
||||
<StagingAreaToolbarNewLayerFromImageMenuItems />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
});
|
||||
|
||||
StagingAreaToolbarMenu.displayName = 'StagingAreaToolbarMenu';
|
||||
@@ -0,0 +1,34 @@
|
||||
/* eslint-disable i18next/no-literal-string */
|
||||
import { MenuItemOption, MenuOptionGroup } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useCanvasSessionContext, zAutoSwitchMode } from 'features/controlLayers/components/SimpleSession/context';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
export const StagingAreaToolbarMenuAutoSwitch = memo(() => {
|
||||
const ctx = useCanvasSessionContext();
|
||||
const autoSwitch = useStore(ctx.$autoSwitch);
|
||||
|
||||
const onChange = useCallback(
|
||||
(val: string | string[]) => {
|
||||
const newAutoSwitch = zAutoSwitchMode.parse(val);
|
||||
ctx.$autoSwitch.set(newAutoSwitch);
|
||||
},
|
||||
[ctx.$autoSwitch]
|
||||
);
|
||||
|
||||
return (
|
||||
<MenuOptionGroup value={autoSwitch} onChange={onChange} title="Auto Switch" type="radio">
|
||||
<MenuItemOption value="off" closeOnSelect={false}>
|
||||
Off
|
||||
</MenuItemOption>
|
||||
<MenuItemOption value="first_progress" closeOnSelect={false}>
|
||||
First Progress
|
||||
</MenuItemOption>
|
||||
<MenuItemOption value="completed" closeOnSelect={false}>
|
||||
Completed
|
||||
</MenuItemOption>
|
||||
</MenuOptionGroup>
|
||||
);
|
||||
});
|
||||
|
||||
StagingAreaToolbarMenuAutoSwitch.displayName = 'StagingAreaToolbarMenuAutoSwitch';
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IconButton, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||
import { MenuGroup, MenuItem } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppStore } from 'app/store/nanostores/store';
|
||||
import { NewLayerIcon } from 'features/controlLayers/components/common/icons';
|
||||
@@ -8,12 +8,11 @@ import { createNewCanvasEntityFromImage } from 'features/imageActions/actions';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiDotsThreeBold } from 'react-icons/pi';
|
||||
import { copyImage } from 'services/api/endpoints/images';
|
||||
|
||||
const uploadImageArg = { image_category: 'general', is_intermediate: true, silent: true } as const;
|
||||
|
||||
export const StagingAreaToolbarSaveAsMenu = memo(() => {
|
||||
export const StagingAreaToolbarNewLayerFromImageMenuItems = memo(() => {
|
||||
const canvasManager = useCanvasManager();
|
||||
const { t } = useTranslation();
|
||||
const ctx = useCanvasSessionContext();
|
||||
@@ -97,47 +96,37 @@ export const StagingAreaToolbarSaveAsMenu = memo(() => {
|
||||
}, [imageName, store, toastSentToCanvas]);
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label={t('controlLayers.newLayerFromImage')}
|
||||
tooltip={t('controlLayers.newLayerFromImage')}
|
||||
icon={<PiDotsThreeBold />}
|
||||
colorScheme="invokeBlue"
|
||||
<MenuGroup title="New Layer From Image">
|
||||
<MenuItem
|
||||
icon={<NewLayerIcon />}
|
||||
onClickCapture={onClickNewInpaintMaskFromImage}
|
||||
isDisabled={!imageName || !shouldShowStagedImage}
|
||||
/>
|
||||
<MenuList>
|
||||
<MenuItem
|
||||
icon={<NewLayerIcon />}
|
||||
onClickCapture={onClickNewInpaintMaskFromImage}
|
||||
isDisabled={!imageName || !shouldShowStagedImage}
|
||||
>
|
||||
{t('controlLayers.inpaintMask')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<NewLayerIcon />}
|
||||
onClickCapture={onClickNewRegionalGuidanceFromImage}
|
||||
isDisabled={!imageName || !shouldShowStagedImage}
|
||||
>
|
||||
{t('controlLayers.regionalGuidance')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<NewLayerIcon />}
|
||||
onClickCapture={onClickNewControlLayerFromImage}
|
||||
isDisabled={!imageName || !shouldShowStagedImage}
|
||||
>
|
||||
{t('controlLayers.controlLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<NewLayerIcon />}
|
||||
onClickCapture={onClickNewRasterLayerFromImage}
|
||||
isDisabled={!imageName || !shouldShowStagedImage}
|
||||
>
|
||||
{t('controlLayers.rasterLayer')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
>
|
||||
{t('controlLayers.inpaintMask')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<NewLayerIcon />}
|
||||
onClickCapture={onClickNewRegionalGuidanceFromImage}
|
||||
isDisabled={!imageName || !shouldShowStagedImage}
|
||||
>
|
||||
{t('controlLayers.regionalGuidance')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<NewLayerIcon />}
|
||||
onClickCapture={onClickNewControlLayerFromImage}
|
||||
isDisabled={!imageName || !shouldShowStagedImage}
|
||||
>
|
||||
{t('controlLayers.controlLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<NewLayerIcon />}
|
||||
onClickCapture={onClickNewRasterLayerFromImage}
|
||||
isDisabled={!imageName || !shouldShowStagedImage}
|
||||
>
|
||||
{t('controlLayers.rasterLayer')}
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
);
|
||||
});
|
||||
|
||||
StagingAreaToolbarSaveAsMenu.displayName = 'StagingAreaToolbarSaveAsMenu';
|
||||
StagingAreaToolbarNewLayerFromImageMenuItems.displayName = 'StagingAreaToolbarNewLayerFromImageMenuItems';
|
||||
Reference in New Issue
Block a user