Skip to content

Commit 17027c4

Browse files
maryhippMary Hipppsychedelicious
authored
Maryhipp/chatgpt UI (#7969)
* add GPTimage1 as allowed base model * fix for non-disabled inpaint layers * lots of boilerplate for adding gpt-image base model and disabling things along with imagen * handle gpt-image dimensions * build graph for gpt-image * lint * feat(ui): make chatgpt model naming consistent * feat(ui): graph builder naming * feat(ui): disable img2img for imagen3 * feat(ui): more naming * feat(ui): support presigned url prefetch * feat(ui): disable neg prompt for chatgpt * docs(ui): update docstring * feat(ui): fix graph building issues for chatgpt * fix(ui): node ids for chatgpt/imagen * chore(ui): typegen --------- Co-authored-by: Mary Hipp <maryhipp@Marys-MacBook-Air.local> Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
1 parent 13d44f4 commit 17027c4

File tree

31 files changed

+282
-443
lines changed

31 files changed

+282
-443
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1322,7 +1322,7 @@
13221322
"unableToCopyDesc": "Your browser does not support clipboard access. Firefox users may be able to fix this by following ",
13231323
"unableToCopyDesc_theseSteps": "these steps",
13241324
"fluxFillIncompatibleWithT2IAndI2I": "FLUX Fill is not compatible with Text to Image or Image to Image. Use other FLUX models for these tasks.",
1325-
"image3IncompatibleWithInpaintAndOutpaint": "Imagen3 does not support Inpainting or Outpainting. Use other models for these tasks.",
1325+
"imagen3IncompatibleGenerationMode": "Imagen3 only supports Text to Image. Use other models for Image to Image, Inpainting and Outpainting tasks.",
13261326
"problemUnpublishingWorkflow": "Problem Unpublishing Workflow",
13271327
"problemUnpublishingWorkflowDescription": "There was a problem unpublishing the workflow. Please try again.",
13281328
"workflowUnpublished": "Workflow Unpublished"

invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { withResult, withResultAsync } from 'common/util/result';
66
import { parseify } from 'common/util/serialize';
77
import { $canvasManager } from 'features/controlLayers/store/ephemeral';
88
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
9+
import { buildChatGPT4oGraph } from 'features/nodes/util/graph/generation/buildChatGPT4oGraph';
910
import { buildCogView4Graph } from 'features/nodes/util/graph/generation/buildCogView4Graph';
1011
import { buildFLUXGraph } from 'features/nodes/util/graph/generation/buildFLUXGraph';
1112
import { buildImagen3Graph } from 'features/nodes/util/graph/generation/buildImagen3Graph';
@@ -51,6 +52,8 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
5152
return await buildCogView4Graph(state, manager);
5253
case 'imagen3':
5354
return await buildImagen3Graph(state, manager);
55+
case 'chatgpt-4o':
56+
return await buildChatGPT4oGraph(state, manager);
5457
default:
5558
assert(false, `No graph builders for base ${base}`);
5659
}
@@ -76,15 +79,15 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
7679
const destination = state.canvasSettings.sendToCanvas ? 'canvas' : 'gallery';
7780

7881
const prepareBatchResult = withResult(() =>
79-
prepareLinearUIBatch(
82+
prepareLinearUIBatch({
8083
state,
8184
g,
8285
prepend,
8386
seedFieldIdentifier,
8487
positivePromptFieldIdentifier,
85-
'canvas',
86-
destination
87-
)
88+
origin: 'canvas',
89+
destination,
90+
})
8891
);
8992

9093
if (prepareBatchResult.isErr()) {

invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedUpscale.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ export const addEnqueueRequestedUpscale = (startAppListening: AppStartListening)
2020

2121
const { g, seedFieldIdentifier, positivePromptFieldIdentifier } = await buildMultidiffusionUpscaleGraph(state);
2222

23-
const batchConfig = prepareLinearUIBatch(
23+
const batchConfig = prepareLinearUIBatch({
2424
state,
2525
g,
2626
prepend,
2727
seedFieldIdentifier,
2828
positivePromptFieldIdentifier,
29-
'upscaling',
30-
'gallery'
31-
);
29+
origin: 'upscaling',
30+
destination: 'gallery',
31+
});
3232

3333
const req = dispatch(queueApi.endpoints.enqueueBatch.initiate(batchConfig, enqueueMutationFixedCacheKeyOptions));
3434
try {

invokeai/frontend/web/src/features/controlLayers/components/CanvasAddEntityButtons.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const CanvasAddEntityButtons = memo(() => {
2424
const isReferenceImageEnabled = useIsEntityTypeEnabled('reference_image');
2525
const isRegionalGuidanceEnabled = useIsEntityTypeEnabled('regional_guidance');
2626
const isControlLayerEnabled = useIsEntityTypeEnabled('control_layer');
27+
const isInpaintLayerEnabled = useIsEntityTypeEnabled('inpaint_mask');
2728

2829
return (
2930
<Flex w="full" h="full" justifyContent="center" gap={4}>
@@ -52,6 +53,7 @@ export const CanvasAddEntityButtons = memo(() => {
5253
justifyContent="flex-start"
5354
leftIcon={<PiPlusBold />}
5455
onClick={addInpaintMask}
56+
isDisabled={!isInpaintLayerEnabled}
5557
>
5658
{t('controlLayers.inpaintMask')}
5759
</Button>

invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/EntityListGlobalActionBarAddLayerMenu.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const EntityListGlobalActionBarAddLayerMenu = memo(() => {
2525
const isReferenceImageEnabled = useIsEntityTypeEnabled('reference_image');
2626
const isRegionalGuidanceEnabled = useIsEntityTypeEnabled('regional_guidance');
2727
const isControlLayerEnabled = useIsEntityTypeEnabled('control_layer');
28+
const isInpaintLayerEnabled = useIsEntityTypeEnabled('inpaint_mask');
2829

2930
return (
3031
<Menu>
@@ -46,7 +47,7 @@ export const EntityListGlobalActionBarAddLayerMenu = memo(() => {
4647
</MenuItem>
4748
</MenuGroup>
4849
<MenuGroup title={t('controlLayers.regional')}>
49-
<MenuItem icon={<PiPlusBold />} onClick={addInpaintMask}>
50+
<MenuItem icon={<PiPlusBold />} onClick={addInpaintMask} isDisabled={!isInpaintLayerEnabled}>
5051
{t('controlLayers.inpaintMask')}
5152
</MenuItem>
5253
<MenuItem icon={<PiPlusBold />} onClick={addRegionalGuidance} isDisabled={!isRegionalGuidanceEnabled}>
Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { useAppSelector } from 'app/store/storeHooks';
2-
import { selectIsCogView4, selectIsImagen3, selectIsSD3 } from 'features/controlLayers/store/paramsSlice';
2+
import {
3+
selectIsChatGTP4o,
4+
selectIsCogView4,
5+
selectIsImagen3,
6+
selectIsSD3,
7+
} from 'features/controlLayers/store/paramsSlice';
38
import type { CanvasEntityType } from 'features/controlLayers/store/types';
49
import { useMemo } from 'react';
510
import type { Equals } from 'tsafe';
@@ -9,23 +14,24 @@ export const useIsEntityTypeEnabled = (entityType: CanvasEntityType) => {
914
const isSD3 = useAppSelector(selectIsSD3);
1015
const isCogView4 = useAppSelector(selectIsCogView4);
1116
const isImagen3 = useAppSelector(selectIsImagen3);
17+
const isChatGPT4o = useAppSelector(selectIsChatGTP4o);
1218

1319
const isEntityTypeEnabled = useMemo<boolean>(() => {
1420
switch (entityType) {
1521
case 'reference_image':
16-
return !isSD3 && !isCogView4 && !isImagen3;
22+
return !isSD3 && !isCogView4 && !isImagen3 && !isChatGPT4o;
1723
case 'regional_guidance':
18-
return !isSD3 && !isCogView4 && !isImagen3;
24+
return !isSD3 && !isCogView4 && !isImagen3 && !isChatGPT4o;
1925
case 'control_layer':
20-
return !isSD3 && !isCogView4 && !isImagen3;
26+
return !isSD3 && !isCogView4 && !isImagen3 && !isChatGPT4o;
2127
case 'inpaint_mask':
22-
return !isImagen3;
28+
return !isImagen3 && !isChatGPT4o;
2329
case 'raster_layer':
24-
return !isImagen3;
30+
return !isImagen3 && !isChatGPT4o;
2531
default:
2632
assert<Equals<typeof entityType, never>>(false);
2733
}
28-
}, [entityType, isSD3, isCogView4, isImagen3]);
34+
}, [entityType, isSD3, isCogView4, isImagen3, isChatGPT4o]);
2935

3036
return isEntityTypeEnabled;
3137
};

invokeai/frontend/web/src/features/controlLayers/konva/CanvasObject/CanvasObjectImage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export class CanvasObjectImage extends CanvasModuleBase {
112112
return;
113113
}
114114

115-
const imageElementResult = await withResultAsync(() => loadImage(imageDTO.image_url));
115+
const imageElementResult = await withResultAsync(() => loadImage(imageDTO.image_url, true));
116116
if (imageElementResult.isErr()) {
117117
// Image loading failed (e.g. the URL to the "physical" image is invalid)
118118
this.onFailedToLoadImage(t('controlLayers.unableToLoadImage', 'Unable to load image'));

invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,8 @@ export class CanvasBboxToolModule extends CanvasModuleBase {
235235
if (tool !== 'bbox') {
236236
return NO_ANCHORS;
237237
}
238-
if (model?.base === 'imagen3') {
239-
// The bbox is not resizable in imagen3 mode
238+
if (model?.base === 'imagen3' || model?.base === 'chatgpt-4o') {
239+
// The bbox is not resizable in these modes
240240
return NO_ANCHORS;
241241
}
242242
return ALL_ANCHORS;

invokeai/frontend/web/src/features/controlLayers/konva/util.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,15 +476,24 @@ export function getImageDataTransparency(imageData: ImageData): Transparency {
476476
/**
477477
* Loads an image from a URL and returns a promise that resolves with the loaded image element.
478478
* @param src The image source URL
479+
* @param fetchUrlFirst Whether to fetch the image's URL first, assuming the provided `src` will redirect to a different URL. This addresses an issue where CORS headers are dropped during a redirect.
479480
* @returns A promise that resolves with the loaded image element
480481
*/
481-
export function loadImage(src: string): Promise<HTMLImageElement> {
482+
export async function loadImage(src: string, fetchUrlFirst?: boolean): Promise<HTMLImageElement> {
483+
const authToken = $authToken.get();
484+
let url = src;
485+
if (authToken && fetchUrlFirst) {
486+
const response = await fetch(`${src}?url_only=true`, { credentials: 'include' });
487+
const data = await response.json();
488+
url = data.url;
489+
}
490+
482491
return new Promise((resolve, reject) => {
483492
const imageElement = new Image();
484493
imageElement.onload = () => resolve(imageElement);
485494
imageElement.onerror = (error) => reject(error);
486495
imageElement.crossOrigin = $authToken.get() ? 'use-credentials' : 'anonymous';
487-
imageElement.src = src;
496+
imageElement.src = url;
488497
});
489498
}
490499

invokeai/frontend/web/src/features/controlLayers/store/canvasSlice.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ import type {
6767
IPMethodV2,
6868
T2IAdapterConfig,
6969
} from './types';
70-
import { getEntityIdentifier, isImagen3AspectRatioID, isRenderableEntity } from './types';
70+
import { getEntityIdentifier, isChatGPT4oAspectRatioID, isImagen3AspectRatioID, isRenderableEntity } from './types';
7171
import {
7272
converters,
7373
getControlLayerState,
@@ -1232,6 +1232,20 @@ export const canvasSlice = createSlice({
12321232
}
12331233
state.bbox.aspectRatio.value = state.bbox.rect.width / state.bbox.rect.height;
12341234
state.bbox.aspectRatio.isLocked = true;
1235+
} else if (state.bbox.modelBase === 'chatgpt-4o' && isChatGPT4oAspectRatioID(id)) {
1236+
// gpt-image has specific output sizes that are not exactly the same as the aspect ratio. Need special handling.
1237+
if (id === '3:2') {
1238+
state.bbox.rect.width = 1536;
1239+
state.bbox.rect.height = 1024;
1240+
} else if (id === '1:1') {
1241+
state.bbox.rect.width = 1024;
1242+
state.bbox.rect.height = 1024;
1243+
} else if (id === '2:3') {
1244+
state.bbox.rect.width = 1024;
1245+
state.bbox.rect.height = 1536;
1246+
}
1247+
state.bbox.aspectRatio.value = state.bbox.rect.width / state.bbox.rect.height;
1248+
state.bbox.aspectRatio.isLocked = true;
12351249
} else {
12361250
state.bbox.aspectRatio.isLocked = true;
12371251
state.bbox.aspectRatio.value = ASPECT_RATIO_MAP[id].ratio;
@@ -1704,7 +1718,7 @@ export const canvasSlice = createSlice({
17041718
const base = model?.base;
17051719
if (isMainModelBase(base) && state.bbox.modelBase !== base) {
17061720
state.bbox.modelBase = base;
1707-
if (base === 'imagen3') {
1721+
if (base === 'imagen3' || base === 'chatgpt-4o') {
17081722
state.bbox.aspectRatio.isLocked = true;
17091723
state.bbox.aspectRatio.value = 1;
17101724
state.bbox.aspectRatio.id = '1:1';
@@ -1843,7 +1857,7 @@ export const canvasPersistConfig: PersistConfig<CanvasState> = {
18431857
};
18441858

18451859
const syncScaledSize = (state: CanvasState) => {
1846-
if (state.bbox.modelBase === 'imagen3') {
1860+
if (state.bbox.modelBase === 'imagen3' || state.bbox.modelBase === 'chatgpt-4o') {
18471861
// Imagen3 has fixed sizes. Scaled bbox is not supported.
18481862
return;
18491863
}

0 commit comments

Comments
 (0)