diff --git a/invokeai/app/invocations/compel.py b/invokeai/app/invocations/compel.py index a22f2ae6b9..303e0a0c84 100644 --- a/invokeai/app/invocations/compel.py +++ b/invokeai/app/invocations/compel.py @@ -92,6 +92,7 @@ class CompelInvocation(BaseInvocation): with ModelPatcher.apply_lora_text_encoder(text_encoder_info.context.model, _lora_loader()),\ ModelPatcher.apply_ti(tokenizer_info.context.model, text_encoder_info.context.model, ti_list) as (tokenizer, ti_manager),\ + ModelPatcher.apply_clip_skip(text_encoder_info.context.model, self.clip.skipped_layers),\ text_encoder_info as text_encoder: compel = Compel( @@ -131,6 +132,24 @@ class CompelInvocation(BaseInvocation): ), ) +class ClipSkipInvocationOutput(BaseInvocationOutput): + """Clip skip node output""" + type: Literal["clip_skip_output"] = "clip_skip_output" + clip: ClipField = Field(None, description="Clip with skipped layers") + +class ClipSkipInvocation(BaseInvocation): + """Skip layers in clip text_encoder model.""" + type: Literal["clip_skip"] = "clip_skip" + + clip: ClipField = Field(None, description="Clip to use") + skipped_layers: int = Field(0, description="Number of layers to skip in text_encoder") + + def invoke(self, context: InvocationContext) -> ClipSkipInvocationOutput: + self.clip.skipped_layers += self.skipped_layers + return ClipSkipInvocationOutput( + clip=self.clip, + ) + def get_max_token_count( tokenizer, prompt: Union[FlattenedPrompt, Blend, Conjunction], diff --git a/invokeai/app/invocations/model.py b/invokeai/app/invocations/model.py index 17297ba417..34836eabd2 100644 --- a/invokeai/app/invocations/model.py +++ b/invokeai/app/invocations/model.py @@ -30,6 +30,7 @@ class UNetField(BaseModel): class ClipField(BaseModel): tokenizer: ModelInfo = Field(description="Info to load tokenizer submodel") text_encoder: ModelInfo = Field(description="Info to load text_encoder submodel") + skipped_layers: int = Field(description="Number of skipped layers in text_encoder") loras: List[LoraInfo] = Field(description="Loras to apply on model loading") @@ -154,6 +155,7 @@ class MainModelLoaderInvocation(BaseInvocation): submodel=SubModelType.TextEncoder, ), loras=[], + skipped_layers=0, ), vae=VaeField( vae=ModelInfo( diff --git a/invokeai/backend/model_management/lora.py b/invokeai/backend/model_management/lora.py index e98d71e85c..b0481f3cfa 100644 --- a/invokeai/backend/model_management/lora.py +++ b/invokeai/backend/model_management/lora.py @@ -615,6 +615,24 @@ class ModelPatcher: text_encoder.resize_token_embeddings(init_tokens_count) + @classmethod + @contextmanager + def apply_clip_skip( + cls, + text_encoder: CLIPTextModel, + clip_skip: int, + ): + skipped_layers = [] + try: + for i in range(clip_skip): + skipped_layers.append(text_encoder.text_model.encoder.layers.pop(-1)) + + yield + + finally: + while len(skipped_layers) > 0: + text_encoder.text_model.encoder.layers.append(skipped_layers.pop()) + class TextualInversionModel: name: str embedding: torch.Tensor # [n, 768]|[n, 1280] diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index da5e15cb08..8e7b78cbc5 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -23,7 +23,7 @@ "dev": "concurrently \"vite dev\" \"yarn run theme:watch\"", "dev:host": "concurrently \"vite dev --host\" \"yarn run theme:watch\"", "build": "yarn run lint && vite build", - "typegen": "npx openapi-typescript http://localhost:9090/openapi.json --output src/services/api/schema.d.ts -t", + "typegen": "npx ts-node scripts/typegen.ts", "preview": "vite preview", "lint:madge": "madge --circular src/main.tsx", "lint:eslint": "eslint --max-warnings=0 .", @@ -84,7 +84,7 @@ "konva": "^9.2.0", "lodash-es": "^4.17.21", "nanostores": "^0.9.2", - "openapi-fetch": "0.4.0", + "openapi-fetch": "^0.6.1", "overlayscrollbars": "^2.2.0", "overlayscrollbars-react": "^0.5.0", "patch-package": "^7.0.0", diff --git a/invokeai/frontend/web/patches/openapi-fetch+0.4.0.patch b/invokeai/frontend/web/patches/openapi-fetch+0.4.0.patch deleted file mode 100644 index d82843f71c..0000000000 --- a/invokeai/frontend/web/patches/openapi-fetch+0.4.0.patch +++ /dev/null @@ -1,55 +0,0 @@ -diff --git a/node_modules/openapi-fetch/dist/index.js b/node_modules/openapi-fetch/dist/index.js -index cd4528a..8976b51 100644 ---- a/node_modules/openapi-fetch/dist/index.js -+++ b/node_modules/openapi-fetch/dist/index.js -@@ -1,5 +1,5 @@ - // settings & const --const DEFAULT_HEADERS = { -+const CONTENT_TYPE_APPLICATION_JSON = { - "Content-Type": "application/json", - }; - const TRAILING_SLASH_RE = /\/*$/; -@@ -29,18 +29,29 @@ export function createFinalURL(url, options) { - } - return finalURL; - } -+function stringifyBody(body) { -+ if (body instanceof ArrayBuffer || body instanceof File || body instanceof DataView || body instanceof Blob || ArrayBuffer.isView(body) || body instanceof URLSearchParams || body instanceof FormData) { -+ return; -+ } -+ -+ if (typeof body === "string") { -+ return body; -+ } -+ -+ return JSON.stringify(body); -+ } -+ - export default function createClient(clientOptions = {}) { - const { fetch = globalThis.fetch, ...options } = clientOptions; -- const defaultHeaders = new Headers({ -- ...DEFAULT_HEADERS, -- ...(options.headers ?? {}), -- }); -+ const defaultHeaders = new Headers(options.headers ?? {}); - async function coreFetch(url, fetchOptions) { - const { headers, body: requestBody, params = {}, parseAs = "json", querySerializer = defaultSerializer, ...init } = fetchOptions || {}; - // URL - const finalURL = createFinalURL(url, { baseUrl: options.baseUrl, params, querySerializer }); -+ // Stringify body if needed -+ const stringifiedBody = stringifyBody(requestBody); - // headers -- const baseHeaders = new Headers(defaultHeaders); // clone defaults (don’t overwrite!) -+ const baseHeaders = new Headers(stringifiedBody ? { ...CONTENT_TYPE_APPLICATION_JSON, ...defaultHeaders } : defaultHeaders); // clone defaults (don’t overwrite!) - const headerOverrides = new Headers(headers); - for (const [k, v] of headerOverrides.entries()) { - if (v === undefined || v === null) -@@ -54,7 +65,7 @@ export default function createClient(clientOptions = {}) { - ...options, - ...init, - headers: baseHeaders, -- body: typeof requestBody === "string" ? requestBody : JSON.stringify(requestBody), -+ body: stringifiedBody ?? requestBody, - }); - // handle empty content - // note: we return `{}` because we want user truthy checks for `.data` or `.error` to succeed diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 9cf1e0bc48..b403fde2c6 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -527,7 +527,8 @@ "showOptionsPanel": "Show Options Panel", "hidePreview": "Hide Preview", "showPreview": "Show Preview", - "controlNetControlMode": "Control Mode" + "controlNetControlMode": "Control Mode", + "clipSkip": "Clip Skip" }, "settings": { "models": "Models", @@ -551,7 +552,8 @@ "generation": "Generation", "ui": "User Interface", "favoriteSchedulers": "Favorite Schedulers", - "favoriteSchedulersPlaceholder": "No schedulers favorited" + "favoriteSchedulersPlaceholder": "No schedulers favorited", + "showAdvancedOptions": "Show Advanced Options" }, "toast": { "serverError": "Server Error", diff --git a/invokeai/frontend/web/scripts/package.json b/invokeai/frontend/web/scripts/package.json new file mode 100644 index 0000000000..3dbc1ca591 --- /dev/null +++ b/invokeai/frontend/web/scripts/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/invokeai/frontend/web/scripts/typegen.ts b/invokeai/frontend/web/scripts/typegen.ts new file mode 100644 index 0000000000..39d0b25d30 --- /dev/null +++ b/invokeai/frontend/web/scripts/typegen.ts @@ -0,0 +1,23 @@ +import fs from 'node:fs'; +import openapiTS from 'openapi-typescript'; + +const OPENAPI_URL = 'http://localhost:9090/openapi.json'; +const OUTPUT_FILE = 'src/services/api/schema.d.ts'; + +async function main() { + process.stdout.write( + `Generating types "${OPENAPI_URL}" --> "${OUTPUT_FILE}"...` + ); + const types = await openapiTS(OPENAPI_URL, { + exportType: true, + transform: (schemaObject, metadata) => { + if ('format' in schemaObject && schemaObject.format === 'binary') { + return schemaObject.nullable ? 'Blob | null' : 'Blob'; + } + }, + }); + fs.writeFileSync(OUTPUT_FILE, types); + process.stdout.write(` OK!\r\n`); +} + +main(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 900fabfee9..59fa48a9b7 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -86,6 +86,7 @@ import { addRequestedBoardImageDeletionListener } from './listeners/boardImagesD import { addSelectionAddedToBatchListener } from './listeners/selectionAddedToBatch'; import { addImageDroppedListener } from './listeners/imageDropped'; import { addImageToDeleteSelectedListener } from './listeners/imageToDeleteSelected'; +import { addModelSelectedListener } from './listeners/modelSelected'; export const listenerMiddleware = createListenerMiddleware(); @@ -220,3 +221,6 @@ addSelectionAddedToBatchListener(); // DND addImageDroppedListener(); + +// Models +addModelSelectedListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts new file mode 100644 index 0000000000..934581d02a --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts @@ -0,0 +1,42 @@ +import { makeToast } from 'app/components/Toaster'; +import { modelSelected } from 'features/parameters/store/actions'; +import { + modelChanged, + vaeSelected, +} from 'features/parameters/store/generationSlice'; +import { zMainModel } from 'features/parameters/store/parameterZodSchemas'; +import { addToast } from 'features/system/store/systemSlice'; +import { startAppListening } from '..'; +import { lorasCleared } from '../../../../../features/lora/store/loraSlice'; + +export const addModelSelectedListener = () => { + startAppListening({ + actionCreator: modelSelected, + effect: (action, { getState, dispatch }) => { + const state = getState(); + const [base_model, type, name] = action.payload.split('/'); + + if (state.generation.model?.base_model !== base_model) { + dispatch( + addToast( + makeToast({ + title: 'Base model changed, clearing submodels', + status: 'warning', + }) + ) + ); + dispatch(vaeSelected(null)); + dispatch(lorasCleared()); + // TODO: controlnet cleared + } + + const newModel = zMainModel.parse({ + id: action.payload, + base_model, + name, + }); + + dispatch(modelChanged(newModel)); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index a89ba01130..0fd0120ce8 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -93,7 +93,8 @@ export type AppFeature = | 'discordLink' | 'bugLink' | 'localization' - | 'consoleLogging'; + | 'consoleLogging' + | 'dynamicPrompting'; /** * A disable-able Stable Diffusion feature @@ -104,7 +105,10 @@ export type SDFeature = | 'variation' | 'symmetry' | 'seamless' - | 'hires'; + | 'hires' + | 'lora' + | 'embedding' + | 'vae'; /** * Configuration options for the InvokeAI UI. diff --git a/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx b/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx index 9a0bc865a4..04bab3717a 100644 --- a/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx +++ b/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx @@ -32,7 +32,7 @@ const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => { const { colorMode } = useColorMode(); return ( - + { '&[data-disabled]': { backgroundColor: mode(base300, base700)(colorMode), color: mode(base600, base400)(colorMode), + cursor: 'not-allowed', }, }, value: { @@ -108,6 +109,10 @@ const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => { color: mode('white', base50)(colorMode), }, }, + '&[data-disabled]': { + color: mode(base500, base600)(colorMode), + cursor: 'not-allowed', + }, }, rightSection: { width: 24, diff --git a/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx b/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx index 585dc106a8..8469af8fc8 100644 --- a/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx +++ b/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx @@ -67,6 +67,7 @@ const IAIMantineSelect = (props: IAISelectProps) => { '&[data-disabled]': { backgroundColor: mode(base300, base700)(colorMode), color: mode(base600, base400)(colorMode), + cursor: 'not-allowed', }, }, value: { @@ -109,6 +110,10 @@ const IAIMantineSelect = (props: IAISelectProps) => { color: mode('white', base50)(colorMode), }, }, + '&[data-disabled]': { + color: mode(base500, base600)(colorMode), + cursor: 'not-allowed', + }, }, rightSection: { width: 32, diff --git a/invokeai/frontend/web/src/common/components/IAIMantineSelectItemWithTooltip.tsx b/invokeai/frontend/web/src/common/components/IAIMantineSelectItemWithTooltip.tsx new file mode 100644 index 0000000000..65ba4020c8 --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAIMantineSelectItemWithTooltip.tsx @@ -0,0 +1,31 @@ +import { Box, Tooltip } from '@chakra-ui/react'; +import { Text } from '@mantine/core'; +import { forwardRef, memo } from 'react'; + +interface ItemProps extends React.ComponentPropsWithoutRef<'div'> { + label: string; + description?: string; + tooltip?: string; + disabled?: boolean; +} + +const IAIMantineSelectItemWithTooltip = forwardRef( + ({ label, tooltip, description, disabled, ...others }: ItemProps, ref) => ( + + + + {label} + {description && ( + + {description} + + )} + + + + ) +); + +IAIMantineSelectItemWithTooltip.displayName = 'IAIMantineSelectItemWithTooltip'; + +export default memo(IAIMantineSelectItemWithTooltip); diff --git a/invokeai/frontend/web/src/common/components/IAISlider.tsx b/invokeai/frontend/web/src/common/components/IAISlider.tsx index a435b50bbf..d923f2ad2d 100644 --- a/invokeai/frontend/web/src/common/components/IAISlider.tsx +++ b/invokeai/frontend/web/src/common/components/IAISlider.tsx @@ -26,7 +26,7 @@ import { } from '@chakra-ui/react'; import { clamp } from 'lodash-es'; -import { useTranslation } from 'react-i18next'; +import { roundDownToMultiple } from 'common/util/roundDownToMultiple'; import { FocusEvent, memo, @@ -36,9 +36,9 @@ import { useMemo, useState, } from 'react'; +import { useTranslation } from 'react-i18next'; import { BiReset } from 'react-icons/bi'; import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton'; -import { roundDownToMultiple } from 'common/util/roundDownToMultiple'; const SLIDER_MARK_STYLES: ChakraProps['sx'] = { mt: 1.5, diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx index 0e41fad994..36d8795615 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx @@ -7,6 +7,7 @@ import IAICollapse from 'common/components/IAICollapse'; import ParamDynamicPromptsCombinatorial from './ParamDynamicPromptsCombinatorial'; import ParamDynamicPromptsToggle from './ParamDynamicPromptsEnabled'; import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts'; +import { useFeatureStatus } from '../../system/hooks/useFeatureStatus'; const selector = createSelector( stateSelector, @@ -21,6 +22,13 @@ const selector = createSelector( const ParamDynamicPromptsCollapse = () => { const { activeLabel } = useAppSelector(selector); + const isDynamicPromptingEnabled = + useFeatureStatus('dynamicPrompting').isFeatureEnabled; + + if (!isDynamicPromptingEnabled) { + return null; + } + return ( diff --git a/invokeai/frontend/web/src/features/embedding/components/AddEmbeddingButton.tsx b/invokeai/frontend/web/src/features/embedding/components/AddEmbeddingButton.tsx index 1dae6f56e6..94ddc08315 100644 --- a/invokeai/frontend/web/src/features/embedding/components/AddEmbeddingButton.tsx +++ b/invokeai/frontend/web/src/features/embedding/components/AddEmbeddingButton.tsx @@ -1,6 +1,6 @@ import IAIIconButton from 'common/components/IAIIconButton'; import { memo } from 'react'; -import { BiCode } from 'react-icons/bi'; +import { FaCode } from 'react-icons/fa'; type Props = { onClick: () => void; @@ -13,15 +13,24 @@ const AddEmbeddingButton = (props: Props) => { size="sm" aria-label="Add Embedding" tooltip="Add Embedding" - icon={} + icon={} sx={{ p: 2, - color: 'base.700', + color: 'base.500', _hover: { - color: 'base.550', + color: 'base.600', }, _active: { + color: 'base.700', + }, + _dark: { color: 'base.500', + _hover: { + color: 'base.400', + }, + _active: { + color: 'base.300', + }, }, }} variant="link" diff --git a/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx b/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx index 3c2ded0166..b5e96b6c92 100644 --- a/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx +++ b/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx @@ -6,24 +6,17 @@ import { PopoverTrigger, Text, } from '@chakra-ui/react'; +import { SelectItem } from '@mantine/core'; +import { RootState } from 'app/store/store'; +import { useAppSelector } from 'app/store/storeHooks'; import IAIMantineMultiSelect from 'common/components/IAIMantineMultiSelect'; +import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip'; +import { MODEL_TYPE_MAP } from 'features/system/components/ModelSelect'; import { forEach } from 'lodash-es'; -import { - PropsWithChildren, - forwardRef, - useCallback, - useMemo, - useRef, -} from 'react'; +import { PropsWithChildren, useCallback, useMemo, useRef } from 'react'; import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models'; import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; -type EmbeddingSelectItem = { - label: string; - value: string; - description?: string; -}; - type Props = PropsWithChildren & { onSelect: (v: string) => void; isOpen: boolean; @@ -35,25 +28,37 @@ const ParamEmbeddingPopover = (props: Props) => { const { data: embeddingQueryData } = useGetTextualInversionModelsQuery(); const inputRef = useRef(null); + const currentMainModel = useAppSelector( + (state: RootState) => state.generation.model + ); + const data = useMemo(() => { if (!embeddingQueryData) { return []; } - const data: EmbeddingSelectItem[] = []; + const data: SelectItem[] = []; forEach(embeddingQueryData.entities, (embedding, _) => { - if (!embedding) return; + if (!embedding) { + return; + } + + const disabled = currentMainModel?.base_model !== embedding.base_model; data.push({ value: embedding.name, label: embedding.name, - description: embedding.description, + group: MODEL_TYPE_MAP[embedding.base_model], + disabled, + tooltip: disabled + ? `Incompatible base model: ${embedding.base_model}` + : undefined, }); }); - return data; - }, [embeddingQueryData]); + return data.sort((a, b) => (a.disabled && !b.disabled ? 1 : -1)); + }, [embeddingQueryData, currentMainModel?.base_model]); const handleChange = useCallback( (v: string[]) => { @@ -108,10 +113,12 @@ const ParamEmbeddingPopover = (props: Props) => { data={data} maxDropdownHeight={400} nothingFound="No Matching Embeddings" - itemComponent={SelectItem} + itemComponent={IAIMantineSelectItemWithTooltip} disabled={data.length === 0} - filter={(value, selected, item: EmbeddingSelectItem) => - item.label.toLowerCase().includes(value.toLowerCase().trim()) || + filter={(value, selected, item: SelectItem) => + item.label + ?.toLowerCase() + .includes(value.toLowerCase().trim()) || item.value.toLowerCase().includes(value.toLowerCase().trim()) } onChange={handleChange} @@ -124,28 +131,3 @@ const ParamEmbeddingPopover = (props: Props) => { }; export default ParamEmbeddingPopover; - -interface ItemProps extends React.ComponentPropsWithoutRef<'div'> { - value: string; - label: string; - description?: string; -} - -const SelectItem = forwardRef( - ({ label, description, ...others }: ItemProps, ref) => { - return ( -
-
- {label} - {description && ( - - {description} - - )} -
-
- ); - } -); - -SelectItem.displayName = 'SelectItem'; diff --git a/invokeai/frontend/web/src/features/lora/components/ParamLoraCollapse.tsx b/invokeai/frontend/web/src/features/lora/components/ParamLoraCollapse.tsx index 6e69f036df..436c32f46b 100644 --- a/invokeai/frontend/web/src/features/lora/components/ParamLoraCollapse.tsx +++ b/invokeai/frontend/web/src/features/lora/components/ParamLoraCollapse.tsx @@ -8,6 +8,7 @@ import { size } from 'lodash-es'; import { memo } from 'react'; import ParamLoraList from './ParamLoraList'; import ParamLoraSelect from './ParamLoraSelect'; +import { useFeatureStatus } from '../../system/hooks/useFeatureStatus'; const selector = createSelector( stateSelector, @@ -23,6 +24,12 @@ const selector = createSelector( const ParamLoraCollapse = () => { const { activeLabel } = useAppSelector(selector); + const isLoraEnabled = useFeatureStatus('lora').isFeatureEnabled; + + if (!isLoraEnabled) { + return null; + } + return ( diff --git a/invokeai/frontend/web/src/features/lora/components/ParamLoraSelect.tsx b/invokeai/frontend/web/src/features/lora/components/ParamLoraSelect.tsx index 9168814f35..a87f481496 100644 --- a/invokeai/frontend/web/src/features/lora/components/ParamLoraSelect.tsx +++ b/invokeai/frontend/web/src/features/lora/components/ParamLoraSelect.tsx @@ -1,19 +1,16 @@ import { Flex, Text } from '@chakra-ui/react'; +import { SelectItem } from '@mantine/core'; import { createSelector } from '@reduxjs/toolkit'; -import { stateSelector } from 'app/store/store'; +import { RootState, stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAIMantineMultiSelect from 'common/components/IAIMantineMultiSelect'; +import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip'; +import { loraAdded } from 'features/lora/store/loraSlice'; +import { MODEL_TYPE_MAP } from 'features/system/components/ModelSelect'; import { forEach } from 'lodash-es'; -import { forwardRef, useCallback, useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import { useGetLoRAModelsQuery } from 'services/api/endpoints/models'; -import { loraAdded } from '../store/loraSlice'; - -type LoraSelectItem = { - label: string; - value: string; - description?: string; -}; const selector = createSelector( stateSelector, @@ -28,27 +25,37 @@ const ParamLoraSelect = () => { const { loras } = useAppSelector(selector); const { data: lorasQueryData } = useGetLoRAModelsQuery(); + const currentMainModel = useAppSelector( + (state: RootState) => state.generation.model + ); + const data = useMemo(() => { if (!lorasQueryData) { return []; } - const data: LoraSelectItem[] = []; + const data: SelectItem[] = []; forEach(lorasQueryData.entities, (lora, id) => { if (!lora || Boolean(id in loras)) { return; } + const disabled = currentMainModel?.base_model !== lora.base_model; + data.push({ value: id, label: lora.name, - description: lora.description, + disabled, + group: MODEL_TYPE_MAP[lora.base_model], + tooltip: disabled + ? `Incompatible base model: ${lora.base_model}` + : undefined, }); }); - return data; - }, [loras, lorasQueryData]); + return data.sort((a, b) => (a.disabled && !b.disabled ? 1 : -1)); + }, [loras, lorasQueryData, currentMainModel?.base_model]); const handleChange = useCallback( (v: string[]) => { @@ -78,10 +85,10 @@ const ParamLoraSelect = () => { data={data} maxDropdownHeight={400} nothingFound="No matching LoRAs" - itemComponent={SelectItem} + itemComponent={IAIMantineSelectItemWithTooltip} disabled={data.length === 0} - filter={(value, selected, item: LoraSelectItem) => - item.label.toLowerCase().includes(value.toLowerCase().trim()) || + filter={(value, selected, item: SelectItem) => + item.label?.toLowerCase().includes(value.toLowerCase().trim()) || item.value.toLowerCase().includes(value.toLowerCase().trim()) } onChange={handleChange} @@ -89,29 +96,4 @@ const ParamLoraSelect = () => { ); }; -interface ItemProps extends React.ComponentPropsWithoutRef<'div'> { - value: string; - label: string; - description?: string; -} - -const SelectItem = forwardRef( - ({ label, description, ...others }: ItemProps, ref) => { - return ( -
-
- {label} - {description && ( - - {description} - - )} -
-
- ); - } -); - -SelectItem.displayName = 'SelectItem'; - export default ParamLoraSelect; diff --git a/invokeai/frontend/web/src/features/lora/store/loraSlice.ts b/invokeai/frontend/web/src/features/lora/store/loraSlice.ts index 7da6018e58..6fe6109c4d 100644 --- a/invokeai/frontend/web/src/features/lora/store/loraSlice.ts +++ b/invokeai/frontend/web/src/features/lora/store/loraSlice.ts @@ -1,18 +1,21 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; +import { LoRAModelParam } from 'features/parameters/store/parameterZodSchemas'; import { LoRAModelConfigEntity } from 'services/api/endpoints/models'; +import { BaseModelType } from 'services/api/types'; export type Lora = { id: string; + base_model: BaseModelType; name: string; weight: number; }; -export const defaultLoRAConfig: Omit = { +export const defaultLoRAConfig = { weight: 0.75, }; export type LoraState = { - loras: Record; + loras: Record; }; export const intialLoraState: LoraState = { @@ -24,13 +27,16 @@ export const loraSlice = createSlice({ initialState: intialLoraState, reducers: { loraAdded: (state, action: PayloadAction) => { - const { name, id } = action.payload; - state.loras[id] = { id, name, ...defaultLoRAConfig }; + const { name, id, base_model } = action.payload; + state.loras[id] = { id, name, base_model, ...defaultLoRAConfig }; }, loraRemoved: (state, action: PayloadAction) => { const id = action.payload; delete state.loras[id]; }, + lorasCleared: (state) => { + state.loras = {}; + }, loraWeightChanged: ( state, action: PayloadAction<{ id: string; weight: number }> @@ -45,7 +51,12 @@ export const loraSlice = createSlice({ }, }); -export const { loraAdded, loraRemoved, loraWeightChanged, loraWeightReset } = - loraSlice.actions; +export const { + loraAdded, + loraRemoved, + loraWeightChanged, + loraWeightReset, + lorasCleared, +} = loraSlice.actions; export default loraSlice.reducer; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addLoRAsToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addLoRAsToGraph.ts index 9712ef4d5f..74cc8b1f57 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addLoRAsToGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addLoRAsToGraph.ts @@ -4,6 +4,7 @@ import { forEach, size } from 'lodash-es'; import { LoraLoaderInvocation } from 'services/api/types'; import { modelIdToLoRAModelField } from '../modelIdToLoRAName'; import { + CLIP_SKIP, LORA_LOADER, MAIN_MODEL_LOADER, NEGATIVE_CONDITIONING, @@ -27,14 +28,19 @@ export const addLoRAsToGraph = ( const loraCount = size(loras); if (loraCount > 0) { - // remove any existing connections from main model loader, we need to insert the lora nodes + // Remove MAIN_MODEL_LOADER unet connection to feed it to LoRAs graph.edges = graph.edges.filter( (e) => !( e.source.node_id === MAIN_MODEL_LOADER && - ['unet', 'clip'].includes(e.source.field) + ['unet'].includes(e.source.field) ) ); + // Remove CLIP_SKIP connections to conditionings to feed it through LoRAs + graph.edges = graph.edges.filter( + (e) => + !(e.source.node_id === CLIP_SKIP && ['clip'].includes(e.source.field)) + ); } // we need to remember the last lora so we can chain from it @@ -73,7 +79,7 @@ export const addLoRAsToGraph = ( graph.edges.push({ source: { - node_id: MAIN_MODEL_LOADER, + node_id: CLIP_SKIP, field: 'clip', }, destination: { diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addVAEToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addVAEToGraph.ts index 4dd3d644ee..9de8f6e99d 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addVAEToGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addVAEToGraph.ts @@ -16,10 +16,12 @@ export const addVAEToGraph = ( graph: NonNullableGraph, state: RootState ): void => { - const { vae: vaeId } = state.generation; - const vae_model = modelIdToVAEModelField(vaeId); + const { vae } = state.generation; + const vae_model = modelIdToVAEModelField(vae?.id || ''); - if (vaeId !== 'auto') { + const isAutoVae = !vae; + + if (!isAutoVae) { graph.nodes[VAE_LOADER] = { type: 'vae_loader', id: VAE_LOADER, @@ -30,7 +32,7 @@ export const addVAEToGraph = ( if (graph.id === TEXT_TO_IMAGE_GRAPH || graph.id === IMAGE_TO_IMAGE_GRAPH) { graph.edges.push({ source: { - node_id: vaeId === 'auto' ? MAIN_MODEL_LOADER : VAE_LOADER, + node_id: isAutoVae ? MAIN_MODEL_LOADER : VAE_LOADER, field: 'vae', }, destination: { @@ -43,7 +45,7 @@ export const addVAEToGraph = ( if (graph.id === IMAGE_TO_IMAGE_GRAPH) { graph.edges.push({ source: { - node_id: vaeId === 'auto' ? MAIN_MODEL_LOADER : VAE_LOADER, + node_id: isAutoVae ? MAIN_MODEL_LOADER : VAE_LOADER, field: 'vae', }, destination: { @@ -56,7 +58,7 @@ export const addVAEToGraph = ( if (graph.id === INPAINT_GRAPH) { graph.edges.push({ source: { - node_id: vaeId === 'auto' ? MAIN_MODEL_LOADER : VAE_LOADER, + node_id: isAutoVae ? MAIN_MODEL_LOADER : VAE_LOADER, field: 'vae', }, destination: { diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts index 1843efef84..419e6af93b 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts @@ -12,6 +12,7 @@ import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph'; import { addLoRAsToGraph } from './addLoRAsToGraph'; import { addVAEToGraph } from './addVAEToGraph'; import { + CLIP_SKIP, IMAGE_TO_IMAGE_GRAPH, IMAGE_TO_LATENTS, LATENTS_TO_IMAGE, @@ -35,11 +36,12 @@ export const buildCanvasImageToImageGraph = ( const { positivePrompt, negativePrompt, - model: modelId, + model: currentModel, cfgScale: cfg_scale, scheduler, steps, img2imgStrength: strength, + clipSkip, iterations, seed, shouldRandomizeSeed, @@ -48,7 +50,7 @@ export const buildCanvasImageToImageGraph = ( // The bounding box determines width and height, not the width and height params const { width, height } = state.canvas.boundingBoxDimensions; - const model = modelIdToMainModelField(modelId); + const model = modelIdToMainModelField(currentModel?.id || ''); /** * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the @@ -82,6 +84,11 @@ export const buildCanvasImageToImageGraph = ( id: MAIN_MODEL_LOADER, model, }, + [CLIP_SKIP]: { + type: 'clip_skip', + id: CLIP_SKIP, + skipped_layers: clipSkip, + }, [LATENTS_TO_IMAGE]: { type: 'l2i', id: LATENTS_TO_IMAGE, @@ -109,6 +116,16 @@ export const buildCanvasImageToImageGraph = ( node_id: MAIN_MODEL_LOADER, field: 'clip', }, + destination: { + node_id: CLIP_SKIP, + field: 'clip', + }, + }, + { + source: { + node_id: CLIP_SKIP, + field: 'clip', + }, destination: { node_id: POSITIVE_CONDITIONING, field: 'clip', @@ -116,7 +133,7 @@ export const buildCanvasImageToImageGraph = ( }, { source: { - node_id: MAIN_MODEL_LOADER, + node_id: CLIP_SKIP, field: 'clip', }, destination: { diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts index c4f9415067..2bac864015 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts @@ -11,6 +11,7 @@ import { modelIdToMainModelField } from '../modelIdToMainModelField'; import { addLoRAsToGraph } from './addLoRAsToGraph'; import { addVAEToGraph } from './addVAEToGraph'; import { + CLIP_SKIP, INPAINT, INPAINT_GRAPH, ITERATE, @@ -34,7 +35,7 @@ export const buildCanvasInpaintGraph = ( const { positivePrompt, negativePrompt, - model: modelId, + model: currentModel, cfgScale: cfg_scale, scheduler, steps, @@ -49,6 +50,7 @@ export const buildCanvasInpaintGraph = ( seamStrength, tileSize, infillMethod, + clipSkip, } = state.generation; // The bounding box determines width and height, not the width and height params @@ -57,7 +59,7 @@ export const buildCanvasInpaintGraph = ( // We may need to set the inpaint width and height to scale the image const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas; - const model = modelIdToMainModelField(modelId); + const model = modelIdToMainModelField(currentModel?.id || ''); const graph: NonNullableGraph = { id: INPAINT_GRAPH, @@ -108,6 +110,11 @@ export const buildCanvasInpaintGraph = ( id: MAIN_MODEL_LOADER, model, }, + [CLIP_SKIP]: { + type: 'clip_skip', + id: CLIP_SKIP, + skipped_layers: clipSkip, + }, [RANGE_OF_SIZE]: { type: 'range_of_size', id: RANGE_OF_SIZE, @@ -122,6 +129,46 @@ export const buildCanvasInpaintGraph = ( }, }, edges: [ + { + source: { + node_id: MAIN_MODEL_LOADER, + field: 'unet', + }, + destination: { + node_id: INPAINT, + field: 'unet', + }, + }, + { + source: { + node_id: MAIN_MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: CLIP_SKIP, + field: 'clip', + }, + }, + { + source: { + node_id: CLIP_SKIP, + field: 'clip', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: CLIP_SKIP, + field: 'clip', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip', + }, + }, { source: { node_id: NEGATIVE_CONDITIONING, @@ -142,36 +189,6 @@ export const buildCanvasInpaintGraph = ( field: 'positive_conditioning', }, }, - { - source: { - node_id: MAIN_MODEL_LOADER, - field: 'clip', - }, - destination: { - node_id: POSITIVE_CONDITIONING, - field: 'clip', - }, - }, - { - source: { - node_id: MAIN_MODEL_LOADER, - field: 'clip', - }, - destination: { - node_id: NEGATIVE_CONDITIONING, - field: 'clip', - }, - }, - { - source: { - node_id: MAIN_MODEL_LOADER, - field: 'unet', - }, - destination: { - node_id: INPAINT, - field: 'unet', - }, - }, { source: { node_id: RANGE_OF_SIZE, diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts index 976ea4fd01..70e167aead 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts @@ -6,6 +6,7 @@ import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph'; import { addLoRAsToGraph } from './addLoRAsToGraph'; import { addVAEToGraph } from './addVAEToGraph'; import { + CLIP_SKIP, LATENTS_TO_IMAGE, MAIN_MODEL_LOADER, NEGATIVE_CONDITIONING, @@ -24,10 +25,11 @@ export const buildCanvasTextToImageGraph = ( const { positivePrompt, negativePrompt, - model: modelId, + model: currentModel, cfgScale: cfg_scale, scheduler, steps, + clipSkip, iterations, seed, shouldRandomizeSeed, @@ -36,7 +38,7 @@ export const buildCanvasTextToImageGraph = ( // The bounding box determines width and height, not the width and height params const { width, height } = state.canvas.boundingBoxDimensions; - const model = modelIdToMainModelField(modelId); + const model = modelIdToMainModelField(currentModel?.id || ''); /** * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the @@ -79,6 +81,11 @@ export const buildCanvasTextToImageGraph = ( id: MAIN_MODEL_LOADER, model, }, + [CLIP_SKIP]: { + type: 'clip_skip', + id: CLIP_SKIP, + skipped_layers: clipSkip, + }, [LATENTS_TO_IMAGE]: { type: 'l2i', id: LATENTS_TO_IMAGE, @@ -110,6 +117,16 @@ export const buildCanvasTextToImageGraph = ( node_id: MAIN_MODEL_LOADER, field: 'clip', }, + destination: { + node_id: CLIP_SKIP, + field: 'clip', + }, + }, + { + source: { + node_id: CLIP_SKIP, + field: 'clip', + }, destination: { node_id: POSITIVE_CONDITIONING, field: 'clip', @@ -117,7 +134,7 @@ export const buildCanvasTextToImageGraph = ( }, { source: { - node_id: MAIN_MODEL_LOADER, + node_id: CLIP_SKIP, field: 'clip', }, destination: { diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts index fe6d1292e4..8adf2cf342 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts @@ -13,6 +13,7 @@ import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph'; import { addLoRAsToGraph } from './addLoRAsToGraph'; import { addVAEToGraph } from './addVAEToGraph'; import { + CLIP_SKIP, IMAGE_COLLECTION, IMAGE_COLLECTION_ITERATE, IMAGE_TO_IMAGE_GRAPH, @@ -37,7 +38,7 @@ export const buildLinearImageToImageGraph = ( const { positivePrompt, negativePrompt, - model: modelId, + model: currentModel, cfgScale: cfg_scale, scheduler, steps, @@ -46,6 +47,7 @@ export const buildLinearImageToImageGraph = ( shouldFitToWidthHeight, width, height, + clipSkip, } = state.generation; const { @@ -71,12 +73,22 @@ export const buildLinearImageToImageGraph = ( throw new Error('No initial image found in state'); } - const model = modelIdToMainModelField(modelId); + const model = modelIdToMainModelField(currentModel?.id || ''); // copy-pasted graph from node editor, filled in with state values & friendly node ids const graph: NonNullableGraph = { id: IMAGE_TO_IMAGE_GRAPH, nodes: { + [MAIN_MODEL_LOADER]: { + type: 'main_model_loader', + id: MAIN_MODEL_LOADER, + model, + }, + [CLIP_SKIP]: { + type: 'clip_skip', + id: CLIP_SKIP, + skipped_layers: clipSkip, + }, [POSITIVE_CONDITIONING]: { type: 'compel', id: POSITIVE_CONDITIONING, @@ -91,11 +103,6 @@ export const buildLinearImageToImageGraph = ( type: 'noise', id: NOISE, }, - [MAIN_MODEL_LOADER]: { - type: 'main_model_loader', - id: MAIN_MODEL_LOADER, - model, - }, [LATENTS_TO_IMAGE]: { type: 'l2i', id: LATENTS_TO_IMAGE, @@ -121,6 +128,26 @@ export const buildLinearImageToImageGraph = ( { source: { node_id: MAIN_MODEL_LOADER, + field: 'unet', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'unet', + }, + }, + { + source: { + node_id: MAIN_MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: CLIP_SKIP, + field: 'clip', + }, + }, + { + source: { + node_id: CLIP_SKIP, field: 'clip', }, destination: { @@ -130,7 +157,7 @@ export const buildLinearImageToImageGraph = ( }, { source: { - node_id: MAIN_MODEL_LOADER, + node_id: CLIP_SKIP, field: 'clip', }, destination: { @@ -168,17 +195,6 @@ export const buildLinearImageToImageGraph = ( field: 'noise', }, }, - - { - source: { - node_id: MAIN_MODEL_LOADER, - field: 'unet', - }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'unet', - }, - }, { source: { node_id: NEGATIVE_CONDITIONING, diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts index 04dccf4983..28ff3656c1 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts @@ -6,6 +6,7 @@ import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph'; import { addLoRAsToGraph } from './addLoRAsToGraph'; import { addVAEToGraph } from './addVAEToGraph'; import { + CLIP_SKIP, LATENTS_TO_IMAGE, MAIN_MODEL_LOADER, NEGATIVE_CONDITIONING, @@ -21,15 +22,16 @@ export const buildLinearTextToImageGraph = ( const { positivePrompt, negativePrompt, - model: modelId, + model: currentModel, cfgScale: cfg_scale, scheduler, steps, width, height, + clipSkip, } = state.generation; - const model = modelIdToMainModelField(modelId); + const model = modelIdToMainModelField(currentModel?.id || ''); /** * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the @@ -44,6 +46,16 @@ export const buildLinearTextToImageGraph = ( const graph: NonNullableGraph = { id: TEXT_TO_IMAGE_GRAPH, nodes: { + [MAIN_MODEL_LOADER]: { + type: 'main_model_loader', + id: MAIN_MODEL_LOADER, + model, + }, + [CLIP_SKIP]: { + type: 'clip_skip', + id: CLIP_SKIP, + skipped_layers: clipSkip, + }, [POSITIVE_CONDITIONING]: { type: 'compel', id: POSITIVE_CONDITIONING, @@ -67,11 +79,6 @@ export const buildLinearTextToImageGraph = ( scheduler, steps, }, - [MAIN_MODEL_LOADER]: { - type: 'main_model_loader', - id: MAIN_MODEL_LOADER, - model, - }, [LATENTS_TO_IMAGE]: { type: 'l2i', id: LATENTS_TO_IMAGE, @@ -80,12 +87,42 @@ export const buildLinearTextToImageGraph = ( edges: [ { source: { - node_id: NEGATIVE_CONDITIONING, - field: 'conditioning', + node_id: MAIN_MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: CLIP_SKIP, + field: 'clip', + }, + }, + { + source: { + node_id: MAIN_MODEL_LOADER, + field: 'unet', }, destination: { node_id: TEXT_TO_LATENTS, - field: 'negative_conditioning', + field: 'unet', + }, + }, + { + source: { + node_id: CLIP_SKIP, + field: 'clip', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: CLIP_SKIP, + field: 'clip', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip', }, }, { @@ -100,32 +137,12 @@ export const buildLinearTextToImageGraph = ( }, { source: { - node_id: MAIN_MODEL_LOADER, - field: 'clip', - }, - destination: { - node_id: POSITIVE_CONDITIONING, - field: 'clip', - }, - }, - { - source: { - node_id: MAIN_MODEL_LOADER, - field: 'clip', - }, - destination: { node_id: NEGATIVE_CONDITIONING, - field: 'clip', - }, - }, - { - source: { - node_id: MAIN_MODEL_LOADER, - field: 'unet', + field: 'conditioning', }, destination: { node_id: TEXT_TO_LATENTS, - field: 'unet', + field: 'negative_conditioning', }, }, { diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts index 7aace48def..256a623bba 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts @@ -10,6 +10,7 @@ export const ITERATE = 'iterate'; export const MAIN_MODEL_LOADER = 'main_model_loader'; export const VAE_LOADER = 'vae_loader'; export const LORA_LOADER = 'lora_loader'; +export const CLIP_SKIP = 'clip_skip'; export const IMAGE_TO_LATENTS = 'image_to_latents'; export const LATENTS_TO_LATENTS = 'latents_to_latents'; export const RESIZE = 'resize_image'; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx new file mode 100644 index 0000000000..984ad833a6 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx @@ -0,0 +1,34 @@ +import { Flex } from '@chakra-ui/react'; +import { createSelector } from '@reduxjs/toolkit'; +import { RootState, stateSelector } from 'app/store/store'; +import { useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAICollapse from 'common/components/IAICollapse'; +import ParamClipSkip from './ParamClipSkip'; + +const selector = createSelector( + stateSelector, + (state: RootState) => { + const clipSkip = state.generation.clipSkip; + return { + activeLabel: clipSkip > 0 ? 'Clip Skip' : undefined, + }; + }, + defaultSelectorOptions +); + +export default function ParamAdvancedCollapse() { + const { activeLabel } = useAppSelector(selector); + const shouldShowAdvancedOptions = useAppSelector( + (state: RootState) => state.ui.shouldShowAdvancedOptions + ); + return ( + shouldShowAdvancedOptions && ( + + + + + + ) + ); +} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamClipSkip.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamClipSkip.tsx new file mode 100644 index 0000000000..8965bc9f0c --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamClipSkip.tsx @@ -0,0 +1,70 @@ +import { RootState } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAISlider from 'common/components/IAISlider'; +import { setClipSkip } from 'features/parameters/store/generationSlice'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +export const clipSkipMap = { + 'sd-1': { + maxClip: 12, + markers: [0, 1, 2, 3, 4, 8, 12], + }, + 'sd-2': { + maxClip: 24, + markers: [0, 1, 2, 3, 5, 10, 15, 20, 24], + }, +}; + +export default function ParamClipSkip() { + const clipSkip = useAppSelector( + (state: RootState) => state.generation.clipSkip + ); + + const { model } = useAppSelector((state: RootState) => state.generation); + + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + + const handleClipSkipChange = useCallback( + (v: number) => { + dispatch(setClipSkip(v)); + }, + [dispatch] + ); + + const handleClipSkipReset = useCallback(() => { + dispatch(setClipSkip(0)); + }, [dispatch]); + + const max = useMemo(() => { + if (!model) { + return clipSkipMap['sd-1'].maxClip; + } + return clipSkipMap[model.base_model].maxClip; + }, [model]); + + const sliderMarks = useMemo(() => { + if (!model) { + return clipSkipMap['sd-1'].markers; + } + return clipSkipMap[model.base_model].markers; + }, [model]); + + return ( + + ); +} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamModelandVAE.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamModelandVAE.tsx deleted file mode 100644 index 1c704a86ef..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamModelandVAE.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Box, Flex } from '@chakra-ui/react'; -import ModelSelect from 'features/system/components/ModelSelect'; -import VAESelect from 'features/system/components/VAESelect'; -import { memo } from 'react'; - -const ParamModelandVAE = () => { - return ( - - - - - - - - - ); -}; - -export default memo(ParamModelandVAE); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamModelandVAEandScheduler.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamModelandVAEandScheduler.tsx new file mode 100644 index 0000000000..a7ae38adc7 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamModelandVAEandScheduler.tsx @@ -0,0 +1,31 @@ +import { Box, Flex } from '@chakra-ui/react'; +import ModelSelect from 'features/system/components/ModelSelect'; +import VAESelect from 'features/system/components/VAESelect'; +import { memo } from 'react'; +import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus'; +import ParamScheduler from './ParamScheduler'; + +const ParamModelandVAEandScheduler = () => { + const isVaeEnabled = useFeatureStatus('vae').isFeatureEnabled; + + return ( + + + + + + + {isVaeEnabled && ( + + + + )} + + + + + + ); +}; + +export default memo(ParamModelandVAEandScheduler); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamNegativeConditioning.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamNegativeConditioning.tsx index 3e5320ad47..513ab64930 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamNegativeConditioning.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamNegativeConditioning.tsx @@ -8,6 +8,7 @@ import { setNegativePrompt } from 'features/parameters/store/generationSlice'; import { ChangeEvent, KeyboardEvent, useCallback, useRef } from 'react'; import { flushSync } from 'react-dom'; import { useTranslation } from 'react-i18next'; +import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus'; const ParamNegativeConditioning = () => { const negativePrompt = useAppSelector( @@ -71,6 +72,8 @@ const ParamNegativeConditioning = () => { [dispatch, onClose, negativePrompt] ); + const isEmbeddingEnabled = useFeatureStatus('embedding').isFeatureEnabled; + return ( { value={negativePrompt} placeholder={t('parameters.negativePromptPlaceholder')} onChange={handleChangePrompt} - onKeyDown={handleKeyDown} resize="vertical" fontSize="sm" minH={16} + {...(isEmbeddingEnabled && { onKeyDown: handleKeyDown })} /> - {!isOpen && ( + {!isOpen && isEmbeddingEnabled && ( state.generation, activeTabNameSelector], - (parameters: GenerationState, activeTabName) => { + [stateSelector, activeTabNameSelector], + ({ generation, ui }, activeTabName) => { return { - prompt: parameters.positivePrompt, + shouldPinParametersPanel: ui.shouldPinParametersPanel, + prompt: generation.positivePrompt, activeTabName, }; }, @@ -41,7 +42,8 @@ const promptInputSelector = createSelector( */ const ParamPositiveConditioning = () => { const dispatch = useAppDispatch(); - const { prompt, activeTabName } = useAppSelector(promptInputSelector); + const { prompt, shouldPinParametersPanel, activeTabName } = + useAppSelector(promptInputSelector); const isReady = useIsReadyToInvoke(); const promptRef = useRef(null); const { isOpen, onClose, onOpen } = useDisclosure(); @@ -100,6 +102,8 @@ const ParamPositiveConditioning = () => { [dispatch, onClose, prompt] ); + const isEmbeddingEnabled = useFeatureStatus('embedding').isFeatureEnabled; + const handleKeyDown = useCallback( (e: KeyboardEvent) => { if (e.key === 'Enter' && e.shiftKey === false && isReady) { @@ -107,11 +111,11 @@ const ParamPositiveConditioning = () => { dispatch(clampSymmetrySteps()); dispatch(userInvoked(activeTabName)); } - if (e.key === '<') { + if (isEmbeddingEnabled && e.key === '<') { onOpen(); } }, - [isReady, dispatch, activeTabName, onOpen] + [isReady, dispatch, activeTabName, onOpen, isEmbeddingEnabled] ); // const handleSelect = (e: MouseEvent) => { @@ -120,7 +124,7 @@ const ParamPositiveConditioning = () => { // }; return ( - + { /> - {!isOpen && ( + {!isOpen && isEmbeddingEnabled && ( diff --git a/invokeai/frontend/web/src/features/parameters/hooks/useRecallParameters.ts b/invokeai/frontend/web/src/features/parameters/hooks/useRecallParameters.ts index f504c62ed6..721b44d329 100644 --- a/invokeai/frontend/web/src/features/parameters/hooks/useRecallParameters.ts +++ b/invokeai/frontend/web/src/features/parameters/hooks/useRecallParameters.ts @@ -1,8 +1,11 @@ +import { useAppToaster } from 'app/components/Toaster'; import { useAppDispatch } from 'app/store/storeHooks'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import { isImageField } from 'services/api/guards'; +import { ImageDTO } from 'services/api/types'; +import { initialImageSelected, modelSelected } from '../store/actions'; import { - modelSelected, setCfgScale, setHeight, setImg2imgStrength, @@ -13,14 +16,10 @@ import { setSteps, setWidth, } from '../store/generationSlice'; -import { isImageField } from 'services/api/guards'; -import { initialImageSelected } from '../store/actions'; -import { useAppToaster } from 'app/components/Toaster'; -import { ImageDTO } from 'services/api/types'; import { isValidCfgScale, isValidHeight, - isValidModel, + isValidMainModel, isValidNegativePrompt, isValidPositivePrompt, isValidScheduler, @@ -159,11 +158,11 @@ export const useRecallParameters = () => { */ const recallModel = useCallback( (model: unknown) => { - if (!isValidModel(model)) { + if (!isValidMainModel(model)) { parameterNotSetToast(); return; } - dispatch(modelSelected(model)); + dispatch(modelSelected(model?.id || '')); parameterSetToast(); }, [dispatch, parameterSetToast, parameterNotSetToast] @@ -296,7 +295,7 @@ export const useRecallParameters = () => { if (isValidCfgScale(cfg_scale)) { dispatch(setCfgScale(cfg_scale)); } - if (isValidModel(model)) { + if (isValidMainModel(model)) { dispatch(modelSelected(model)); } if (isValidPositivePrompt(positive_conditioning)) { diff --git a/invokeai/frontend/web/src/features/parameters/store/actions.ts b/invokeai/frontend/web/src/features/parameters/store/actions.ts index 2fb56c0883..a74a2f633d 100644 --- a/invokeai/frontend/web/src/features/parameters/store/actions.ts +++ b/invokeai/frontend/web/src/features/parameters/store/actions.ts @@ -4,3 +4,5 @@ import { ImageDTO } from 'services/api/types'; export const initialImageSelected = createAction( 'generation/initialImageSelected' ); + +export const modelSelected = createAction('generation/modelSelected'); diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 960a41bb45..79372914b4 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -2,20 +2,23 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import { DEFAULT_SCHEDULER_NAME } from 'app/constants'; import { configChanged } from 'features/system/store/configSlice'; +import { setShouldShowAdvancedOptions } from 'features/ui/store/uiSlice'; import { clamp } from 'lodash-es'; import { ImageDTO } from 'services/api/types'; +import { clipSkipMap } from '../components/Parameters/Advanced/ParamClipSkip'; import { CfgScaleParam, HeightParam, - ModelParam, + MainModelParam, NegativePromptParam, PositivePromptParam, SchedulerParam, SeedParam, StepsParam, StrengthParam, - VAEParam, + VaeModelParam, WidthParam, + zMainModel, } from './parameterZodSchemas'; export interface GenerationState { @@ -47,10 +50,11 @@ export interface GenerationState { shouldUseSymmetry: boolean; horizontalSymmetrySteps: number; verticalSymmetrySteps: number; - model: ModelParam; - vae: VAEParam; + model: MainModelParam | null; + vae: VaeModelParam | null; seamlessXAxis: boolean; seamlessYAxis: boolean; + clipSkip: number; } export const initialGenerationState: GenerationState = { @@ -81,10 +85,11 @@ export const initialGenerationState: GenerationState = { shouldUseSymmetry: false, horizontalSymmetrySteps: 0, verticalSymmetrySteps: 0, - model: '', - vae: '', + model: null, + vae: null, seamlessXAxis: false, seamlessYAxis: false, + clipSkip: 0, }; const initialState: GenerationState = initialGenerationState; @@ -212,19 +217,46 @@ export const generationSlice = createSlice({ state.initialImage = { imageName: image_name, width, height }; }, modelSelected: (state, action: PayloadAction) => { + const [base_model, type, name] = action.payload.split('/'); + + state.model = zMainModel.parse({ + id: action.payload, + base_model, + name, + type, + }); + + // Clamp ClipSkip Based On Selected Model + const { maxClip } = clipSkipMap[state.model.base_model]; + state.clipSkip = clamp(state.clipSkip, 0, maxClip); + }, + modelChanged: (state, action: PayloadAction) => { state.model = action.payload; }, - vaeSelected: (state, action: PayloadAction) => { + vaeSelected: (state, action: PayloadAction) => { state.vae = action.payload; }, + setClipSkip: (state, action: PayloadAction) => { + state.clipSkip = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(configChanged, (state, action) => { const defaultModel = action.payload.sd?.defaultModel; + if (defaultModel && !state.model) { - state.model = defaultModel; + const [base_model, model_type, model_name] = defaultModel.split('/'); + state.model = zMainModel.parse({ + id: defaultModel, + name: model_name, + base_model, + }); } }); + builder.addCase(setShouldShowAdvancedOptions, (state, action) => { + const advancedOptionsStatus = action.payload; + if (!advancedOptionsStatus) state.clipSkip = 0; + }); }, }); @@ -260,11 +292,12 @@ export const { setHorizontalSymmetrySteps, setVerticalSymmetrySteps, initialImageChanged, - modelSelected, + modelChanged, vaeSelected, setShouldUseNoiseSettings, setSeamlessXAxis, setSeamlessYAxis, + setClipSkip, } = generationSlice.actions; export default generationSlice.reducer; diff --git a/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts b/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts index 12d77beeb9..074162e5ab 100644 --- a/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts +++ b/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts @@ -126,29 +126,63 @@ export type HeightParam = z.infer; export const isValidHeight = (val: unknown): val is HeightParam => zHeight.safeParse(val).success; +const zBaseModel = z.enum(['sd-1', 'sd-2']); + +export type BaseModelParam = z.infer; + /** * Zod schema for model parameter * TODO: Make this a dynamically generated enum? */ -export const zModel = z.string(); +export const zMainModel = z.object({ + id: z.string(), + name: z.string(), + base_model: zBaseModel, +}); + /** * Type alias for model parameter, inferred from its zod schema */ -export type ModelParam = z.infer; -/** - * Zod schema for VAE parameter - * TODO: Make this a dynamically generated enum? - */ -export const zVAE = z.string(); -/** - * Type alias for model parameter, inferred from its zod schema - */ -export type VAEParam = z.infer; +export type MainModelParam = z.infer; /** * Validates/type-guards a value as a model parameter */ -export const isValidModel = (val: unknown): val is ModelParam => - zModel.safeParse(val).success; +export const isValidMainModel = (val: unknown): val is MainModelParam => + zMainModel.safeParse(val).success; +/** + * Zod schema for VAE parameter + */ +export const zVaeModel = z.object({ + id: z.string(), + name: z.string(), + base_model: zBaseModel, +}); +/** + * Type alias for model parameter, inferred from its zod schema + */ +export type VaeModelParam = z.infer; +/** + * Validates/type-guards a value as a model parameter + */ +export const isValidVaeModel = (val: unknown): val is VaeModelParam => + zVaeModel.safeParse(val).success; +/** + * Zod schema for LoRA + */ +export const zLoRAModel = z.object({ + id: z.string(), + name: z.string(), + base_model: zBaseModel, +}); +/** + * Type alias for model parameter, inferred from its zod schema + */ +export type LoRAModelParam = z.infer; +/** + * Validates/type-guards a value as a model parameter + */ +export const isValidLoRAModel = (val: unknown): val is LoRAModelParam => + zLoRAModel.safeParse(val).success; /** * Zod schema for l2l strength parameter diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index 4eeee3e4c6..6b5aa830d9 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -3,10 +3,10 @@ import { useTranslation } from 'react-i18next'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIMantineSelect from 'common/components/IAIMantineSelect'; -import { modelSelected } from 'features/parameters/store/generationSlice'; import { SelectItem } from '@mantine/core'; import { RootState } from 'app/store/store'; +import { modelSelected } from 'features/parameters/store/actions'; import { forEach, isString } from 'lodash-es'; import { useGetMainModelsQuery } from 'services/api/endpoints/models'; @@ -19,7 +19,7 @@ const ModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const selectedModelId = useAppSelector( + const currentModel = useAppSelector( (state: RootState) => state.generation.model ); @@ -48,8 +48,8 @@ const ModelSelect = () => { }, [mainModels]); const selectedModel = useMemo( - () => mainModels?.entities[selectedModelId], - [mainModels?.entities, selectedModelId] + () => mainModels?.entities[currentModel?.id || ''], + [mainModels?.entities, currentModel] ); const handleChangeModel = useCallback( @@ -63,7 +63,13 @@ const ModelSelect = () => { ); useEffect(() => { - if (selectedModelId && mainModels?.ids.includes(selectedModelId)) { + if (isLoading) { + // return early here to avoid resetting model selection before we've loaded the available models + return; + } + + if (selectedModel && mainModels?.ids.includes(selectedModel?.id)) { + // the selected model is an available model, no need to change it return; } @@ -74,7 +80,7 @@ const ModelSelect = () => { } handleChangeModel(firstModel); - }, [handleChangeModel, mainModels?.ids, selectedModelId]); + }, [handleChangeModel, isLoading, mainModels?.ids, selectedModel]); return isLoading ? ( { 0 ? 'Select a model' : 'No models detected!'} data={data} error={data.length === 0} diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx index c9508bb5fe..43a487ba5a 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -30,6 +30,7 @@ import { } from 'features/system/store/systemSlice'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { + setShouldShowAdvancedOptions, setShouldShowProgressInViewer, setShouldUseCanvasBetaLayout, setShouldUseSliders, @@ -64,6 +65,7 @@ const selector = createSelector( shouldUseCanvasBetaLayout, shouldUseSliders, shouldShowProgressInViewer, + shouldShowAdvancedOptions, } = ui; return { @@ -76,6 +78,7 @@ const selector = createSelector( consoleLogLevel, shouldLogToConsole, shouldAntialiasProgressImage, + shouldShowAdvancedOptions, }; }, { @@ -87,6 +90,7 @@ type ConfigOptions = { shouldShowDeveloperSettings: boolean; shouldShowResetWebUiText: boolean; shouldShowBetaLayout: boolean; + shouldShowAdvancedOptionsSettings: boolean; }; type SettingsModalProps = { @@ -103,6 +107,8 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { const shouldShowDeveloperSettings = config?.shouldShowDeveloperSettings ?? true; const shouldShowResetWebUiText = config?.shouldShowResetWebUiText ?? true; + const shouldShowAdvancedOptionsSettings = + config?.shouldShowAdvancedOptionsSettings ?? true; useEffect(() => { if (!shouldShowDeveloperSettings) { @@ -132,6 +138,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { consoleLogLevel, shouldLogToConsole, shouldAntialiasProgressImage, + shouldShowAdvancedOptions, } = useAppSelector(selector); const handleClickResetWebUI = useCallback(() => { @@ -189,6 +196,15 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { dispatch(setShouldConfirmOnDelete(e.target.checked)) } /> + {shouldShowAdvancedOptionsSettings && ( + ) => + dispatch(setShouldShowAdvancedOptions(e.target.checked)) + } + /> + )} diff --git a/invokeai/frontend/web/src/features/system/components/VAESelect.tsx b/invokeai/frontend/web/src/features/system/components/VAESelect.tsx index 33901b5bef..50e82b0699 100644 --- a/invokeai/frontend/web/src/features/system/components/VAESelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/VAESelect.tsx @@ -9,7 +9,9 @@ import { forEach } from 'lodash-es'; import { useGetVaeModelsQuery } from 'services/api/endpoints/models'; import { RootState } from 'app/store/store'; +import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip'; import { vaeSelected } from 'features/parameters/store/generationSlice'; +import { zVaeModel } from 'features/parameters/store/parameterZodSchemas'; import { MODEL_TYPE_MAP } from './ModelSelect'; const VAESelect = () => { @@ -18,7 +20,11 @@ const VAESelect = () => { const { data: vaeModels } = useGetVaeModelsQuery(); - const selectedModelId = useAppSelector( + const currentMainModel = useAppSelector( + (state: RootState) => state.generation.model + ); + + const selectedVae = useAppSelector( (state: RootState) => state.generation.vae ); @@ -29,8 +35,8 @@ const VAESelect = () => { const data: SelectItem[] = [ { - value: 'auto', - label: 'Automatic', + value: 'default', + label: 'Default', group: 'Default', }, ]; @@ -40,46 +46,65 @@ const VAESelect = () => { return; } + const disabled = currentMainModel?.base_model !== model.base_model; + data.push({ value: id, label: model.name, group: MODEL_TYPE_MAP[model.base_model], + disabled, + tooltip: disabled + ? `Incompatible base model: ${model.base_model}` + : undefined, }); }); - return data; - }, [vaeModels]); + return data.sort((a, b) => (a.disabled && !b.disabled ? 1 : -1)); + }, [vaeModels, currentMainModel?.base_model]); - const selectedModel = useMemo( - () => vaeModels?.entities[selectedModelId], - [vaeModels?.entities, selectedModelId] + const selectedVaeModel = useMemo( + () => (selectedVae?.id ? vaeModels?.entities[selectedVae?.id] : null), + [vaeModels?.entities, selectedVae] ); const handleChangeModel = useCallback( (v: string | null) => { - if (!v) { + if (!v || v === 'default') { + dispatch(vaeSelected(null)); return; } - dispatch(vaeSelected(v)); + + const [base_model, type, name] = v.split('/'); + + const model = zVaeModel.parse({ + id: v, + name, + base_model, + }); + + dispatch(vaeSelected(model)); }, [dispatch] ); useEffect(() => { - if (selectedModelId && vaeModels?.ids.includes(selectedModelId)) { + if (selectedVae && vaeModels?.ids.includes(selectedVae.id)) { return; } - handleChangeModel('auto'); - }, [handleChangeModel, vaeModels?.ids, selectedModelId]); + dispatch(vaeSelected(null)); + }, [handleChangeModel, vaeModels?.ids, selectedVae, dispatch]); return ( ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/PinParametersPanelButton.tsx b/invokeai/frontend/web/src/features/ui/components/PinParametersPanelButton.tsx index 30cc1d2158..5d4cc4b9d7 100644 --- a/invokeai/frontend/web/src/features/ui/components/PinParametersPanelButton.tsx +++ b/invokeai/frontend/web/src/features/ui/components/PinParametersPanelButton.tsx @@ -33,12 +33,21 @@ const PinParametersPanelButton = (props: PinParametersPanelButtonProps) => { variant="ghost" size="sm" sx={{ - color: 'base.700', + color: 'base.500', _hover: { - color: 'base.550', + color: 'base.600', }, _active: { + color: 'base.700', + }, + _dark: { color: 'base.500', + _hover: { + color: 'base.400', + }, + _active: { + color: 'base.300', + }, }, ...sx, }} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx index 5f5c7ad46b..b333a0caf2 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx @@ -6,8 +6,7 @@ import IAICollapse from 'common/components/IAICollapse'; import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale'; import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight'; import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations'; -import ParamModelandVAE from 'features/parameters/components/Parameters/Core/ParamModelandVAE'; -import ParamScheduler from 'features/parameters/components/Parameters/Core/ParamScheduler'; +import ParamModelandVAEandScheduler from 'features/parameters/components/Parameters/Core/ParamModelandVAEandScheduler'; import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps'; import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth'; import ImageToImageFit from 'features/parameters/components/Parameters/ImageToImage/ImageToImageFit'; @@ -48,7 +47,7 @@ const ImageToImageTabCoreParameters = () => { > {shouldUseSliders ? ( <> - + @@ -65,8 +64,7 @@ const ImageToImageTabCoreParameters = () => {
- - + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx index 32b71d6187..16c0f44158 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx @@ -1,5 +1,6 @@ import ParamDynamicPromptsCollapse from 'features/dynamicPrompts/components/ParamDynamicPromptsCollapse'; import ParamLoraCollapse from 'features/lora/components/ParamLoraCollapse'; +import ParamAdvancedCollapse from 'features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse'; import ParamControlNetCollapse from 'features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse'; import ParamNegativeConditioning from 'features/parameters/components/Parameters/Core/ParamNegativeConditioning'; import ParamPositiveConditioning from 'features/parameters/components/Parameters/Core/ParamPositiveConditioning'; @@ -25,6 +26,7 @@ const ImageToImageTabParameters = () => { + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx index 9211e095ba..b007497db2 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx @@ -7,8 +7,7 @@ import IAICollapse from 'common/components/IAICollapse'; import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale'; import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight'; import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations'; -import ParamModelandVAE from 'features/parameters/components/Parameters/Core/ParamModelandVAE'; -import ParamScheduler from 'features/parameters/components/Parameters/Core/ParamScheduler'; +import ParamModelandVAEandScheduler from 'features/parameters/components/Parameters/Core/ParamModelandVAEandScheduler'; import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps'; import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth'; import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull'; @@ -44,7 +43,7 @@ const TextToImageTabCoreParameters = () => { > {shouldUseSliders ? ( <> - + @@ -61,8 +60,7 @@ const TextToImageTabCoreParameters = () => {
- - + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabParameters.tsx index 6291b69a8e..987f4ff0bc 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabParameters.tsx @@ -1,5 +1,6 @@ import ParamDynamicPromptsCollapse from 'features/dynamicPrompts/components/ParamDynamicPromptsCollapse'; import ParamLoraCollapse from 'features/lora/components/ParamLoraCollapse'; +import ParamAdvancedCollapse from 'features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse'; import ParamControlNetCollapse from 'features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse'; import ParamNegativeConditioning from 'features/parameters/components/Parameters/Core/ParamNegativeConditioning'; import ParamPositiveConditioning from 'features/parameters/components/Parameters/Core/ParamPositiveConditioning'; @@ -27,6 +28,7 @@ const TextToImageTabParameters = () => { + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx index 330cd8b31e..ecce61c218 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx @@ -8,8 +8,7 @@ import ParamBoundingBoxHeight from 'features/parameters/components/Parameters/Ca import ParamBoundingBoxWidth from 'features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth'; import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale'; import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations'; -import ParamModelandVAE from 'features/parameters/components/Parameters/Core/ParamModelandVAE'; -import ParamScheduler from 'features/parameters/components/Parameters/Core/ParamScheduler'; +import ParamModelandVAEandScheduler from 'features/parameters/components/Parameters/Core/ParamModelandVAEandScheduler'; import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps'; import ImageToImageStrength from 'features/parameters/components/Parameters/ImageToImage/ImageToImageStrength'; import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull'; @@ -45,7 +44,7 @@ const UnifiedCanvasCoreParameters = () => { > {shouldUseSliders ? ( <> - + @@ -62,8 +61,7 @@ const UnifiedCanvasCoreParameters = () => { - - + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx index 63ed4cc1cf..6c19a61372 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx @@ -1,5 +1,6 @@ import ParamDynamicPromptsCollapse from 'features/dynamicPrompts/components/ParamDynamicPromptsCollapse'; import ParamLoraCollapse from 'features/lora/components/ParamLoraCollapse'; +import ParamAdvancedCollapse from 'features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse'; import ParamInfillAndScalingCollapse from 'features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse'; import ParamSeamCorrectionCollapse from 'features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamCorrectionCollapse'; import ParamControlNetCollapse from 'features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse'; @@ -25,6 +26,7 @@ const UnifiedCanvasParameters = () => { + ); }; diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index 861bf49405..04fee42126 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -20,6 +20,7 @@ export const initialUIState: UIState = { shouldHidePreview: false, shouldShowProgressInViewer: true, shouldShowEmbeddingPicker: false, + shouldShowAdvancedOptions: false, favoriteSchedulers: [], }; @@ -100,6 +101,9 @@ export const uiSlice = createSlice({ toggleEmbeddingPicker: (state) => { state.shouldShowEmbeddingPicker = !state.shouldShowEmbeddingPicker; }, + setShouldShowAdvancedOptions: (state, action: PayloadAction) => { + state.shouldShowAdvancedOptions = action.payload; + }, }, extraReducers(builder) { builder.addCase(initialImageChanged, (state) => { @@ -127,6 +131,7 @@ export const { setShouldShowProgressInViewer, favoriteSchedulersChanged, toggleEmbeddingPicker, + setShouldShowAdvancedOptions, } = uiSlice.actions; export default uiSlice.reducer; diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts index ad0250e56d..2356446030 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts @@ -28,5 +28,6 @@ export interface UIState { shouldShowGallery: boolean; shouldShowProgressInViewer: boolean; shouldShowEmbeddingPicker: boolean; + shouldShowAdvancedOptions: boolean; favoriteSchedulers: SchedulerParam[]; } diff --git a/invokeai/frontend/web/src/services/api/schema.d.ts b/invokeai/frontend/web/src/services/api/schema.d.ts index d7e50d004e..5add18a73f 100644 --- a/invokeai/frontend/web/src/services/api/schema.d.ts +++ b/invokeai/frontend/web/src/services/api/schema.d.ts @@ -75,25 +75,37 @@ export type paths = { * @description Gets a list of models */ get: operations["list_models"]; - /** - * Update Model - * @description Add Model - */ - post: operations["update_model"]; - }; - "/api/v1/models/import": { /** * Import Model * @description Add a model using its local path, repo_id, or remote URL */ post: operations["import_model"]; }; - "/api/v1/models/{model_name}": { + "/api/v1/models/{base_model}/{model_type}/{model_name}": { /** * Delete Model * @description Delete Model */ delete: operations["del_model"]; + /** + * Update Model + * @description Add Model + */ + patch: operations["update_model"]; + }; + "/api/v1/models/convert/{base_model}/{model_type}/{model_name}": { + /** + * Convert Model + * @description Convert a checkpoint model into a diffusers model + */ + put: operations["convert_model"]; + }; + "/api/v1/models/merge/{base_model}": { + /** + * Merge Models + * @description Convert a checkpoint model into a diffusers model + */ + put: operations["merge_models"]; }; "/api/v1/images/": { /** @@ -234,23 +246,6 @@ export type components = { */ b?: number; }; - /** AddModelResult */ - AddModelResult: { - /** - * Name - * @description The name of the model after import - */ - name: string; - /** @description The type of model */ - model_type: components["schemas"]["ModelType"]; - /** @description The base model */ - base_model: components["schemas"]["BaseModelType"]; - /** - * Config - * @description The configuration of the model - */ - config: components["schemas"]["ModelConfigBase"]; - }; /** * BaseModelType * @description An enumeration. @@ -324,6 +319,48 @@ export type components = { */ image_name: string; }; + /** Body_import_model */ + Body_import_model: { + /** + * Location + * @description A model path, repo_id or URL to import + */ + location: string; + /** + * Prediction Type + * @description Prediction type for SDv2 checkpoint files + * @default v_prediction + * @enum {string} + */ + prediction_type?: "v_prediction" | "epsilon" | "sample"; + }; + /** Body_merge_models */ + Body_merge_models: { + /** + * Model Names + * @description model name + */ + model_names: (string)[]; + /** + * Merged Model Name + * @description Name of destination model + */ + merged_model_name: string; + /** + * Alpha + * @description Alpha weighting strength to apply to 2d and 3d models + * @default 0.5 + */ + alpha?: number; + /** @description Interpolation method */ + interp: components["schemas"]["MergeInterpolationMethod"]; + /** + * Force + * @description Force merging of models created with different versions of diffusers + * @default false + */ + force?: boolean; + }; /** Body_remove_board_image */ Body_remove_board_image: { /** @@ -343,7 +380,7 @@ export type components = { * File * Format: binary */ - file: string; + file: Blob; }; /** * CannyImageProcessorInvocation @@ -385,55 +422,6 @@ export type components = { */ high_threshold?: number; }; - /** CkptModelInfo */ - CkptModelInfo: { - /** - * Description - * @description A description of the model - */ - description?: string; - /** - * Model Name - * @description The name of the model - */ - model_name: string; - /** - * Model Type - * @description The type of the model - */ - model_type: string; - /** - * Format - * @default ckpt - * @enum {string} - */ - format?: "ckpt"; - /** - * Config - * @description The path to the model config - */ - config: string; - /** - * Weights - * @description The path to the model weights - */ - weights: string; - /** - * Vae - * @description The path to the model VAE - */ - vae: string; - /** - * Width - * @description The width of the model - */ - width?: number; - /** - * Height - * @description The height of the model - */ - height?: number; - }; /** ClipField */ ClipField: { /** @@ -446,12 +434,68 @@ export type components = { * @description Info to load text_encoder submodel */ text_encoder: components["schemas"]["ModelInfo"]; + /** + * Skipped Layers + * @description Number of skipped layers in text_encoder + */ + skipped_layers: number; /** * Loras * @description Loras to apply on model loading */ loras: (components["schemas"]["LoraInfo"])[]; }; + /** + * ClipSkipInvocation + * @description Skip layers in clip text_encoder model. + */ + ClipSkipInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default clip_skip + * @enum {string} + */ + type?: "clip_skip"; + /** + * Clip + * @description Clip to use + */ + clip?: components["schemas"]["ClipField"]; + /** + * Skipped Layers + * @description Number of layers to skip in text_encoder + * @default 0 + */ + skipped_layers?: number; + }; + /** + * ClipSkipInvocationOutput + * @description Clip skip node output + */ + ClipSkipInvocationOutput: { + /** + * Type + * @default clip_skip_output + * @enum {string} + */ + type?: "clip_skip_output"; + /** + * Clip + * @description Clip with skipped layers + */ + clip?: components["schemas"]["ClipField"]; + }; /** * CollectInvocation * @description Collects values into a collection @@ -780,19 +824,6 @@ export type components = { */ control?: components["schemas"]["ControlField"]; }; - /** CreateModelRequest */ - CreateModelRequest: { - /** - * Name - * @description The name of the model - */ - name: string; - /** - * Info - * @description The model info - */ - info: components["schemas"]["CkptModelInfo"] | components["schemas"]["DiffusersModelInfo"]; - }; /** * CvInpaintInvocation * @description Simple inpaint using opencv. @@ -826,45 +857,6 @@ export type components = { */ mask?: components["schemas"]["ImageField"]; }; - /** DiffusersModelInfo */ - DiffusersModelInfo: { - /** - * Description - * @description A description of the model - */ - description?: string; - /** - * Model Name - * @description The name of the model - */ - model_name: string; - /** - * Model Type - * @description The type of the model - */ - model_type: string; - /** - * Format - * @default folder - * @enum {string} - */ - format?: "folder"; - /** - * Vae - * @description The VAE repo to use for this model - */ - vae?: components["schemas"]["VaeRepo"]; - /** - * Repo Id - * @description The repo ID to use for this model - */ - repo_id?: string; - /** - * Path - * @description The path to the model - */ - path?: string; - }; /** * DivideInvocation * @description Divides two numbers @@ -1054,7 +1046,7 @@ export type components = { * @description The nodes in this graph */ nodes?: { - [key: string]: (components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]) | undefined; + [key: string]: (components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]) | undefined; }; /** * Edges @@ -1097,7 +1089,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: (components["schemas"]["ImageOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["PromptOutput"] | components["schemas"]["PromptCollectionOutput"] | components["schemas"]["CompelOutput"] | components["schemas"]["IntOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["IntCollectionOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]) | undefined; + [key: string]: (components["schemas"]["ImageOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["PromptOutput"] | components["schemas"]["PromptCollectionOutput"] | components["schemas"]["CompelOutput"] | components["schemas"]["ClipSkipInvocationOutput"] | components["schemas"]["IntOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["IntCollectionOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]) | undefined; }; /** * Errors @@ -1999,24 +1991,6 @@ export type components = { */ thumbnail_url: string; }; - /** ImportModelResponse */ - ImportModelResponse: { - /** - * Name - * @description The name of the imported model - */ - name: string; - /** - * Info - * @description The model info - */ - info: components["schemas"]["AddModelResult"]; - /** - * Status - * @description The status of the API response - */ - status: string; - }; /** * InfillColorInvocation * @description Infills transparent areas of an image with a solid color @@ -2964,6 +2938,12 @@ export type components = { */ min_confidence?: number; }; + /** + * MergeInterpolationMethod + * @description An enumeration. + * @enum {string} + */ + MergeInterpolationMethod: "weighted_sum" | "sigmoid" | "inv_sigmoid" | "add_difference"; /** * MidasDepthImageProcessorInvocation * @description Applies Midas depth processing to image @@ -3056,16 +3036,6 @@ export type components = { */ thr_d?: number; }; - /** ModelConfigBase */ - ModelConfigBase: { - /** Path */ - path: string; - /** Description */ - description?: string; - /** Model Format */ - model_format?: string; - error?: components["schemas"]["ModelError"]; - }; /** * ModelError * @description An enumeration. @@ -3128,7 +3098,7 @@ export type components = { /** ModelsList */ ModelsList: { /** Models */ - models: (components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"])[]; + models: (components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"])[]; }; /** * MultiplyInvocation @@ -4406,24 +4376,6 @@ export type components = { * @enum {string} */ VaeModelFormat: "checkpoint" | "diffusers"; - /** VaeRepo */ - VaeRepo: { - /** - * Repo Id - * @description The repo ID to use for this VAE - */ - repo_id: string; - /** - * Path - * @description The path to the VAE - */ - path?: string; - /** - * Subfolder - * @description The subfolder to use for this VAE - */ - subfolder?: string; - }; /** ValidationError */ ValidationError: { /** Location */ @@ -4461,18 +4413,18 @@ export type components = { */ image?: components["schemas"]["ImageField"]; }; - /** - * StableDiffusion1ModelFormat - * @description An enumeration. - * @enum {string} - */ - StableDiffusion1ModelFormat: "checkpoint" | "diffusers"; /** * StableDiffusion2ModelFormat * @description An enumeration. * @enum {string} */ StableDiffusion2ModelFormat: "checkpoint" | "diffusers"; + /** + * StableDiffusion1ModelFormat + * @description An enumeration. + * @enum {string} + */ + StableDiffusion1ModelFormat: "checkpoint" | "diffusers"; }; responses: never; parameters: never; @@ -4583,7 +4535,7 @@ export type operations = { }; requestBody: { content: { - "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; + "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; }; }; responses: { @@ -4620,7 +4572,7 @@ export type operations = { }; requestBody: { content: { - "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; + "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; }; }; responses: { @@ -4839,59 +4791,35 @@ export type operations = { }; }; }; - /** - * Update Model - * @description Add Model - */ - update_model: { - requestBody: { - content: { - "application/json": components["schemas"]["CreateModelRequest"]; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; /** * Import Model * @description Add a model using its local path, repo_id, or remote URL */ import_model: { - parameters: { - query: { - /** @description A model path, repo_id or URL to import */ - name: string; - /** @description Prediction type for SDv2 checkpoint files */ - prediction_type?: "v_prediction" | "epsilon" | "sample"; + requestBody: { + content: { + "application/json": components["schemas"]["Body_import_model"]; }; }; responses: { /** @description The model imported successfully */ 201: { content: { - "application/json": components["schemas"]["ImportModelResponse"]; + "application/json": components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; }; }; /** @description The model could not be found */ 404: never; + /** @description There is already a model corresponding to this path or repo_id */ + 409: never; /** @description Validation Error */ 422: { content: { "application/json": components["schemas"]["HTTPValidationError"]; }; }; + /** @description The model appeared to import successfully, but could not be found in the model manager */ + 424: never; }; }; /** @@ -4901,6 +4829,11 @@ export type operations = { del_model: { parameters: { path: { + /** @description Base model */ + base_model: components["schemas"]["BaseModelType"]; + /** @description The type of model */ + model_type: components["schemas"]["ModelType"]; + /** @description model name */ model_name: string; }; }; @@ -4923,6 +4856,114 @@ export type operations = { }; }; }; + /** + * Update Model + * @description Add Model + */ + update_model: { + parameters: { + path: { + /** @description Base model */ + base_model: components["schemas"]["BaseModelType"]; + /** @description The type of model */ + model_type: components["schemas"]["ModelType"]; + /** @description model name */ + model_name: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; + }; + }; + responses: { + /** @description The model was updated successfully */ + 200: { + content: { + "application/json": components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; + }; + }; + /** @description Bad request */ + 400: never; + /** @description The model could not be found */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Convert Model + * @description Convert a checkpoint model into a diffusers model + */ + convert_model: { + parameters: { + path: { + /** @description Base model */ + base_model: components["schemas"]["BaseModelType"]; + /** @description The type of model */ + model_type: components["schemas"]["ModelType"]; + /** @description model name */ + model_name: string; + }; + }; + responses: { + /** @description Model converted successfully */ + 200: { + content: { + "application/json": components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; + }; + }; + /** @description Bad request */ + 400: never; + /** @description Model not found */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Merge Models + * @description Convert a checkpoint model into a diffusers model + */ + merge_models: { + parameters: { + path: { + /** @description Base model */ + base_model: components["schemas"]["BaseModelType"]; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_merge_models"]; + }; + }; + responses: { + /** @description Model converted successfully */ + 200: { + content: { + "application/json": components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"]; + }; + }; + /** @description Incompatible models */ + 400: never; + /** @description One or more models not found */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; /** * List Images With Metadata * @description Gets a list of images diff --git a/invokeai/frontend/web/src/services/api/thunks/image.ts b/invokeai/frontend/web/src/services/api/thunks/image.ts index d6e502bc54..71eedb0327 100644 --- a/invokeai/frontend/web/src/services/api/thunks/image.ts +++ b/invokeai/frontend/web/src/services/api/thunks/image.ts @@ -157,8 +157,6 @@ export const imageUploaded = createAppAsyncThunk< session_id, } = arg; const { post } = $client.get(); - const formData = new FormData(); - formData.append('file', file); const { data, error, response } = await post('/api/v1/images/', { params: { query: { @@ -167,10 +165,12 @@ export const imageUploaded = createAppAsyncThunk< session_id, }, }, - // TODO: Proper handling of `multipart/form-data` is coming soon, will fix type issues - // https://github.com/drwpow/openapi-typescript/issues/1123 - // @ts-ignore - body: formData, + body: { file }, + bodySerializer: (body) => { + const formData = new FormData(); + formData.append('file', body.file); + return formData; + }, }); if (error) { diff --git a/invokeai/frontend/web/tsconfig.json b/invokeai/frontend/web/tsconfig.json index 6f74cf3681..e722e2f9a8 100644 --- a/invokeai/frontend/web/tsconfig.json +++ b/invokeai/frontend/web/tsconfig.json @@ -23,5 +23,11 @@ }, "include": ["src/**/*.ts", "src/**/*.tsx", "*.d.ts"], "exclude": ["src/services/fixtures/*", "node_modules", "dist"], - "references": [{ "path": "./tsconfig.node.json" }] + "references": [{ "path": "./tsconfig.node.json" }], + "ts-node": { + "compilerOptions": { + "jsx": "preserve" + }, + "esm": true + } } diff --git a/invokeai/frontend/web/yarn.lock b/invokeai/frontend/web/yarn.lock index 9bca858f2b..99819d37dc 100644 --- a/invokeai/frontend/web/yarn.lock +++ b/invokeai/frontend/web/yarn.lock @@ -5386,10 +5386,10 @@ open@^8.4.0: is-docker "^2.1.1" is-wsl "^2.2.0" -openapi-fetch@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/openapi-fetch/-/openapi-fetch-0.4.0.tgz#45c368321ba6c15bc2e168e7dc3fbf322e9cca6d" - integrity sha512-4lzZtH5J1ZH9EXfmpcmKi0gOgjy0hc6BAcucAdCmLHY6jZopMeGP51vD3Cd4rE1nTFMfJzmYDc8ar0+364gBVw== +openapi-fetch@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/openapi-fetch/-/openapi-fetch-0.6.1.tgz#90d785ead213b82beb8f094a756ad9320ba28b32" + integrity sha512-CGWPqqtL31uC2e4eEU9NHoqYMXnJ7Jk4H/4Yguil4tO22MIZi91hlQJ/51E8CiaKdSTODh03yF4ndjIOABVHUw== openapi-types@^12.1.3: version "12.1.3"