feat(ui): rework simple session initial state

This commit is contained in:
psychedelicious
2025-06-16 18:59:01 +10:00
parent c31cb0b106
commit 161624c722
5 changed files with 109 additions and 56 deletions

View File

@@ -1,12 +1,10 @@
/* eslint-disable i18next/no-literal-string */
import { Button, Divider, Flex, Grid, Heading, Text } from '@invoke-ai/ui-library';
import { Alert, Button, Divider, Flex, Grid, Heading, Text } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { InitialStateAddAStyleReference } from 'features/controlLayers/components/SimpleSession/InitialStateAddAStyleReference';
import { InitialStateEditImageCard } from 'features/controlLayers/components/SimpleSession/InitialStateEditImageCard';
import { InitialStateGenerateFromText } from 'features/controlLayers/components/SimpleSession/InitialStateGenerateFromText';
import { InitialStateUseALayoutImageCard } from 'features/controlLayers/components/SimpleSession/InitialStateUseALayoutImageCard';
import { toast } from 'features/toast/toast';
import { InitialStateMainModelPicker } from 'features/controlLayers/components/SimpleSession/InitialStateMainModelPicker';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { memo, useCallback } from 'react';
@@ -14,14 +12,6 @@ export const InitialState = memo(() => {
const dispatch = useAppDispatch();
const newCanvasSession = useCallback(() => {
dispatch(setActiveTab('canvas'));
toast({
title: 'Switched to Canvas',
description: 'You are in advanced mode yadda yadda.',
status: 'info',
position: 'top',
// isClosable: false,
duration: 5000,
});
}, [dispatch]);
return (
@@ -31,24 +21,30 @@ export const InitialState = memo(() => {
</Flex>
<Divider />
<Flex flexDir="column" h="full" justifyContent="center" mx={16}>
<Heading mb={4}>Choose a starting method.</Heading>
<Text fontSize="md" fontStyle="italic" mb={6}>
Drag an image onto a card or click the upload icon.
</Text>
<Grid gridTemplateColumns="1fr 1fr" gridTemplateRows="1fr 1fr" gap={4}>
<Heading mb={4}>Get started with Invoke.</Heading>
<Flex flexDir="column" gap={4}>
<Grid gridTemplateColumns="1fr 1fr" gap={4}>
<InitialStateMainModelPicker />
<Flex flexDir="column" gap={2}>
<Text>
Want to learn what prompts work best for each model?{' '}
<Button as="a" variant="link" href="#" size="sm">
Check our our Model Guide.
</Button>
</Text>
</Flex>
</Grid>
<InitialStateGenerateFromText />
<InitialStateAddAStyleReference />
<InitialStateUseALayoutImageCard />
<InitialStateEditImageCard />
</Grid>
<Text fontSize="md" color="base.300" alignSelf="center" mt={6}>
or{' '}
<Button variant="link" onClick={newCanvasSession}>
start from a blank canvas.
</Button>
</Text>
<Alert status="info" borderRadius="base" flexDir="column" gap={2} overflow="unset">
<Text fontSize="md" fontWeight="semibold">
Looking to get more control, edit, and iterate on your images?
</Text>
<Button variant="link" onClick={newCanvasSession}>
Navigate to Canvas for more capabilities.
</Button>
</Alert>
</Flex>
</Flex>
</Flex>
);

View File

@@ -4,34 +4,43 @@ import { Flex, Heading, Icon, Text } from '@invoke-ai/ui-library';
import { useAppStore } from 'app/store/nanostores/store';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { InitialStateButtonGridItem } from 'features/controlLayers/components/SimpleSession/InitialStateButtonGridItem';
import { newCanvasFromImageDndTarget } from 'features/dnd/dnd';
import { getDefaultRefImageConfig } from 'features/controlLayers/hooks/addLayerHooks';
import { refImageAdded } from 'features/controlLayers/store/refImagesSlice';
import { imageDTOToImageWithDims } from 'features/controlLayers/store/util';
import { addGlobalReferenceImageDndTarget, newCanvasFromImageDndTarget } from 'features/dnd/dnd';
import { DndDropTarget } from 'features/dnd/DndDropTarget';
import { newCanvasFromImage } from 'features/imageActions/actions';
import { memo, useCallback } from 'react';
import { memo, useMemo } from 'react';
import { PiUploadBold, PiUserCircleGearBold } from 'react-icons/pi';
import type { ImageDTO } from 'services/api/types';
const NEW_CANVAS_OPTIONS = { type: 'reference_image' } as const;
const dndTargetData = newCanvasFromImageDndTarget.getData(NEW_CANVAS_OPTIONS);
const dndTargetData = addGlobalReferenceImageDndTarget.getData();
export const InitialStateAddAStyleReference = memo(() => {
const { getState, dispatch } = useAppStore();
const { dispatch, getState } = useAppStore();
const onUpload = useCallback(
(imageDTO: ImageDTO) => {
newCanvasFromImage({ imageDTO, getState, dispatch, ...NEW_CANVAS_OPTIONS });
},
const uploadOptions = useMemo(
() =>
({
onUpload: (imageDTO: ImageDTO) => {
const config = getDefaultRefImageConfig(getState);
config.image = imageDTOToImageWithDims(imageDTO);
dispatch(refImageAdded({ overrides: { config } }));
},
allowMultiple: false,
}) as const,
[dispatch, getState]
);
const uploadApi = useImageUploadButton({ allowMultiple: false, onUpload });
const uploadApi = useImageUploadButton(uploadOptions);
return (
<InitialStateButtonGridItem {...uploadApi.getUploadButtonProps()}>
<InitialStateButtonGridItem {...uploadApi.getUploadButtonProps()} position="relative" gap={8}>
<Icon as={PiUserCircleGearBold} boxSize={8} color="base.500" />
<Heading size="sm">Add a Style Reference</Heading>
<Text color="base.300">Add an image to transfer its look.</Text>
<Flex w="full" justifyContent="flex-end" p={2}>
<Flex flexDir="column" alignItems="flex-start" gap={2}>
<Heading size="sm">Add a Style Reference</Heading>
<Text color="base.300">Add an image to transfer its look.</Text>
</Flex>
<Flex position="absolute" right={3} bottom={3}>
<PiUploadBold />
<input {...uploadApi.getUploadInputProps()} />
</Flex>

View File

@@ -1,21 +1,19 @@
import type { GridItemProps } from '@invoke-ai/ui-library';
import { Button, forwardRef, GridItem } from '@invoke-ai/ui-library';
import type { ButtonProps } from '@invoke-ai/ui-library';
import { Button, forwardRef } from '@invoke-ai/ui-library';
import { memo } from 'react';
export const InitialStateButtonGridItem = memo(
forwardRef(({ children, ...rest }: GridItemProps, ref) => {
forwardRef(({ children, ...rest }: ButtonProps, ref) => {
return (
<GridItem
<Button
ref={ref}
as={Button}
variant="outline"
display="flex"
position="relative"
flexDir="column"
alignItems="center"
borderWidth={1}
borderRadius="base"
p={2}
p={4}
pt={6}
gap={2}
w="full"
@@ -23,7 +21,7 @@ export const InitialStateButtonGridItem = memo(
{...rest}
>
{children}
</GridItem>
</Button>
);
})
);

View File

@@ -15,11 +15,13 @@ const focusOnPrompt = () => {
export const InitialStateGenerateFromText = memo(() => {
return (
<InitialStateButtonGridItem onClick={focusOnPrompt}>
<InitialStateButtonGridItem onClick={focusOnPrompt} position="relative" gap={8}>
<Icon as={PiTextAaBold} boxSize={8} color="base.500" />
<Heading size="sm">Generate from Text</Heading>
<Text color="base.300">Enter a prompt and Invoke.</Text>
<Flex w="full" justifyContent="flex-end" p={2}>
<Flex flexDir="column" alignItems="flex-start" gap={2}>
<Heading size="sm">Generate from Text</Heading>
<Text color="base.300">Enter a prompt and Invoke.</Text>
</Flex>
<Flex position="absolute" right={3} bottom={3}>
<PiCursorTextBold />
</Flex>
</InitialStateButtonGridItem>

View File

@@ -0,0 +1,48 @@
/* eslint-disable i18next/no-literal-string */
import { Flex, FormControl, FormLabel, Icon } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { ModelPicker } from 'features/parameters/components/ModelPicker';
import { modelSelected } from 'features/parameters/store/actions';
import { memo, useCallback, useMemo } from 'react';
import { MdMoneyOff } from 'react-icons/md';
import { useMainModels } from 'services/api/hooks/modelsByType';
import { useSelectedModelConfig } from 'services/api/hooks/useSelectedModelConfig';
import { type AnyModelConfig, isCheckpointMainModelConfig } from 'services/api/types';
export const InitialStateMainModelPicker = memo(() => {
const dispatch = useAppDispatch();
const [modelConfigs] = useMainModels();
const selectedModelConfig = useSelectedModelConfig();
const onChange = useCallback(
(modelConfig: AnyModelConfig) => {
dispatch(modelSelected(modelConfig));
},
[dispatch]
);
const isFluxDevSelected = useMemo(
() =>
selectedModelConfig &&
isCheckpointMainModelConfig(selectedModelConfig) &&
selectedModelConfig.config_path === 'flux-dev',
[selectedModelConfig]
);
return (
<FormControl orientation="vertical" alignItems="unset">
<InformationalPopover feature="paramModel">
<FormLabel>Select your Model</FormLabel>
</InformationalPopover>
{isFluxDevSelected && (
<InformationalPopover feature="fluxDevLicense" hideDisable={true}>
<Flex justifyContent="flex-start">
<Icon as={MdMoneyOff} />
</Flex>
</InformationalPopover>
)}
<ModelPicker modelConfigs={modelConfigs} selectedModelConfig={selectedModelConfig} onChange={onChange} grouped />
</FormControl>
);
});
InitialStateMainModelPicker.displayName = 'InitialStateMainModelPicker';