Skip to content

Commit b7af45d

Browse files
authored
Merge pull request #6 from invoke-ai/feat/generate-reference-images
feat(generate): reference images, ui tweaks
2 parents da62d88 + 1a8d7d9 commit b7af45d

32 files changed

Lines changed: 3500 additions & 137 deletions

invokeai/frontend/webv2/public/locales/en.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"active": "Active",
55
"add": "Add",
66
"all": "All",
7+
"apply": "Apply",
78
"archived": "Archived",
89
"asc": "Asc",
910
"assets": "Assets",
@@ -15,6 +16,7 @@
1516
"close": "Close",
1617
"configure": "Configure",
1718
"copy": "Copy",
19+
"crop": "Crop",
1820
"clipboardBlocked": "Clipboard access was blocked by the browser.",
1921
"couldNotCopy": "Could not copy",
2022
"countOfTotal": "{{count}} of {{total}}",
@@ -53,6 +55,7 @@
5355
"required": "Required",
5456
"refresh": "Refresh",
5557
"reload": "Reload",
58+
"reset": "Reset",
5659
"retry": "Retry",
5760
"saved": "Saved",
5861
"save": "Save",
@@ -673,6 +676,34 @@
673676
"promptTemplate": "Prompt Template",
674677
"promptTriggerOptions": "Prompt trigger options",
675678
"random": "Random",
679+
"activeSteps": "Active steps",
680+
"clearReferenceImages": "Remove all",
681+
"collapseReferenceImage": "Collapse reference image",
682+
"cropReferenceImage": "Crop reference image",
683+
"cropReferenceImageHelp": "Drag the crop box to move it, or drag its handles to resize. Apply to create a cropped reference image.",
684+
"disableReferenceImage": "Disable reference image",
685+
"enableReferenceImage": "Enable reference image",
686+
"expandReferenceImage": "Expand reference image",
687+
"imageInfluence": "Image influence",
688+
"mode": "Mode",
689+
"modeBoth": "Both",
690+
"modeComposition": "Composition",
691+
"modeStyle": "Style",
692+
"pullBboxIntoReferenceImage": "Pull Bbox into Reference Image",
693+
"referenceImage": "Reference Image",
694+
"referenceImageModel": "Reference image model",
695+
"referenceImages": "Reference images",
696+
"referenceImagesHelp": "Upload an image or drag images from Gallery here.",
697+
"referenceImagesUnsupported": "Reference images are not supported by the selected model.",
698+
"removeReferenceImage": "Remove reference image",
699+
"styleVariant": "Style variant",
700+
"styleVariantPrecise": "Precise",
701+
"styleVariantPreciseDesc": "Applies a precise visual style, eliminating subject influence.",
702+
"styleVariantStandard": "Standard",
703+
"styleVariantStandardDesc": "Applies visual style (colors, textures) without considering its layout. Previously called Style Only.",
704+
"styleVariantStrong": "Strong",
705+
"styleVariantStrongDesc": "Applies a strong visual style, with a slightly reduced composition influence.",
706+
"useSize": "Use size",
676707
"removeConcept": "Remove concept",
677708
"removeConceptNamed": "Remove {{name}}",
678709
"resizeNegativePrompt": "Resize negative prompt",
@@ -709,8 +740,10 @@
709740
"useModelDefaultWidth": "Use model default width",
710741
"usePrompt": "Use prompt",
711742
"usingBundledVae": "Using the VAE bundled with the model.",
743+
"upload": "Upload",
712744
"vae": "VAE",
713745
"vaePrecision": "VAE precision",
746+
"weight": "Weight",
714747
"width": "Width",
715748
"xAxis": "X axis",
716749
"yAxis": "Y axis"

invokeai/frontend/webv2/src/theme/recipes.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ const dropdownContent = {
5353

5454
const dropdownItem = {
5555
_highlighted: { bg: 'bg.subtle' },
56-
_focusVisible: { outline: '2px solid', outlineColor: 'accent.solid', outlineOffset: '-2px' },
56+
_focusVisible: {
57+
outline: '2px solid',
58+
outlineColor: 'accent.solid',
59+
outlineOffset: '-2px',
60+
},
5761
};
5862

5963
const dropdownGroupLabel = {
@@ -223,15 +227,27 @@ export const rowRecipe = defineRecipe({
223227
transition: 'background var(--wb-motion-duration-fast) ease, color var(--wb-motion-duration-fast) ease',
224228
w: 'full',
225229
_hover: { bg: 'bg.muted' },
226-
_focusVisible: { outline: '2px solid', outlineColor: 'accent.solid', outlineOffset: '-2px' },
230+
_focusVisible: {
231+
outline: '2px solid',
232+
outlineColor: 'accent.solid',
233+
outlineOffset: '-2px',
234+
},
227235
_disabled: { cursor: 'not-allowed', opacity: 0.5 },
228236
},
229237
variants: {
230238
active: {
231239
none: {},
232240
muted: { bg: 'bg.muted' },
233-
brand: { bg: 'brand.subtle', color: 'brand.fg', _hover: { bg: 'brand.subtle' } },
234-
accent: { bg: 'accent.solid', color: 'accent.contrast', _hover: { bg: 'accent.solid' } },
241+
brand: {
242+
bg: 'brand.subtle',
243+
color: 'brand.fg',
244+
_hover: { bg: 'brand.subtle' },
245+
},
246+
accent: {
247+
bg: 'accent.solid',
248+
color: 'accent.contrast',
249+
_hover: { bg: 'accent.solid' },
250+
},
235251
},
236252
},
237253
defaultVariants: { active: 'none' },
@@ -270,8 +286,7 @@ export const fieldLabelRecipe = defineRecipe({
270286
color: 'fg.muted',
271287
fontSize: '2xs',
272288
fontWeight: '600',
273-
letterSpacing: '0.02em',
274-
textTransform: 'uppercase',
289+
letterSpacing: '0.03em',
275290
},
276291
});
277292

@@ -295,7 +310,11 @@ export const themeCardRecipe = defineSlotRecipe({
295310
transition:
296311
'border-color var(--wb-motion-duration-fast) ease, background var(--wb-motion-duration-fast) ease, transform var(--wb-motion-duration-fast) ease',
297312
_hover: { borderColor: 'border.emphasized' },
298-
_focusVisible: { outline: '2px solid', outlineColor: 'accent.solid', outlineOffset: '2px' },
313+
_focusVisible: {
314+
outline: '2px solid',
315+
outlineColor: 'accent.solid',
316+
outlineOffset: '2px',
317+
},
299318
},
300319
preview: {
301320
borderColor: 'border.subtle',
@@ -306,7 +325,12 @@ export const themeCardRecipe = defineSlotRecipe({
306325
overflow: 'hidden',
307326
},
308327
swatch: { flex: '1' },
309-
body: { alignItems: 'flex-start', display: 'flex', flexDirection: 'column', gap: '0.5' },
328+
body: {
329+
alignItems: 'flex-start',
330+
display: 'flex',
331+
flexDirection: 'column',
332+
gap: '0.5',
333+
},
310334
name: { color: 'fg', fontSize: 'sm', fontWeight: '600' },
311335
description: { color: 'fg.subtle', fontSize: '2xs', lineHeight: '1.3' },
312336
indicator: {
Lines changed: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { ReactNode } from 'react';
22

3-
import { HStack, Stack, Text, useRecipe, type StackProps } from '@chakra-ui/react';
3+
import { Field as ChakraField, HStack, Stack, Text, useRecipe, type StackProps } from '@chakra-ui/react';
44
import { fieldLabelRecipe } from '@theme/recipes';
5+
import { useMemo } from 'react';
56

67
/**
78
* The shared, theme-aware uppercase field label. Backed by `fieldLabelRecipe` so
@@ -18,31 +19,106 @@ export const FieldLabel = ({ children }: { children: ReactNode }) => {
1819
);
1920
};
2021

21-
export interface FieldProps extends Omit<StackProps, 'title'> {
22+
export interface FieldProps extends Omit<StackProps, 'disabled' | 'id' | 'readOnly' | 'required' | 'title'> {
23+
id?: string;
2224
label: string;
2325
labelEnd?: ReactNode;
24-
/** Validation error, shown in place of `helpText`. Mark the control itself invalid via `aria-invalid`. */
26+
disabled?: boolean;
27+
orientation?: 'horizontal' | 'vertical';
28+
invalid?: boolean;
29+
readOnly?: boolean;
30+
required?: boolean;
31+
/** Validation error, shown in place of `helpText`; also marks the field invalid by default. */
2532
error?: string | null;
2633
helpText?: string;
2734
children: ReactNode;
2835
}
2936

3037
/** A labelled form field: an uppercase label stacked above its control, with an optional help/error line below. */
31-
export const Field = ({ children, error, helpText, label, labelEnd, ...rest }: FieldProps) => (
32-
<Stack flex="1" gap="1.5" minW="0" {...rest}>
33-
<HStack align="center" justify="space-between" minW="0">
34-
<FieldLabel>{label}</FieldLabel>
35-
{labelEnd}
36-
</HStack>
37-
{children}
38-
{error ? (
39-
<Text color="fg.error" fontSize="2xs" role="alert">
40-
{error}
41-
</Text>
42-
) : helpText ? (
43-
<Text color="fg.subtle" fontSize="2xs">
44-
{helpText}
45-
</Text>
46-
) : null}
47-
</Stack>
48-
);
38+
export const Field = ({
39+
children,
40+
disabled,
41+
error,
42+
helpText,
43+
id,
44+
invalid,
45+
label,
46+
labelEnd,
47+
orientation = 'vertical',
48+
readOnly,
49+
required,
50+
...rest
51+
}: FieldProps) => {
52+
const recipe = useRecipe({ recipe: fieldLabelRecipe });
53+
const isHorizontal = orientation === 'horizontal';
54+
const isInvalid = invalid ?? Boolean(error);
55+
const ids = useMemo(
56+
() =>
57+
id
58+
? {
59+
errorText: `${id}-error`,
60+
helperText: `${id}-help`,
61+
label: `${id}-label`,
62+
}
63+
: undefined,
64+
[id]
65+
);
66+
const labelContent = <ChakraField.Label css={recipe()}>{label}</ChakraField.Label>;
67+
const message = error ? (
68+
<ChakraField.ErrorText color="fg.error" fontSize="2xs" role="alert">
69+
{error}
70+
</ChakraField.ErrorText>
71+
) : helpText ? (
72+
<ChakraField.HelperText color="fg.subtle" fontSize="2xs">
73+
{helpText}
74+
</ChakraField.HelperText>
75+
) : null;
76+
77+
if (!isHorizontal) {
78+
return (
79+
<ChakraField.Root
80+
asChild
81+
disabled={disabled}
82+
id={id}
83+
ids={ids}
84+
invalid={isInvalid}
85+
readOnly={readOnly}
86+
required={required}
87+
unstyled
88+
>
89+
<Stack flex="1" gap="1.5" minW="0" {...rest}>
90+
<HStack align="center" justify="space-between" minW="0">
91+
{labelContent}
92+
{labelEnd}
93+
</HStack>
94+
{children}
95+
{message}
96+
</Stack>
97+
</ChakraField.Root>
98+
);
99+
}
100+
101+
return (
102+
<ChakraField.Root
103+
asChild
104+
disabled={disabled}
105+
id={id}
106+
ids={ids}
107+
invalid={isInvalid}
108+
readOnly={readOnly}
109+
required={required}
110+
unstyled
111+
>
112+
<Stack align="flex-start" direction="row" flex="1" gap="1.5" minW="0" {...rest}>
113+
<HStack align="center" flexShrink="0" justify="space-between" minH="8" minW="0">
114+
{labelContent}
115+
{labelEnd}
116+
</HStack>
117+
<Stack flex="1" gap="1.5" minW="0" w="full">
118+
{children}
119+
{message}
120+
</Stack>
121+
</Stack>
122+
</ChakraField.Root>
123+
);
124+
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import type {
2+
CollectionItem,
3+
SelectContentProps,
4+
SelectIndicatorGroupProps,
5+
SelectPositionerProps,
6+
SelectRootProps,
7+
SelectTriggerProps,
8+
SelectValueTextProps,
9+
} from '@chakra-ui/react';
10+
import type { Key, ReactNode } from 'react';
11+
12+
import { Portal, Select as ChakraSelect } from '@chakra-ui/react';
13+
14+
const getDefaultItemKey = <T extends CollectionItem>(item: T, index: number): Key => {
15+
const keyedItem = item as { id?: Key; value?: Key };
16+
17+
return keyedItem.value ?? keyedItem.id ?? index;
18+
};
19+
20+
const renderDefaultItem = <T extends CollectionItem>(item: T): ReactNode => {
21+
const labelledItem = item as { label?: ReactNode; value?: ReactNode };
22+
23+
return labelledItem.label ?? labelledItem.value;
24+
};
25+
26+
export interface SelectProps<T extends CollectionItem> extends Omit<SelectRootProps<T>, 'children'> {
27+
contentProps?: SelectContentProps;
28+
getItemKey?: (item: T, index: number) => Key;
29+
indicatorGroupProps?: SelectIndicatorGroupProps;
30+
itemIndicator?: boolean;
31+
portalled?: boolean;
32+
positionerProps?: SelectPositionerProps;
33+
renderItem?: (item: T) => ReactNode;
34+
triggerProps?: SelectTriggerProps;
35+
valueText?: ReactNode;
36+
valueTextProps?: SelectValueTextProps;
37+
}
38+
39+
/** Workbench Select: Chakra's custom Select with the standard trigger, portal, and item markup pre-wired. */
40+
export const Select = <T extends CollectionItem>({
41+
collection,
42+
contentProps,
43+
getItemKey = getDefaultItemKey,
44+
indicatorGroupProps,
45+
itemIndicator = true,
46+
portalled = true,
47+
positionerProps,
48+
renderItem = renderDefaultItem,
49+
triggerProps,
50+
valueText,
51+
valueTextProps,
52+
...rootProps
53+
}: SelectProps<T>) => (
54+
<ChakraSelect.Root collection={collection} {...rootProps}>
55+
<ChakraSelect.HiddenSelect />
56+
<ChakraSelect.Control>
57+
<ChakraSelect.Trigger {...triggerProps}>
58+
<ChakraSelect.ValueText {...valueTextProps}>{valueText}</ChakraSelect.ValueText>
59+
</ChakraSelect.Trigger>
60+
<ChakraSelect.IndicatorGroup {...indicatorGroupProps}>
61+
<ChakraSelect.Indicator />
62+
</ChakraSelect.IndicatorGroup>
63+
</ChakraSelect.Control>
64+
<Portal disabled={!portalled}>
65+
<ChakraSelect.Positioner {...positionerProps}>
66+
<ChakraSelect.Content {...contentProps}>
67+
{collection.items.map((item, index) => (
68+
<ChakraSelect.Item key={getItemKey(item, index)} item={item}>
69+
<ChakraSelect.ItemText>{renderItem(item)}</ChakraSelect.ItemText>
70+
{itemIndicator ? <ChakraSelect.ItemIndicator /> : null}
71+
</ChakraSelect.Item>
72+
))}
73+
</ChakraSelect.Content>
74+
</ChakraSelect.Positioner>
75+
</Portal>
76+
</ChakraSelect.Root>
77+
);

0 commit comments

Comments
 (0)