diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index 7b1e24457f..0460ab34fe 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -770,6 +770,7 @@
"allPrompts": "All Prompts",
"cfgScale": "CFG scale",
"cfgRescaleMultiplier": "$t(parameters.cfgRescaleMultiplier)",
+ "clipSkip": "$t(parameters.clipSkip)",
"createdBy": "Created By",
"generationMode": "Generation Mode",
"guidance": "Guidance",
@@ -1290,6 +1291,7 @@
"remixImage": "Remix Image",
"usePrompt": "Use Prompt",
"useSeed": "Use Seed",
+ "useClipSkip": "Use CLIP Skip",
"width": "Width",
"gaussianBlur": "Gaussian Blur",
"boxBlur": "Box Blur",
diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/saveCanvasHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/saveCanvasHooks.ts
index f164b1f2f1..5d3f79cf5f 100644
--- a/invokeai/frontend/web/src/features/controlLayers/hooks/saveCanvasHooks.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/hooks/saveCanvasHooks.ts
@@ -16,6 +16,7 @@ import {
rgRefImageImageChanged,
} from 'features/controlLayers/store/canvasSlice';
import {
+ selectCLIPSkip,
selectMainModelConfig,
selectNegativePrompt,
selectPositivePrompt,
@@ -81,6 +82,7 @@ const useSaveCanvas = ({ region, saveToGallery, toastOk, toastError, onSave, wit
if (withMetadata) {
metadata = selectCanvasMetadata(state);
+ metadata.clip_skip = selectCLIPSkip(state);
metadata.positive_prompt = selectPositivePrompt(state);
metadata.negative_prompt = selectNegativePrompt(state);
metadata.seed = selectSeed(state);
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts
index 2a5820ffd1..8f14bbceff 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts
@@ -484,7 +484,7 @@ export const selectCFGScale = createParamsSelector((params) => params.cfgScale);
export const selectGuidance = createParamsSelector((params) => params.guidance);
export const selectSteps = createParamsSelector((params) => params.steps);
export const selectCFGRescaleMultiplier = createParamsSelector((params) => params.cfgRescaleMultiplier);
-export const selectCLIPSKip = createParamsSelector((params) => params.clipSkip);
+export const selectCLIPSkip = createParamsSelector((params) => params.clipSkip);
export const selectCanvasCoherenceEdgeSize = createParamsSelector((params) => params.canvasCoherenceEdgeSize);
export const selectCanvasCoherenceMinDenoise = createParamsSelector((params) => params.canvasCoherenceMinDenoise);
export const selectCanvasCoherenceMode = createParamsSelector((params) => params.canvasCoherenceMode);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemMetadataRecallActionsCanvasGenerateTabs.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemMetadataRecallActionsCanvasGenerateTabs.tsx
index e02701b4e2..9f1cd668d5 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemMetadataRecallActionsCanvasGenerateTabs.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemMetadataRecallActionsCanvasGenerateTabs.tsx
@@ -2,6 +2,7 @@ import { Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
import { SubMenuButtonContent, useSubMenu } from 'common/hooks/useSubMenu';
import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { useRecallAll } from 'features/gallery/hooks/useRecallAll';
+import { useRecallCLIPSkip } from 'features/gallery/hooks/useRecallCLIPSkip';
import { useRecallDimensions } from 'features/gallery/hooks/useRecallDimensions';
import { useRecallPrompts } from 'features/gallery/hooks/useRecallPrompts';
import { useRecallRemix } from 'features/gallery/hooks/useRecallRemix';
@@ -28,6 +29,7 @@ export const ImageMenuItemMetadataRecallActionsCanvasGenerateTabs = memo(() => {
const recallPrompts = useRecallPrompts(imageDTO);
const recallSeed = useRecallSeed(imageDTO);
const recallDimensions = useRecallDimensions(imageDTO);
+ const recallCLIPSkip = useRecallCLIPSkip(imageDTO);
return (
}>
@@ -55,6 +57,9 @@ export const ImageMenuItemMetadataRecallActionsCanvasGenerateTabs = memo(() => {
} onClick={recallDimensions.recall} isDisabled={!recallDimensions.isEnabled}>
{t('parameters.useSize')}
+ } onClick={recallCLIPSkip.recall} isDisabled={!recallCLIPSkip.isEnabled}>
+ {t('parameters.useClipSkip')}
+
diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useRecallCLIPSkip.ts b/invokeai/frontend/web/src/features/gallery/hooks/useRecallCLIPSkip.ts
new file mode 100644
index 0000000000..478703c461
--- /dev/null
+++ b/invokeai/frontend/web/src/features/gallery/hooks/useRecallCLIPSkip.ts
@@ -0,0 +1,65 @@
+import { useAppSelector, useAppStore } from 'app/store/storeHooks';
+import { MetadataHandlers, MetadataUtils } from 'features/metadata/parsing';
+import { selectActiveTab } from 'features/ui/store/uiSelectors';
+import type { TabName } from 'features/ui/store/uiTypes';
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata';
+import type { ImageDTO } from 'services/api/types';
+
+const ALLOWED_TABS: TabName[] = ['canvas', 'generate', 'upscaling'];
+
+export const useRecallCLIPSkip = (imageDTO: ImageDTO) => {
+ const store = useAppStore();
+ const tab = useAppSelector(selectActiveTab);
+ const [hasCLIPSkip, setCLIPSkip] = useState(false);
+
+ const { metadata, isLoading } = useDebouncedMetadata(imageDTO.image_name);
+
+ useEffect(() => {
+ const parse = async () => {
+ try {
+ await MetadataHandlers.CLIPSkip.parse(metadata, store);
+ setCLIPSkip(true);
+ } catch {
+ setCLIPSkip(false);
+ }
+ };
+
+ parse();
+ }, [metadata, store]);
+
+ const isEnabled = useMemo(() => {
+ if (isLoading) {
+ return false;
+ }
+
+ if (!ALLOWED_TABS.includes(tab)) {
+ return false;
+ }
+
+ if (!metadata) {
+ return false;
+ }
+
+ if (!hasCLIPSkip) {
+ return false;
+ }
+
+ return true;
+ }, [hasCLIPSkip, isLoading, metadata, tab]);
+
+ const recall = useCallback(() => {
+ if (!metadata) {
+ return;
+ }
+ if (!isEnabled) {
+ return;
+ }
+ MetadataUtils.recallByHandler({ metadata, handler: MetadataHandlers.CLIPSkip, store });
+ }, [metadata, isEnabled, store]);
+
+ return {
+ recall,
+ isEnabled,
+ };
+};
diff --git a/invokeai/frontend/web/src/features/metadata/parsing.tsx b/invokeai/frontend/web/src/features/metadata/parsing.tsx
index 3db8c3022c..c703dd0a1b 100644
--- a/invokeai/frontend/web/src/features/metadata/parsing.tsx
+++ b/invokeai/frontend/web/src/features/metadata/parsing.tsx
@@ -15,6 +15,7 @@ import {
selectBase,
setCfgRescaleMultiplier,
setCfgScale,
+ setClipSkip,
setGuidance,
setImg2imgStrength,
setRefinerCFGScale,
@@ -41,6 +42,7 @@ import { modelSelected } from 'features/parameters/store/actions';
import type {
ParameterCFGRescaleMultiplier,
ParameterCFGScale,
+ ParameterCLIPSkip,
ParameterGuidance,
ParameterHeight,
ParameterModel,
@@ -63,6 +65,7 @@ import {
zLoRAWeight,
zParameterCFGRescaleMultiplier,
zParameterCFGScale,
+ zParameterCLIPSkip,
zParameterGuidance,
zParameterImageDimension,
zParameterNegativePrompt,
@@ -321,6 +324,24 @@ const CFGRescaleMultiplier: SingleMetadataHandler
};
//#endregion CFG Rescale Multiplier
+//#region CLIP Skip
+const CLIPSkip: SingleMetadataHandler = {
+ [SingleMetadataKey]: true,
+ type: 'CLIPSkip',
+ parse: (metadata, _store) => {
+ const raw = getProperty(metadata, 'clip_skip');
+ const parsed = zParameterCLIPSkip.parse(raw);
+ return Promise.resolve(parsed);
+ },
+ recall: (value, store) => {
+ store.dispatch(setClipSkip(value));
+ },
+ i18nKey: 'metadata.clipSkip',
+ LabelComponent: MetadataLabel,
+ ValueComponent: ({ value }: SingleMetadataValueProps) => ,
+};
+//#endregion CLIP Skip
+
//#region Guidance
const Guidance: SingleMetadataHandler = {
[SingleMetadataKey]: true,
@@ -883,6 +904,7 @@ export const MetadataHandlers = {
NegativePrompt,
CFGScale,
CFGRescaleMultiplier,
+ CLIPSkip,
Guidance,
Scheduler,
Width,
diff --git a/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamClipSkip.tsx b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamClipSkip.tsx
index 1b437fb22f..f7abd6f86e 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamClipSkip.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamClipSkip.tsx
@@ -1,14 +1,14 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
-import { selectCLIPSKip, selectModel, setClipSkip } from 'features/controlLayers/store/paramsSlice';
+import { selectCLIPSkip, selectModel, setClipSkip } from 'features/controlLayers/store/paramsSlice';
import { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
import { selectCLIPSkipConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
const ParamClipSkip = () => {
- const clipSkip = useAppSelector(selectCLIPSKip);
+ const clipSkip = useAppSelector(selectCLIPSkip);
const config = useAppSelector(selectCLIPSkipConfig);
const model = useAppSelector(selectModel);
diff --git a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts
index b54ce3270f..14c45b61a8 100644
--- a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts
+++ b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts
@@ -4,8 +4,6 @@ import { buildZodTypeGuard } from 'common/util/zodUtils';
import { zModelIdentifierField, zSchedulerField } from 'features/nodes/types/common';
import { z } from 'zod';
-import { CLIP_SKIP_MAP } from './constants';
-
/**
* Schemas, types and type guards for parameters.
*
@@ -196,20 +194,21 @@ export const [zLoRAWeight, isParameterLoRAWeight] = buildParameter(z.number());
export type ParameterLoRAWeight = z.infer;
// #endregion
-// #region Max. CLIP
-export const [zParameterMaxCLIP, isParameterMaxCLIP] = buildParameter(
- z.union([
- z.discriminatedUnion('model', [
- z.object({ model: z.literal('sd-1'), maxClip: z.number().min(0).max(CLIP_SKIP_MAP['sd-1'].maxClip) }),
- z.object({ model: z.literal('sd-2'), maxClip: z.number().min(0).max(CLIP_SKIP_MAP['sd-2'].maxClip) }),
- z.object({ model: z.literal('sdxl'), maxClip: z.number().min(0).max(CLIP_SKIP_MAP['sdxl'].maxClip) }),
- z.object({
- model: z.literal('sdxl-refiner'),
- maxClip: z.number().min(0).max(CLIP_SKIP_MAP['sdxl-refiner'].maxClip),
- }),
- ]),
- z.object({ model: z.string(), maxClip: z.number().min(0).max(0) }),
- ])
-);
-export type ParameterMaxCLIP = z.infer;
+// #region CLIP skip
+// export const [zParameterCLIPSkip, isParameterCLIPSkip] = buildParameter(
+// z.union([
+// z.discriminatedUnion('model', [
+// z.object({ model: z.literal('sd-1'), clipSkip: z.number().min(0).max(CLIP_SKIP_MAP['sd-1'].maxClip) }),
+// z.object({ model: z.literal('sd-2'), clipSkip: z.number().min(0).max(CLIP_SKIP_MAP['sd-2'].maxClip) }),
+// z.object({ model: z.literal('sdxl'), clipSkip: z.number().min(0).max(CLIP_SKIP_MAP['sdxl'].maxClip) }),
+// z.object({
+// model: z.literal('sdxl-refiner'),
+// clipSkip: z.number().min(0).max(CLIP_SKIP_MAP['sdxl-refiner'].maxClip),
+// }),
+// ]),
+// z.object({ model: z.string(), clipSkip: z.number().min(0).max(0) }),
+// ])
+// );
+export const [zParameterCLIPSkip, isParameterCLIPSkip] = buildParameter(z.number().int().min(0));
+export type ParameterCLIPSkip = z.infer;
// #endregion
diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts
index c8d48a674c..fdc6561869 100644
--- a/invokeai/frontend/web/src/services/api/schema.ts
+++ b/invokeai/frontend/web/src/services/api/schema.ts
@@ -10292,6 +10292,11 @@ export type components = {
* @description The id of the board the image belongs to, if one exists.
*/
board_id?: string | null;
+ /**
+ * Clip Skip
+ * @description The number of skipped CLIP layers
+ */
+ clip_skip?: number | null;
};
/**
* ImageField
diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts
index 98f0d98034..3dfde9610c 100644
--- a/invokeai/frontend/web/src/services/api/types.ts
+++ b/invokeai/frontend/web/src/services/api/types.ts
@@ -61,6 +61,7 @@ const _zImageDTO = z.object({
starred: z.boolean(),
has_workflow: z.boolean(),
board_id: z.string().nullish(),
+ clip_skip: z.number().int().min(0).nullish(),
});
export type ImageDTO = z.infer;
assert>();