mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into stalker7779/modular_rescale_cfg
This commit is contained in:
commit
d014dc94fd
@ -1,3 +1,5 @@
|
||||
from typing import Callable
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from PIL import Image
|
||||
@ -21,7 +23,7 @@ from invokeai.backend.tiles.tiles import calc_tiles_min_overlap
|
||||
from invokeai.backend.tiles.utils import TBLR, Tile
|
||||
|
||||
|
||||
@invocation("spandrel_image_to_image", title="Image-to-Image", tags=["upscale"], category="upscale", version="1.1.0")
|
||||
@invocation("spandrel_image_to_image", title="Image-to-Image", tags=["upscale"], category="upscale", version="1.2.0")
|
||||
class SpandrelImageToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||
"""Run any spandrel image-to-image model (https://github.com/chaiNNer-org/spandrel)."""
|
||||
|
||||
@ -34,8 +36,19 @@ class SpandrelImageToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||
tile_size: int = InputField(
|
||||
default=512, description="The tile size for tiled image-to-image. Set to 0 to disable tiling."
|
||||
)
|
||||
scale: float = InputField(
|
||||
default=4.0,
|
||||
gt=0.0,
|
||||
le=16.0,
|
||||
description="The final scale of the output image. If the model does not upscale the image, this will be ignored.",
|
||||
)
|
||||
fit_to_multiple_of_8: bool = InputField(
|
||||
default=False,
|
||||
description="If true, the output image will be resized to the nearest multiple of 8 in both dimensions.",
|
||||
)
|
||||
|
||||
def _scale_tile(self, tile: Tile, scale: int) -> Tile:
|
||||
@classmethod
|
||||
def scale_tile(cls, tile: Tile, scale: int) -> Tile:
|
||||
return Tile(
|
||||
coords=TBLR(
|
||||
top=tile.coords.top * scale,
|
||||
@ -51,20 +64,22 @@ class SpandrelImageToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||
),
|
||||
)
|
||||
|
||||
@torch.inference_mode()
|
||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||
# Images are converted to RGB, because most models don't support an alpha channel. In the future, we may want to
|
||||
# revisit this.
|
||||
image = context.images.get_pil(self.image.image_name, mode="RGB")
|
||||
|
||||
@classmethod
|
||||
def upscale_image(
|
||||
cls,
|
||||
image: Image.Image,
|
||||
tile_size: int,
|
||||
spandrel_model: SpandrelImageToImageModel,
|
||||
is_canceled: Callable[[], bool],
|
||||
) -> Image.Image:
|
||||
# Compute the image tiles.
|
||||
if self.tile_size > 0:
|
||||
if tile_size > 0:
|
||||
min_overlap = 20
|
||||
tiles = calc_tiles_min_overlap(
|
||||
image_height=image.height,
|
||||
image_width=image.width,
|
||||
tile_height=self.tile_size,
|
||||
tile_width=self.tile_size,
|
||||
tile_height=tile_size,
|
||||
tile_width=tile_size,
|
||||
min_overlap=min_overlap,
|
||||
)
|
||||
else:
|
||||
@ -85,60 +100,123 @@ class SpandrelImageToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||
# Prepare input image for inference.
|
||||
image_tensor = SpandrelImageToImageModel.pil_to_tensor(image)
|
||||
|
||||
# Load the model.
|
||||
spandrel_model_info = context.models.load(self.image_to_image_model)
|
||||
# Scale the tiles for re-assembling the final image.
|
||||
scale = spandrel_model.scale
|
||||
scaled_tiles = [cls.scale_tile(tile, scale=scale) for tile in tiles]
|
||||
|
||||
# Prepare the output tensor.
|
||||
_, channels, height, width = image_tensor.shape
|
||||
output_tensor = torch.zeros(
|
||||
(height * scale, width * scale, channels), dtype=torch.uint8, device=torch.device("cpu")
|
||||
)
|
||||
|
||||
image_tensor = image_tensor.to(device=spandrel_model.device, dtype=spandrel_model.dtype)
|
||||
|
||||
# Run the model on each tile.
|
||||
with spandrel_model_info as spandrel_model:
|
||||
assert isinstance(spandrel_model, SpandrelImageToImageModel)
|
||||
for tile, scaled_tile in tqdm(list(zip(tiles, scaled_tiles, strict=True)), desc="Upscaling Tiles"):
|
||||
# Exit early if the invocation has been canceled.
|
||||
if is_canceled():
|
||||
raise CanceledException
|
||||
|
||||
# Scale the tiles for re-assembling the final image.
|
||||
scale = spandrel_model.scale
|
||||
scaled_tiles = [self._scale_tile(tile, scale=scale) for tile in tiles]
|
||||
# Extract the current tile from the input tensor.
|
||||
input_tile = image_tensor[
|
||||
:, :, tile.coords.top : tile.coords.bottom, tile.coords.left : tile.coords.right
|
||||
].to(device=spandrel_model.device, dtype=spandrel_model.dtype)
|
||||
|
||||
# Prepare the output tensor.
|
||||
_, channels, height, width = image_tensor.shape
|
||||
output_tensor = torch.zeros(
|
||||
(height * scale, width * scale, channels), dtype=torch.uint8, device=torch.device("cpu")
|
||||
)
|
||||
# Run the model on the tile.
|
||||
output_tile = spandrel_model.run(input_tile)
|
||||
|
||||
image_tensor = image_tensor.to(device=spandrel_model.device, dtype=spandrel_model.dtype)
|
||||
# Convert the output tile into the output tensor's format.
|
||||
# (N, C, H, W) -> (C, H, W)
|
||||
output_tile = output_tile.squeeze(0)
|
||||
# (C, H, W) -> (H, W, C)
|
||||
output_tile = output_tile.permute(1, 2, 0)
|
||||
output_tile = output_tile.clamp(0, 1)
|
||||
output_tile = (output_tile * 255).to(dtype=torch.uint8, device=torch.device("cpu"))
|
||||
|
||||
for tile, scaled_tile in tqdm(list(zip(tiles, scaled_tiles, strict=True)), desc="Upscaling Tiles"):
|
||||
# Exit early if the invocation has been canceled.
|
||||
if context.util.is_canceled():
|
||||
raise CanceledException
|
||||
|
||||
# Extract the current tile from the input tensor.
|
||||
input_tile = image_tensor[
|
||||
:, :, tile.coords.top : tile.coords.bottom, tile.coords.left : tile.coords.right
|
||||
].to(device=spandrel_model.device, dtype=spandrel_model.dtype)
|
||||
|
||||
# Run the model on the tile.
|
||||
output_tile = spandrel_model.run(input_tile)
|
||||
|
||||
# Convert the output tile into the output tensor's format.
|
||||
# (N, C, H, W) -> (C, H, W)
|
||||
output_tile = output_tile.squeeze(0)
|
||||
# (C, H, W) -> (H, W, C)
|
||||
output_tile = output_tile.permute(1, 2, 0)
|
||||
output_tile = output_tile.clamp(0, 1)
|
||||
output_tile = (output_tile * 255).to(dtype=torch.uint8, device=torch.device("cpu"))
|
||||
|
||||
# Merge the output tile into the output tensor.
|
||||
# We only keep half of the overlap on the top and left side of the tile. We do this in case there are
|
||||
# edge artifacts. We don't bother with any 'blending' in the current implementation - for most upscalers
|
||||
# it seems unnecessary, but we may find a need in the future.
|
||||
top_overlap = scaled_tile.overlap.top // 2
|
||||
left_overlap = scaled_tile.overlap.left // 2
|
||||
output_tensor[
|
||||
scaled_tile.coords.top + top_overlap : scaled_tile.coords.bottom,
|
||||
scaled_tile.coords.left + left_overlap : scaled_tile.coords.right,
|
||||
:,
|
||||
] = output_tile[top_overlap:, left_overlap:, :]
|
||||
# Merge the output tile into the output tensor.
|
||||
# We only keep half of the overlap on the top and left side of the tile. We do this in case there are
|
||||
# edge artifacts. We don't bother with any 'blending' in the current implementation - for most upscalers
|
||||
# it seems unnecessary, but we may find a need in the future.
|
||||
top_overlap = scaled_tile.overlap.top // 2
|
||||
left_overlap = scaled_tile.overlap.left // 2
|
||||
output_tensor[
|
||||
scaled_tile.coords.top + top_overlap : scaled_tile.coords.bottom,
|
||||
scaled_tile.coords.left + left_overlap : scaled_tile.coords.right,
|
||||
:,
|
||||
] = output_tile[top_overlap:, left_overlap:, :]
|
||||
|
||||
# Convert the output tensor to a PIL image.
|
||||
np_image = output_tensor.detach().numpy().astype(np.uint8)
|
||||
pil_image = Image.fromarray(np_image)
|
||||
|
||||
return pil_image
|
||||
|
||||
@torch.inference_mode()
|
||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||
# Images are converted to RGB, because most models don't support an alpha channel. In the future, we may want to
|
||||
# revisit this.
|
||||
image = context.images.get_pil(self.image.image_name, mode="RGB")
|
||||
|
||||
# Load the model.
|
||||
spandrel_model_info = context.models.load(self.image_to_image_model)
|
||||
|
||||
# The target size of the image, determined by the provided scale. We'll run the upscaler until we hit this size.
|
||||
# Later, we may mutate this value if the model doesn't upscale the image or if the user requested a multiple of 8.
|
||||
target_width = int(image.width * self.scale)
|
||||
target_height = int(image.height * self.scale)
|
||||
|
||||
# Do the upscaling.
|
||||
with spandrel_model_info as spandrel_model:
|
||||
assert isinstance(spandrel_model, SpandrelImageToImageModel)
|
||||
|
||||
# First pass of upscaling. Note: `pil_image` will be mutated.
|
||||
pil_image = self.upscale_image(image, self.tile_size, spandrel_model, context.util.is_canceled)
|
||||
|
||||
# Some models don't upscale the image, but we have no way to know this in advance. We'll check if the model
|
||||
# upscaled the image and run the loop below if it did. We'll require the model to upscale both dimensions
|
||||
# to be considered an upscale model.
|
||||
is_upscale_model = pil_image.width > image.width and pil_image.height > image.height
|
||||
|
||||
if is_upscale_model:
|
||||
# This is an upscale model, so we should keep upscaling until we reach the target size.
|
||||
iterations = 1
|
||||
while pil_image.width < target_width or pil_image.height < target_height:
|
||||
pil_image = self.upscale_image(pil_image, self.tile_size, spandrel_model, context.util.is_canceled)
|
||||
iterations += 1
|
||||
|
||||
# Sanity check to prevent excessive or infinite loops. All known upscaling models are at least 2x.
|
||||
# Our max scale is 16x, so with a 2x model, we should never exceed 16x == 2^4 -> 4 iterations.
|
||||
# We'll allow one extra iteration "just in case" and bail at 5 upscaling iterations. In practice,
|
||||
# we should never reach this limit.
|
||||
if iterations >= 5:
|
||||
context.logger.warning(
|
||||
"Upscale loop reached maximum iteration count of 5, stopping upscaling early."
|
||||
)
|
||||
break
|
||||
else:
|
||||
# This model doesn't upscale the image. We should ignore the scale parameter, modifying the output size
|
||||
# to be the same as the processed image size.
|
||||
|
||||
# The output size is now the size of the processed image.
|
||||
target_width = pil_image.width
|
||||
target_height = pil_image.height
|
||||
|
||||
# Warn the user if they requested a scale greater than 1.
|
||||
if self.scale > 1:
|
||||
context.logger.warning(
|
||||
"Model does not increase the size of the image, but a greater scale than 1 was requested. Image will not be scaled."
|
||||
)
|
||||
|
||||
# We may need to resize the image to a multiple of 8. Use floor division to ensure we don't scale the image up
|
||||
# in the final resize
|
||||
if self.fit_to_multiple_of_8:
|
||||
target_width = int(target_width // 8 * 8)
|
||||
target_height = int(target_height // 8 * 8)
|
||||
|
||||
# Final resize. Per PIL documentation, Lanczos provides the best quality for both upscale and downscale.
|
||||
# See: https://pillow.readthedocs.io/en/stable/handbook/concepts.html#filters-comparison-table
|
||||
pil_image = pil_image.resize((target_width, target_height), resample=Image.Resampling.LANCZOS)
|
||||
|
||||
image_dto = context.images.save(image=pil_image)
|
||||
return ImageOutput.build(image_dto)
|
||||
|
@ -1027,6 +1027,7 @@
|
||||
"imageActions": "Image Actions",
|
||||
"sendToImg2Img": "Send to Image to Image",
|
||||
"sendToUnifiedCanvas": "Send To Unified Canvas",
|
||||
"sendToUpscale": "Send To Upscale",
|
||||
"showOptionsPanel": "Show Side Panel (O or T)",
|
||||
"shuffle": "Shuffle Seed",
|
||||
"steps": "Steps",
|
||||
@ -1640,6 +1641,19 @@
|
||||
"layers_one": "Layer",
|
||||
"layers_other": "Layers"
|
||||
},
|
||||
"upscaling": {
|
||||
"creativity": "Creativity",
|
||||
"structure": "Structure",
|
||||
"upscaleModel": "Upscale Model",
|
||||
"scale": "Scale",
|
||||
"missingModelsWarning": "Visit the <LinkComponent>Model Manager</LinkComponent> to install the required models:",
|
||||
"mainModelDesc": "Main model (SD1.5 or SDXL architecture)",
|
||||
"tileControlNetModelDesc": "Tile ControlNet model for the chosen main model architecture",
|
||||
"upscaleModelDesc": "Upscale (image to image) model",
|
||||
"missingUpscaleInitialImage": "Missing initial image for upscaling",
|
||||
"missingUpscaleModel": "Missing upscale model",
|
||||
"missingTileControlNetModel": "No valid tile ControlNet models installed"
|
||||
},
|
||||
"ui": {
|
||||
"tabs": {
|
||||
"generation": "Generation",
|
||||
@ -1651,7 +1665,9 @@
|
||||
"models": "Models",
|
||||
"modelsTab": "$t(ui.tabs.models) $t(common.tab)",
|
||||
"queue": "Queue",
|
||||
"queueTab": "$t(ui.tabs.queue) $t(common.tab)"
|
||||
"queueTab": "$t(ui.tabs.queue) $t(common.tab)",
|
||||
"upscaling": "Upscaling",
|
||||
"upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ import { addWorkflowLoadRequestedListener } from 'app/store/middleware/listenerM
|
||||
import type { AppDispatch, RootState } from 'app/store/store';
|
||||
|
||||
import { addArchivedOrDeletedBoardListener } from './listeners/addArchivedOrDeletedBoardListener';
|
||||
import { addEnqueueRequestedUpscale } from './listeners/enqueueRequestedUpscale';
|
||||
|
||||
export const listenerMiddleware = createListenerMiddleware();
|
||||
|
||||
@ -85,6 +86,7 @@ addGalleryOffsetChangedListener(startAppListening);
|
||||
addEnqueueRequestedCanvasListener(startAppListening);
|
||||
addEnqueueRequestedNodes(startAppListening);
|
||||
addEnqueueRequestedLinear(startAppListening);
|
||||
addEnqueueRequestedUpscale(startAppListening);
|
||||
addAnyEnqueuedListener(startAppListening);
|
||||
addBatchEnqueuedListener(startAppListening);
|
||||
|
||||
|
@ -0,0 +1,36 @@
|
||||
import { enqueueRequested } from 'app/store/actions';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
|
||||
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
||||
import { buildMultidiffusionUpscaleGraph } from 'features/nodes/util/graph/buildMultidiffusionUpscaleGraph';
|
||||
import { queueApi } from 'services/api/endpoints/queue';
|
||||
|
||||
export const addEnqueueRequestedUpscale = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||
enqueueRequested.match(action) && action.payload.tabName === 'upscaling',
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
const state = getState();
|
||||
const { shouldShowProgressInViewer } = state.ui;
|
||||
const { prepend } = action.payload;
|
||||
|
||||
const graph = await buildMultidiffusionUpscaleGraph(state);
|
||||
|
||||
const batchConfig = prepareLinearUIBatch(state, graph, prepend);
|
||||
|
||||
const req = dispatch(
|
||||
queueApi.endpoints.enqueueBatch.initiate(batchConfig, {
|
||||
fixedCacheKey: 'enqueueBatch',
|
||||
})
|
||||
);
|
||||
try {
|
||||
await req.unwrap();
|
||||
if (shouldShowProgressInViewer) {
|
||||
dispatch(isImageViewerOpenChanged(true));
|
||||
}
|
||||
} finally {
|
||||
req.reset();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
@ -23,6 +23,7 @@ import {
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
|
||||
export const dndDropped = createAction<{
|
||||
@ -243,6 +244,20 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Image dropped on upscale initial image
|
||||
*/
|
||||
if (
|
||||
overData.actionType === 'SET_UPSCALE_INITIAL_IMAGE' &&
|
||||
activeData.payloadType === 'IMAGE_DTO' &&
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
const { imageDTO } = activeData.payload;
|
||||
|
||||
dispatch(upscaleInitialImageChanged(imageDTO));
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple images dropped on user board
|
||||
*/
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { omit } from 'lodash-es';
|
||||
@ -89,6 +90,15 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
||||
return;
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_UPSCALE_INITIAL_IMAGE') {
|
||||
dispatch(upscaleInitialImageChanged(imageDTO));
|
||||
toast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: 'set as upscale initial image',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_CONTROL_ADAPTER_IMAGE') {
|
||||
const { id } = postUploadAction;
|
||||
dispatch(
|
||||
|
@ -10,6 +10,7 @@ import { heightChanged, widthChanged } from 'features/controlLayers/store/contro
|
||||
import { loraRemoved } from 'features/lora/store/loraSlice';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice';
|
||||
import { upscaleModelChanged } from 'features/parameters/store/upscaleSlice';
|
||||
import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
|
||||
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import { refinerModelChanged } from 'features/sdxl/store/sdxlSlice';
|
||||
@ -17,7 +18,12 @@ import { forEach } from 'lodash-es';
|
||||
import type { Logger } from 'roarr';
|
||||
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
||||
import type { AnyModelConfig } from 'services/api/types';
|
||||
import { isNonRefinerMainModelConfig, isRefinerMainModelModelConfig, isVAEModelConfig } from 'services/api/types';
|
||||
import {
|
||||
isNonRefinerMainModelConfig,
|
||||
isRefinerMainModelModelConfig,
|
||||
isSpandrelImageToImageModelConfig,
|
||||
isVAEModelConfig,
|
||||
} from 'services/api/types';
|
||||
|
||||
export const addModelsLoadedListener = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
@ -36,6 +42,7 @@ export const addModelsLoadedListener = (startAppListening: AppStartListening) =>
|
||||
handleVAEModels(models, state, dispatch, log);
|
||||
handleLoRAModels(models, state, dispatch, log);
|
||||
handleControlAdapterModels(models, state, dispatch, log);
|
||||
handleSpandrelImageToImageModels(models, state, dispatch, log);
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -177,3 +184,23 @@ const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log)
|
||||
dispatch(controlAdapterModelCleared({ id: ca.id }));
|
||||
});
|
||||
};
|
||||
|
||||
const handleSpandrelImageToImageModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||
const currentUpscaleModel = state.upscale.upscaleModel;
|
||||
const upscaleModels = models.filter(isSpandrelImageToImageModelConfig);
|
||||
|
||||
if (currentUpscaleModel) {
|
||||
const isCurrentUpscaleModelAvailable = upscaleModels.some((m) => m.key === currentUpscaleModel.key);
|
||||
if (isCurrentUpscaleModelAvailable) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const firstModel = upscaleModels[0];
|
||||
if (firstModel) {
|
||||
dispatch(upscaleModelChanged(firstModel));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(upscaleModelChanged(null));
|
||||
};
|
||||
|
@ -26,6 +26,7 @@ import { workflowSettingsPersistConfig, workflowSettingsSlice } from 'features/n
|
||||
import { workflowPersistConfig, workflowSlice } from 'features/nodes/store/workflowSlice';
|
||||
import { generationPersistConfig, generationSlice } from 'features/parameters/store/generationSlice';
|
||||
import { postprocessingPersistConfig, postprocessingSlice } from 'features/parameters/store/postprocessingSlice';
|
||||
import { upscalePersistConfig, upscaleSlice } from 'features/parameters/store/upscaleSlice';
|
||||
import { queueSlice } from 'features/queue/store/queueSlice';
|
||||
import { sdxlPersistConfig, sdxlSlice } from 'features/sdxl/store/sdxlSlice';
|
||||
import { configSlice } from 'features/system/store/configSlice';
|
||||
@ -69,6 +70,7 @@ const allReducers = {
|
||||
[controlLayersSlice.name]: undoable(controlLayersSlice.reducer, controlLayersUndoableConfig),
|
||||
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
|
||||
[api.reducerPath]: api.reducer,
|
||||
[upscaleSlice.name]: upscaleSlice.reducer,
|
||||
};
|
||||
|
||||
const rootReducer = combineReducers(allReducers);
|
||||
@ -114,6 +116,7 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
|
||||
[hrfPersistConfig.name]: hrfPersistConfig,
|
||||
[controlLayersPersistConfig.name]: controlLayersPersistConfig,
|
||||
[workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig,
|
||||
[upscalePersistConfig.name]: upscalePersistConfig,
|
||||
};
|
||||
|
||||
const unserialize: UnserializeFunction = (data, key) => {
|
||||
|
@ -21,6 +21,10 @@ const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (ac
|
||||
postUploadAction = { type: 'SET_CANVAS_INITIAL_IMAGE' };
|
||||
}
|
||||
|
||||
if (activeTabName === 'upscaling') {
|
||||
postUploadAction = { type: 'SET_UPSCALE_INITIAL_IMAGE' };
|
||||
}
|
||||
|
||||
return postUploadAction;
|
||||
});
|
||||
|
||||
|
@ -15,6 +15,7 @@ import type { Templates } from 'features/nodes/store/types';
|
||||
import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
||||
import { selectUpscalelice } from 'features/parameters/store/upscaleSlice';
|
||||
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import i18n from 'i18next';
|
||||
@ -40,8 +41,19 @@ const createSelector = (templates: Templates) =>
|
||||
selectDynamicPromptsSlice,
|
||||
selectControlLayersSlice,
|
||||
activeTabNameSelector,
|
||||
selectUpscalelice,
|
||||
],
|
||||
(controlAdapters, generation, system, nodes, workflowSettings, dynamicPrompts, controlLayers, activeTabName) => {
|
||||
(
|
||||
controlAdapters,
|
||||
generation,
|
||||
system,
|
||||
nodes,
|
||||
workflowSettings,
|
||||
dynamicPrompts,
|
||||
controlLayers,
|
||||
activeTabName,
|
||||
upscale
|
||||
) => {
|
||||
const { model } = generation;
|
||||
const { size } = controlLayers.present;
|
||||
const { positivePrompt } = controlLayers.present;
|
||||
@ -194,6 +206,16 @@ const createSelector = (templates: Templates) =>
|
||||
reasons.push({ prefix, content });
|
||||
}
|
||||
});
|
||||
} else if (activeTabName === 'upscaling') {
|
||||
if (!upscale.upscaleInitialImage) {
|
||||
reasons.push({ content: i18n.t('upscaling.missingUpscaleInitialImage') });
|
||||
}
|
||||
if (!upscale.upscaleModel) {
|
||||
reasons.push({ content: i18n.t('upscaling.missingUpscaleModel') });
|
||||
}
|
||||
if (!upscale.tileControlnetModel) {
|
||||
reasons.push({ content: i18n.t('upscaling.missingTileControlNetModel') });
|
||||
}
|
||||
} else {
|
||||
// Handling for all other tabs
|
||||
selectControlAdapterAll(controlAdapters)
|
||||
|
@ -62,6 +62,10 @@ export type CanvasInitialImageDropData = BaseDropData & {
|
||||
actionType: 'SET_CANVAS_INITIAL_IMAGE';
|
||||
};
|
||||
|
||||
type UpscaleInitialImageDropData = BaseDropData & {
|
||||
actionType: 'SET_UPSCALE_INITIAL_IMAGE';
|
||||
};
|
||||
|
||||
type NodesImageDropData = BaseDropData & {
|
||||
actionType: 'SET_NODES_IMAGE';
|
||||
context: {
|
||||
@ -98,7 +102,8 @@ export type TypesafeDroppableData =
|
||||
| IPALayerImageDropData
|
||||
| RGLayerIPAdapterImageDropData
|
||||
| IILayerImageDropData
|
||||
| SelectForCompareDropData;
|
||||
| SelectForCompareDropData
|
||||
| UpscaleInitialImageDropData;
|
||||
|
||||
type BaseDragData = {
|
||||
id: string;
|
||||
|
@ -27,6 +27,8 @@ export const isValidDrop = (overData?: TypesafeDroppableData | null, activeData?
|
||||
return payloadType === 'IMAGE_DTO';
|
||||
case 'SET_CANVAS_INITIAL_IMAGE':
|
||||
return payloadType === 'IMAGE_DTO';
|
||||
case 'SET_UPSCALE_INITIAL_IMAGE':
|
||||
return payloadType === 'IMAGE_DTO';
|
||||
case 'SET_NODES_IMAGE':
|
||||
return payloadType === 'IMAGE_DTO';
|
||||
case 'SELECT_FOR_COMPARE':
|
||||
|
@ -13,6 +13,7 @@ import { sentImageToCanvas, sentImageToImg2Img } from 'features/gallery/store/ac
|
||||
import { imageToCompareChanged } from 'features/gallery/store/gallerySlice';
|
||||
import { $templates } from 'features/nodes/store/nodesSlice';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||
@ -124,6 +125,11 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
||||
dispatch(imageToCompareChanged(imageDTO));
|
||||
}, [dispatch, imageDTO]);
|
||||
|
||||
const handleSendToUpscale = useCallback(() => {
|
||||
dispatch(upscaleInitialImageChanged(imageDTO));
|
||||
dispatch(setActiveTab('upscaling'));
|
||||
}, [dispatch, imageDTO]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem as="a" href={imageDTO.image_url} target="_blank" icon={<PiShareFatBold />}>
|
||||
@ -185,6 +191,9 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
||||
{t('parameters.sendToUnifiedCanvas')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem icon={<PiShareFatBold />} onClickCapture={handleSendToUpscale} id="send-to-upscale">
|
||||
{t('parameters.sendToUpscale')}
|
||||
</MenuItem>
|
||||
<MenuDivider />
|
||||
<MenuItem icon={<PiFoldersBold />} onClickCapture={handleChangeBoard}>
|
||||
{t('boards.changeBoard')}
|
||||
|
@ -9,7 +9,13 @@ import CurrentImageButtons from './CurrentImageButtons';
|
||||
import { ViewerToggleMenu } from './ViewerToggleMenu';
|
||||
|
||||
export const ViewerToolbar = memo(() => {
|
||||
const tab = useAppSelector(activeTabNameSelector);
|
||||
const showToggle = useAppSelector((s) => {
|
||||
const tab = activeTabNameSelector(s);
|
||||
if (tab === 'upscaling' || tab === 'workflows') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return (
|
||||
<Flex w="full" gap={2}>
|
||||
<Flex flex={1} justifyContent="center">
|
||||
@ -23,7 +29,7 @@ export const ViewerToolbar = memo(() => {
|
||||
</Flex>
|
||||
<Flex flex={1} justifyContent="center">
|
||||
<Flex gap={2} marginInlineStart="auto">
|
||||
{tab !== 'workflows' && <ViewerToggleMenu />}
|
||||
{showToggle && <ViewerToggleMenu />}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Button, Text, useToast } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { $installModelsTab } from 'features/modelManagerV2/subpanels/InstallModels';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
@ -44,6 +45,7 @@ const ToastDescription = () => {
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(setActiveTab('models'));
|
||||
$installModelsTab.set(3);
|
||||
toast.close(TOAST_ID);
|
||||
}, [dispatch, toast]);
|
||||
|
||||
|
@ -30,7 +30,7 @@ export const StarterModelsResultItem = ({ result }: Props) => {
|
||||
<Flex alignItems="center" justifyContent="space-between" w="100%" gap={3}>
|
||||
<Flex fontSize="sm" flexDir="column">
|
||||
<Flex gap={3}>
|
||||
<Badge h="min-content">{result.type.replace('_', ' ')}</Badge>
|
||||
<Badge h="min-content">{result.type.replaceAll('_', ' ')}</Badge>
|
||||
<ModelBaseBadge base={result.base} />
|
||||
<Text fontWeight="semibold">{result.name}</Text>
|
||||
</Flex>
|
||||
|
@ -18,14 +18,17 @@ export const StarterModelsResults = ({ results }: StarterModelsResultsProps) =>
|
||||
|
||||
const filteredResults = useMemo(() => {
|
||||
return results.filter((result) => {
|
||||
const name = result.name.toLowerCase();
|
||||
const type = result.type.toLowerCase();
|
||||
return name.includes(searchTerm.toLowerCase()) || type.includes(searchTerm.toLowerCase());
|
||||
const trimmedSearchTerm = searchTerm.trim().toLowerCase();
|
||||
const matchStrings = [result.name.toLowerCase(), result.type.toLowerCase(), result.description.toLowerCase()];
|
||||
if (result.type === 'spandrel_image_to_image') {
|
||||
matchStrings.push('upscale');
|
||||
}
|
||||
return matchStrings.some((matchString) => matchString.includes(trimmedSearchTerm));
|
||||
});
|
||||
}, [results, searchTerm]);
|
||||
|
||||
const handleSearch: ChangeEventHandler<HTMLInputElement> = useCallback((e) => {
|
||||
setSearchTerm(e.target.value.trim());
|
||||
setSearchTerm(e.target.value);
|
||||
}, []);
|
||||
|
||||
const clearSearch = useCallback(() => {
|
||||
|
@ -1,28 +1,28 @@
|
||||
import { Box, Flex, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { StarterModelsForm } from 'features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterModelsForm';
|
||||
import { useMemo } from 'react';
|
||||
import { atom } from 'nanostores';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMainModels } from 'services/api/hooks/modelsByType';
|
||||
|
||||
import { HuggingFaceForm } from './AddModelPanel/HuggingFaceFolder/HuggingFaceForm';
|
||||
import { InstallModelForm } from './AddModelPanel/InstallModelForm';
|
||||
import { ModelInstallQueue } from './AddModelPanel/ModelInstallQueue/ModelInstallQueue';
|
||||
import { ScanModelsForm } from './AddModelPanel/ScanFolder/ScanFolderForm';
|
||||
|
||||
export const $installModelsTab = atom(0);
|
||||
|
||||
export const InstallModels = () => {
|
||||
const { t } = useTranslation();
|
||||
const [mainModels, { data }] = useMainModels();
|
||||
const defaultIndex = useMemo(() => {
|
||||
if (data && mainModels.length) {
|
||||
return 0;
|
||||
}
|
||||
return 3;
|
||||
}, [data, mainModels.length]);
|
||||
const index = useStore($installModelsTab);
|
||||
const onChange = useCallback((index: number) => {
|
||||
$installModelsTab.set(index);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Flex layerStyle="first" borderRadius="base" w="full" h="full" flexDir="column" gap={4}>
|
||||
<Heading fontSize="xl">{t('modelManager.addModel')}</Heading>
|
||||
<Tabs variant="collapse" height="50%" display="flex" flexDir="column" defaultIndex={defaultIndex}>
|
||||
<Tabs variant="collapse" height="50%" display="flex" flexDir="column" index={index} onChange={onChange}>
|
||||
<TabList>
|
||||
<Tab>{t('modelManager.urlOrLocalPath')}</Tab>
|
||||
<Tab>{t('modelManager.huggingFace')}</Tab>
|
||||
|
@ -0,0 +1,246 @@
|
||||
import type { RootState } from 'app/store/store';
|
||||
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
|
||||
import type { GraphType } from 'features/nodes/util/graph/generation/Graph';
|
||||
import { Graph } from 'features/nodes/util/graph/generation/Graph';
|
||||
import { isNonRefinerMainModelConfig, isSpandrelImageToImageModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import {
|
||||
CLIP_SKIP,
|
||||
CONTROL_NET_COLLECT,
|
||||
IMAGE_TO_LATENTS,
|
||||
LATENTS_TO_IMAGE,
|
||||
MAIN_MODEL_LOADER,
|
||||
NEGATIVE_CONDITIONING,
|
||||
NOISE,
|
||||
POSITIVE_CONDITIONING,
|
||||
SDXL_MODEL_LOADER,
|
||||
SPANDREL,
|
||||
TILED_MULTI_DIFFUSION_DENOISE_LATENTS,
|
||||
UNSHARP_MASK,
|
||||
VAE_LOADER,
|
||||
} from './constants';
|
||||
import { addLoRAs } from './generation/addLoRAs';
|
||||
import { addSDXLLoRas } from './generation/addSDXLLoRAs';
|
||||
import { getBoardField, getSDXLStylePrompts } from './graphBuilderUtils';
|
||||
|
||||
export const buildMultidiffusionUpscaleGraph = async (state: RootState): Promise<GraphType> => {
|
||||
const { model, cfgScale: cfg_scale, scheduler, steps, vaePrecision, seed, vae } = state.generation;
|
||||
const { positivePrompt, negativePrompt } = state.controlLayers.present;
|
||||
const { upscaleModel, upscaleInitialImage, structure, creativity, tileControlnetModel, scale } = state.upscale;
|
||||
|
||||
assert(model, 'No model found in state');
|
||||
assert(upscaleModel, 'No upscale model found in state');
|
||||
assert(upscaleInitialImage, 'No initial image found in state');
|
||||
assert(tileControlnetModel, 'Tile controlnet is required');
|
||||
|
||||
const g = new Graph();
|
||||
|
||||
const upscaleNode = g.addNode({
|
||||
id: SPANDREL,
|
||||
type: 'spandrel_image_to_image',
|
||||
image: upscaleInitialImage,
|
||||
image_to_image_model: upscaleModel,
|
||||
fit_to_multiple_of_8: true,
|
||||
scale,
|
||||
});
|
||||
|
||||
const unsharpMaskNode2 = g.addNode({
|
||||
id: `${UNSHARP_MASK}_2`,
|
||||
type: 'unsharp_mask',
|
||||
radius: 2,
|
||||
strength: 60,
|
||||
});
|
||||
|
||||
g.addEdge(upscaleNode, 'image', unsharpMaskNode2, 'image');
|
||||
|
||||
const noiseNode = g.addNode({
|
||||
id: NOISE,
|
||||
type: 'noise',
|
||||
seed,
|
||||
});
|
||||
|
||||
g.addEdge(unsharpMaskNode2, 'width', noiseNode, 'width');
|
||||
g.addEdge(unsharpMaskNode2, 'height', noiseNode, 'height');
|
||||
|
||||
const i2lNode = g.addNode({
|
||||
id: IMAGE_TO_LATENTS,
|
||||
type: 'i2l',
|
||||
fp32: vaePrecision === 'fp32',
|
||||
tiled: true,
|
||||
});
|
||||
|
||||
g.addEdge(unsharpMaskNode2, 'image', i2lNode, 'image');
|
||||
|
||||
const l2iNode = g.addNode({
|
||||
type: 'l2i',
|
||||
id: LATENTS_TO_IMAGE,
|
||||
fp32: vaePrecision === 'fp32',
|
||||
tiled: true,
|
||||
board: getBoardField(state),
|
||||
is_intermediate: false,
|
||||
});
|
||||
|
||||
const tiledMultidiffusionNode = g.addNode({
|
||||
id: TILED_MULTI_DIFFUSION_DENOISE_LATENTS,
|
||||
type: 'tiled_multi_diffusion_denoise_latents',
|
||||
tile_height: 1024, // is this dependent on base model
|
||||
tile_width: 1024, // is this dependent on base model
|
||||
tile_overlap: 128,
|
||||
steps,
|
||||
cfg_scale,
|
||||
scheduler,
|
||||
denoising_start: ((creativity * -1 + 10) * 4.99) / 100,
|
||||
denoising_end: 1,
|
||||
});
|
||||
|
||||
let posCondNode;
|
||||
let negCondNode;
|
||||
let modelNode;
|
||||
|
||||
if (model.base === 'sdxl') {
|
||||
const { positiveStylePrompt, negativeStylePrompt } = getSDXLStylePrompts(state);
|
||||
|
||||
posCondNode = g.addNode({
|
||||
type: 'sdxl_compel_prompt',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
prompt: positivePrompt,
|
||||
style: positiveStylePrompt,
|
||||
});
|
||||
negCondNode = g.addNode({
|
||||
type: 'sdxl_compel_prompt',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
prompt: negativePrompt,
|
||||
style: negativeStylePrompt,
|
||||
});
|
||||
modelNode = g.addNode({
|
||||
type: 'sdxl_model_loader',
|
||||
id: SDXL_MODEL_LOADER,
|
||||
model,
|
||||
});
|
||||
g.addEdge(modelNode, 'clip', posCondNode, 'clip');
|
||||
g.addEdge(modelNode, 'clip', negCondNode, 'clip');
|
||||
g.addEdge(modelNode, 'clip2', posCondNode, 'clip2');
|
||||
g.addEdge(modelNode, 'clip2', negCondNode, 'clip2');
|
||||
g.addEdge(modelNode, 'unet', tiledMultidiffusionNode, 'unet');
|
||||
addSDXLLoRas(state, g, tiledMultidiffusionNode, modelNode, null, posCondNode, negCondNode);
|
||||
|
||||
const modelConfig = await fetchModelConfigWithTypeGuard(model.key, isNonRefinerMainModelConfig);
|
||||
|
||||
g.upsertMetadata({
|
||||
cfg_scale,
|
||||
positive_prompt: positivePrompt,
|
||||
negative_prompt: negativePrompt,
|
||||
positive_style_prompt: positiveStylePrompt,
|
||||
negative_style_prompt: negativeStylePrompt,
|
||||
model: Graph.getModelMetadataField(modelConfig),
|
||||
seed,
|
||||
steps,
|
||||
scheduler,
|
||||
vae: vae ?? undefined,
|
||||
});
|
||||
} else {
|
||||
posCondNode = g.addNode({
|
||||
type: 'compel',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
prompt: positivePrompt,
|
||||
});
|
||||
negCondNode = g.addNode({
|
||||
type: 'compel',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
prompt: negativePrompt,
|
||||
});
|
||||
modelNode = g.addNode({
|
||||
type: 'main_model_loader',
|
||||
id: MAIN_MODEL_LOADER,
|
||||
model,
|
||||
});
|
||||
const clipSkipNode = g.addNode({
|
||||
type: 'clip_skip',
|
||||
id: CLIP_SKIP,
|
||||
});
|
||||
|
||||
g.addEdge(modelNode, 'clip', clipSkipNode, 'clip');
|
||||
g.addEdge(clipSkipNode, 'clip', posCondNode, 'clip');
|
||||
g.addEdge(clipSkipNode, 'clip', negCondNode, 'clip');
|
||||
g.addEdge(modelNode, 'unet', tiledMultidiffusionNode, 'unet');
|
||||
addLoRAs(state, g, tiledMultidiffusionNode, modelNode, null, clipSkipNode, posCondNode, negCondNode);
|
||||
|
||||
const modelConfig = await fetchModelConfigWithTypeGuard(model.key, isNonRefinerMainModelConfig);
|
||||
const upscaleModelConfig = await fetchModelConfigWithTypeGuard(upscaleModel.key, isSpandrelImageToImageModelConfig);
|
||||
|
||||
g.upsertMetadata({
|
||||
cfg_scale,
|
||||
positive_prompt: positivePrompt,
|
||||
negative_prompt: negativePrompt,
|
||||
model: Graph.getModelMetadataField(modelConfig),
|
||||
seed,
|
||||
steps,
|
||||
scheduler,
|
||||
vae: vae ?? undefined,
|
||||
upscale_model: Graph.getModelMetadataField(upscaleModelConfig),
|
||||
creativity,
|
||||
structure,
|
||||
});
|
||||
}
|
||||
|
||||
g.setMetadataReceivingNode(l2iNode);
|
||||
g.addEdgeToMetadata(upscaleNode, 'width', 'width');
|
||||
g.addEdgeToMetadata(upscaleNode, 'height', 'height');
|
||||
|
||||
let vaeNode;
|
||||
if (vae) {
|
||||
vaeNode = g.addNode({
|
||||
id: VAE_LOADER,
|
||||
type: 'vae_loader',
|
||||
vae_model: vae,
|
||||
});
|
||||
}
|
||||
|
||||
g.addEdge(vaeNode || modelNode, 'vae', i2lNode, 'vae');
|
||||
g.addEdge(vaeNode || modelNode, 'vae', l2iNode, 'vae');
|
||||
|
||||
g.addEdge(noiseNode, 'noise', tiledMultidiffusionNode, 'noise');
|
||||
g.addEdge(i2lNode, 'latents', tiledMultidiffusionNode, 'latents');
|
||||
g.addEdge(posCondNode, 'conditioning', tiledMultidiffusionNode, 'positive_conditioning');
|
||||
g.addEdge(negCondNode, 'conditioning', tiledMultidiffusionNode, 'negative_conditioning');
|
||||
|
||||
g.addEdge(tiledMultidiffusionNode, 'latents', l2iNode, 'latents');
|
||||
|
||||
const controlnetNode1 = g.addNode({
|
||||
id: 'controlnet_1',
|
||||
type: 'controlnet',
|
||||
control_model: tileControlnetModel,
|
||||
control_mode: 'balanced',
|
||||
resize_mode: 'just_resize',
|
||||
control_weight: (structure + 10) * 0.0325 + 0.3,
|
||||
begin_step_percent: 0,
|
||||
end_step_percent: (structure + 10) * 0.025 + 0.3,
|
||||
});
|
||||
|
||||
g.addEdge(unsharpMaskNode2, 'image', controlnetNode1, 'image');
|
||||
|
||||
const controlnetNode2 = g.addNode({
|
||||
id: 'controlnet_2',
|
||||
type: 'controlnet',
|
||||
control_model: tileControlnetModel,
|
||||
control_mode: 'balanced',
|
||||
resize_mode: 'just_resize',
|
||||
control_weight: ((structure + 10) * 0.0325 + 0.15) * 0.45,
|
||||
begin_step_percent: (structure + 10) * 0.025 + 0.3,
|
||||
end_step_percent: 0.85,
|
||||
});
|
||||
|
||||
g.addEdge(unsharpMaskNode2, 'image', controlnetNode2, 'image');
|
||||
|
||||
const collectNode = g.addNode({
|
||||
id: CONTROL_NET_COLLECT,
|
||||
type: 'collect',
|
||||
});
|
||||
g.addEdge(controlnetNode1, 'control', collectNode, 'item');
|
||||
g.addEdge(controlnetNode2, 'control', collectNode, 'item');
|
||||
|
||||
g.addEdge(collectNode, 'collection', tiledMultidiffusionNode, 'control');
|
||||
|
||||
return g.getGraph();
|
||||
};
|
@ -37,6 +37,7 @@ export const IP_ADAPTER_COLLECT = 'ip_adapter_collect';
|
||||
export const T2I_ADAPTER_COLLECT = 't2i_adapter_collect';
|
||||
export const METADATA = 'core_metadata';
|
||||
export const ESRGAN = 'esrgan';
|
||||
export const SPANDREL = 'spandrel';
|
||||
export const SDXL_MODEL_LOADER = 'sdxl_model_loader';
|
||||
export const SDXL_DENOISE_LATENTS = 'sdxl_denoise_latents';
|
||||
export const SDXL_REFINER_MODEL_LOADER = 'sdxl_refiner_model_loader';
|
||||
@ -53,6 +54,8 @@ export const PROMPT_REGION_NEGATIVE_COND_PREFIX = 'prompt_region_negative_cond';
|
||||
export const PROMPT_REGION_POSITIVE_COND_INVERTED_PREFIX = 'prompt_region_positive_cond_inverted';
|
||||
export const POSITIVE_CONDITIONING_COLLECT = 'positive_conditioning_collect';
|
||||
export const NEGATIVE_CONDITIONING_COLLECT = 'negative_conditioning_collect';
|
||||
export const UNSHARP_MASK = 'unsharp_mask';
|
||||
export const TILED_MULTI_DIFFUSION_DENOISE_LATENTS = 'tiled_multi_diffusion_denoise_latents';
|
||||
|
||||
// friendly graph ids
|
||||
export const CONTROL_LAYERS_GRAPH = 'control_layers_graph';
|
||||
|
@ -522,6 +522,21 @@ describe('Graph', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('addEdgeToMetadata', () => {
|
||||
it('should add an edge to the metadata node', () => {
|
||||
const g = new Graph();
|
||||
const n1 = g.addNode({
|
||||
id: 'n1',
|
||||
type: 'img_resize',
|
||||
});
|
||||
g.upsertMetadata({ test: 'test' });
|
||||
g.addEdgeToMetadata(n1, 'width', 'width');
|
||||
const metadata = g._getMetadataNode();
|
||||
expect(g.getEdgesFrom(n1).length).toBe(1);
|
||||
expect(g.getEdgesTo(metadata as unknown as AnyInvocation).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setMetadataReceivingNode', () => {
|
||||
it('should set the metadata receiving node', () => {
|
||||
const g = new Graph();
|
||||
|
@ -372,6 +372,21 @@ export class Graph {
|
||||
return metadataNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an edge from a node to a metadata field. Use this when the metadata value is dynamic depending on a node.
|
||||
* @param fromNode The node to add an edge from
|
||||
* @param fromField The field of the node to add an edge from
|
||||
* @param metadataField The metadata field to add an edge to (will overwrite hard-coded metadata)
|
||||
* @returns
|
||||
*/
|
||||
addEdgeToMetadata<TFrom extends AnyInvocation>(
|
||||
fromNode: TFrom,
|
||||
fromField: OutputFields<TFrom>,
|
||||
metadataField: string
|
||||
): Edge {
|
||||
// @ts-expect-error `Graph` excludes `core_metadata` nodes due to its excessively wide typing
|
||||
return this.addEdge(fromNode, fromField, this._getMetadataNode(), metadataField);
|
||||
}
|
||||
/**
|
||||
* Set the node that should receive metadata. All other edges from the metadata node are deleted.
|
||||
* @param node The node to set as the receiving node
|
||||
|
@ -8,7 +8,7 @@ import type { Invocation, S } from 'services/api/types';
|
||||
export const addLoRAs = (
|
||||
state: RootState,
|
||||
g: Graph,
|
||||
denoise: Invocation<'denoise_latents'>,
|
||||
denoise: Invocation<'denoise_latents'> | Invocation<'tiled_multi_diffusion_denoise_latents'>,
|
||||
modelLoader: Invocation<'main_model_loader'>,
|
||||
seamless: Invocation<'seamless'> | null,
|
||||
clipSkip: Invocation<'clip_skip'>,
|
||||
|
@ -8,7 +8,7 @@ import type { Invocation, S } from 'services/api/types';
|
||||
export const addSDXLLoRas = (
|
||||
state: RootState,
|
||||
g: Graph,
|
||||
denoise: Invocation<'denoise_latents'>,
|
||||
denoise: Invocation<'denoise_latents'> | Invocation<'tiled_multi_diffusion_denoise_latents'>,
|
||||
modelLoader: Invocation<'sdxl_model_loader'>,
|
||||
seamless: Invocation<'seamless'> | null,
|
||||
posCond: Invocation<'sdxl_compel_prompt'>,
|
||||
|
@ -0,0 +1,52 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { creativityChanged } from 'features/parameters/store/upscaleSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const ParamCreativity = () => {
|
||||
const creativity = useAppSelector((s) => s.upscale.creativity);
|
||||
const initial = 0;
|
||||
const sliderMin = -10;
|
||||
const sliderMax = 10;
|
||||
const numberInputMin = -10;
|
||||
const numberInputMax = 10;
|
||||
const coarseStep = 1;
|
||||
const fineStep = 1;
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const marks = useMemo(() => [sliderMin, 0, sliderMax], [sliderMax, sliderMin]);
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(creativityChanged(v));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<FormLabel>{t('upscaling.creativity')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={creativity}
|
||||
defaultValue={initial}
|
||||
min={sliderMin}
|
||||
max={sliderMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
marks={marks}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={creativity}
|
||||
defaultValue={initial}
|
||||
min={numberInputMin}
|
||||
max={numberInputMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamCreativity);
|
@ -63,7 +63,7 @@ const ParamESRGANModel = () => {
|
||||
|
||||
return (
|
||||
<FormControl orientation="vertical">
|
||||
<FormLabel>{t('models.esrganModel')} </FormLabel>
|
||||
<FormLabel>{t('models.esrganModel')}</FormLabel>
|
||||
<Combobox value={value} onChange={onChange} options={options} />
|
||||
</FormControl>
|
||||
);
|
||||
|
@ -0,0 +1,56 @@
|
||||
import { Box, Combobox, FormControl, FormLabel, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useModelCombobox } from 'common/hooks/useModelCombobox';
|
||||
import { upscaleModelChanged } from 'features/parameters/store/upscaleSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSpandrelImageToImageModels } from 'services/api/hooks/modelsByType';
|
||||
import type { SpandrelImageToImageModelConfig } from 'services/api/types';
|
||||
|
||||
const ParamSpandrelModel = () => {
|
||||
const { t } = useTranslation();
|
||||
const [modelConfigs, { isLoading }] = useSpandrelImageToImageModels();
|
||||
|
||||
const model = useAppSelector((s) => s.upscale.upscaleModel);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const tooltipLabel = useMemo(() => {
|
||||
if (!modelConfigs.length || !model) {
|
||||
return;
|
||||
}
|
||||
return modelConfigs.find((m) => m.key === model?.key)?.description;
|
||||
}, [modelConfigs, model]);
|
||||
|
||||
const _onChange = useCallback(
|
||||
(v: SpandrelImageToImageModelConfig | null) => {
|
||||
dispatch(upscaleModelChanged(v));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const { options, value, onChange, placeholder, noOptionsMessage } = useModelCombobox({
|
||||
modelConfigs,
|
||||
onChange: _onChange,
|
||||
selectedModel: model,
|
||||
isLoading,
|
||||
});
|
||||
|
||||
return (
|
||||
<FormControl orientation="vertical">
|
||||
<FormLabel>{t('upscaling.upscaleModel')}</FormLabel>
|
||||
<Tooltip label={tooltipLabel}>
|
||||
<Box w="full">
|
||||
<Combobox
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
noOptionsMessage={noOptionsMessage}
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamSpandrelModel);
|
@ -0,0 +1,52 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { structureChanged } from 'features/parameters/store/upscaleSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const ParamStructure = () => {
|
||||
const structure = useAppSelector((s) => s.upscale.structure);
|
||||
const initial = 0;
|
||||
const sliderMin = -10;
|
||||
const sliderMax = 10;
|
||||
const numberInputMin = -10;
|
||||
const numberInputMax = 10;
|
||||
const coarseStep = 1;
|
||||
const fineStep = 1;
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const marks = useMemo(() => [sliderMin, 0, sliderMax], [sliderMax, sliderMin]);
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(structureChanged(v));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<FormLabel>{t('upscaling.structure')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={structure}
|
||||
defaultValue={initial}
|
||||
min={sliderMin}
|
||||
max={sliderMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
marks={marks}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={structure}
|
||||
defaultValue={initial}
|
||||
min={numberInputMin}
|
||||
max={numberInputMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamStructure);
|
@ -0,0 +1,76 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import type { ParameterSpandrelImageToImageModel } from 'features/parameters/types/parameterSchemas';
|
||||
import type { ControlNetModelConfig, ImageDTO } from 'services/api/types';
|
||||
|
||||
interface UpscaleState {
|
||||
_version: 1;
|
||||
upscaleModel: ParameterSpandrelImageToImageModel | null;
|
||||
upscaleInitialImage: ImageDTO | null;
|
||||
structure: number;
|
||||
creativity: number;
|
||||
tileControlnetModel: ControlNetModelConfig | null;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
const initialUpscaleState: UpscaleState = {
|
||||
_version: 1,
|
||||
upscaleModel: null,
|
||||
upscaleInitialImage: null,
|
||||
structure: 0,
|
||||
creativity: 0,
|
||||
tileControlnetModel: null,
|
||||
scale: 4,
|
||||
};
|
||||
|
||||
export const upscaleSlice = createSlice({
|
||||
name: 'upscale',
|
||||
initialState: initialUpscaleState,
|
||||
reducers: {
|
||||
upscaleModelChanged: (state, action: PayloadAction<ParameterSpandrelImageToImageModel | null>) => {
|
||||
state.upscaleModel = action.payload;
|
||||
},
|
||||
upscaleInitialImageChanged: (state, action: PayloadAction<ImageDTO | null>) => {
|
||||
state.upscaleInitialImage = action.payload;
|
||||
},
|
||||
structureChanged: (state, action: PayloadAction<number>) => {
|
||||
state.structure = action.payload;
|
||||
},
|
||||
creativityChanged: (state, action: PayloadAction<number>) => {
|
||||
state.creativity = action.payload;
|
||||
},
|
||||
tileControlnetModelChanged: (state, action: PayloadAction<ControlNetModelConfig | null>) => {
|
||||
state.tileControlnetModel = action.payload;
|
||||
},
|
||||
scaleChanged: (state, action: PayloadAction<number>) => {
|
||||
state.scale = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
upscaleModelChanged,
|
||||
upscaleInitialImageChanged,
|
||||
structureChanged,
|
||||
creativityChanged,
|
||||
tileControlnetModelChanged,
|
||||
scaleChanged,
|
||||
} = upscaleSlice.actions;
|
||||
|
||||
export const selectUpscalelice = (state: RootState) => state.upscale;
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
const migrateUpscaleState = (state: any): any => {
|
||||
if (!('_version' in state)) {
|
||||
state._version = 1;
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export const upscalePersistConfig: PersistConfig<UpscaleState> = {
|
||||
name: upscaleSlice.name,
|
||||
initialState: initialUpscaleState,
|
||||
migrate: migrateUpscaleState,
|
||||
persistDenylist: [],
|
||||
};
|
@ -126,6 +126,11 @@ const zParameterT2IAdapterModel = zModelIdentifierField;
|
||||
export type ParameterT2IAdapterModel = z.infer<typeof zParameterT2IAdapterModel>;
|
||||
// #endregion
|
||||
|
||||
// #region VAE Model
|
||||
const zParameterSpandrelImageToImageModel = zModelIdentifierField;
|
||||
export type ParameterSpandrelImageToImageModel = z.infer<typeof zParameterSpandrelImageToImageModel>;
|
||||
// #endregion
|
||||
|
||||
// #region Strength (l2l strength)
|
||||
export const zParameterStrength = z.number().min(0).max(1);
|
||||
export type ParameterStrength = z.infer<typeof zParameterStrength>;
|
||||
|
@ -7,10 +7,14 @@ import ParamCFGRescaleMultiplier from 'features/parameters/components/Advanced/P
|
||||
import ParamClipSkip from 'features/parameters/components/Advanced/ParamClipSkip';
|
||||
import ParamSeamlessXAxis from 'features/parameters/components/Seamless/ParamSeamlessXAxis';
|
||||
import ParamSeamlessYAxis from 'features/parameters/components/Seamless/ParamSeamlessYAxis';
|
||||
import { ParamSeedNumberInput } from 'features/parameters/components/Seed/ParamSeedNumberInput';
|
||||
import { ParamSeedRandomize } from 'features/parameters/components/Seed/ParamSeedRandomize';
|
||||
import { ParamSeedShuffle } from 'features/parameters/components/Seed/ParamSeedShuffle';
|
||||
import ParamVAEModelSelect from 'features/parameters/components/VAEModel/ParamVAEModelSelect';
|
||||
import ParamVAEPrecision from 'features/parameters/components/VAEModel/ParamVAEPrecision';
|
||||
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
||||
import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetModelConfigQuery } from 'services/api/endpoints/models';
|
||||
@ -26,6 +30,8 @@ const formLabelProps2: FormLabelProps = {
|
||||
export const AdvancedSettingsAccordion = memo(() => {
|
||||
const vaeKey = useAppSelector((state) => state.generation.vae?.key);
|
||||
const { currentData: vaeConfig } = useGetModelConfigQuery(vaeKey ?? skipToken);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
|
||||
const selectBadges = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectGenerationSlice, (generation) => {
|
||||
@ -48,9 +54,12 @@ export const AdvancedSettingsAccordion = memo(() => {
|
||||
if (generation.seamlessXAxis || generation.seamlessYAxis) {
|
||||
badges.push('seamless');
|
||||
}
|
||||
if (activeTabName === 'upscaling' && !generation.shouldRandomizeSeed) {
|
||||
badges.push('Manual Seed');
|
||||
}
|
||||
return badges;
|
||||
}),
|
||||
[vaeConfig]
|
||||
[vaeConfig, activeTabName]
|
||||
);
|
||||
const badges = useAppSelector(selectBadges);
|
||||
const { t } = useTranslation();
|
||||
@ -66,16 +75,27 @@ export const AdvancedSettingsAccordion = memo(() => {
|
||||
<ParamVAEModelSelect />
|
||||
<ParamVAEPrecision />
|
||||
</Flex>
|
||||
<FormControlGroup formLabelProps={formLabelProps}>
|
||||
<ParamClipSkip />
|
||||
<ParamCFGRescaleMultiplier />
|
||||
</FormControlGroup>
|
||||
<Flex gap={4} w="full">
|
||||
<FormControlGroup formLabelProps={formLabelProps2}>
|
||||
<ParamSeamlessXAxis />
|
||||
<ParamSeamlessYAxis />
|
||||
</FormControlGroup>
|
||||
</Flex>
|
||||
{activeTabName === 'upscaling' && (
|
||||
<Flex gap={4} alignItems="center">
|
||||
<ParamSeedNumberInput />
|
||||
<ParamSeedShuffle />
|
||||
<ParamSeedRandomize />
|
||||
</Flex>
|
||||
)}
|
||||
{activeTabName !== 'upscaling' && (
|
||||
<>
|
||||
<FormControlGroup formLabelProps={formLabelProps}>
|
||||
<ParamClipSkip />
|
||||
<ParamCFGRescaleMultiplier />
|
||||
</FormControlGroup>
|
||||
<Flex gap={4} w="full">
|
||||
<FormControlGroup formLabelProps={formLabelProps2}>
|
||||
<ParamSeamlessXAxis />
|
||||
<ParamSeamlessYAxis />
|
||||
</FormControlGroup>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</StandaloneAccordion>
|
||||
);
|
||||
|
@ -0,0 +1,68 @@
|
||||
import { Button, Flex, ListItem, Text, UnorderedList } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { $installModelsTab } from 'features/modelManagerV2/subpanels/InstallModels';
|
||||
import { tileControlnetModelChanged } from 'features/parameters/store/upscaleSlice';
|
||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useControlNetModels } from 'services/api/hooks/modelsByType';
|
||||
|
||||
export const MultidiffusionWarning = () => {
|
||||
const { t } = useTranslation();
|
||||
const model = useAppSelector((s) => s.generation.model);
|
||||
const { tileControlnetModel, upscaleModel } = useAppSelector((s) => s.upscale);
|
||||
const dispatch = useAppDispatch();
|
||||
const [modelConfigs, { isLoading }] = useControlNetModels();
|
||||
const disabledTabs = useAppSelector((s) => s.config.disabledTabs);
|
||||
const shouldShowButton = useMemo(() => !disabledTabs.includes('models'), [disabledTabs]);
|
||||
|
||||
useEffect(() => {
|
||||
const validModel = modelConfigs.find((cnetModel) => {
|
||||
return cnetModel.base === model?.base && cnetModel.name.toLowerCase().includes('tile');
|
||||
});
|
||||
dispatch(tileControlnetModelChanged(validModel || null));
|
||||
}, [model?.base, modelConfigs, dispatch]);
|
||||
|
||||
const warnings = useMemo(() => {
|
||||
const _warnings: string[] = [];
|
||||
if (!model) {
|
||||
_warnings.push(t('upscaling.mainModelDesc'));
|
||||
}
|
||||
if (!tileControlnetModel) {
|
||||
_warnings.push(t('upscaling.tileControlNetModelDesc'));
|
||||
}
|
||||
if (!upscaleModel) {
|
||||
_warnings.push(t('upscaling.upscaleModelDesc'));
|
||||
}
|
||||
return _warnings;
|
||||
}, [model, upscaleModel, tileControlnetModel, t]);
|
||||
|
||||
const handleGoToModelManager = useCallback(() => {
|
||||
dispatch(setActiveTab('models'));
|
||||
$installModelsTab.set(3);
|
||||
}, [dispatch]);
|
||||
|
||||
if (!warnings.length || isLoading || !shouldShowButton) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex bg="error.500" borderRadius="base" padding={4} direction="column" fontSize="sm" gap={2}>
|
||||
<Text>
|
||||
<Trans
|
||||
i18nKey="upscaling.missingModelsWarning"
|
||||
components={{
|
||||
LinkComponent: (
|
||||
<Button size="sm" flexGrow={0} variant="link" color="base.50" onClick={handleGoToModelManager} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
<UnorderedList>
|
||||
{warnings.map((warning) => (
|
||||
<ListItem key={warning}>{warning}</ListItem>
|
||||
))}
|
||||
</UnorderedList>
|
||||
</Flex>
|
||||
);
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||
import type { TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
||||
import { t } from 'i18next';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { PiArrowCounterClockwiseBold } from 'react-icons/pi';
|
||||
import type { PostUploadAction } from 'services/api/types';
|
||||
|
||||
export const UpscaleInitialImage = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const imageDTO = useAppSelector((s) => s.upscale.upscaleInitialImage);
|
||||
|
||||
const droppableData = useMemo<TypesafeDroppableData | undefined>(
|
||||
() => ({
|
||||
actionType: 'SET_UPSCALE_INITIAL_IMAGE',
|
||||
id: 'upscale-intial-image',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const postUploadAction = useMemo<PostUploadAction>(
|
||||
() => ({
|
||||
type: 'SET_UPSCALE_INITIAL_IMAGE',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const onReset = useCallback(() => {
|
||||
dispatch(upscaleInitialImageChanged(null));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Flex justifyContent="flex-start">
|
||||
<Flex position="relative" w={36} h={36} alignItems="center" justifyContent="center">
|
||||
<IAIDndImage
|
||||
droppableData={droppableData}
|
||||
imageDTO={imageDTO || undefined}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
{imageDTO && (
|
||||
<Flex position="absolute" flexDir="column" top={1} insetInlineEnd={1} gap={1}>
|
||||
<IAIDndImageIcon
|
||||
onClick={onReset}
|
||||
icon={<PiArrowCounterClockwiseBold size={16} />}
|
||||
tooltip={t('controlnet.resetControlImage')}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
@ -0,0 +1,50 @@
|
||||
import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { scaleChanged } from 'features/parameters/store/upscaleSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const marks = [2, 4, 8];
|
||||
|
||||
const formatValue = (val: string | number) => `${val}x`;
|
||||
|
||||
export const UpscaleScaleSlider = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const scale = useAppSelector((s) => s.upscale.scale);
|
||||
|
||||
const onChange = useCallback(
|
||||
(val: number) => {
|
||||
dispatch(scaleChanged(val));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl orientation="vertical" gap={0}>
|
||||
<FormLabel m={0}>{t('upscaling.scale')}</FormLabel>
|
||||
<Flex w="full" gap={4}>
|
||||
<CompositeSlider
|
||||
min={2}
|
||||
max={8}
|
||||
value={scale}
|
||||
onChange={onChange}
|
||||
marks={marks}
|
||||
formatValue={formatValue}
|
||||
defaultValue={4}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
maxW={20}
|
||||
value={scale}
|
||||
onChange={onChange}
|
||||
defaultValue={4}
|
||||
min={2}
|
||||
max={16}
|
||||
format={formatValue}
|
||||
/>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
);
|
||||
});
|
||||
|
||||
UpscaleScaleSlider.displayName = 'UpscaleScaleSlider';
|
@ -0,0 +1,74 @@
|
||||
import { Expander, Flex, StandaloneAccordion } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import ParamCreativity from 'features/parameters/components/Upscale/ParamCreativity';
|
||||
import ParamSpandrelModel from 'features/parameters/components/Upscale/ParamSpandrelModel';
|
||||
import ParamStructure from 'features/parameters/components/Upscale/ParamStructure';
|
||||
import { selectUpscalelice } from 'features/parameters/store/upscaleSlice';
|
||||
import { UpscaleScaleSlider } from 'features/settingsAccordions/components/UpscaleSettingsAccordion/UpscaleScaleSlider';
|
||||
import { useExpanderToggle } from 'features/settingsAccordions/hooks/useExpanderToggle';
|
||||
import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MultidiffusionWarning } from './MultidiffusionWarning';
|
||||
import { UpscaleInitialImage } from './UpscaleInitialImage';
|
||||
|
||||
const selector = createMemoizedSelector([selectUpscalelice], (upscaleSlice) => {
|
||||
const { upscaleModel, upscaleInitialImage, scale } = upscaleSlice;
|
||||
|
||||
const badges: string[] = [];
|
||||
|
||||
if (upscaleModel) {
|
||||
badges.push(upscaleModel.name);
|
||||
}
|
||||
|
||||
if (upscaleInitialImage) {
|
||||
// Output height and width are scaled and rounded down to the nearest multiple of 8
|
||||
const outputWidth = Math.floor((upscaleInitialImage.width * scale) / 8) * 8;
|
||||
const outputHeight = Math.floor((upscaleInitialImage.height * scale) / 8) * 8;
|
||||
|
||||
badges.push(`${outputWidth}×${outputHeight}`);
|
||||
}
|
||||
|
||||
return { badges };
|
||||
});
|
||||
|
||||
export const UpscaleSettingsAccordion = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const { badges } = useAppSelector(selector);
|
||||
const { isOpen: isOpenAccordion, onToggle: onToggleAccordion } = useStandaloneAccordionToggle({
|
||||
id: 'upscale-settings',
|
||||
defaultIsOpen: true,
|
||||
});
|
||||
|
||||
const { isOpen: isOpenExpander, onToggle: onToggleExpander } = useExpanderToggle({
|
||||
id: 'upscale-settings-advanced',
|
||||
defaultIsOpen: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<StandaloneAccordion label="Upscale" badges={badges} isOpen={isOpenAccordion} onToggle={onToggleAccordion}>
|
||||
<Flex pt={4} px={4} w="full" h="full" flexDir="column" data-testid="upscale-settings-accordion">
|
||||
<Flex flexDir="column" gap={4}>
|
||||
<Flex gap={4}>
|
||||
<UpscaleInitialImage />
|
||||
<Flex direction="column" w="full" alignItems="center" gap={2}>
|
||||
<ParamSpandrelModel />
|
||||
<UpscaleScaleSlider />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<MultidiffusionWarning />
|
||||
</Flex>
|
||||
<Expander label={t('accordions.advanced.options')} isOpen={isOpenExpander} onToggle={onToggleExpander}>
|
||||
<Flex gap={4} pb={4} flexDir="column">
|
||||
<ParamCreativity />
|
||||
<ParamStructure />
|
||||
</Flex>
|
||||
</Expander>
|
||||
</Flex>
|
||||
</StandaloneAccordion>
|
||||
);
|
||||
});
|
||||
|
||||
UpscaleSettingsAccordion.displayName = 'UpscaleSettingsAccordion';
|
@ -11,7 +11,7 @@ import StatusIndicator from 'features/system/components/StatusIndicator';
|
||||
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||
import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton';
|
||||
import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons';
|
||||
import ParametersPanelTextToImage from 'features/ui/components/ParametersPanelTextToImage';
|
||||
import ParametersPanelTextToImage from 'features/ui/components/ParametersPanels/ParametersPanelTextToImage';
|
||||
import ModelManagerTab from 'features/ui/components/tabs/ModelManagerTab';
|
||||
import NodesTab from 'features/ui/components/tabs/NodesTab';
|
||||
import QueueTab from 'features/ui/components/tabs/QueueTab';
|
||||
@ -28,19 +28,23 @@ import type { CSSProperties, MouseEvent, ReactElement, ReactNode } from 'react';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MdZoomOutMap } from 'react-icons/md';
|
||||
import { PiFlowArrowBold } from 'react-icons/pi';
|
||||
import { RiBox2Line, RiBrushLine, RiInputMethodLine, RiPlayList2Fill } from 'react-icons/ri';
|
||||
import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
|
||||
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||
|
||||
import ParametersPanel from './ParametersPanel';
|
||||
import ParametersPanelCanvas from './ParametersPanels/ParametersPanelCanvas';
|
||||
import ParametersPanelUpscale from './ParametersPanels/ParametersPanelUpscale';
|
||||
import ResizeHandle from './tabs/ResizeHandle';
|
||||
import UpscalingTab from './tabs/UpscalingTab';
|
||||
|
||||
type TabData = {
|
||||
id: InvokeTabName;
|
||||
translationKey: string;
|
||||
icon: ReactElement;
|
||||
content: ReactNode;
|
||||
parametersPanel?: ReactNode;
|
||||
};
|
||||
|
||||
const TAB_DATA: Record<InvokeTabName, TabData> = {
|
||||
@ -49,18 +53,28 @@ const TAB_DATA: Record<InvokeTabName, TabData> = {
|
||||
translationKey: 'ui.tabs.generation',
|
||||
icon: <RiInputMethodLine />,
|
||||
content: <TextToImageTab />,
|
||||
parametersPanel: <ParametersPanelTextToImage />,
|
||||
},
|
||||
canvas: {
|
||||
id: 'canvas',
|
||||
translationKey: 'ui.tabs.canvas',
|
||||
icon: <RiBrushLine />,
|
||||
content: <UnifiedCanvasTab />,
|
||||
parametersPanel: <ParametersPanelCanvas />,
|
||||
},
|
||||
upscaling: {
|
||||
id: 'upscaling',
|
||||
translationKey: 'ui.tabs.upscaling',
|
||||
icon: <MdZoomOutMap />,
|
||||
content: <UpscalingTab />,
|
||||
parametersPanel: <ParametersPanelUpscale />,
|
||||
},
|
||||
workflows: {
|
||||
id: 'workflows',
|
||||
translationKey: 'ui.tabs.workflows',
|
||||
icon: <PiFlowArrowBold />,
|
||||
content: <NodesTab />,
|
||||
parametersPanel: <NodeEditorPanelGroup />,
|
||||
},
|
||||
models: {
|
||||
id: 'models',
|
||||
@ -81,7 +95,6 @@ const enabledTabsSelector = createMemoizedSelector(selectConfigSlice, (config) =
|
||||
);
|
||||
|
||||
const NO_GALLERY_PANEL_TABS: InvokeTabName[] = ['models', 'queue'];
|
||||
const NO_OPTIONS_PANEL_TABS: InvokeTabName[] = ['models', 'queue'];
|
||||
const panelStyles: CSSProperties = { height: '100%', width: '100%' };
|
||||
const GALLERY_MIN_SIZE_PX = 310;
|
||||
const GALLERY_MIN_SIZE_PCT = 20;
|
||||
@ -103,7 +116,6 @@ const InvokeTabs = () => {
|
||||
e.target.blur();
|
||||
}
|
||||
}, []);
|
||||
const shouldShowOptionsPanel = useMemo(() => !NO_OPTIONS_PANEL_TABS.includes(activeTabName), [activeTabName]);
|
||||
const shouldShowGalleryPanel = useMemo(() => !NO_GALLERY_PANEL_TABS.includes(activeTabName), [activeTabName]);
|
||||
|
||||
const tabs = useMemo(
|
||||
@ -232,7 +244,7 @@ const InvokeTabs = () => {
|
||||
style={panelStyles}
|
||||
storage={panelStorage}
|
||||
>
|
||||
{shouldShowOptionsPanel && (
|
||||
{!!TAB_DATA[activeTabName].parametersPanel && (
|
||||
<>
|
||||
<Panel
|
||||
id="options-panel"
|
||||
@ -244,7 +256,7 @@ const InvokeTabs = () => {
|
||||
onExpand={optionsPanel.onExpand}
|
||||
collapsible
|
||||
>
|
||||
<ParametersPanelComponent />
|
||||
{TAB_DATA[activeTabName].parametersPanel}
|
||||
</Panel>
|
||||
<ResizeHandle
|
||||
id="options-main-handle"
|
||||
@ -280,23 +292,10 @@ const InvokeTabs = () => {
|
||||
</>
|
||||
)}
|
||||
</PanelGroup>
|
||||
{shouldShowOptionsPanel && <FloatingParametersPanelButtons panelApi={optionsPanel} />}
|
||||
{!!TAB_DATA[activeTabName].parametersPanel && <FloatingParametersPanelButtons panelApi={optionsPanel} />}
|
||||
{shouldShowGalleryPanel && <FloatingGalleryButton panelApi={galleryPanel} />}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(InvokeTabs);
|
||||
|
||||
const ParametersPanelComponent = memo(() => {
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
|
||||
if (activeTabName === 'workflows') {
|
||||
return <NodeEditorPanelGroup />;
|
||||
}
|
||||
if (activeTabName === 'generation') {
|
||||
return <ParametersPanelTextToImage />;
|
||||
}
|
||||
return <ParametersPanel />;
|
||||
});
|
||||
ParametersPanelComponent.displayName = 'ParametersPanelComponent';
|
||||
|
@ -10,7 +10,6 @@ import { ControlSettingsAccordion } from 'features/settingsAccordions/components
|
||||
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
|
||||
import { ImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion';
|
||||
import { RefinerSettingsAccordion } from 'features/settingsAccordions/components/RefinerSettingsAccordion/RefinerSettingsAccordion';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo } from 'react';
|
||||
@ -20,8 +19,7 @@ const overlayScrollbarsStyles: CSSProperties = {
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
const ParametersPanel = () => {
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const ParametersPanelCanvas = () => {
|
||||
const isSDXL = useAppSelector((s) => s.generation.model?.base === 'sdxl');
|
||||
|
||||
return (
|
||||
@ -34,8 +32,8 @@ const ParametersPanel = () => {
|
||||
{isSDXL ? <SDXLPrompts /> : <Prompts />}
|
||||
<ImageSettingsAccordion />
|
||||
<GenerationSettingsAccordion />
|
||||
{activeTabName !== 'generation' && <ControlSettingsAccordion />}
|
||||
{activeTabName === 'canvas' && <CompositingSettingsAccordion />}
|
||||
<ControlSettingsAccordion />
|
||||
<CompositingSettingsAccordion />
|
||||
{isSDXL && <RefinerSettingsAccordion />}
|
||||
<AdvancedSettingsAccordion />
|
||||
</Flex>
|
||||
@ -46,4 +44,4 @@ const ParametersPanel = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParametersPanel);
|
||||
export default memo(ParametersPanelCanvas);
|
@ -0,0 +1,41 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
||||
import { Prompts } from 'features/parameters/components/Prompts/Prompts';
|
||||
import QueueControls from 'features/queue/components/QueueControls';
|
||||
import { SDXLPrompts } from 'features/sdxl/components/SDXLPrompts/SDXLPrompts';
|
||||
import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
|
||||
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
|
||||
import { UpscaleSettingsAccordion } from 'features/settingsAccordions/components/UpscaleSettingsAccordion/UpscaleSettingsAccordion';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
const overlayScrollbarsStyles: CSSProperties = {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
const ParametersPanelUpscale = () => {
|
||||
const isSDXL = useAppSelector((s) => s.generation.model?.base === 'sdxl');
|
||||
|
||||
return (
|
||||
<Flex w="full" h="full" flexDir="column" gap={2}>
|
||||
<QueueControls />
|
||||
<Flex w="full" h="full" position="relative">
|
||||
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
{isSDXL ? <SDXLPrompts /> : <Prompts />}
|
||||
<UpscaleSettingsAccordion />
|
||||
<GenerationSettingsAccordion />
|
||||
<AdvancedSettingsAccordion />
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParametersPanelUpscale);
|
@ -0,0 +1,13 @@
|
||||
import { Box } from '@invoke-ai/ui-library';
|
||||
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
|
||||
import { memo } from 'react';
|
||||
|
||||
const UpscalingTab = () => {
|
||||
return (
|
||||
<Box layerStyle="first" position="relative" w="full" h="full" p={2} borderRadius="base">
|
||||
<ImageViewer />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(UpscalingTab);
|
@ -1,3 +1,3 @@
|
||||
export const TAB_NUMBER_MAP = ['generation', 'canvas', 'workflows', 'models', 'queue'] as const;
|
||||
export const TAB_NUMBER_MAP = ['generation', 'canvas', 'upscaling', 'workflows', 'models', 'queue'] as const;
|
||||
|
||||
export type InvokeTabName = (typeof TAB_NUMBER_MAP)[number];
|
||||
|
@ -104,6 +104,10 @@ export const imagesApi = api.injectEndpoints({
|
||||
type: 'Board',
|
||||
id: boardId,
|
||||
},
|
||||
{
|
||||
type: 'BoardImagesTotal',
|
||||
id: boardId,
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
@ -136,6 +140,10 @@ export const imagesApi = api.injectEndpoints({
|
||||
type: 'Board',
|
||||
id: boardId,
|
||||
},
|
||||
{
|
||||
type: 'BoardImagesTotal',
|
||||
id: boardId,
|
||||
},
|
||||
];
|
||||
|
||||
return tags;
|
||||
@ -169,6 +177,10 @@ export const imagesApi = api.injectEndpoints({
|
||||
type: 'Board',
|
||||
id: boardId,
|
||||
},
|
||||
{
|
||||
type: 'BoardImagesTotal',
|
||||
id: boardId,
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
@ -300,6 +312,10 @@ export const imagesApi = api.injectEndpoints({
|
||||
type: 'Board',
|
||||
id: boardId,
|
||||
},
|
||||
{
|
||||
type: 'BoardImagesTotal',
|
||||
id: boardId,
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
@ -362,6 +378,10 @@ export const imagesApi = api.injectEndpoints({
|
||||
},
|
||||
{ type: 'Board', id: board_id },
|
||||
{ type: 'Board', id: imageDTO.board_id ?? 'none' },
|
||||
{
|
||||
type: 'BoardImagesTotal',
|
||||
id: imageDTO.board_id ?? 'none',
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
@ -393,6 +413,11 @@ export const imagesApi = api.injectEndpoints({
|
||||
},
|
||||
{ type: 'Board', id: imageDTO.board_id ?? 'none' },
|
||||
{ type: 'Board', id: 'none' },
|
||||
{
|
||||
type: 'BoardImagesTotal',
|
||||
id: imageDTO.board_id ?? 'none',
|
||||
},
|
||||
{ type: 'BoardImagesTotal', id: 'none' },
|
||||
];
|
||||
},
|
||||
}),
|
||||
@ -434,6 +459,10 @@ export const imagesApi = api.injectEndpoints({
|
||||
tags.push({ type: 'Image', id: imageDTO.image_name });
|
||||
}
|
||||
tags.push({ type: 'Board', id: board_id });
|
||||
tags.push({
|
||||
type: 'BoardImagesTotal',
|
||||
id: board_id ?? 'none',
|
||||
});
|
||||
return tags;
|
||||
},
|
||||
}),
|
||||
@ -480,6 +509,10 @@ export const imagesApi = api.injectEndpoints({
|
||||
}
|
||||
tags.push({ type: 'Image', id: image_name });
|
||||
tags.push({ type: 'Board', id: board_id });
|
||||
tags.push({
|
||||
type: 'BoardImagesTotal',
|
||||
id: board_id ?? 'none',
|
||||
});
|
||||
});
|
||||
|
||||
return tags;
|
||||
|
@ -6568,7 +6568,7 @@ export type components = {
|
||||
tiled?: boolean;
|
||||
/**
|
||||
* Tile Size
|
||||
* @description The tile size for VAE tiling in pixels (image space). If set to 0, the default tile size for the
|
||||
* @description The tile size for VAE tiling in pixels (image space). If set to 0, the default tile size for the model will be used. Larger tile sizes generally produce better results at the cost of higher memory usage.
|
||||
* @default 0
|
||||
*/
|
||||
tile_size?: number;
|
||||
@ -7304,146 +7304,146 @@ export type components = {
|
||||
project_id: string | null;
|
||||
};
|
||||
InvocationOutputMap: {
|
||||
noise: components["schemas"]["NoiseOutput"];
|
||||
pair_tile_image: components["schemas"]["PairTileImageOutput"];
|
||||
color_correct: components["schemas"]["ImageOutput"];
|
||||
tile_to_properties: components["schemas"]["TileToPropertiesOutput"];
|
||||
float_to_int: components["schemas"]["IntegerOutput"];
|
||||
rand_int: components["schemas"]["IntegerOutput"];
|
||||
latents: components["schemas"]["LatentsOutput"];
|
||||
canvas_paste_back: components["schemas"]["ImageOutput"];
|
||||
controlnet: components["schemas"]["ControlOutput"];
|
||||
img_blur: components["schemas"]["ImageOutput"];
|
||||
freeu: components["schemas"]["UNetOutput"];
|
||||
string: components["schemas"]["StringOutput"];
|
||||
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
|
||||
boolean: components["schemas"]["BooleanOutput"];
|
||||
lresize: components["schemas"]["LatentsOutput"];
|
||||
mask_from_id: components["schemas"]["ImageOutput"];
|
||||
string_split: components["schemas"]["String2Output"];
|
||||
create_gradient_mask: components["schemas"]["GradientMaskOutput"];
|
||||
seamless: components["schemas"]["SeamlessModeOutput"];
|
||||
merge_tiles_to_image: components["schemas"]["ImageOutput"];
|
||||
canny_image_processor: components["schemas"]["ImageOutput"];
|
||||
crop_latents: components["schemas"]["LatentsOutput"];
|
||||
mask_edge: components["schemas"]["ImageOutput"];
|
||||
img_paste: components["schemas"]["ImageOutput"];
|
||||
zoe_depth_image_processor: components["schemas"]["ImageOutput"];
|
||||
img_nsfw: components["schemas"]["ImageOutput"];
|
||||
img_mul: components["schemas"]["ImageOutput"];
|
||||
spandrel_image_to_image: components["schemas"]["ImageOutput"];
|
||||
tomask: components["schemas"]["ImageOutput"];
|
||||
color_map_image_processor: components["schemas"]["ImageOutput"];
|
||||
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
|
||||
infill_rgba: components["schemas"]["ImageOutput"];
|
||||
model_identifier: components["schemas"]["ModelIdentifierOutput"];
|
||||
metadata: components["schemas"]["MetadataOutput"];
|
||||
img_ilerp: components["schemas"]["ImageOutput"];
|
||||
add: components["schemas"]["IntegerOutput"];
|
||||
img_channel_multiply: components["schemas"]["ImageOutput"];
|
||||
integer: components["schemas"]["IntegerOutput"];
|
||||
integer_collection: components["schemas"]["IntegerCollectionOutput"];
|
||||
img_crop: components["schemas"]["ImageOutput"];
|
||||
show_image: components["schemas"]["ImageOutput"];
|
||||
string_replace: components["schemas"]["StringOutput"];
|
||||
prompt_from_file: components["schemas"]["StringCollectionOutput"];
|
||||
string_join: components["schemas"]["StringOutput"];
|
||||
metadata_item: components["schemas"]["MetadataItemOutput"];
|
||||
lblend: components["schemas"]["LatentsOutput"];
|
||||
t2i_adapter: components["schemas"]["T2IAdapterOutput"];
|
||||
infill_cv2: components["schemas"]["ImageOutput"];
|
||||
sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"];
|
||||
core_metadata: components["schemas"]["MetadataOutput"];
|
||||
invert_tensor_mask: components["schemas"]["MaskOutput"];
|
||||
integer_math: components["schemas"]["IntegerOutput"];
|
||||
content_shuffle_image_processor: components["schemas"]["ImageOutput"];
|
||||
dynamic_prompt: components["schemas"]["StringCollectionOutput"];
|
||||
lineart_anime_image_processor: components["schemas"]["ImageOutput"];
|
||||
string_split_neg: components["schemas"]["StringPosNegOutput"];
|
||||
round_float: components["schemas"]["FloatOutput"];
|
||||
rand_float: components["schemas"]["FloatOutput"];
|
||||
lora_collection_loader: components["schemas"]["LoRALoaderOutput"];
|
||||
midas_depth_image_processor: components["schemas"]["ImageOutput"];
|
||||
random_range: components["schemas"]["IntegerCollectionOutput"];
|
||||
sub: components["schemas"]["IntegerOutput"];
|
||||
infill_lama: components["schemas"]["ImageOutput"];
|
||||
float_range: components["schemas"]["FloatCollectionOutput"];
|
||||
save_image: components["schemas"]["ImageOutput"];
|
||||
iterate: components["schemas"]["IterateInvocationOutput"];
|
||||
hed_image_processor: components["schemas"]["ImageOutput"];
|
||||
dw_openpose_image_processor: components["schemas"]["ImageOutput"];
|
||||
scheduler: components["schemas"]["SchedulerOutput"];
|
||||
string_collection: components["schemas"]["StringCollectionOutput"];
|
||||
lineart_image_processor: components["schemas"]["ImageOutput"];
|
||||
image: components["schemas"]["ImageOutput"];
|
||||
merge_metadata: components["schemas"]["MetadataOutput"];
|
||||
image_collection: components["schemas"]["ImageCollectionOutput"];
|
||||
img_watermark: components["schemas"]["ImageOutput"];
|
||||
pidi_image_processor: components["schemas"]["ImageOutput"];
|
||||
sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
||||
collect: components["schemas"]["CollectInvocationOutput"];
|
||||
lora_selector: components["schemas"]["LoRASelectorOutput"];
|
||||
tile_image_processor: components["schemas"]["ImageOutput"];
|
||||
denoise_latents: components["schemas"]["LatentsOutput"];
|
||||
sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
||||
img_conv: components["schemas"]["ImageOutput"];
|
||||
face_mask_detection: components["schemas"]["FaceMaskOutput"];
|
||||
infill_patchmatch: components["schemas"]["ImageOutput"];
|
||||
rectangle_mask: components["schemas"]["MaskOutput"];
|
||||
img_lerp: components["schemas"]["ImageOutput"];
|
||||
tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"];
|
||||
face_identifier: components["schemas"]["ImageOutput"];
|
||||
step_param_easing: components["schemas"]["FloatCollectionOutput"];
|
||||
unsharp_mask: components["schemas"]["ImageOutput"];
|
||||
mediapipe_face_processor: components["schemas"]["ImageOutput"];
|
||||
calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"];
|
||||
lscale: components["schemas"]["LatentsOutput"];
|
||||
color: components["schemas"]["ColorOutput"];
|
||||
lora_loader: components["schemas"]["LoRALoaderOutput"];
|
||||
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
|
||||
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
|
||||
conditioning: components["schemas"]["ConditioningOutput"];
|
||||
float_collection: components["schemas"]["FloatCollectionOutput"];
|
||||
img_pad_crop: components["schemas"]["ImageOutput"];
|
||||
mul: components["schemas"]["IntegerOutput"];
|
||||
heuristic_resize: components["schemas"]["ImageOutput"];
|
||||
create_denoise_mask: components["schemas"]["DenoiseMaskOutput"];
|
||||
img_chan: components["schemas"]["ImageOutput"];
|
||||
leres_image_processor: components["schemas"]["ImageOutput"];
|
||||
infill_tile: components["schemas"]["ImageOutput"];
|
||||
i2l: components["schemas"]["LatentsOutput"];
|
||||
string_join_three: components["schemas"]["StringOutput"];
|
||||
ip_adapter: components["schemas"]["IPAdapterOutput"];
|
||||
main_model_loader: components["schemas"]["ModelLoaderOutput"];
|
||||
float: components["schemas"]["FloatOutput"];
|
||||
compel: components["schemas"]["ConditioningOutput"];
|
||||
range_of_size: components["schemas"]["IntegerCollectionOutput"];
|
||||
normalbae_image_processor: components["schemas"]["ImageOutput"];
|
||||
ideal_size: components["schemas"]["IdealSizeOutput"];
|
||||
conditioning_collection: components["schemas"]["ConditioningCollectionOutput"];
|
||||
depth_anything_image_processor: components["schemas"]["ImageOutput"];
|
||||
mask_combine: components["schemas"]["ImageOutput"];
|
||||
l2i: components["schemas"]["ImageOutput"];
|
||||
latents_collection: components["schemas"]["LatentsCollectionOutput"];
|
||||
float_math: components["schemas"]["FloatOutput"];
|
||||
img_hue_adjust: components["schemas"]["ImageOutput"];
|
||||
img_scale: components["schemas"]["ImageOutput"];
|
||||
esrgan: components["schemas"]["ImageOutput"];
|
||||
vae_loader: components["schemas"]["VAEOutput"];
|
||||
sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"];
|
||||
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
|
||||
segment_anything_processor: components["schemas"]["ImageOutput"];
|
||||
img_resize: components["schemas"]["ImageOutput"];
|
||||
range: components["schemas"]["IntegerCollectionOutput"];
|
||||
calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"];
|
||||
mlsd_image_processor: components["schemas"]["ImageOutput"];
|
||||
img_channel_offset: components["schemas"]["ImageOutput"];
|
||||
cv_inpaint: components["schemas"]["ImageOutput"];
|
||||
image_mask_to_tensor: components["schemas"]["MaskOutput"];
|
||||
blank_image: components["schemas"]["ImageOutput"];
|
||||
div: components["schemas"]["IntegerOutput"];
|
||||
alpha_mask_to_tensor: components["schemas"]["MaskOutput"];
|
||||
lblend: components["schemas"]["LatentsOutput"];
|
||||
img_channel_multiply: components["schemas"]["ImageOutput"];
|
||||
float_collection: components["schemas"]["FloatCollectionOutput"];
|
||||
face_off: components["schemas"]["FaceOffOutput"];
|
||||
mlsd_image_processor: components["schemas"]["ImageOutput"];
|
||||
color_map_image_processor: components["schemas"]["ImageOutput"];
|
||||
img_paste: components["schemas"]["ImageOutput"];
|
||||
img_scale: components["schemas"]["ImageOutput"];
|
||||
zoe_depth_image_processor: components["schemas"]["ImageOutput"];
|
||||
tile_to_properties: components["schemas"]["TileToPropertiesOutput"];
|
||||
img_blur: components["schemas"]["ImageOutput"];
|
||||
merge_tiles_to_image: components["schemas"]["ImageOutput"];
|
||||
latents_collection: components["schemas"]["LatentsCollectionOutput"];
|
||||
img_mul: components["schemas"]["ImageOutput"];
|
||||
image_mask_to_tensor: components["schemas"]["MaskOutput"];
|
||||
conditioning_collection: components["schemas"]["ConditioningCollectionOutput"];
|
||||
cv_inpaint: components["schemas"]["ImageOutput"];
|
||||
img_pad_crop: components["schemas"]["ImageOutput"];
|
||||
lresize: components["schemas"]["LatentsOutput"];
|
||||
conditioning: components["schemas"]["ConditioningOutput"];
|
||||
dynamic_prompt: components["schemas"]["StringCollectionOutput"];
|
||||
tomask: components["schemas"]["ImageOutput"];
|
||||
mul: components["schemas"]["IntegerOutput"];
|
||||
seamless: components["schemas"]["SeamlessModeOutput"];
|
||||
canny_image_processor: components["schemas"]["ImageOutput"];
|
||||
metadata_item: components["schemas"]["MetadataItemOutput"];
|
||||
add: components["schemas"]["IntegerOutput"];
|
||||
crop_latents: components["schemas"]["LatentsOutput"];
|
||||
integer_collection: components["schemas"]["IntegerCollectionOutput"];
|
||||
sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"];
|
||||
string_split: components["schemas"]["String2Output"];
|
||||
tile_image_processor: components["schemas"]["ImageOutput"];
|
||||
infill_cv2: components["schemas"]["ImageOutput"];
|
||||
tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"];
|
||||
collect: components["schemas"]["CollectInvocationOutput"];
|
||||
image_collection: components["schemas"]["ImageCollectionOutput"];
|
||||
save_image: components["schemas"]["ImageOutput"];
|
||||
controlnet: components["schemas"]["ControlOutput"];
|
||||
float_math: components["schemas"]["FloatOutput"];
|
||||
sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"];
|
||||
i2l: components["schemas"]["LatentsOutput"];
|
||||
infill_lama: components["schemas"]["ImageOutput"];
|
||||
sub: components["schemas"]["IntegerOutput"];
|
||||
div: components["schemas"]["IntegerOutput"];
|
||||
face_mask_detection: components["schemas"]["FaceMaskOutput"];
|
||||
esrgan: components["schemas"]["ImageOutput"];
|
||||
mask_combine: components["schemas"]["ImageOutput"];
|
||||
ip_adapter: components["schemas"]["IPAdapterOutput"];
|
||||
blank_image: components["schemas"]["ImageOutput"];
|
||||
heuristic_resize: components["schemas"]["ImageOutput"];
|
||||
rand_int: components["schemas"]["IntegerOutput"];
|
||||
lora_selector: components["schemas"]["LoRASelectorOutput"];
|
||||
unsharp_mask: components["schemas"]["ImageOutput"];
|
||||
face_identifier: components["schemas"]["ImageOutput"];
|
||||
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
|
||||
infill_patchmatch: components["schemas"]["ImageOutput"];
|
||||
img_nsfw: components["schemas"]["ImageOutput"];
|
||||
lineart_anime_image_processor: components["schemas"]["ImageOutput"];
|
||||
compel: components["schemas"]["ConditioningOutput"];
|
||||
rectangle_mask: components["schemas"]["MaskOutput"];
|
||||
lora_collection_loader: components["schemas"]["LoRALoaderOutput"];
|
||||
freeu: components["schemas"]["UNetOutput"];
|
||||
img_hue_adjust: components["schemas"]["ImageOutput"];
|
||||
pidi_image_processor: components["schemas"]["ImageOutput"];
|
||||
content_shuffle_image_processor: components["schemas"]["ImageOutput"];
|
||||
mediapipe_face_processor: components["schemas"]["ImageOutput"];
|
||||
string_split_neg: components["schemas"]["StringPosNegOutput"];
|
||||
img_conv: components["schemas"]["ImageOutput"];
|
||||
lora_loader: components["schemas"]["LoRALoaderOutput"];
|
||||
color_correct: components["schemas"]["ImageOutput"];
|
||||
img_ilerp: components["schemas"]["ImageOutput"];
|
||||
noise: components["schemas"]["NoiseOutput"];
|
||||
float_range: components["schemas"]["FloatCollectionOutput"];
|
||||
dw_openpose_image_processor: components["schemas"]["ImageOutput"];
|
||||
float_to_int: components["schemas"]["IntegerOutput"];
|
||||
invert_tensor_mask: components["schemas"]["MaskOutput"];
|
||||
random_range: components["schemas"]["IntegerCollectionOutput"];
|
||||
latents: components["schemas"]["LatentsOutput"];
|
||||
leres_image_processor: components["schemas"]["ImageOutput"];
|
||||
t2i_adapter: components["schemas"]["T2IAdapterOutput"];
|
||||
pair_tile_image: components["schemas"]["PairTileImageOutput"];
|
||||
mask_edge: components["schemas"]["ImageOutput"];
|
||||
metadata: components["schemas"]["MetadataOutput"];
|
||||
string_join: components["schemas"]["StringOutput"];
|
||||
core_metadata: components["schemas"]["MetadataOutput"];
|
||||
canvas_paste_back: components["schemas"]["ImageOutput"];
|
||||
sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
||||
img_channel_offset: components["schemas"]["ImageOutput"];
|
||||
lineart_image_processor: components["schemas"]["ImageOutput"];
|
||||
midas_depth_image_processor: components["schemas"]["ImageOutput"];
|
||||
lscale: components["schemas"]["LatentsOutput"];
|
||||
string: components["schemas"]["StringOutput"];
|
||||
integer: components["schemas"]["IntegerOutput"];
|
||||
string_replace: components["schemas"]["StringOutput"];
|
||||
depth_anything_image_processor: components["schemas"]["ImageOutput"];
|
||||
main_model_loader: components["schemas"]["ModelLoaderOutput"];
|
||||
image: components["schemas"]["ImageOutput"];
|
||||
prompt_from_file: components["schemas"]["StringCollectionOutput"];
|
||||
sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
||||
mask_from_id: components["schemas"]["ImageOutput"];
|
||||
normalbae_image_processor: components["schemas"]["ImageOutput"];
|
||||
infill_rgba: components["schemas"]["ImageOutput"];
|
||||
step_param_easing: components["schemas"]["FloatCollectionOutput"];
|
||||
hed_image_processor: components["schemas"]["ImageOutput"];
|
||||
img_chan: components["schemas"]["ImageOutput"];
|
||||
float: components["schemas"]["FloatOutput"];
|
||||
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
|
||||
segment_anything_processor: components["schemas"]["ImageOutput"];
|
||||
range_of_size: components["schemas"]["IntegerCollectionOutput"];
|
||||
boolean: components["schemas"]["BooleanOutput"];
|
||||
iterate: components["schemas"]["IterateInvocationOutput"];
|
||||
denoise_latents: components["schemas"]["LatentsOutput"];
|
||||
calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"];
|
||||
color: components["schemas"]["ColorOutput"];
|
||||
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
|
||||
scheduler: components["schemas"]["SchedulerOutput"];
|
||||
rand_float: components["schemas"]["FloatOutput"];
|
||||
create_denoise_mask: components["schemas"]["DenoiseMaskOutput"];
|
||||
range: components["schemas"]["IntegerCollectionOutput"];
|
||||
img_watermark: components["schemas"]["ImageOutput"];
|
||||
spandrel_image_to_image: components["schemas"]["ImageOutput"];
|
||||
show_image: components["schemas"]["ImageOutput"];
|
||||
string_collection: components["schemas"]["StringCollectionOutput"];
|
||||
infill_tile: components["schemas"]["ImageOutput"];
|
||||
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
|
||||
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
|
||||
ideal_size: components["schemas"]["IdealSizeOutput"];
|
||||
img_lerp: components["schemas"]["ImageOutput"];
|
||||
l2i: components["schemas"]["ImageOutput"];
|
||||
create_gradient_mask: components["schemas"]["GradientMaskOutput"];
|
||||
vae_loader: components["schemas"]["VAEOutput"];
|
||||
calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"];
|
||||
alpha_mask_to_tensor: components["schemas"]["MaskOutput"];
|
||||
integer_math: components["schemas"]["IntegerOutput"];
|
||||
model_identifier: components["schemas"]["ModelIdentifierOutput"];
|
||||
img_crop: components["schemas"]["ImageOutput"];
|
||||
img_resize: components["schemas"]["ImageOutput"];
|
||||
round_float: components["schemas"]["FloatOutput"];
|
||||
};
|
||||
/**
|
||||
* InvocationStartedEvent
|
||||
@ -7783,7 +7783,7 @@ export type components = {
|
||||
tiled?: boolean;
|
||||
/**
|
||||
* Tile Size
|
||||
* @description The tile size for VAE tiling in pixels (image space). If set to 0, the default tile size for the
|
||||
* @description The tile size for VAE tiling in pixels (image space). If set to 0, the default tile size for the model will be used. Larger tile sizes generally produce better results at the cost of higher memory usage.
|
||||
* @default 0
|
||||
*/
|
||||
tile_size?: number;
|
||||
@ -11982,6 +11982,24 @@ export type components = {
|
||||
* @default null
|
||||
*/
|
||||
image_to_image_model?: components["schemas"]["ModelIdentifierField"];
|
||||
/**
|
||||
* Tile Size
|
||||
* @description The tile size for tiled image-to-image. Set to 0 to disable tiling.
|
||||
* @default 512
|
||||
*/
|
||||
tile_size?: number;
|
||||
/**
|
||||
* Scale
|
||||
* @description The final scale of the output image. If the model does not upscale the image, this will be ignored.
|
||||
* @default 4
|
||||
*/
|
||||
scale?: number;
|
||||
/**
|
||||
* Fit To Multiple Of 8
|
||||
* @description If true, the output image will be resized to the nearest multiple of 8 in both dimensions.
|
||||
* @default false
|
||||
*/
|
||||
fit_to_multiple_of_8?: boolean;
|
||||
/**
|
||||
* type
|
||||
* @default spandrel_image_to_image
|
||||
|
@ -205,6 +205,10 @@ type CanvasInitialImageAction = {
|
||||
type: 'SET_CANVAS_INITIAL_IMAGE';
|
||||
};
|
||||
|
||||
type UpscaleInitialImageAction = {
|
||||
type: 'SET_UPSCALE_INITIAL_IMAGE';
|
||||
};
|
||||
|
||||
type ToastAction = {
|
||||
type: 'TOAST';
|
||||
title?: string;
|
||||
@ -223,4 +227,5 @@ export type PostUploadAction =
|
||||
| CALayerImagePostUploadAction
|
||||
| IPALayerImagePostUploadAction
|
||||
| RGLayerIPAdapterImagePostUploadAction
|
||||
| IILayerImagePostUploadAction;
|
||||
| IILayerImagePostUploadAction
|
||||
| UpscaleInitialImageAction;
|
||||
|
@ -55,7 +55,7 @@ dependencies = [
|
||||
"transformers==4.41.1",
|
||||
|
||||
# Core application dependencies, pinned for reproducible builds.
|
||||
"fastapi-events==0.11.0",
|
||||
"fastapi-events==0.11.1",
|
||||
"fastapi==0.111.0",
|
||||
"huggingface-hub==0.23.1",
|
||||
"pydantic-settings==2.2.1",
|
||||
|
Loading…
Reference in New Issue
Block a user