mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-01-23 02:48:11 -05:00
Compare commits
1 Commits
v5.3.0
...
ebr/pin-py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe6ba3571a |
@@ -17,21 +17,21 @@ If you just want to use Invoke, you should use the [installer][installer link].
|
|||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1. Run through the [requirements][requirements link].
|
1. Run through the [requirements][requirements link].
|
||||||
2. [Fork and clone][forking link] the [InvokeAI repo][repo link].
|
1. [Fork and clone][forking link] the [InvokeAI repo][repo link].
|
||||||
3. Create an directory for user data (images, models, db, etc). This is typically at `~/invokeai`, but if you already have a non-dev install, you may want to create a separate directory for the dev install.
|
1. Create an directory for user data (images, models, db, etc). This is typically at `~/invokeai`, but if you already have a non-dev install, you may want to create a separate directory for the dev install.
|
||||||
4. Create a python virtual environment inside the directory you just created:
|
1. Create a python virtual environment inside the directory you just created:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 -m venv .venv --prompt InvokeAI-Dev
|
python3 -m venv .venv --prompt InvokeAI-Dev
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Activate the venv (you'll need to do this every time you want to run the app):
|
1. Activate the venv (you'll need to do this every time you want to run the app):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Install the repo as an [editable install][editable install link]:
|
1. Install the repo as an [editable install][editable install link]:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pip install -e ".[dev,test,xformers]" --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu121
|
pip install -e ".[dev,test,xformers]" --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu121
|
||||||
@@ -39,27 +39,24 @@ If you just want to use Invoke, you should use the [installer][installer link].
|
|||||||
|
|
||||||
Refer to the [manual installation][manual install link]] instructions for more determining the correct install options. `xformers` is optional, but `dev` and `test` are not.
|
Refer to the [manual installation][manual install link]] instructions for more determining the correct install options. `xformers` is optional, but `dev` and `test` are not.
|
||||||
|
|
||||||
7. Install the frontend dev toolchain:
|
1. Install the frontend dev toolchain:
|
||||||
|
|
||||||
- [`nodejs`](https://nodejs.org/) (recommend v20 LTS)
|
- [`nodejs`](https://nodejs.org/) (recommend v20 LTS)
|
||||||
- [`pnpm`](https://pnpm.io/8.x/installation) (must be v8 - not v9!)
|
- [`pnpm`](https://pnpm.io/installation#installing-a-specific-version) (must be v8 - not v9!)
|
||||||
|
|
||||||
8. Do a production build of the frontend:
|
1. Do a production build of the frontend:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd PATH_TO_INVOKEAI_REPO/invokeai/frontend/web
|
|
||||||
pnpm i
|
|
||||||
pnpm build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
9. Start the application:
|
1. Start the application:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd PATH_TO_INVOKEAI_REPO
|
|
||||||
python scripts/invokeai-web.py
|
python scripts/invokeai-web.py
|
||||||
```
|
```
|
||||||
|
|
||||||
10. Access the UI at `localhost:9090`.
|
1. Access the UI at `localhost:9090`.
|
||||||
|
|
||||||
## Updating the UI
|
## Updating the UI
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,6 @@ class ApplyMaskTensorToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
|||||||
|
|
||||||
mask: TensorField = InputField(description="The mask tensor to apply.")
|
mask: TensorField = InputField(description="The mask tensor to apply.")
|
||||||
image: ImageField = InputField(description="The image to apply the mask to.")
|
image: ImageField = InputField(description="The image to apply the mask to.")
|
||||||
invert: bool = InputField(default=False, description="Whether to invert the mask.")
|
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
image = context.images.get_pil(self.image.image_name, mode="RGBA")
|
image = context.images.get_pil(self.image.image_name, mode="RGBA")
|
||||||
@@ -180,9 +179,6 @@ class ApplyMaskTensorToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
|||||||
mask = mask > 0.5
|
mask = mask > 0.5
|
||||||
mask_np = (mask.float() * 255).byte().cpu().numpy().astype(np.uint8)
|
mask_np = (mask.float() * 255).byte().cpu().numpy().astype(np.uint8)
|
||||||
|
|
||||||
if self.invert:
|
|
||||||
mask_np = 255 - mask_np
|
|
||||||
|
|
||||||
# Apply the mask only to the alpha channel where the original alpha is non-zero. This preserves the original
|
# Apply the mask only to the alpha channel where the original alpha is non-zero. This preserves the original
|
||||||
# image's transparency - else the transparent regions would end up as opaque black.
|
# image's transparency - else the transparent regions would end up as opaque black.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
from copy import deepcopy
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Callable, Optional, Union
|
from typing import TYPE_CHECKING, Callable, Optional, Union
|
||||||
@@ -222,7 +221,7 @@ class ImagesInterface(InvocationContextInterface):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_pil(self, image_name: str, mode: IMAGE_MODES | None = None) -> Image:
|
def get_pil(self, image_name: str, mode: IMAGE_MODES | None = None) -> Image:
|
||||||
"""Gets an image as a PIL Image object. This method returns a copy of the image.
|
"""Gets an image as a PIL Image object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
image_name: The name of the image to get.
|
image_name: The name of the image to get.
|
||||||
@@ -234,15 +233,11 @@ class ImagesInterface(InvocationContextInterface):
|
|||||||
image = self._services.images.get_pil_image(image_name)
|
image = self._services.images.get_pil_image(image_name)
|
||||||
if mode and mode != image.mode:
|
if mode and mode != image.mode:
|
||||||
try:
|
try:
|
||||||
# convert makes a copy!
|
|
||||||
image = image.convert(mode)
|
image = image.convert(mode)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._services.logger.warning(
|
self._services.logger.warning(
|
||||||
f"Could not convert image from {image.mode} to {mode}. Using original mode instead."
|
f"Could not convert image from {image.mode} to {mode}. Using original mode instead."
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
# copy the image to prevent the user from modifying the original
|
|
||||||
image = image.copy()
|
|
||||||
return image
|
return image
|
||||||
|
|
||||||
def get_metadata(self, image_name: str) -> Optional[MetadataField]:
|
def get_metadata(self, image_name: str) -> Optional[MetadataField]:
|
||||||
@@ -295,15 +290,15 @@ class TensorsInterface(InvocationContextInterface):
|
|||||||
return name
|
return name
|
||||||
|
|
||||||
def load(self, name: str) -> Tensor:
|
def load(self, name: str) -> Tensor:
|
||||||
"""Loads a tensor by name. This method returns a copy of the tensor.
|
"""Loads a tensor by name.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: The name of the tensor to load.
|
name: The name of the tensor to load.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The tensor.
|
The loaded tensor.
|
||||||
"""
|
"""
|
||||||
return self._services.tensors.load(name).clone()
|
return self._services.tensors.load(name)
|
||||||
|
|
||||||
|
|
||||||
class ConditioningInterface(InvocationContextInterface):
|
class ConditioningInterface(InvocationContextInterface):
|
||||||
@@ -321,16 +316,16 @@ class ConditioningInterface(InvocationContextInterface):
|
|||||||
return name
|
return name
|
||||||
|
|
||||||
def load(self, name: str) -> ConditioningFieldData:
|
def load(self, name: str) -> ConditioningFieldData:
|
||||||
"""Loads conditioning data by name. This method returns a copy of the conditioning data.
|
"""Loads conditioning data by name.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: The name of the conditioning data to load.
|
name: The name of the conditioning data to load.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The conditioning data.
|
The loaded conditioning data.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return deepcopy(self._services.conditioning.load(name))
|
return self._services.conditioning.load(name)
|
||||||
|
|
||||||
|
|
||||||
class ModelsInterface(InvocationContextInterface):
|
class ModelsInterface(InvocationContextInterface):
|
||||||
|
|||||||
@@ -713,12 +713,8 @@
|
|||||||
"convertToDiffusersHelpText4": "This is a one time process only. It might take around 30s-60s depending on the specifications of your computer.",
|
"convertToDiffusersHelpText4": "This is a one time process only. It might take around 30s-60s depending on the specifications of your computer.",
|
||||||
"convertToDiffusersHelpText5": "Please make sure you have enough disk space. Models generally vary between 2GB-7GB in size.",
|
"convertToDiffusersHelpText5": "Please make sure you have enough disk space. Models generally vary between 2GB-7GB in size.",
|
||||||
"convertToDiffusersHelpText6": "Do you wish to convert this model?",
|
"convertToDiffusersHelpText6": "Do you wish to convert this model?",
|
||||||
"noDefaultSettings": "No default settings configured for this model. Visit the Model Manager to add default settings.",
|
|
||||||
"defaultSettings": "Default Settings",
|
"defaultSettings": "Default Settings",
|
||||||
"defaultSettingsSaved": "Default Settings Saved",
|
"defaultSettingsSaved": "Default Settings Saved",
|
||||||
"defaultSettingsOutOfSync": "Some settings do not match the model's defaults:",
|
|
||||||
"restoreDefaultSettings": "Click to use the model's default settings.",
|
|
||||||
"usingDefaultSettings": "Using model's default settings",
|
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"deleteConfig": "Delete Config",
|
"deleteConfig": "Delete Config",
|
||||||
"deleteModel": "Delete Model",
|
"deleteModel": "Delete Model",
|
||||||
@@ -803,6 +799,7 @@
|
|||||||
"uploadImage": "Upload Image",
|
"uploadImage": "Upload Image",
|
||||||
"urlOrLocalPath": "URL or Local Path",
|
"urlOrLocalPath": "URL or Local Path",
|
||||||
"urlOrLocalPathHelper": "URLs should point to a single file. Local paths can point to a single file or folder for a single diffusers model.",
|
"urlOrLocalPathHelper": "URLs should point to a single file. Local paths can point to a single file or folder for a single diffusers model.",
|
||||||
|
"useDefaultSettings": "Use Default Settings",
|
||||||
"vae": "VAE",
|
"vae": "VAE",
|
||||||
"vaePrecision": "VAE Precision",
|
"vaePrecision": "VAE Precision",
|
||||||
"variant": "Variant",
|
"variant": "Variant",
|
||||||
@@ -1112,9 +1109,6 @@
|
|||||||
"enableInformationalPopovers": "Enable Informational Popovers",
|
"enableInformationalPopovers": "Enable Informational Popovers",
|
||||||
"informationalPopoversDisabled": "Informational Popovers Disabled",
|
"informationalPopoversDisabled": "Informational Popovers Disabled",
|
||||||
"informationalPopoversDisabledDesc": "Informational popovers have been disabled. Enable them in Settings.",
|
"informationalPopoversDisabledDesc": "Informational popovers have been disabled. Enable them in Settings.",
|
||||||
"enableModelDescriptions": "Enable Model Descriptions in Dropdowns",
|
|
||||||
"modelDescriptionsDisabled": "Model Descriptions in Dropdowns Disabled",
|
|
||||||
"modelDescriptionsDisabledDesc": "Model descriptions in dropdowns have been disabled. Enable them in Settings.",
|
|
||||||
"enableInvisibleWatermark": "Enable Invisible Watermark",
|
"enableInvisibleWatermark": "Enable Invisible Watermark",
|
||||||
"enableNSFWChecker": "Enable NSFW Checker",
|
"enableNSFWChecker": "Enable NSFW Checker",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
@@ -1682,8 +1676,6 @@
|
|||||||
"controlLayer": "Control Layer",
|
"controlLayer": "Control Layer",
|
||||||
"inpaintMask": "Inpaint Mask",
|
"inpaintMask": "Inpaint Mask",
|
||||||
"regionalGuidance": "Regional Guidance",
|
"regionalGuidance": "Regional Guidance",
|
||||||
"canvasAsRasterLayer": "$t(controlLayers.canvas) as $t(controlLayers.rasterLayer)",
|
|
||||||
"canvasAsControlLayer": "$t(controlLayers.canvas) as $t(controlLayers.controlLayer)",
|
|
||||||
"referenceImage": "Reference Image",
|
"referenceImage": "Reference Image",
|
||||||
"regionalReferenceImage": "Regional Reference Image",
|
"regionalReferenceImage": "Regional Reference Image",
|
||||||
"globalReferenceImage": "Global Reference Image",
|
"globalReferenceImage": "Global Reference Image",
|
||||||
@@ -1759,7 +1751,6 @@
|
|||||||
"newGallerySessionDesc": "This will clear the canvas and all settings except for your model selection. Generations will be sent to the gallery.",
|
"newGallerySessionDesc": "This will clear the canvas and all settings except for your model selection. Generations will be sent to the gallery.",
|
||||||
"newCanvasSession": "New Canvas Session",
|
"newCanvasSession": "New Canvas Session",
|
||||||
"newCanvasSessionDesc": "This will clear the canvas and all settings except for your model selection. Generations will be staged on the canvas.",
|
"newCanvasSessionDesc": "This will clear the canvas and all settings except for your model selection. Generations will be staged on the canvas.",
|
||||||
"replaceCurrent": "Replace Current",
|
|
||||||
"controlMode": {
|
"controlMode": {
|
||||||
"controlMode": "Control Mode",
|
"controlMode": "Control Mode",
|
||||||
"balanced": "Balanced",
|
"balanced": "Balanced",
|
||||||
@@ -1889,24 +1880,16 @@
|
|||||||
"apply": "Apply",
|
"apply": "Apply",
|
||||||
"cancel": "Cancel"
|
"cancel": "Cancel"
|
||||||
},
|
},
|
||||||
"selectObject": {
|
"segment": {
|
||||||
"selectObject": "Select Object",
|
"autoMask": "Auto Mask",
|
||||||
"pointType": "Point Type",
|
"pointType": "Point Type",
|
||||||
"invertSelection": "Invert Selection",
|
|
||||||
"include": "Include",
|
"include": "Include",
|
||||||
"exclude": "Exclude",
|
"exclude": "Exclude",
|
||||||
"neutral": "Neutral",
|
"neutral": "Neutral",
|
||||||
"apply": "Apply",
|
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"saveAs": "Save As",
|
"saveAs": "Save As",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"process": "Process",
|
"process": "Process"
|
||||||
"help1": "Select a single target object. Add <Bold>Include</Bold> and <Bold>Exclude</Bold> points to indicate which parts of the layer are part of the target object.",
|
|
||||||
"help2": "Start with one <Bold>Include</Bold> point within the target object. Add more points to refine the selection. Fewer points typically produce better results.",
|
|
||||||
"help3": "Invert the selection to select everything except the target object.",
|
|
||||||
"clickToAdd": "Click on the layer to add a point",
|
|
||||||
"dragToMove": "Drag a point to move it",
|
|
||||||
"clickToRemove": "Click on a point to remove it"
|
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"snapToGrid": {
|
"snapToGrid": {
|
||||||
@@ -1947,8 +1930,6 @@
|
|||||||
"newRegionalReferenceImage": "New Regional Reference Image",
|
"newRegionalReferenceImage": "New Regional Reference Image",
|
||||||
"newControlLayer": "New Control Layer",
|
"newControlLayer": "New Control Layer",
|
||||||
"newRasterLayer": "New Raster Layer",
|
"newRasterLayer": "New Raster Layer",
|
||||||
"newInpaintMask": "New Inpaint Mask",
|
|
||||||
"newRegionalGuidance": "New Regional Guidance",
|
|
||||||
"cropCanvasToBbox": "Crop Canvas to Bbox"
|
"cropCanvasToBbox": "Crop Canvas to Bbox"
|
||||||
},
|
},
|
||||||
"stagingArea": {
|
"stagingArea": {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
controlLayerAdded,
|
controlLayerAdded,
|
||||||
entityRasterized,
|
entityRasterized,
|
||||||
entitySelected,
|
entitySelected,
|
||||||
inpaintMaskAdded,
|
|
||||||
rasterLayerAdded,
|
rasterLayerAdded,
|
||||||
referenceImageAdded,
|
referenceImageAdded,
|
||||||
referenceImageIPAdapterImageChanged,
|
referenceImageIPAdapterImageChanged,
|
||||||
@@ -18,7 +17,6 @@ import {
|
|||||||
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
||||||
import type {
|
import type {
|
||||||
CanvasControlLayerState,
|
CanvasControlLayerState,
|
||||||
CanvasInpaintMaskState,
|
|
||||||
CanvasRasterLayerState,
|
CanvasRasterLayerState,
|
||||||
CanvasReferenceImageState,
|
CanvasReferenceImageState,
|
||||||
CanvasRegionalGuidanceState,
|
CanvasRegionalGuidanceState,
|
||||||
@@ -112,46 +110,6 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Image dropped on Inpaint Mask
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
overData.actionType === 'ADD_INPAINT_MASK_FROM_IMAGE' &&
|
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
|
||||||
activeData.payload.imageDTO
|
|
||||||
) {
|
|
||||||
const imageObject = imageDTOToImageObject(activeData.payload.imageDTO);
|
|
||||||
const { x, y } = selectCanvasSlice(getState()).bbox.rect;
|
|
||||||
const overrides: Partial<CanvasInpaintMaskState> = {
|
|
||||||
objects: [imageObject],
|
|
||||||
position: { x, y },
|
|
||||||
};
|
|
||||||
dispatch(inpaintMaskAdded({ overrides, isSelected: true }));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Image dropped on Regional Guidance
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
overData.actionType === 'ADD_REGIONAL_GUIDANCE_FROM_IMAGE' &&
|
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
|
||||||
activeData.payload.imageDTO
|
|
||||||
) {
|
|
||||||
const imageObject = imageDTOToImageObject(activeData.payload.imageDTO);
|
|
||||||
const { x, y } = selectCanvasSlice(getState()).bbox.rect;
|
|
||||||
const overrides: Partial<CanvasRegionalGuidanceState> = {
|
|
||||||
objects: [imageObject],
|
|
||||||
position: { x, y },
|
|
||||||
};
|
|
||||||
dispatch(rgAdded({ overrides, isSelected: true }));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image dropped on Raster layer
|
* Image dropped on Raster layer
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import type { GroupBase } from 'chakra-react-select';
|
import type { GroupBase } from 'chakra-react-select';
|
||||||
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
|
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
|
||||||
import type { ModelIdentifierField } from 'features/nodes/types/common';
|
import type { ModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import { selectSystemShouldEnableModelDescriptions } from 'features/system/store/systemSlice';
|
|
||||||
import { groupBy, reduce } from 'lodash-es';
|
import { groupBy, reduce } from 'lodash-es';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -17,6 +16,7 @@ type UseGroupedModelComboboxArg<T extends AnyModelConfig> = {
|
|||||||
getIsDisabled?: (model: T) => boolean;
|
getIsDisabled?: (model: T) => boolean;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
groupByType?: boolean;
|
groupByType?: boolean;
|
||||||
|
showDescriptions?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type UseGroupedModelComboboxReturn = {
|
type UseGroupedModelComboboxReturn = {
|
||||||
@@ -38,8 +38,15 @@ export const useGroupedModelCombobox = <T extends AnyModelConfig>(
|
|||||||
): UseGroupedModelComboboxReturn => {
|
): UseGroupedModelComboboxReturn => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const base = useAppSelector(selectBaseWithSDXLFallback);
|
const base = useAppSelector(selectBaseWithSDXLFallback);
|
||||||
const shouldShowModelDescriptions = useAppSelector(selectSystemShouldEnableModelDescriptions);
|
const {
|
||||||
const { modelConfigs, selectedModel, getIsDisabled, onChange, isLoading, groupByType = false } = arg;
|
modelConfigs,
|
||||||
|
selectedModel,
|
||||||
|
getIsDisabled,
|
||||||
|
onChange,
|
||||||
|
isLoading,
|
||||||
|
groupByType = false,
|
||||||
|
showDescriptions = false,
|
||||||
|
} = arg;
|
||||||
const options = useMemo<GroupBase<ComboboxOption>[]>(() => {
|
const options = useMemo<GroupBase<ComboboxOption>[]>(() => {
|
||||||
if (!modelConfigs) {
|
if (!modelConfigs) {
|
||||||
return [];
|
return [];
|
||||||
@@ -53,7 +60,7 @@ export const useGroupedModelCombobox = <T extends AnyModelConfig>(
|
|||||||
options: val.map((model) => ({
|
options: val.map((model) => ({
|
||||||
label: model.name,
|
label: model.name,
|
||||||
value: model.key,
|
value: model.key,
|
||||||
description: (shouldShowModelDescriptions && model.description) || undefined,
|
description: (showDescriptions && model.description) || undefined,
|
||||||
isDisabled: getIsDisabled ? getIsDisabled(model) : false,
|
isDisabled: getIsDisabled ? getIsDisabled(model) : false,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
@@ -63,7 +70,7 @@ export const useGroupedModelCombobox = <T extends AnyModelConfig>(
|
|||||||
);
|
);
|
||||||
_options.sort((a) => (a.label?.split('/')[0]?.toLowerCase().includes(base) ? -1 : 1));
|
_options.sort((a) => (a.label?.split('/')[0]?.toLowerCase().includes(base) ? -1 : 1));
|
||||||
return _options;
|
return _options;
|
||||||
}, [modelConfigs, groupByType, getIsDisabled, base, shouldShowModelDescriptions]);
|
}, [modelConfigs, groupByType, getIsDisabled, base, showDescriptions]);
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import type { ModelIdentifierField } from 'features/nodes/types/common';
|
import type { ModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import { selectSystemShouldEnableModelDescriptions } from 'features/system/store/systemSlice';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { AnyModelConfig } from 'services/api/types';
|
import type { AnyModelConfig } from 'services/api/types';
|
||||||
@@ -26,16 +24,13 @@ type UseModelComboboxReturn = {
|
|||||||
export const useModelCombobox = <T extends AnyModelConfig>(arg: UseModelComboboxArg<T>): UseModelComboboxReturn => {
|
export const useModelCombobox = <T extends AnyModelConfig>(arg: UseModelComboboxArg<T>): UseModelComboboxReturn => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { modelConfigs, selectedModel, getIsDisabled, onChange, isLoading, optionsFilter = () => true } = arg;
|
const { modelConfigs, selectedModel, getIsDisabled, onChange, isLoading, optionsFilter = () => true } = arg;
|
||||||
const shouldShowModelDescriptions = useAppSelector(selectSystemShouldEnableModelDescriptions);
|
|
||||||
|
|
||||||
const options = useMemo<ComboboxOption[]>(() => {
|
const options = useMemo<ComboboxOption[]>(() => {
|
||||||
return modelConfigs.filter(optionsFilter).map((model) => ({
|
return modelConfigs.filter(optionsFilter).map((model) => ({
|
||||||
label: model.name,
|
label: model.name,
|
||||||
value: model.key,
|
value: model.key,
|
||||||
description: (shouldShowModelDescriptions && model.description) || undefined,
|
|
||||||
isDisabled: getIsDisabled ? getIsDisabled(model) : false,
|
isDisabled: getIsDisabled ? getIsDisabled(model) : false,
|
||||||
}));
|
}));
|
||||||
}, [optionsFilter, getIsDisabled, modelConfigs, shouldShowModelDescriptions]);
|
}, [optionsFilter, getIsDisabled, modelConfigs]);
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => options.find((m) => (selectedModel ? m.value === selectedModel.key : false)),
|
() => options.find((m) => (selectedModel ? m.value === selectedModel.key : false)),
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const CanvasAlertsPreserveMask = memo(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert status="warning" borderRadius="base" fontSize="sm" shadow="md" w="fit-content">
|
<Alert status="warning" borderRadius="base" fontSize="sm" shadow="md" w="fit-content" alignSelf="flex-end">
|
||||||
<AlertIcon />
|
<AlertIcon />
|
||||||
<AlertTitle>{t('controlLayers.settings.preserveMask.alert')}</AlertTitle>
|
<AlertTitle>{t('controlLayers.settings.preserveMask.alert')}</AlertTitle>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ const CanvasAlertsSelectedEntityStatusContent = memo(({ entityIdentifier, adapte
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert status={alert.status} borderRadius="base" fontSize="sm" shadow="md" w="fit-content">
|
<Alert status={alert.status} borderRadius="base" fontSize="sm" shadow="md" w="fit-content" alignSelf="flex-end">
|
||||||
<AlertIcon />
|
<AlertIcon />
|
||||||
<AlertTitle>{alert.title}</AlertTitle>
|
<AlertTitle>{alert.title}</AlertTitle>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ const AlertWrapper = ({
|
|||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
shadow="md"
|
shadow="md"
|
||||||
w="fit-content"
|
w="fit-content"
|
||||||
|
alignSelf="flex-end"
|
||||||
>
|
>
|
||||||
<Flex w="full" alignItems="center">
|
<Flex w="full" alignItems="center">
|
||||||
<AlertIcon />
|
<AlertIcon />
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { MenuGroup } from '@invoke-ai/ui-library';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { ControlLayerMenuItems } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItems';
|
import { ControlLayerMenuItems } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItems';
|
||||||
import { InpaintMaskMenuItems } from 'features/controlLayers/components/InpaintMask/InpaintMaskMenuItems';
|
import { InpaintMaskMenuItems } from 'features/controlLayers/components/InpaintMask/InpaintMaskMenuItems';
|
||||||
@@ -9,9 +8,7 @@ import {
|
|||||||
EntityIdentifierContext,
|
EntityIdentifierContext,
|
||||||
useEntityIdentifierContext,
|
useEntityIdentifierContext,
|
||||||
} from 'features/controlLayers/contexts/EntityIdentifierContext';
|
} from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { useEntityTypeString } from 'features/controlLayers/hooks/useEntityTypeString';
|
|
||||||
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
|
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
|
||||||
import type { PropsWithChildren } from 'react';
|
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import type { Equals } from 'tsafe';
|
import type { Equals } from 'tsafe';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
@@ -49,20 +46,9 @@ export const CanvasContextMenuSelectedEntityMenuItems = memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityIdentifierContext.Provider value={selectedEntityIdentifier}>
|
<EntityIdentifierContext.Provider value={selectedEntityIdentifier}>
|
||||||
<CanvasContextMenuSelectedEntityMenuGroup>
|
|
||||||
<CanvasContextMenuSelectedEntityMenuItemsContent />
|
<CanvasContextMenuSelectedEntityMenuItemsContent />
|
||||||
</CanvasContextMenuSelectedEntityMenuGroup>
|
|
||||||
</EntityIdentifierContext.Provider>
|
</EntityIdentifierContext.Provider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
CanvasContextMenuSelectedEntityMenuItems.displayName = 'CanvasContextMenuSelectedEntityMenuItems';
|
CanvasContextMenuSelectedEntityMenuItems.displayName = 'CanvasContextMenuSelectedEntityMenuItems';
|
||||||
|
|
||||||
const CanvasContextMenuSelectedEntityMenuGroup = memo((props: PropsWithChildren) => {
|
|
||||||
const entityIdentifier = useEntityIdentifierContext();
|
|
||||||
const title = useEntityTypeString(entityIdentifier.type);
|
|
||||||
|
|
||||||
return <MenuGroup title={title}>{props.children}</MenuGroup>;
|
|
||||||
});
|
|
||||||
|
|
||||||
CanvasContextMenuSelectedEntityMenuGroup.displayName = 'CanvasContextMenuSelectedEntityMenuGroup';
|
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ export const CanvasDropArea = memo(() => {
|
|||||||
data={addControlLayerFromImageDropData}
|
data={addControlLayerFromImageDropData}
|
||||||
/>
|
/>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
|
|
||||||
<GridItem position="relative">
|
<GridItem position="relative">
|
||||||
<IAIDroppable
|
<IAIDroppable
|
||||||
dropLabel={t('controlLayers.canvasContextMenu.newRegionalReferenceImage')}
|
dropLabel={t('controlLayers.canvasContextMenu.newRegionalReferenceImage')}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const EntityListGlobalActionBarAddLayerMenu = memo(() => {
|
|||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
as={IconButton}
|
as={IconButton}
|
||||||
minW={8}
|
size="sm"
|
||||||
variant="link"
|
variant="link"
|
||||||
alignSelf="stretch"
|
alignSelf="stretch"
|
||||||
tooltip={t('controlLayers.addLayer')}
|
tooltip={t('controlLayers.addLayer')}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Flex, Spacer } from '@invoke-ai/ui-library';
|
import { Flex, Spacer } from '@invoke-ai/ui-library';
|
||||||
import { EntityListGlobalActionBarAddLayerMenu } from 'features/controlLayers/components/CanvasEntityList/EntityListGlobalActionBarAddLayerMenu';
|
import { EntityListGlobalActionBarAddLayerMenu } from 'features/controlLayers/components/CanvasEntityList/EntityListGlobalActionBarAddLayerMenu';
|
||||||
|
import { EntityListSelectedEntityActionBarAutoMaskButton } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBarAutoMaskButton';
|
||||||
import { EntityListSelectedEntityActionBarDuplicateButton } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBarDuplicateButton';
|
import { EntityListSelectedEntityActionBarDuplicateButton } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBarDuplicateButton';
|
||||||
import { EntityListSelectedEntityActionBarFill } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBarFill';
|
import { EntityListSelectedEntityActionBarFill } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBarFill';
|
||||||
import { EntityListSelectedEntityActionBarFilterButton } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBarFilterButton';
|
import { EntityListSelectedEntityActionBarFilterButton } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBarFilterButton';
|
||||||
import { EntityListSelectedEntityActionBarOpacity } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBarOpacity';
|
import { EntityListSelectedEntityActionBarOpacity } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBarOpacity';
|
||||||
import { EntityListSelectedEntityActionBarSelectObjectButton } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBarSelectObjectButton';
|
|
||||||
import { EntityListSelectedEntityActionBarTransformButton } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBarTransformButton';
|
import { EntityListSelectedEntityActionBarTransformButton } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBarTransformButton';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ export const EntityListSelectedEntityActionBar = memo(() => {
|
|||||||
<Spacer />
|
<Spacer />
|
||||||
<EntityListSelectedEntityActionBarFill />
|
<EntityListSelectedEntityActionBarFill />
|
||||||
<Flex h="full">
|
<Flex h="full">
|
||||||
<EntityListSelectedEntityActionBarSelectObjectButton />
|
<EntityListSelectedEntityActionBarAutoMaskButton />
|
||||||
<EntityListSelectedEntityActionBarFilterButton />
|
<EntityListSelectedEntityActionBarFilterButton />
|
||||||
<EntityListSelectedEntityActionBarTransformButton />
|
<EntityListSelectedEntityActionBarTransformButton />
|
||||||
<EntityListSelectedEntityActionBarSaveToAssetsButton />
|
<EntityListSelectedEntityActionBarSaveToAssetsButton />
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/sel
|
|||||||
import { isSegmentableEntityIdentifier } from 'features/controlLayers/store/types';
|
import { isSegmentableEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiShapesFill } from 'react-icons/pi';
|
import { PiMaskHappyBold } from 'react-icons/pi';
|
||||||
|
|
||||||
export const EntityListSelectedEntityActionBarSelectObjectButton = memo(() => {
|
export const EntityListSelectedEntityActionBarAutoMaskButton = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
||||||
const segment = useEntitySegmentAnything(selectedEntityIdentifier);
|
const segment = useEntitySegmentAnything(selectedEntityIdentifier);
|
||||||
@@ -24,14 +24,14 @@ export const EntityListSelectedEntityActionBarSelectObjectButton = memo(() => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
onClick={segment.start}
|
onClick={segment.start}
|
||||||
isDisabled={segment.isDisabled}
|
isDisabled={segment.isDisabled}
|
||||||
minW={8}
|
size="sm"
|
||||||
variant="link"
|
variant="link"
|
||||||
alignSelf="stretch"
|
alignSelf="stretch"
|
||||||
aria-label={t('controlLayers.selectObject.selectObject')}
|
aria-label={t('controlLayers.segment.autoMask')}
|
||||||
tooltip={t('controlLayers.selectObject.selectObject')}
|
tooltip={t('controlLayers.segment.autoMask')}
|
||||||
icon={<PiShapesFill />}
|
icon={<PiMaskHappyBold />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
EntityListSelectedEntityActionBarSelectObjectButton.displayName = 'EntityListSelectedEntityActionBarSelectObjectButton';
|
EntityListSelectedEntityActionBarAutoMaskButton.displayName = 'EntityListSelectedEntityActionBarAutoMaskButton';
|
||||||
@@ -23,7 +23,7 @@ export const EntityListSelectedEntityActionBarDuplicateButton = memo(() => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
isDisabled={!selectedEntityIdentifier || isBusy}
|
isDisabled={!selectedEntityIdentifier || isBusy}
|
||||||
minW={8}
|
size="sm"
|
||||||
variant="link"
|
variant="link"
|
||||||
alignSelf="stretch"
|
alignSelf="stretch"
|
||||||
aria-label={t('controlLayers.duplicate')}
|
aria-label={t('controlLayers.duplicate')}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/sel
|
|||||||
import { isFilterableEntityIdentifier } from 'features/controlLayers/store/types';
|
import { isFilterableEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiShootingStarFill } from 'react-icons/pi';
|
import { PiShootingStarBold } from 'react-icons/pi';
|
||||||
|
|
||||||
export const EntityListSelectedEntityActionBarFilterButton = memo(() => {
|
export const EntityListSelectedEntityActionBarFilterButton = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -24,12 +24,12 @@ export const EntityListSelectedEntityActionBarFilterButton = memo(() => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
onClick={filter.start}
|
onClick={filter.start}
|
||||||
isDisabled={filter.isDisabled}
|
isDisabled={filter.isDisabled}
|
||||||
minW={8}
|
size="sm"
|
||||||
variant="link"
|
variant="link"
|
||||||
alignSelf="stretch"
|
alignSelf="stretch"
|
||||||
aria-label={t('controlLayers.filter.filter')}
|
aria-label={t('controlLayers.filter.filter')}
|
||||||
tooltip={t('controlLayers.filter.filter')}
|
tooltip={t('controlLayers.filter.filter')}
|
||||||
icon={<PiShootingStarFill />}
|
icon={<PiShootingStarBold />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const EntityListSelectedEntityActionBarSaveToAssetsButton = memo(() => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
isDisabled={!selectedEntityIdentifier || isBusy}
|
isDisabled={!selectedEntityIdentifier || isBusy}
|
||||||
minW={8}
|
size="sm"
|
||||||
variant="link"
|
variant="link"
|
||||||
alignSelf="stretch"
|
alignSelf="stretch"
|
||||||
aria-label={t('controlLayers.saveLayerToAssets')}
|
aria-label={t('controlLayers.saveLayerToAssets')}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const EntityListSelectedEntityActionBarTransformButton = memo(() => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
onClick={transform.start}
|
onClick={transform.start}
|
||||||
isDisabled={transform.isDisabled}
|
isDisabled={transform.isDisabled}
|
||||||
minW={8}
|
size="sm"
|
||||||
variant="link"
|
variant="link"
|
||||||
alignSelf="stretch"
|
alignSelf="stretch"
|
||||||
aria-label={t('controlLayers.transform.transform')}
|
aria-label={t('controlLayers.transform.transform')}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { CanvasDropArea } from 'features/controlLayers/components/CanvasDropArea
|
|||||||
import { Filter } from 'features/controlLayers/components/Filters/Filter';
|
import { Filter } from 'features/controlLayers/components/Filters/Filter';
|
||||||
import { CanvasHUD } from 'features/controlLayers/components/HUD/CanvasHUD';
|
import { CanvasHUD } from 'features/controlLayers/components/HUD/CanvasHUD';
|
||||||
import { InvokeCanvasComponent } from 'features/controlLayers/components/InvokeCanvasComponent';
|
import { InvokeCanvasComponent } from 'features/controlLayers/components/InvokeCanvasComponent';
|
||||||
import { SelectObject } from 'features/controlLayers/components/SelectObject/SelectObject';
|
import { SegmentAnything } from 'features/controlLayers/components/SegmentAnything/SegmentAnything';
|
||||||
import { StagingAreaIsStagingGate } from 'features/controlLayers/components/StagingArea/StagingAreaIsStagingGate';
|
import { StagingAreaIsStagingGate } from 'features/controlLayers/components/StagingArea/StagingAreaIsStagingGate';
|
||||||
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
|
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
|
||||||
import { CanvasToolbar } from 'features/controlLayers/components/Toolbar/CanvasToolbar';
|
import { CanvasToolbar } from 'features/controlLayers/components/Toolbar/CanvasToolbar';
|
||||||
@@ -71,16 +71,12 @@ export const CanvasMainPanelContent = memo(() => {
|
|||||||
>
|
>
|
||||||
<InvokeCanvasComponent />
|
<InvokeCanvasComponent />
|
||||||
<CanvasManagerProviderGate>
|
<CanvasManagerProviderGate>
|
||||||
<Flex
|
{showHUD && (
|
||||||
position="absolute"
|
<Flex position="absolute" top={1} insetInlineStart={1} pointerEvents="none">
|
||||||
flexDir="column"
|
<CanvasHUD />
|
||||||
top={1}
|
</Flex>
|
||||||
insetInlineStart={1}
|
)}
|
||||||
pointerEvents="none"
|
<Flex flexDir="column" position="absolute" top={1} insetInlineEnd={1} pointerEvents="none" gap={2}>
|
||||||
gap={2}
|
|
||||||
alignItems="flex-start"
|
|
||||||
>
|
|
||||||
{showHUD && <CanvasHUD />}
|
|
||||||
<CanvasAlertsSelectedEntityStatus />
|
<CanvasAlertsSelectedEntityStatus />
|
||||||
<CanvasAlertsPreserveMask />
|
<CanvasAlertsPreserveMask />
|
||||||
<CanvasAlertsSendingToGallery />
|
<CanvasAlertsSendingToGallery />
|
||||||
@@ -106,7 +102,7 @@ export const CanvasMainPanelContent = memo(() => {
|
|||||||
<CanvasManagerProviderGate>
|
<CanvasManagerProviderGate>
|
||||||
<Filter />
|
<Filter />
|
||||||
<Transform />
|
<Transform />
|
||||||
<SelectObject />
|
<SegmentAnything />
|
||||||
</CanvasManagerProviderGate>
|
</CanvasManagerProviderGate>
|
||||||
</Flex>
|
</Flex>
|
||||||
<CanvasDropArea />
|
<CanvasDropArea />
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/s
|
|||||||
import type { CanvasEntityIdentifier, ControlModeV2 } from 'features/controlLayers/store/types';
|
import type { CanvasEntityIdentifier, ControlModeV2 } from 'features/controlLayers/store/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiBoundingBoxBold, PiShootingStarFill, PiUploadBold } from 'react-icons/pi';
|
import { PiBoundingBoxBold, PiShootingStarBold, PiUploadBold } from 'react-icons/pi';
|
||||||
import type { ControlNetModelConfig, PostUploadAction, T2IAdapterModelConfig } from 'services/api/types';
|
import type { ControlNetModelConfig, PostUploadAction, T2IAdapterModelConfig } from 'services/api/types';
|
||||||
|
|
||||||
const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIdentifier<'control_layer'>) => {
|
const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIdentifier<'control_layer'>) => {
|
||||||
@@ -93,7 +93,7 @@ export const ControlLayerControlAdapter = memo(() => {
|
|||||||
variant="link"
|
variant="link"
|
||||||
aria-label={t('controlLayers.filter.filter')}
|
aria-label={t('controlLayers.filter.filter')}
|
||||||
tooltip={t('controlLayers.filter.filter')}
|
tooltip={t('controlLayers.filter.filter')}
|
||||||
icon={<PiShootingStarFill />}
|
icon={<PiShootingStarBold />}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={pullBboxIntoLayer}
|
onClick={pullBboxIntoLayer}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/c
|
|||||||
import { CanvasEntityMenuItemsDuplicate } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDuplicate';
|
import { CanvasEntityMenuItemsDuplicate } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDuplicate';
|
||||||
import { CanvasEntityMenuItemsFilter } from 'features/controlLayers/components/common/CanvasEntityMenuItemsFilter';
|
import { CanvasEntityMenuItemsFilter } from 'features/controlLayers/components/common/CanvasEntityMenuItemsFilter';
|
||||||
import { CanvasEntityMenuItemsSave } from 'features/controlLayers/components/common/CanvasEntityMenuItemsSave';
|
import { CanvasEntityMenuItemsSave } from 'features/controlLayers/components/common/CanvasEntityMenuItemsSave';
|
||||||
import { CanvasEntityMenuItemsSelectObject } from 'features/controlLayers/components/common/CanvasEntityMenuItemsSelectObject';
|
import { CanvasEntityMenuItemsSegment } from 'features/controlLayers/components/common/CanvasEntityMenuItemsSegment';
|
||||||
import { CanvasEntityMenuItemsTransform } from 'features/controlLayers/components/common/CanvasEntityMenuItemsTransform';
|
import { CanvasEntityMenuItemsTransform } from 'features/controlLayers/components/common/CanvasEntityMenuItemsTransform';
|
||||||
import { ControlLayerMenuItemsConvertToSubMenu } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItemsConvertToSubMenu';
|
import { ControlLayerMenuItemsConvertToSubMenu } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItemsConvertToSubMenu';
|
||||||
import { ControlLayerMenuItemsCopyToSubMenu } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItemsCopyToSubMenu';
|
import { ControlLayerMenuItemsCopyToSubMenu } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItemsCopyToSubMenu';
|
||||||
@@ -24,13 +24,14 @@ export const ControlLayerMenuItems = memo(() => {
|
|||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<CanvasEntityMenuItemsTransform />
|
<CanvasEntityMenuItemsTransform />
|
||||||
<CanvasEntityMenuItemsFilter />
|
<CanvasEntityMenuItemsFilter />
|
||||||
<CanvasEntityMenuItemsSelectObject />
|
<CanvasEntityMenuItemsSegment />
|
||||||
<ControlLayerMenuItemsTransparencyEffect />
|
<ControlLayerMenuItemsTransparencyEffect />
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<ControlLayerMenuItemsCopyToSubMenu />
|
|
||||||
<ControlLayerMenuItemsConvertToSubMenu />
|
|
||||||
<CanvasEntityMenuItemsCropToBbox />
|
<CanvasEntityMenuItemsCropToBbox />
|
||||||
<CanvasEntityMenuItemsSave />
|
<CanvasEntityMenuItemsSave />
|
||||||
|
<MenuDivider />
|
||||||
|
<ControlLayerMenuItemsConvertToSubMenu />
|
||||||
|
<ControlLayerMenuItemsCopyToSubMenu />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,4 @@
|
|||||||
import {
|
import { Button, ButtonGroup, Flex, Heading, Spacer } from '@invoke-ai/ui-library';
|
||||||
Button,
|
|
||||||
ButtonGroup,
|
|
||||||
Flex,
|
|
||||||
Heading,
|
|
||||||
Menu,
|
|
||||||
MenuButton,
|
|
||||||
MenuItem,
|
|
||||||
MenuList,
|
|
||||||
Spacer,
|
|
||||||
Spinner,
|
|
||||||
} from '@invoke-ai/ui-library';
|
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useFocusRegion, useIsRegionFocused } from 'common/hooks/focus';
|
import { useFocusRegion, useIsRegionFocused } from 'common/hooks/focus';
|
||||||
@@ -26,7 +15,7 @@ import { IMAGE_FILTERS } from 'features/controlLayers/store/filters';
|
|||||||
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiCaretDownBold } from 'react-icons/pi';
|
import { PiArrowsCounterClockwiseBold, PiCheckBold, PiShootingStarBold, PiXBold } from 'react-icons/pi';
|
||||||
|
|
||||||
const FilterContent = memo(
|
const FilterContent = memo(
|
||||||
({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
||||||
@@ -57,22 +46,6 @@ const FilterContent = memo(
|
|||||||
return IMAGE_FILTERS[config.type].validateConfig?.(config as never) ?? true;
|
return IMAGE_FILTERS[config.type].validateConfig?.(config as never) ?? true;
|
||||||
}, [config]);
|
}, [config]);
|
||||||
|
|
||||||
const saveAsInpaintMask = useCallback(() => {
|
|
||||||
adapter.filterer.saveAs('inpaint_mask');
|
|
||||||
}, [adapter.filterer]);
|
|
||||||
|
|
||||||
const saveAsRegionalGuidance = useCallback(() => {
|
|
||||||
adapter.filterer.saveAs('regional_guidance');
|
|
||||||
}, [adapter.filterer]);
|
|
||||||
|
|
||||||
const saveAsRasterLayer = useCallback(() => {
|
|
||||||
adapter.filterer.saveAs('raster_layer');
|
|
||||||
}, [adapter.filterer]);
|
|
||||||
|
|
||||||
const saveAsControlLayer = useCallback(() => {
|
|
||||||
adapter.filterer.saveAs('control_layer');
|
|
||||||
}, [adapter.filterer]);
|
|
||||||
|
|
||||||
useRegisteredHotkeys({
|
useRegisteredHotkeys({
|
||||||
id: 'applyFilter',
|
id: 'applyFilter',
|
||||||
category: 'canvas',
|
category: 'canvas',
|
||||||
@@ -116,56 +89,40 @@ const FilterContent = memo(
|
|||||||
<ButtonGroup isAttached={false} size="sm" w="full">
|
<ButtonGroup isAttached={false} size="sm" w="full">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
leftIcon={<PiShootingStarBold />}
|
||||||
onClick={adapter.filterer.processImmediate}
|
onClick={adapter.filterer.processImmediate}
|
||||||
|
isLoading={isProcessing}
|
||||||
loadingText={t('controlLayers.filter.process')}
|
loadingText={t('controlLayers.filter.process')}
|
||||||
isDisabled={isProcessing || !isValid || autoProcess}
|
isDisabled={!isValid || autoProcess}
|
||||||
>
|
>
|
||||||
{t('controlLayers.filter.process')}
|
{t('controlLayers.filter.process')}
|
||||||
{isProcessing && <Spinner ms={3} boxSize={5} color="base.600" />}
|
|
||||||
</Button>
|
</Button>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Button
|
<Button
|
||||||
|
leftIcon={<PiArrowsCounterClockwiseBold />}
|
||||||
onClick={adapter.filterer.reset}
|
onClick={adapter.filterer.reset}
|
||||||
isDisabled={isProcessing}
|
isLoading={isProcessing}
|
||||||
loadingText={t('controlLayers.filter.reset')}
|
loadingText={t('controlLayers.filter.reset')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{t('controlLayers.filter.reset')}
|
{t('controlLayers.filter.reset')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={adapter.filterer.apply}
|
|
||||||
loadingText={t('controlLayers.filter.apply')}
|
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
isDisabled={isProcessing || !isValid || !hasProcessed}
|
leftIcon={<PiCheckBold />}
|
||||||
|
onClick={adapter.filterer.apply}
|
||||||
|
isLoading={isProcessing}
|
||||||
|
loadingText={t('controlLayers.filter.apply')}
|
||||||
|
isDisabled={!isValid || !hasProcessed}
|
||||||
>
|
>
|
||||||
{t('controlLayers.filter.apply')}
|
{t('controlLayers.filter.apply')}
|
||||||
</Button>
|
</Button>
|
||||||
<Menu>
|
<Button
|
||||||
<MenuButton
|
|
||||||
as={Button}
|
|
||||||
loadingText={t('controlLayers.selectObject.saveAs')}
|
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
isDisabled={isProcessing || !isValid || !hasProcessed}
|
leftIcon={<PiXBold />}
|
||||||
rightIcon={<PiCaretDownBold />}
|
onClick={adapter.filterer.cancel}
|
||||||
|
loadingText={t('controlLayers.filter.cancel')}
|
||||||
>
|
>
|
||||||
{t('controlLayers.selectObject.saveAs')}
|
|
||||||
</MenuButton>
|
|
||||||
<MenuList>
|
|
||||||
<MenuItem isDisabled={!isValid || !hasProcessed} onClick={saveAsInpaintMask}>
|
|
||||||
{t('controlLayers.newInpaintMask')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem isDisabled={!isValid || !hasProcessed} onClick={saveAsRegionalGuidance}>
|
|
||||||
{t('controlLayers.newRegionalGuidance')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem isDisabled={!isValid || !hasProcessed} onClick={saveAsControlLayer}>
|
|
||||||
{t('controlLayers.newControlLayer')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem isDisabled={!isValid || !hasProcessed} onClick={saveAsRasterLayer}>
|
|
||||||
{t('controlLayers.newRasterLayer')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
<Button variant="ghost" onClick={adapter.filterer.cancel} loadingText={t('controlLayers.filter.cancel')}>
|
|
||||||
{t('controlLayers.filter.cancel')}
|
{t('controlLayers.filter.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const InpaintMask = memo(({ id }: Props) => {
|
export const InpaintMask = memo(({ id }: Props) => {
|
||||||
const entityIdentifier = useMemo<CanvasEntityIdentifier<'inpaint_mask'>>(() => ({ id, type: 'inpaint_mask' }), [id]);
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'inpaint_mask' }), [id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ export const InpaintMaskMenuItems = memo(() => {
|
|||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<CanvasEntityMenuItemsTransform />
|
<CanvasEntityMenuItemsTransform />
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<InpaintMaskMenuItemsCopyToSubMenu />
|
|
||||||
<InpaintMaskMenuItemsConvertToSubMenu />
|
|
||||||
<CanvasEntityMenuItemsCropToBbox />
|
<CanvasEntityMenuItemsCropToBbox />
|
||||||
|
<MenuDivider />
|
||||||
|
<InpaintMaskMenuItemsConvertToSubMenu />
|
||||||
|
<InpaintMaskMenuItemsCopyToSubMenu />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/c
|
|||||||
import { CanvasEntityMenuItemsDuplicate } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDuplicate';
|
import { CanvasEntityMenuItemsDuplicate } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDuplicate';
|
||||||
import { CanvasEntityMenuItemsFilter } from 'features/controlLayers/components/common/CanvasEntityMenuItemsFilter';
|
import { CanvasEntityMenuItemsFilter } from 'features/controlLayers/components/common/CanvasEntityMenuItemsFilter';
|
||||||
import { CanvasEntityMenuItemsSave } from 'features/controlLayers/components/common/CanvasEntityMenuItemsSave';
|
import { CanvasEntityMenuItemsSave } from 'features/controlLayers/components/common/CanvasEntityMenuItemsSave';
|
||||||
import { CanvasEntityMenuItemsSelectObject } from 'features/controlLayers/components/common/CanvasEntityMenuItemsSelectObject';
|
import { CanvasEntityMenuItemsSegment } from 'features/controlLayers/components/common/CanvasEntityMenuItemsSegment';
|
||||||
import { CanvasEntityMenuItemsTransform } from 'features/controlLayers/components/common/CanvasEntityMenuItemsTransform';
|
import { CanvasEntityMenuItemsTransform } from 'features/controlLayers/components/common/CanvasEntityMenuItemsTransform';
|
||||||
import { RasterLayerMenuItemsConvertToSubMenu } from 'features/controlLayers/components/RasterLayer/RasterLayerMenuItemsConvertToSubMenu';
|
import { RasterLayerMenuItemsConvertToSubMenu } from 'features/controlLayers/components/RasterLayer/RasterLayerMenuItemsConvertToSubMenu';
|
||||||
import { RasterLayerMenuItemsCopyToSubMenu } from 'features/controlLayers/components/RasterLayer/RasterLayerMenuItemsCopyToSubMenu';
|
import { RasterLayerMenuItemsCopyToSubMenu } from 'features/controlLayers/components/RasterLayer/RasterLayerMenuItemsCopyToSubMenu';
|
||||||
@@ -23,12 +23,13 @@ export const RasterLayerMenuItems = memo(() => {
|
|||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<CanvasEntityMenuItemsTransform />
|
<CanvasEntityMenuItemsTransform />
|
||||||
<CanvasEntityMenuItemsFilter />
|
<CanvasEntityMenuItemsFilter />
|
||||||
<CanvasEntityMenuItemsSelectObject />
|
<CanvasEntityMenuItemsSegment />
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<RasterLayerMenuItemsCopyToSubMenu />
|
|
||||||
<RasterLayerMenuItemsConvertToSubMenu />
|
|
||||||
<CanvasEntityMenuItemsCropToBbox />
|
<CanvasEntityMenuItemsCropToBbox />
|
||||||
<CanvasEntityMenuItemsSave />
|
<CanvasEntityMenuItemsSave />
|
||||||
|
<MenuDivider />
|
||||||
|
<RasterLayerMenuItemsConvertToSubMenu />
|
||||||
|
<RasterLayerMenuItemsCopyToSubMenu />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,10 +16,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const RegionalGuidance = memo(({ id }: Props) => {
|
export const RegionalGuidance = memo(({ id }: Props) => {
|
||||||
const entityIdentifier = useMemo<CanvasEntityIdentifier<'regional_guidance'>>(
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'regional_guidance' }), [id]);
|
||||||
() => ({ id, type: 'regional_guidance' }),
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ export const RegionalGuidanceMenuItems = memo(() => {
|
|||||||
<CanvasEntityMenuItemsTransform />
|
<CanvasEntityMenuItemsTransform />
|
||||||
<RegionalGuidanceMenuItemsAutoNegative />
|
<RegionalGuidanceMenuItemsAutoNegative />
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<RegionalGuidanceMenuItemsCopyToSubMenu />
|
|
||||||
<RegionalGuidanceMenuItemsConvertToSubMenu />
|
|
||||||
<CanvasEntityMenuItemsCropToBbox />
|
<CanvasEntityMenuItemsCropToBbox />
|
||||||
|
<MenuDivider />
|
||||||
|
<RegionalGuidanceMenuItemsConvertToSubMenu />
|
||||||
|
<RegionalGuidanceMenuItemsCopyToSubMenu />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,36 +3,28 @@ import {
|
|||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
Flex,
|
Flex,
|
||||||
Heading,
|
Heading,
|
||||||
Icon,
|
|
||||||
ListItem,
|
|
||||||
Menu,
|
Menu,
|
||||||
MenuButton,
|
MenuButton,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
MenuList,
|
MenuList,
|
||||||
Spacer,
|
Spacer,
|
||||||
Spinner,
|
|
||||||
Text,
|
|
||||||
Tooltip,
|
|
||||||
UnorderedList,
|
|
||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useFocusRegion, useIsRegionFocused } from 'common/hooks/focus';
|
import { useFocusRegion, useIsRegionFocused } from 'common/hooks/focus';
|
||||||
import { CanvasAutoProcessSwitch } from 'features/controlLayers/components/CanvasAutoProcessSwitch';
|
import { CanvasAutoProcessSwitch } from 'features/controlLayers/components/CanvasAutoProcessSwitch';
|
||||||
import { CanvasOperationIsolatedLayerPreviewSwitch } from 'features/controlLayers/components/CanvasOperationIsolatedLayerPreviewSwitch';
|
import { CanvasOperationIsolatedLayerPreviewSwitch } from 'features/controlLayers/components/CanvasOperationIsolatedLayerPreviewSwitch';
|
||||||
import { SelectObjectInvert } from 'features/controlLayers/components/SelectObject/SelectObjectInvert';
|
import { SegmentAnythingPointType } from 'features/controlLayers/components/SegmentAnything/SegmentAnythingPointType';
|
||||||
import { SelectObjectPointType } from 'features/controlLayers/components/SelectObject/SelectObjectPointType';
|
|
||||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||||
import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterControlLayer';
|
import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterControlLayer';
|
||||||
import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterRasterLayer';
|
import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterRasterLayer';
|
||||||
import { selectAutoProcess } from 'features/controlLayers/store/canvasSettingsSlice';
|
import { selectAutoProcess } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||||
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||||
import type { PropsWithChildren } from 'react';
|
|
||||||
import { memo, useCallback, useRef } from 'react';
|
import { memo, useCallback, useRef } from 'react';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiCaretDownBold, PiInfoBold } from 'react-icons/pi';
|
import { PiArrowsCounterClockwiseBold, PiFloppyDiskBold, PiStarBold, PiXBold } from 'react-icons/pi';
|
||||||
|
|
||||||
const SelectObjectContent = memo(
|
const SegmentAnythingContent = memo(
|
||||||
({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
@@ -89,86 +81,72 @@ const SelectObjectContent = memo(
|
|||||||
transitionProperty="height"
|
transitionProperty="height"
|
||||||
transitionDuration="normal"
|
transitionDuration="normal"
|
||||||
>
|
>
|
||||||
<Flex w="full" gap={4} alignItems="center">
|
<Flex w="full" gap={4}>
|
||||||
<Flex gap={2}>
|
|
||||||
<Heading size="md" color="base.300" userSelect="none">
|
<Heading size="md" color="base.300" userSelect="none">
|
||||||
{t('controlLayers.selectObject.selectObject')}
|
{t('controlLayers.segment.autoMask')}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Tooltip label={<SelectObjectHelpTooltipContent />}>
|
|
||||||
<Flex alignItems="center">
|
|
||||||
<Icon as={PiInfoBold} color="base.500" />
|
|
||||||
</Flex>
|
|
||||||
</Tooltip>
|
|
||||||
</Flex>
|
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<CanvasAutoProcessSwitch />
|
<CanvasAutoProcessSwitch />
|
||||||
<CanvasOperationIsolatedLayerPreviewSwitch />
|
<CanvasOperationIsolatedLayerPreviewSwitch />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex w="full" justifyContent="space-between" py={2}>
|
<SegmentAnythingPointType adapter={adapter} />
|
||||||
<SelectObjectPointType adapter={adapter} />
|
|
||||||
<SelectObjectInvert adapter={adapter} />
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<ButtonGroup isAttached={false} size="sm" w="full">
|
<ButtonGroup isAttached={false} size="sm" w="full">
|
||||||
<Button
|
<Button
|
||||||
|
leftIcon={<PiStarBold />}
|
||||||
onClick={adapter.segmentAnything.processImmediate}
|
onClick={adapter.segmentAnything.processImmediate}
|
||||||
loadingText={t('controlLayers.selectObject.process')}
|
isLoading={isProcessing}
|
||||||
|
loadingText={t('controlLayers.segment.process')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
isDisabled={isProcessing || !hasPoints || autoProcess}
|
isDisabled={!hasPoints || autoProcess}
|
||||||
>
|
>
|
||||||
{t('controlLayers.selectObject.process')}
|
{t('controlLayers.segment.process')}
|
||||||
{isProcessing && <Spinner ms={3} boxSize={5} color="base.600" />}
|
|
||||||
</Button>
|
</Button>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Button
|
<Button
|
||||||
|
leftIcon={<PiArrowsCounterClockwiseBold />}
|
||||||
onClick={adapter.segmentAnything.reset}
|
onClick={adapter.segmentAnything.reset}
|
||||||
isDisabled={isProcessing || !hasPoints}
|
isLoading={isProcessing}
|
||||||
loadingText={t('controlLayers.selectObject.reset')}
|
loadingText={t('controlLayers.segment.reset')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{t('controlLayers.selectObject.reset')}
|
{t('controlLayers.segment.reset')}
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={adapter.segmentAnything.apply}
|
|
||||||
loadingText={t('controlLayers.selectObject.apply')}
|
|
||||||
variant="ghost"
|
|
||||||
isDisabled={isProcessing || !hasImageState}
|
|
||||||
>
|
|
||||||
{t('controlLayers.selectObject.apply')}
|
|
||||||
</Button>
|
</Button>
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
as={Button}
|
as={Button}
|
||||||
loadingText={t('controlLayers.selectObject.saveAs')}
|
leftIcon={<PiFloppyDiskBold />}
|
||||||
|
isLoading={isProcessing}
|
||||||
|
loadingText={t('controlLayers.segment.saveAs')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
isDisabled={isProcessing || !hasImageState}
|
isDisabled={!hasImageState}
|
||||||
rightIcon={<PiCaretDownBold />}
|
|
||||||
>
|
>
|
||||||
{t('controlLayers.selectObject.saveAs')}
|
{t('controlLayers.segment.saveAs')}
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem isDisabled={isProcessing || !hasImageState} onClick={saveAsInpaintMask}>
|
<MenuItem isDisabled={!hasImageState} onClick={saveAsInpaintMask}>
|
||||||
{t('controlLayers.newInpaintMask')}
|
{t('controlLayers.inpaintMask')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem isDisabled={isProcessing || !hasImageState} onClick={saveAsRegionalGuidance}>
|
<MenuItem isDisabled={!hasImageState} onClick={saveAsRegionalGuidance}>
|
||||||
{t('controlLayers.newRegionalGuidance')}
|
{t('controlLayers.regionalGuidance')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem isDisabled={isProcessing || !hasImageState} onClick={saveAsControlLayer}>
|
<MenuItem isDisabled={!hasImageState} onClick={saveAsControlLayer}>
|
||||||
{t('controlLayers.newControlLayer')}
|
{t('controlLayers.controlLayer')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem isDisabled={isProcessing || !hasImageState} onClick={saveAsRasterLayer}>
|
<MenuItem isDisabled={!hasImageState} onClick={saveAsRasterLayer}>
|
||||||
{t('controlLayers.newRasterLayer')}
|
{t('controlLayers.rasterLayer')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
<Button
|
<Button
|
||||||
|
leftIcon={<PiXBold />}
|
||||||
onClick={adapter.segmentAnything.cancel}
|
onClick={adapter.segmentAnything.cancel}
|
||||||
isDisabled={isProcessing}
|
isLoading={isProcessing}
|
||||||
loadingText={t('common.cancel')}
|
loadingText={t('common.cancel')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{t('controlLayers.selectObject.cancel')}
|
{t('controlLayers.segment.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -176,9 +154,9 @@ const SelectObjectContent = memo(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
SelectObjectContent.displayName = 'SegmentAnythingContent';
|
SegmentAnythingContent.displayName = 'SegmentAnythingContent';
|
||||||
|
|
||||||
export const SelectObject = memo(() => {
|
export const SegmentAnything = () => {
|
||||||
const canvasManager = useCanvasManager();
|
const canvasManager = useCanvasManager();
|
||||||
const adapter = useStore(canvasManager.stateApi.$segmentingAdapter);
|
const adapter = useStore(canvasManager.stateApi.$segmentingAdapter);
|
||||||
|
|
||||||
@@ -186,38 +164,5 @@ export const SelectObject = memo(() => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <SelectObjectContent adapter={adapter} />;
|
return <SegmentAnythingContent adapter={adapter} />;
|
||||||
});
|
};
|
||||||
|
|
||||||
SelectObject.displayName = 'SelectObject';
|
|
||||||
|
|
||||||
const Bold = (props: PropsWithChildren) => (
|
|
||||||
<Text as="span" fontWeight="semibold">
|
|
||||||
{props.children}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
|
|
||||||
const SelectObjectHelpTooltipContent = memo(() => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex gap={3} flexDir="column">
|
|
||||||
<Text>
|
|
||||||
<Trans i18nKey="controlLayers.selectObject.help1" components={{ Bold: <Bold /> }} />
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
<Trans i18nKey="controlLayers.selectObject.help2" components={{ Bold: <Bold /> }} />
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
<Trans i18nKey="controlLayers.selectObject.help3" />
|
|
||||||
</Text>
|
|
||||||
<UnorderedList>
|
|
||||||
<ListItem>{t('controlLayers.selectObject.clickToAdd')}</ListItem>
|
|
||||||
<ListItem>{t('controlLayers.selectObject.dragToMove')}</ListItem>
|
|
||||||
<ListItem>{t('controlLayers.selectObject.clickToRemove')}</ListItem>
|
|
||||||
</UnorderedList>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
SelectObjectHelpTooltipContent.displayName = 'SelectObjectHelpTooltipContent';
|
|
||||||
@@ -6,7 +6,7 @@ import { SAM_POINT_LABEL_STRING_TO_NUMBER, zSAMPointLabelString } from 'features
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const SelectObjectPointType = memo(
|
export const SegmentAnythingPointType = memo(
|
||||||
({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const pointType = useStore(adapter.segmentAnything.$pointTypeString);
|
const pointType = useStore(adapter.segmentAnything.$pointTypeString);
|
||||||
@@ -21,15 +21,15 @@ export const SelectObjectPointType = memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl w="min-content">
|
<FormControl w="full">
|
||||||
<FormLabel m={0}>{t('controlLayers.selectObject.pointType')}</FormLabel>
|
<FormLabel>{t('controlLayers.segment.pointType')}</FormLabel>
|
||||||
<RadioGroup value={pointType} onChange={onChange} w="full" size="md">
|
<RadioGroup value={pointType} onChange={onChange} w="full" size="md">
|
||||||
<Flex alignItems="center" w="full" gap={4} fontWeight="semibold" color="base.300">
|
<Flex alignItems="center" w="full" gap={4} fontWeight="semibold" color="base.300">
|
||||||
<Radio value="foreground">
|
<Radio value="foreground">
|
||||||
<Text>{t('controlLayers.selectObject.include')}</Text>
|
<Text>{t('controlLayers.segment.include')}</Text>
|
||||||
</Radio>
|
</Radio>
|
||||||
<Radio value="background">
|
<Radio value="background">
|
||||||
<Text>{t('controlLayers.selectObject.exclude')}</Text>
|
<Text>{t('controlLayers.segment.exclude')}</Text>
|
||||||
</Radio>
|
</Radio>
|
||||||
</Flex>
|
</Flex>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
@@ -38,4 +38,4 @@ export const SelectObjectPointType = memo(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
SelectObjectPointType.displayName = 'SelectObject';
|
SegmentAnythingPointType.displayName = 'SegmentAnythingPointType';
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterControlLayer';
|
|
||||||
import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterRasterLayer';
|
|
||||||
import { memo, useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
export const SelectObjectInvert = memo(
|
|
||||||
({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const invert = useStore(adapter.segmentAnything.$invert);
|
|
||||||
|
|
||||||
const onChange = useCallback(() => {
|
|
||||||
adapter.segmentAnything.$invert.set(!adapter.segmentAnything.$invert.get());
|
|
||||||
}, [adapter.segmentAnything.$invert]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormControl w="min-content">
|
|
||||||
<FormLabel m={0}>{t('controlLayers.selectObject.invertSelection')}</FormLabel>
|
|
||||||
<Switch size="sm" isChecked={invert} onChange={onChange} />
|
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
SelectObjectInvert.displayName = 'SelectObjectInvert';
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button, ButtonGroup, Flex, Heading, Spacer, Spinner } from '@invoke-ai/ui-library';
|
import { Button, ButtonGroup, Flex, Heading, Spacer } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useFocusRegion, useIsRegionFocused } from 'common/hooks/focus';
|
import { useFocusRegion, useIsRegionFocused } from 'common/hooks/focus';
|
||||||
import { CanvasOperationIsolatedLayerPreviewSwitch } from 'features/controlLayers/components/CanvasOperationIsolatedLayerPreviewSwitch';
|
import { CanvasOperationIsolatedLayerPreviewSwitch } from 'features/controlLayers/components/CanvasOperationIsolatedLayerPreviewSwitch';
|
||||||
@@ -8,6 +8,7 @@ import type { CanvasEntityAdapter } from 'features/controlLayers/konva/CanvasEnt
|
|||||||
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiArrowsCounterClockwiseBold, PiCheckBold, PiXBold } from 'react-icons/pi';
|
||||||
|
|
||||||
const TransformContent = memo(({ adapter }: { adapter: CanvasEntityAdapter }) => {
|
const TransformContent = memo(({ adapter }: { adapter: CanvasEntityAdapter }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -61,28 +62,30 @@ const TransformContent = memo(({ adapter }: { adapter: CanvasEntityAdapter }) =>
|
|||||||
|
|
||||||
<TransformFitToBboxButtons adapter={adapter} />
|
<TransformFitToBboxButtons adapter={adapter} />
|
||||||
|
|
||||||
<ButtonGroup isAttached={false} size="sm" w="full" alignItems="center">
|
<ButtonGroup isAttached={false} size="sm" w="full">
|
||||||
{isProcessing && <Spinner ms={3} boxSize={5} color="base.600" />}
|
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Button
|
<Button
|
||||||
|
leftIcon={<PiArrowsCounterClockwiseBold />}
|
||||||
onClick={adapter.transformer.resetTransform}
|
onClick={adapter.transformer.resetTransform}
|
||||||
isDisabled={isProcessing}
|
isLoading={isProcessing}
|
||||||
loadingText={t('controlLayers.transform.reset')}
|
loadingText={t('controlLayers.transform.reset')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{t('controlLayers.transform.reset')}
|
{t('controlLayers.transform.reset')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
leftIcon={<PiCheckBold />}
|
||||||
onClick={adapter.transformer.applyTransform}
|
onClick={adapter.transformer.applyTransform}
|
||||||
isDisabled={isProcessing}
|
isLoading={isProcessing}
|
||||||
loadingText={t('controlLayers.transform.apply')}
|
loadingText={t('controlLayers.transform.apply')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{t('controlLayers.transform.apply')}
|
{t('controlLayers.transform.apply')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
leftIcon={<PiXBold />}
|
||||||
onClick={adapter.transformer.stopTransform}
|
onClick={adapter.transformer.stopTransform}
|
||||||
isDisabled={isProcessing}
|
isLoading={isProcessing}
|
||||||
loadingText={t('common.cancel')}
|
loadingText={t('common.cancel')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useStore } from '@nanostores/react';
|
|||||||
import type { CanvasEntityAdapter } from 'features/controlLayers/konva/CanvasEntity/types';
|
import type { CanvasEntityAdapter } from 'features/controlLayers/konva/CanvasEntity/types';
|
||||||
import { memo, useCallback, useMemo, useState } from 'react';
|
import { memo, useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiArrowsOutBold } from 'react-icons/pi';
|
||||||
import type { Equals } from 'tsafe';
|
import type { Equals } from 'tsafe';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@@ -59,9 +60,10 @@ export const TransformFitToBboxButtons = memo(({ adapter }: { adapter: CanvasEnt
|
|||||||
<Combobox options={options} value={value} onChange={onChange} isSearchable={false} isClearable={false} />
|
<Combobox options={options} value={value} onChange={onChange} isSearchable={false} isClearable={false} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
|
leftIcon={<PiArrowsOutBold />}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
isDisabled={isProcessing}
|
isLoading={isProcessing}
|
||||||
loadingText={t('controlLayers.transform.fitToBbox')}
|
loadingText={t('controlLayers.transform.fitToBbox')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { MenuItem } from '@invoke-ai/ui-library';
|
|||||||
import { useEntityAdapterSafe } from 'features/controlLayers/contexts/EntityAdapterContext';
|
import { useEntityAdapterSafe } from 'features/controlLayers/contexts/EntityAdapterContext';
|
||||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { useCopyLayerToClipboard } from 'features/controlLayers/hooks/useCopyLayerToClipboard';
|
import { useCopyLayerToClipboard } from 'features/controlLayers/hooks/useCopyLayerToClipboard';
|
||||||
import { useEntityIsEmpty } from 'features/controlLayers/hooks/useEntityIsEmpty';
|
|
||||||
import { useIsEntityInteractable } from 'features/controlLayers/hooks/useEntityIsInteractable';
|
import { useIsEntityInteractable } from 'features/controlLayers/hooks/useEntityIsInteractable';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -13,7 +12,6 @@ export const CanvasEntityMenuItemsCopyToClipboard = memo(() => {
|
|||||||
const entityIdentifier = useEntityIdentifierContext();
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
const adapter = useEntityAdapterSafe(entityIdentifier);
|
const adapter = useEntityAdapterSafe(entityIdentifier);
|
||||||
const isInteractable = useIsEntityInteractable(entityIdentifier);
|
const isInteractable = useIsEntityInteractable(entityIdentifier);
|
||||||
const isEmpty = useEntityIsEmpty(entityIdentifier);
|
|
||||||
const copyLayerToClipboard = useCopyLayerToClipboard();
|
const copyLayerToClipboard = useCopyLayerToClipboard();
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
@@ -21,7 +19,7 @@ export const CanvasEntityMenuItemsCopyToClipboard = memo(() => {
|
|||||||
}, [copyLayerToClipboard, adapter]);
|
}, [copyLayerToClipboard, adapter]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem onClick={onClick} icon={<PiCopyBold />} isDisabled={!isInteractable || isEmpty}>
|
<MenuItem onClick={onClick} icon={<PiCopyBold />} isDisabled={!isInteractable}>
|
||||||
{t('common.clipboard')}
|
{t('common.clipboard')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useEntityIdentifierContext } from 'features/controlLayers/contexts/Enti
|
|||||||
import { useEntityFilter } from 'features/controlLayers/hooks/useEntityFilter';
|
import { useEntityFilter } from 'features/controlLayers/hooks/useEntityFilter';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiShootingStarFill } from 'react-icons/pi';
|
import { PiShootingStarBold } from 'react-icons/pi';
|
||||||
|
|
||||||
export const CanvasEntityMenuItemsFilter = memo(() => {
|
export const CanvasEntityMenuItemsFilter = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -11,7 +11,7 @@ export const CanvasEntityMenuItemsFilter = memo(() => {
|
|||||||
const filter = useEntityFilter(entityIdentifier);
|
const filter = useEntityFilter(entityIdentifier);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem onClick={filter.start} icon={<PiShootingStarFill />} isDisabled={filter.isDisabled}>
|
<MenuItem onClick={filter.start} icon={<PiShootingStarBold />} isDisabled={filter.isDisabled}>
|
||||||
{t('controlLayers.filter.filter')}
|
{t('controlLayers.filter.filter')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,18 +3,18 @@ import { useEntityIdentifierContext } from 'features/controlLayers/contexts/Enti
|
|||||||
import { useEntitySegmentAnything } from 'features/controlLayers/hooks/useEntitySegmentAnything';
|
import { useEntitySegmentAnything } from 'features/controlLayers/hooks/useEntitySegmentAnything';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiShapesFill } from 'react-icons/pi';
|
import { PiMaskHappyBold } from 'react-icons/pi';
|
||||||
|
|
||||||
export const CanvasEntityMenuItemsSelectObject = memo(() => {
|
export const CanvasEntityMenuItemsSegment = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const entityIdentifier = useEntityIdentifierContext();
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
const segmentAnything = useEntitySegmentAnything(entityIdentifier);
|
const segmentAnything = useEntitySegmentAnything(entityIdentifier);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem onClick={segmentAnything.start} icon={<PiShapesFill />} isDisabled={segmentAnything.isDisabled}>
|
<MenuItem onClick={segmentAnything.start} icon={<PiMaskHappyBold />} isDisabled={segmentAnything.isDisabled}>
|
||||||
{t('controlLayers.selectObject.selectObject')}
|
{t('controlLayers.segment.autoMask')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
CanvasEntityMenuItemsSelectObject.displayName = 'CanvasEntityMenuItemsSelectObject';
|
CanvasEntityMenuItemsSegment.displayName = 'CanvasEntityMenuItemsSegment';
|
||||||
@@ -24,9 +24,7 @@ import {
|
|||||||
selectEntityOrThrow,
|
selectEntityOrThrow,
|
||||||
} from 'features/controlLayers/store/selectors';
|
} from 'features/controlLayers/store/selectors';
|
||||||
import type {
|
import type {
|
||||||
CanvasControlLayerState,
|
|
||||||
CanvasEntityIdentifier,
|
CanvasEntityIdentifier,
|
||||||
CanvasInpaintMaskState,
|
|
||||||
CanvasRasterLayerState,
|
CanvasRasterLayerState,
|
||||||
CanvasRegionalGuidanceState,
|
CanvasRegionalGuidanceState,
|
||||||
ControlNetConfig,
|
ControlNetConfig,
|
||||||
@@ -46,8 +44,6 @@ import { useCallback } from 'react';
|
|||||||
import { modelConfigsAdapterSelectors, selectModelConfigsQuery } from 'services/api/endpoints/models';
|
import { modelConfigsAdapterSelectors, selectModelConfigsQuery } from 'services/api/endpoints/models';
|
||||||
import type { ControlNetModelConfig, ImageDTO, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
import type { ControlNetModelConfig, ImageDTO, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
||||||
import { isControlNetOrT2IAdapterModelConfig, isIPAdapterModelConfig } from 'services/api/types';
|
import { isControlNetOrT2IAdapterModelConfig, isIPAdapterModelConfig } from 'services/api/types';
|
||||||
import type { Equals } from 'tsafe';
|
|
||||||
import { assert } from 'tsafe';
|
|
||||||
|
|
||||||
export const selectDefaultControlAdapter = createSelector(
|
export const selectDefaultControlAdapter = createSelector(
|
||||||
selectModelConfigsQuery,
|
selectModelConfigsQuery,
|
||||||
@@ -128,60 +124,6 @@ export const useNewRasterLayerFromImage = () => {
|
|||||||
return func;
|
return func;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useNewControlLayerFromImage = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const bboxRect = useAppSelector(selectBboxRect);
|
|
||||||
const func = useCallback(
|
|
||||||
(imageDTO: ImageDTO) => {
|
|
||||||
const imageObject = imageDTOToImageObject(imageDTO);
|
|
||||||
const overrides: Partial<CanvasControlLayerState> = {
|
|
||||||
position: { x: bboxRect.x, y: bboxRect.y },
|
|
||||||
objects: [imageObject],
|
|
||||||
};
|
|
||||||
dispatch(controlLayerAdded({ overrides, isSelected: true }));
|
|
||||||
},
|
|
||||||
[bboxRect.x, bboxRect.y, dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
return func;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useNewInpaintMaskFromImage = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const bboxRect = useAppSelector(selectBboxRect);
|
|
||||||
const func = useCallback(
|
|
||||||
(imageDTO: ImageDTO) => {
|
|
||||||
const imageObject = imageDTOToImageObject(imageDTO);
|
|
||||||
const overrides: Partial<CanvasInpaintMaskState> = {
|
|
||||||
position: { x: bboxRect.x, y: bboxRect.y },
|
|
||||||
objects: [imageObject],
|
|
||||||
};
|
|
||||||
dispatch(inpaintMaskAdded({ overrides, isSelected: true }));
|
|
||||||
},
|
|
||||||
[bboxRect.x, bboxRect.y, dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
return func;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useNewRegionalGuidanceFromImage = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const bboxRect = useAppSelector(selectBboxRect);
|
|
||||||
const func = useCallback(
|
|
||||||
(imageDTO: ImageDTO) => {
|
|
||||||
const imageObject = imageDTOToImageObject(imageDTO);
|
|
||||||
const overrides: Partial<CanvasRegionalGuidanceState> = {
|
|
||||||
position: { x: bboxRect.x, y: bboxRect.y },
|
|
||||||
objects: [imageObject],
|
|
||||||
};
|
|
||||||
dispatch(rgAdded({ overrides, isSelected: true }));
|
|
||||||
},
|
|
||||||
[bboxRect.x, bboxRect.y, dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
return func;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a function that adds a new canvas with the given image as the initial image, replicating the img2img flow:
|
* Returns a function that adds a new canvas with the given image as the initial image, replicating the img2img flow:
|
||||||
* - Reset the canvas
|
* - Reset the canvas
|
||||||
@@ -196,31 +138,18 @@ export const useNewCanvasFromImage = () => {
|
|||||||
const bboxRect = useAppSelector(selectBboxRect);
|
const bboxRect = useAppSelector(selectBboxRect);
|
||||||
const base = useAppSelector(selectBboxModelBase);
|
const base = useAppSelector(selectBboxModelBase);
|
||||||
const func = useCallback(
|
const func = useCallback(
|
||||||
(imageDTO: ImageDTO, type: CanvasRasterLayerState['type'] | CanvasControlLayerState['type']) => {
|
(imageDTO: ImageDTO) => {
|
||||||
// Calculate the new bbox dimensions to fit the image's aspect ratio at the optimal size
|
// Calculate the new bbox dimensions to fit the image's aspect ratio at the optimal size
|
||||||
const ratio = imageDTO.width / imageDTO.height;
|
const ratio = imageDTO.width / imageDTO.height;
|
||||||
const optimalDimension = getOptimalDimension(base);
|
const optimalDimension = getOptimalDimension(base);
|
||||||
const { width, height } = calculateNewSize(ratio, optimalDimension ** 2, base);
|
const { width, height } = calculateNewSize(ratio, optimalDimension ** 2, base);
|
||||||
|
|
||||||
// The overrides need to include the layer's ID so we can transform the layer it is initialized
|
// The overrides need to include the layer's ID so we can transform the layer it is initialized
|
||||||
let overrides: Partial<CanvasRasterLayerState> | Partial<CanvasControlLayerState>;
|
const overrides = {
|
||||||
|
|
||||||
if (type === 'raster_layer') {
|
|
||||||
overrides = {
|
|
||||||
id: getPrefixedId('raster_layer'),
|
id: getPrefixedId('raster_layer'),
|
||||||
position: { x: bboxRect.x, y: bboxRect.y },
|
position: { x: bboxRect.x, y: bboxRect.y },
|
||||||
objects: [imageDTOToImageObject(imageDTO)],
|
objects: [imageDTOToImageObject(imageDTO)],
|
||||||
} satisfies Partial<CanvasRasterLayerState>;
|
} satisfies Partial<CanvasRasterLayerState>;
|
||||||
} else if (type === 'control_layer') {
|
|
||||||
overrides = {
|
|
||||||
id: getPrefixedId('control_layer'),
|
|
||||||
position: { x: bboxRect.x, y: bboxRect.y },
|
|
||||||
objects: [imageDTOToImageObject(imageDTO)],
|
|
||||||
} satisfies Partial<CanvasControlLayerState>;
|
|
||||||
} else {
|
|
||||||
// Catch unhandled types
|
|
||||||
assert<Equals<typeof type, never>>(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
CanvasEntityAdapterBase.registerInitCallback(async (adapter) => {
|
CanvasEntityAdapterBase.registerInitCallback(async (adapter) => {
|
||||||
// Skip the callback if the adapter is not the one we are creating
|
// Skip the callback if the adapter is not the one we are creating
|
||||||
@@ -237,16 +166,7 @@ export const useNewCanvasFromImage = () => {
|
|||||||
dispatch(canvasReset());
|
dispatch(canvasReset());
|
||||||
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
|
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
|
||||||
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
|
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
|
||||||
|
dispatch(rasterLayerAdded({ overrides, isSelected: true }));
|
||||||
// The type casts are safe because the type is checked above
|
|
||||||
if (type === 'raster_layer') {
|
|
||||||
dispatch(rasterLayerAdded({ overrides: overrides as Partial<CanvasRasterLayerState>, isSelected: true }));
|
|
||||||
} else if (type === 'control_layer') {
|
|
||||||
dispatch(controlLayerAdded({ overrides: overrides as Partial<CanvasControlLayerState>, isSelected: true }));
|
|
||||||
} else {
|
|
||||||
// Catch unhandled types
|
|
||||||
assert<Equals<typeof type, never>>(false);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[base, bboxRect.x, bboxRect.y, dispatch]
|
[base, bboxRect.x, bboxRect.y, dispatch]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterControlLayer';
|
import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterControlLayer';
|
||||||
import type { CanvasEntityAdapterInpaintMask } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterInpaintMask';
|
import type { CanvasEntityAdapterInpaintMask } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterInpaintMask';
|
||||||
import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterRasterLayer';
|
import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterRasterLayer';
|
||||||
@@ -8,9 +7,6 @@ import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard';
|
|||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { serializeError } from 'serialize-error';
|
|
||||||
|
|
||||||
const log = logger('canvas');
|
|
||||||
|
|
||||||
export const useCopyLayerToClipboard = () => {
|
export const useCopyLayerToClipboard = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -30,13 +26,11 @@ export const useCopyLayerToClipboard = () => {
|
|||||||
const canvas = adapter.getCanvas();
|
const canvas = adapter.getCanvas();
|
||||||
const blob = await canvasToBlob(canvas);
|
const blob = await canvasToBlob(canvas);
|
||||||
copyBlobToClipboard(blob);
|
copyBlobToClipboard(blob);
|
||||||
log.trace('Layer copied to clipboard');
|
|
||||||
toast({
|
toast({
|
||||||
status: 'info',
|
status: 'info',
|
||||||
title: t('toast.layerCopiedToClipboard'),
|
title: t('toast.layerCopiedToClipboard'),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error({ error: serializeError(error) }, 'Problem copying layer to clipboard');
|
|
||||||
toast({
|
toast({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
title: t('toast.problemCopyingLayer'),
|
title: t('toast.problemCopyingLayer'),
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { buildSelectHasObjects } from 'features/controlLayers/store/selectors';
|
|
||||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
|
|
||||||
export const useEntityIsEmpty = (entityIdentifier: CanvasEntityIdentifier) => {
|
|
||||||
const selectHasObjects = useMemo(() => buildSelectHasObjects(entityIdentifier), [entityIdentifier]);
|
|
||||||
const hasObjects = useAppSelector(selectHasObjects);
|
|
||||||
|
|
||||||
return !hasObjects;
|
|
||||||
};
|
|
||||||
@@ -52,9 +52,8 @@ export const useEntityTransform = (entityIdentifier: CanvasEntityIdentifier | nu
|
|||||||
if (!adapter) {
|
if (!adapter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
imageViewer.close();
|
|
||||||
await adapter.transformer.startTransform();
|
await adapter.transformer.startTransform();
|
||||||
}, [isDisabled, entityIdentifier, canvasManager, imageViewer]);
|
}, [isDisabled, entityIdentifier, canvasManager]);
|
||||||
|
|
||||||
const fitToBbox = useCallback(async () => {
|
const fitToBbox = useCallback(async () => {
|
||||||
if (isDisabled) {
|
if (isDisabled) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { getPrefixedId } from 'features/controlLayers/konva/util';
|
|||||||
import { selectAutoProcess } from 'features/controlLayers/store/canvasSettingsSlice';
|
import { selectAutoProcess } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||||
import type { FilterConfig } from 'features/controlLayers/store/filters';
|
import type { FilterConfig } from 'features/controlLayers/store/filters';
|
||||||
import { getFilterForModel, IMAGE_FILTERS } from 'features/controlLayers/store/filters';
|
import { getFilterForModel, IMAGE_FILTERS } from 'features/controlLayers/store/filters';
|
||||||
import type { CanvasEntityType, CanvasImageState } from 'features/controlLayers/store/types';
|
import type { CanvasImageState } from 'features/controlLayers/store/types';
|
||||||
import { imageDTOToImageObject } from 'features/controlLayers/store/util';
|
import { imageDTOToImageObject } from 'features/controlLayers/store/util';
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
@@ -15,8 +15,6 @@ import type { Logger } from 'roarr';
|
|||||||
import { serializeError } from 'serialize-error';
|
import { serializeError } from 'serialize-error';
|
||||||
import { buildSelectModelConfig } from 'services/api/hooks/modelsByType';
|
import { buildSelectModelConfig } from 'services/api/hooks/modelsByType';
|
||||||
import { isControlNetOrT2IAdapterModelConfig } from 'services/api/types';
|
import { isControlNetOrT2IAdapterModelConfig } from 'services/api/types';
|
||||||
import type { Equals } from 'tsafe';
|
|
||||||
import { assert } from 'tsafe';
|
|
||||||
|
|
||||||
type CanvasEntityFiltererConfig = {
|
type CanvasEntityFiltererConfig = {
|
||||||
processDebounceMs: number;
|
processDebounceMs: number;
|
||||||
@@ -222,50 +220,6 @@ export class CanvasEntityFilterer extends CanvasModuleBase {
|
|||||||
this.manager.stateApi.$filteringAdapter.set(null);
|
this.manager.stateApi.$filteringAdapter.set(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
saveAs = (type: Exclude<CanvasEntityType, 'reference_image'>) => {
|
|
||||||
const imageState = this.imageState;
|
|
||||||
if (!imageState) {
|
|
||||||
this.log.warn('No image state to apply filter to');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.log.trace('Applying filter');
|
|
||||||
this.parent.bufferRenderer.commitBuffer();
|
|
||||||
const rect = this.parent.transformer.getRelativeRect();
|
|
||||||
const arg = {
|
|
||||||
overrides: {
|
|
||||||
objects: [imageState],
|
|
||||||
position: {
|
|
||||||
x: Math.round(rect.x),
|
|
||||||
y: Math.round(rect.y),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
isSelected: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'raster_layer':
|
|
||||||
this.manager.stateApi.addRasterLayer(arg);
|
|
||||||
break;
|
|
||||||
case 'control_layer':
|
|
||||||
this.manager.stateApi.addControlLayer(arg);
|
|
||||||
break;
|
|
||||||
case 'inpaint_mask':
|
|
||||||
this.manager.stateApi.addInpaintMask(arg);
|
|
||||||
break;
|
|
||||||
case 'regional_guidance':
|
|
||||||
this.manager.stateApi.addRegionalGuidance(arg);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert<Equals<typeof type, never>>(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.imageState = null;
|
|
||||||
this.unsubscribe();
|
|
||||||
this.$isFiltering.set(false);
|
|
||||||
this.$hasProcessed.set(false);
|
|
||||||
this.manager.stateApi.$filteringAdapter.set(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
reset = () => {
|
reset = () => {
|
||||||
this.log.trace('Resetting filter');
|
this.log.trace('Resetting filter');
|
||||||
|
|
||||||
|
|||||||
@@ -172,11 +172,6 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
|
|||||||
*/
|
*/
|
||||||
$hasPoints = computed(this.$points, (points) => points.length > 0);
|
$hasPoints = computed(this.$points, (points) => points.length > 0);
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the module should invert the mask image.
|
|
||||||
*/
|
|
||||||
$invert = atom<boolean>(false);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The masked image object, if it exists.
|
* The masked image object, if it exists.
|
||||||
*/
|
*/
|
||||||
@@ -298,9 +293,6 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
|
|||||||
if (this.$isDraggingPoint.get()) {
|
if (this.$isDraggingPoint.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.evt.button !== 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// This event should not bubble up to the parent, stage or any other nodes
|
// This event should not bubble up to the parent, stage or any other nodes
|
||||||
e.cancelBubble = true;
|
e.cancelBubble = true;
|
||||||
circle.destroy();
|
circle.destroy();
|
||||||
@@ -461,19 +453,6 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// When the invert flag changes, process if autoProcess is enabled
|
|
||||||
this.subscriptions.add(
|
|
||||||
this.$invert.listen(() => {
|
|
||||||
if (this.$points.get().length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.manager.stateApi.getSettings().autoProcess) {
|
|
||||||
this.process();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// When auto-process is enabled, process the points if they have not been processed
|
// When auto-process is enabled, process the points if they have not been processed
|
||||||
this.subscriptions.add(
|
this.subscriptions.add(
|
||||||
this.manager.stateApi.createStoreSubscription(selectAutoProcess, (autoProcess) => {
|
this.manager.stateApi.createStoreSubscription(selectAutoProcess, (autoProcess) => {
|
||||||
@@ -547,9 +526,7 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const invert = this.$invert.get();
|
const hash = stableHash(points);
|
||||||
|
|
||||||
const hash = stableHash({ points, invert });
|
|
||||||
if (hash === this.$lastProcessedHash.get()) {
|
if (hash === this.$lastProcessedHash.get()) {
|
||||||
this.log.trace('Already processed points');
|
this.log.trace('Already processed points');
|
||||||
return;
|
return;
|
||||||
@@ -576,7 +553,7 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
|
|||||||
this.abortController = controller;
|
this.abortController = controller;
|
||||||
|
|
||||||
// Build the graph for segmenting the image, using the rasterized image DTO
|
// Build the graph for segmenting the image, using the rasterized image DTO
|
||||||
const { graph, outputNodeId } = CanvasSegmentAnythingModule.buildGraph(rasterizeResult.value, points, invert);
|
const { graph, outputNodeId } = this.buildGraph(rasterizeResult.value, points);
|
||||||
|
|
||||||
// Run the graph and get the segmented image output
|
// Run the graph and get the segmented image output
|
||||||
const segmentResult = await withResultAsync(() =>
|
const segmentResult = await withResultAsync(() =>
|
||||||
@@ -813,7 +790,6 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
|
|||||||
this.$points.set([]);
|
this.$points.set([]);
|
||||||
this.$imageState.set(null);
|
this.$imageState.set(null);
|
||||||
this.$pointType.set(1);
|
this.$pointType.set(1);
|
||||||
this.$invert.set(false);
|
|
||||||
this.$lastProcessedHash.set('');
|
this.$lastProcessedHash.set('');
|
||||||
this.$isProcessing.set(false);
|
this.$isProcessing.set(false);
|
||||||
|
|
||||||
@@ -829,11 +805,7 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
|
|||||||
/**
|
/**
|
||||||
* Builds a graph for segmenting an image with the given image DTO.
|
* Builds a graph for segmenting an image with the given image DTO.
|
||||||
*/
|
*/
|
||||||
static buildGraph = (
|
buildGraph = ({ image_name }: ImageDTO, points: SAMPointWithId[]): { graph: Graph; outputNodeId: string } => {
|
||||||
{ image_name }: ImageDTO,
|
|
||||||
points: SAMPointWithId[],
|
|
||||||
invert: boolean
|
|
||||||
): { graph: Graph; outputNodeId: string } => {
|
|
||||||
const graph = new Graph(getPrefixedId('canvas_segment_anything'));
|
const graph = new Graph(getPrefixedId('canvas_segment_anything'));
|
||||||
|
|
||||||
// TODO(psyche): When SAM2 is available in transformers, use it here
|
// TODO(psyche): When SAM2 is available in transformers, use it here
|
||||||
@@ -852,7 +824,6 @@ export class CanvasSegmentAnythingModule extends CanvasModuleBase {
|
|||||||
id: getPrefixedId('apply_tensor_mask_to_image'),
|
id: getPrefixedId('apply_tensor_mask_to_image'),
|
||||||
type: 'apply_tensor_mask_to_image',
|
type: 'apply_tensor_mask_to_image',
|
||||||
image: { image_name },
|
image: { image_name },
|
||||||
invert,
|
|
||||||
});
|
});
|
||||||
graph.addEdge(segmentAnything, 'mask', applyMask, 'mask');
|
graph.addEdge(segmentAnything, 'mask', applyMask, 'mask');
|
||||||
|
|
||||||
|
|||||||
@@ -51,16 +51,10 @@ export class CanvasStagingAreaModule extends CanvasModuleBase {
|
|||||||
/**
|
/**
|
||||||
* Sync the $isStaging flag with the redux state. $isStaging is used by the manager to determine the global busy
|
* Sync the $isStaging flag with the redux state. $isStaging is used by the manager to determine the global busy
|
||||||
* state of the canvas.
|
* state of the canvas.
|
||||||
*
|
|
||||||
* We also set the $shouldShowStagedImage flag when we enter staging mode, so that the staged images are shown,
|
|
||||||
* even if the user disabled this in the last staging session.
|
|
||||||
*/
|
*/
|
||||||
this.subscriptions.add(
|
this.subscriptions.add(
|
||||||
this.manager.stateApi.createStoreSubscription(selectIsStaging, (isStaging, oldIsStaging) => {
|
this.manager.stateApi.createStoreSubscription(selectIsStaging, (isStaging) => {
|
||||||
this.$isStaging.set(isStaging);
|
this.$isStaging.set(isStaging);
|
||||||
if (isStaging && !oldIsStaging) {
|
|
||||||
this.$shouldShowStagedImage.set(true);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,14 +216,12 @@ export class CanvasEraserToolModule extends CanvasModuleBase {
|
|||||||
*/
|
*/
|
||||||
onStagePointerDown = async (e: KonvaEventObject<PointerEvent>) => {
|
onStagePointerDown = async (e: KonvaEventObject<PointerEvent>) => {
|
||||||
const cursorPos = this.parent.$cursorPos.get();
|
const cursorPos = this.parent.$cursorPos.get();
|
||||||
const isPrimaryPointerDown = this.parent.$isPrimaryPointerDown.get();
|
|
||||||
const selectedEntity = this.manager.stateApi.getSelectedEntityAdapter();
|
const selectedEntity = this.manager.stateApi.getSelectedEntityAdapter();
|
||||||
|
|
||||||
if (!cursorPos || !selectedEntity || !isPrimaryPointerDown) {
|
if (!cursorPos || !selectedEntity) {
|
||||||
/**
|
/**
|
||||||
* Can't do anything without:
|
* Can't do anything without:
|
||||||
* - A cursor position: the cursor is not on the stage
|
* - A cursor position: the cursor is not on the stage
|
||||||
* - The mouse is down: the user is not drawing
|
|
||||||
* - A selected entity: there is no entity to draw on
|
* - A selected entity: there is no entity to draw on
|
||||||
*/
|
*/
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -160,16 +160,11 @@ export class CanvasToolModule extends CanvasModuleBase {
|
|||||||
const stage = this.manager.stage;
|
const stage = this.manager.stage;
|
||||||
const tool = this.$tool.get();
|
const tool = this.$tool.get();
|
||||||
const segmentingAdapter = this.manager.stateApi.$segmentingAdapter.get();
|
const segmentingAdapter = this.manager.stateApi.$segmentingAdapter.get();
|
||||||
const transformingAdapter = this.manager.stateApi.$transformingAdapter.get();
|
|
||||||
|
|
||||||
if (this.manager.stage.getIsDragging()) {
|
if ((this.manager.stage.getIsDragging() || tool === 'view') && !segmentingAdapter) {
|
||||||
this.tools.view.syncCursorStyle();
|
|
||||||
} else if (tool === 'view') {
|
|
||||||
this.tools.view.syncCursorStyle();
|
this.tools.view.syncCursorStyle();
|
||||||
} else if (segmentingAdapter) {
|
} else if (segmentingAdapter) {
|
||||||
segmentingAdapter.segmentAnything.syncCursorStyle();
|
segmentingAdapter.segmentAnything.syncCursorStyle();
|
||||||
} else if (transformingAdapter) {
|
|
||||||
// The transformer handles cursor style via events
|
|
||||||
} else if (this.manager.stateApi.$isFiltering.get()) {
|
} else if (this.manager.stateApi.$isFiltering.get()) {
|
||||||
stage.setCursor('not-allowed');
|
stage.setCursor('not-allowed');
|
||||||
} else if (this.manager.stagingArea.$isStaging.get()) {
|
} else if (this.manager.stagingArea.$isStaging.get()) {
|
||||||
|
|||||||
@@ -349,27 +349,6 @@ export const buildSelectIsSelected = (entityIdentifier: CanvasEntityIdentifier)
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a selector that selects if the entity is empty.
|
|
||||||
*
|
|
||||||
* Reference images are considered empty if the IP adapter is empty.
|
|
||||||
*
|
|
||||||
* Other entities are considered empty if they have no objects.
|
|
||||||
*/
|
|
||||||
export const buildSelectHasObjects = (entityIdentifier: CanvasEntityIdentifier) => {
|
|
||||||
return createSelector(selectCanvasSlice, (canvas) => {
|
|
||||||
const entity = selectEntity(canvas, entityIdentifier);
|
|
||||||
|
|
||||||
if (!entity) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (entity.type === 'reference_image') {
|
|
||||||
return entity.ipAdapter.image !== null;
|
|
||||||
}
|
|
||||||
return entity.objects.length > 0;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const selectWidth = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.width);
|
export const selectWidth = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.width);
|
||||||
export const selectHeight = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.height);
|
export const selectHeight = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.height);
|
||||||
export const selectAspectRatioID = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.aspectRatio.id);
|
export const selectAspectRatioID = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.aspectRatio.id);
|
||||||
|
|||||||
@@ -42,14 +42,6 @@ export type AddControlLayerFromImageDropData = BaseDropData & {
|
|||||||
actionType: 'ADD_CONTROL_LAYER_FROM_IMAGE';
|
actionType: 'ADD_CONTROL_LAYER_FROM_IMAGE';
|
||||||
};
|
};
|
||||||
|
|
||||||
type AddInpaintMaskFromImageDropData = BaseDropData & {
|
|
||||||
actionType: 'ADD_INPAINT_MASK_FROM_IMAGE';
|
|
||||||
};
|
|
||||||
|
|
||||||
type AddRegionalGuidanceFromImageDropData = BaseDropData & {
|
|
||||||
actionType: 'ADD_REGIONAL_GUIDANCE_FROM_IMAGE';
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AddRegionalReferenceImageFromImageDropData = BaseDropData & {
|
export type AddRegionalReferenceImageFromImageDropData = BaseDropData & {
|
||||||
actionType: 'ADD_REGIONAL_REFERENCE_IMAGE_FROM_IMAGE';
|
actionType: 'ADD_REGIONAL_REFERENCE_IMAGE_FROM_IMAGE';
|
||||||
};
|
};
|
||||||
@@ -61,7 +53,7 @@ export type AddGlobalReferenceImageFromImageDropData = BaseDropData & {
|
|||||||
export type ReplaceLayerImageDropData = BaseDropData & {
|
export type ReplaceLayerImageDropData = BaseDropData & {
|
||||||
actionType: 'REPLACE_LAYER_WITH_IMAGE';
|
actionType: 'REPLACE_LAYER_WITH_IMAGE';
|
||||||
context: {
|
context: {
|
||||||
entityIdentifier: CanvasEntityIdentifier<'control_layer' | 'raster_layer' | 'inpaint_mask' | 'regional_guidance'>;
|
entityIdentifier: CanvasEntityIdentifier<'control_layer' | 'raster_layer'>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -106,9 +98,7 @@ export type TypesafeDroppableData =
|
|||||||
| AddControlLayerFromImageDropData
|
| AddControlLayerFromImageDropData
|
||||||
| ReplaceLayerImageDropData
|
| ReplaceLayerImageDropData
|
||||||
| AddRegionalReferenceImageFromImageDropData
|
| AddRegionalReferenceImageFromImageDropData
|
||||||
| AddGlobalReferenceImageFromImageDropData
|
| AddGlobalReferenceImageFromImageDropData;
|
||||||
| AddInpaintMaskFromImageDropData
|
|
||||||
| AddRegionalGuidanceFromImageDropData;
|
|
||||||
|
|
||||||
type BaseDragData = {
|
type BaseDragData = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ export const isValidDrop = (overData?: TypesafeDroppableData | null, activeData?
|
|||||||
case 'SET_RG_IP_ADAPTER_IMAGE':
|
case 'SET_RG_IP_ADAPTER_IMAGE':
|
||||||
case 'ADD_RASTER_LAYER_FROM_IMAGE':
|
case 'ADD_RASTER_LAYER_FROM_IMAGE':
|
||||||
case 'ADD_CONTROL_LAYER_FROM_IMAGE':
|
case 'ADD_CONTROL_LAYER_FROM_IMAGE':
|
||||||
case 'ADD_INPAINT_MASK_FROM_IMAGE':
|
|
||||||
case 'ADD_REGIONAL_GUIDANCE_FROM_IMAGE':
|
|
||||||
case 'SET_UPSCALE_INITIAL_IMAGE':
|
case 'SET_UPSCALE_INITIAL_IMAGE':
|
||||||
case 'SET_NODES_IMAGE':
|
case 'SET_NODES_IMAGE':
|
||||||
case 'SELECT_FOR_COMPARE':
|
case 'SELECT_FOR_COMPARE':
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { MenuItem } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { useNewCanvasFromImage } from 'features/controlLayers/hooks/addLayerHooks';
|
||||||
|
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||||
|
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||||
|
import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiFileBold } from 'react-icons/pi';
|
||||||
|
|
||||||
|
export const ImageMenuItemNewCanvasFromImage = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const imageDTO = useImageDTOContext();
|
||||||
|
const imageViewer = useImageViewer();
|
||||||
|
const newCanvasFromImage = useNewCanvasFromImage();
|
||||||
|
const isBusy = useCanvasIsBusy();
|
||||||
|
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
newCanvasFromImage(imageDTO);
|
||||||
|
dispatch(setActiveTab('canvas'));
|
||||||
|
imageViewer.close();
|
||||||
|
toast({
|
||||||
|
id: 'SENT_TO_CANVAS',
|
||||||
|
title: t('toast.sentToCanvas'),
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
}, [dispatch, imageDTO, imageViewer, newCanvasFromImage, t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem icon={<PiFileBold />} onClickCapture={onClick} isDisabled={isBusy}>
|
||||||
|
{t('controlLayers.newCanvasFromImage')}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ImageMenuItemNewCanvasFromImage.displayName = 'ImageMenuItemNewCanvasFromImage';
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
import { Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
|
||||||
import { SubMenuButtonContent, useSubMenu } from 'common/hooks/useSubMenu';
|
|
||||||
import { NewLayerIcon } from 'features/controlLayers/components/common/icons';
|
|
||||||
import {
|
|
||||||
useNewCanvasFromImage,
|
|
||||||
useNewControlLayerFromImage,
|
|
||||||
useNewInpaintMaskFromImage,
|
|
||||||
useNewRasterLayerFromImage,
|
|
||||||
useNewRegionalGuidanceFromImage,
|
|
||||||
} from 'features/controlLayers/hooks/addLayerHooks';
|
|
||||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
|
||||||
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
|
||||||
import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
|
|
||||||
import { sentImageToCanvas } from 'features/gallery/store/actions';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
|
||||||
import { memo, useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { PiFileBold, PiPlusBold } from 'react-icons/pi';
|
|
||||||
|
|
||||||
export const ImageMenuItemNewFromImageSubMenu = memo(() => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const subMenu = useSubMenu();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const imageDTO = useImageDTOContext();
|
|
||||||
const imageViewer = useImageViewer();
|
|
||||||
const isBusy = useCanvasIsBusy();
|
|
||||||
const newRasterLayerFromImage = useNewRasterLayerFromImage();
|
|
||||||
const newControlLayerFromImage = useNewControlLayerFromImage();
|
|
||||||
const newInpaintMaskFromImage = useNewInpaintMaskFromImage();
|
|
||||||
const newRegionalGuidanceFromImage = useNewRegionalGuidanceFromImage();
|
|
||||||
const newCanvasFromImage = useNewCanvasFromImage();
|
|
||||||
|
|
||||||
const onClickNewCanvasWithRasterLayerFromImage = useCallback(() => {
|
|
||||||
newCanvasFromImage(imageDTO, 'raster_layer');
|
|
||||||
dispatch(setActiveTab('canvas'));
|
|
||||||
imageViewer.close();
|
|
||||||
toast({
|
|
||||||
id: 'SENT_TO_CANVAS',
|
|
||||||
title: t('toast.sentToCanvas'),
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
}, [dispatch, imageDTO, imageViewer, newCanvasFromImage, t]);
|
|
||||||
|
|
||||||
const onClickNewCanvasWithControlLayerFromImage = useCallback(() => {
|
|
||||||
newCanvasFromImage(imageDTO, 'control_layer');
|
|
||||||
dispatch(setActiveTab('canvas'));
|
|
||||||
imageViewer.close();
|
|
||||||
toast({
|
|
||||||
id: 'SENT_TO_CANVAS',
|
|
||||||
title: t('toast.sentToCanvas'),
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
}, [dispatch, imageDTO, imageViewer, newCanvasFromImage, t]);
|
|
||||||
|
|
||||||
const onClickNewRasterLayerFromImage = useCallback(() => {
|
|
||||||
dispatch(sentImageToCanvas());
|
|
||||||
newRasterLayerFromImage(imageDTO);
|
|
||||||
dispatch(setActiveTab('canvas'));
|
|
||||||
imageViewer.close();
|
|
||||||
toast({
|
|
||||||
id: 'SENT_TO_CANVAS',
|
|
||||||
title: t('toast.sentToCanvas'),
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
}, [dispatch, imageDTO, imageViewer, newRasterLayerFromImage, t]);
|
|
||||||
|
|
||||||
const onClickNewControlLayerFromImage = useCallback(() => {
|
|
||||||
dispatch(sentImageToCanvas());
|
|
||||||
newControlLayerFromImage(imageDTO);
|
|
||||||
dispatch(setActiveTab('canvas'));
|
|
||||||
imageViewer.close();
|
|
||||||
toast({
|
|
||||||
id: 'SENT_TO_CANVAS',
|
|
||||||
title: t('toast.sentToCanvas'),
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
}, [dispatch, imageDTO, imageViewer, newControlLayerFromImage, t]);
|
|
||||||
|
|
||||||
const onClickNewInpaintMaskFromImage = useCallback(() => {
|
|
||||||
dispatch(sentImageToCanvas());
|
|
||||||
newInpaintMaskFromImage(imageDTO);
|
|
||||||
dispatch(setActiveTab('canvas'));
|
|
||||||
imageViewer.close();
|
|
||||||
toast({
|
|
||||||
id: 'SENT_TO_CANVAS',
|
|
||||||
title: t('toast.sentToCanvas'),
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
}, [dispatch, imageDTO, imageViewer, newInpaintMaskFromImage, t]);
|
|
||||||
|
|
||||||
const onClickNewRegionalGuidanceFromImage = useCallback(() => {
|
|
||||||
dispatch(sentImageToCanvas());
|
|
||||||
newRegionalGuidanceFromImage(imageDTO);
|
|
||||||
dispatch(setActiveTab('canvas'));
|
|
||||||
imageViewer.close();
|
|
||||||
toast({
|
|
||||||
id: 'SENT_TO_CANVAS',
|
|
||||||
title: t('toast.sentToCanvas'),
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
}, [dispatch, imageDTO, imageViewer, newRegionalGuidanceFromImage, t]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MenuItem {...subMenu.parentMenuItemProps} icon={<PiPlusBold />}>
|
|
||||||
<Menu {...subMenu.menuProps}>
|
|
||||||
<MenuButton {...subMenu.menuButtonProps}>
|
|
||||||
<SubMenuButtonContent label="New from Image" />
|
|
||||||
</MenuButton>
|
|
||||||
<MenuList {...subMenu.menuListProps}>
|
|
||||||
<MenuItem icon={<PiFileBold />} onClickCapture={onClickNewCanvasWithRasterLayerFromImage} isDisabled={isBusy}>
|
|
||||||
{t('controlLayers.canvasAsRasterLayer')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
icon={<PiFileBold />}
|
|
||||||
onClickCapture={onClickNewCanvasWithControlLayerFromImage}
|
|
||||||
isDisabled={isBusy}
|
|
||||||
>
|
|
||||||
{t('controlLayers.canvasAsControlLayer')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem icon={<NewLayerIcon />} onClickCapture={onClickNewInpaintMaskFromImage} isDisabled={isBusy}>
|
|
||||||
{t('controlLayers.inpaintMask')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem icon={<NewLayerIcon />} onClickCapture={onClickNewRegionalGuidanceFromImage} isDisabled={isBusy}>
|
|
||||||
{t('controlLayers.regionalGuidance')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem icon={<NewLayerIcon />} onClickCapture={onClickNewControlLayerFromImage} isDisabled={isBusy}>
|
|
||||||
{t('controlLayers.controlLayer')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem icon={<NewLayerIcon />} onClickCapture={onClickNewRasterLayerFromImage} isDisabled={isBusy}>
|
|
||||||
{t('controlLayers.rasterLayer')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
ImageMenuItemNewFromImageSubMenu.displayName = 'ImageMenuItemNewFromImageSubMenu';
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { MenuItem } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { NewLayerIcon } from 'features/controlLayers/components/common/icons';
|
||||||
|
import { useNewRasterLayerFromImage } from 'features/controlLayers/hooks/addLayerHooks';
|
||||||
|
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||||
|
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||||
|
import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
|
||||||
|
import { sentImageToCanvas } from 'features/gallery/store/actions';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const ImageMenuItemNewLayerFromImage = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const imageDTO = useImageDTOContext();
|
||||||
|
const imageViewer = useImageViewer();
|
||||||
|
const newRasterLayerFromImage = useNewRasterLayerFromImage();
|
||||||
|
const isBusy = useCanvasIsBusy();
|
||||||
|
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
dispatch(sentImageToCanvas());
|
||||||
|
newRasterLayerFromImage(imageDTO);
|
||||||
|
dispatch(setActiveTab('canvas'));
|
||||||
|
imageViewer.close();
|
||||||
|
toast({
|
||||||
|
id: 'SENT_TO_CANVAS',
|
||||||
|
title: t('toast.sentToCanvas'),
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
}, [dispatch, imageDTO, imageViewer, newRasterLayerFromImage, t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem icon={<NewLayerIcon />} onClickCapture={onClick} isDisabled={isBusy}>
|
||||||
|
{t('controlLayers.newLayerFromImage')}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ImageMenuItemNewLayerFromImage.displayName = 'ImageMenuItemNewLayerFromImage';
|
||||||
@@ -7,7 +7,8 @@ import { ImageMenuItemDelete } from 'features/gallery/components/ImageContextMen
|
|||||||
import { ImageMenuItemDownload } from 'features/gallery/components/ImageContextMenu/ImageMenuItemDownload';
|
import { ImageMenuItemDownload } from 'features/gallery/components/ImageContextMenu/ImageMenuItemDownload';
|
||||||
import { ImageMenuItemLoadWorkflow } from 'features/gallery/components/ImageContextMenu/ImageMenuItemLoadWorkflow';
|
import { ImageMenuItemLoadWorkflow } from 'features/gallery/components/ImageContextMenu/ImageMenuItemLoadWorkflow';
|
||||||
import { ImageMenuItemMetadataRecallActions } from 'features/gallery/components/ImageContextMenu/ImageMenuItemMetadataRecallActions';
|
import { ImageMenuItemMetadataRecallActions } from 'features/gallery/components/ImageContextMenu/ImageMenuItemMetadataRecallActions';
|
||||||
import { ImageMenuItemNewFromImageSubMenu } from 'features/gallery/components/ImageContextMenu/ImageMenuItemNewFromImageSubMenu';
|
import { ImageMenuItemNewCanvasFromImage } from 'features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImage';
|
||||||
|
import { ImageMenuItemNewLayerFromImage } from 'features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImage';
|
||||||
import { ImageMenuItemOpenInNewTab } from 'features/gallery/components/ImageContextMenu/ImageMenuItemOpenInNewTab';
|
import { ImageMenuItemOpenInNewTab } from 'features/gallery/components/ImageContextMenu/ImageMenuItemOpenInNewTab';
|
||||||
import { ImageMenuItemOpenInViewer } from 'features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer';
|
import { ImageMenuItemOpenInViewer } from 'features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer';
|
||||||
import { ImageMenuItemSelectForCompare } from 'features/gallery/components/ImageContextMenu/ImageMenuItemSelectForCompare';
|
import { ImageMenuItemSelectForCompare } from 'features/gallery/components/ImageContextMenu/ImageMenuItemSelectForCompare';
|
||||||
@@ -38,7 +39,8 @@ const SingleSelectionMenuItems = ({ imageDTO }: SingleSelectionMenuItemsProps) =
|
|||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<ImageMenuItemSendToUpscale />
|
<ImageMenuItemSendToUpscale />
|
||||||
<CanvasManagerProviderGate>
|
<CanvasManagerProviderGate>
|
||||||
<ImageMenuItemNewFromImageSubMenu />
|
<ImageMenuItemNewLayerFromImage />
|
||||||
|
<ImageMenuItemNewCanvasFromImage />
|
||||||
</CanvasManagerProviderGate>
|
</CanvasManagerProviderGate>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<ImageMenuItemChangeBoard />
|
<ImageMenuItemChangeBoard />
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export const ImageViewer = memo(({ closeButton }: Props) => {
|
|||||||
{hasImageToCompare && <ImageComparison containerDims={containerDims} />}
|
{hasImageToCompare && <ImageComparison containerDims={containerDims} />}
|
||||||
</Box>
|
</Box>
|
||||||
<ImageComparisonDroppable />
|
<ImageComparisonDroppable />
|
||||||
<Box position="absolute" top={14} insetInlineStart={2}>
|
<Box position="absolute" top={14} insetInlineEnd={2}>
|
||||||
<CanvasAlertsSendingToCanvas />
|
<CanvasAlertsSendingToCanvas />
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -1,160 +1,15 @@
|
|||||||
import { Flex, IconButton, ListItem, Text, UnorderedList } from '@invoke-ai/ui-library';
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
import { EMPTY_OBJECT } from 'app/store/constants';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import { selectModel } from 'features/controlLayers/store/paramsSlice';
|
||||||
selectCFGRescaleMultiplier,
|
|
||||||
selectCFGScale,
|
|
||||||
selectGuidance,
|
|
||||||
selectModel,
|
|
||||||
selectScheduler,
|
|
||||||
selectSteps,
|
|
||||||
selectVAE,
|
|
||||||
selectVAEPrecision,
|
|
||||||
} from 'features/controlLayers/store/paramsSlice';
|
|
||||||
import { selectHeight, selectWidth } from 'features/controlLayers/store/selectors';
|
|
||||||
import { setDefaultSettings } from 'features/parameters/store/actions';
|
import { setDefaultSettings } from 'features/parameters/store/actions';
|
||||||
import { isNil } from 'lodash-es';
|
import { useCallback } from 'react';
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiSparkleFill } from 'react-icons/pi';
|
import { PiSparkleFill } from 'react-icons/pi';
|
||||||
import { modelConfigsAdapterSelectors, useGetModelConfigsQuery } from 'services/api/endpoints/models';
|
|
||||||
import type { S } from 'services/api/types';
|
|
||||||
import { isNonRefinerMainModelConfig } from 'services/api/types';
|
|
||||||
|
|
||||||
export const UseDefaultSettingsButton = () => {
|
export const UseDefaultSettingsButton = () => {
|
||||||
const model = useAppSelector(selectModel);
|
const model = useAppSelector(selectModel);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { data: modelConfigs } = useGetModelConfigsQuery();
|
|
||||||
|
|
||||||
const scheduler = useAppSelector(selectScheduler);
|
|
||||||
const steps = useAppSelector(selectSteps);
|
|
||||||
const vae = useAppSelector(selectVAE);
|
|
||||||
const vaePrecision = useAppSelector(selectVAEPrecision);
|
|
||||||
const width = useAppSelector(selectWidth);
|
|
||||||
const height = useAppSelector(selectHeight);
|
|
||||||
const guidance = useAppSelector(selectGuidance);
|
|
||||||
const cfg = useAppSelector(selectCFGScale);
|
|
||||||
const cfgRescale = useAppSelector(selectCFGRescaleMultiplier);
|
|
||||||
|
|
||||||
const modelConfig = useMemo(() => {
|
|
||||||
if (!modelConfigs) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (model === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return modelConfigsAdapterSelectors.selectById(modelConfigs, model.key);
|
|
||||||
}, [modelConfigs, model]);
|
|
||||||
|
|
||||||
const hasDefaultSettings = useMemo(() => {
|
|
||||||
const settings = modelConfig && isNonRefinerMainModelConfig(modelConfig) && modelConfig.default_settings;
|
|
||||||
return settings && Object.values(settings).some((setting) => !!setting);
|
|
||||||
}, [modelConfig]);
|
|
||||||
|
|
||||||
const defaultSettings = useMemo<S['MainModelDefaultSettings']>(() => {
|
|
||||||
return modelConfig && isNonRefinerMainModelConfig(modelConfig) && modelConfig.default_settings
|
|
||||||
? modelConfig.default_settings
|
|
||||||
: EMPTY_OBJECT;
|
|
||||||
}, [modelConfig]);
|
|
||||||
|
|
||||||
const outOfSyncSettings = useMemo(() => {
|
|
||||||
const settings = [];
|
|
||||||
if (hasDefaultSettings) {
|
|
||||||
const {
|
|
||||||
vae: defaultVAE,
|
|
||||||
vae_precision: defaultVAEPrecision,
|
|
||||||
cfg_scale: defaultCfg,
|
|
||||||
cfg_rescale_multiplier: defaultCfgRescale,
|
|
||||||
steps: defaultSteps,
|
|
||||||
scheduler: defaultScheduler,
|
|
||||||
width: defaultWidth,
|
|
||||||
height: defaultHeight,
|
|
||||||
guidance: defaultGuidance,
|
|
||||||
} = defaultSettings;
|
|
||||||
|
|
||||||
if (!isNil(defaultVAE) && vae && defaultVAE !== vae.key) {
|
|
||||||
settings.push(t('modelManager.vae'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNil(defaultVAE) && !vae && defaultVAE !== 'default') {
|
|
||||||
settings.push(t('modelManager.vae'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNil(defaultVAEPrecision) && defaultVAEPrecision !== vaePrecision) {
|
|
||||||
settings.push(t('modelManager.vaePrecision'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNil(defaultCfg) && defaultCfg !== cfg) {
|
|
||||||
settings.push(t('parameters.cfgScale'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNil(defaultCfgRescale) && defaultCfgRescale !== cfgRescale) {
|
|
||||||
settings.push(t('parameters.cfgRescaleMultiplier'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNil(defaultSteps) && defaultSteps !== steps) {
|
|
||||||
settings.push(t('parameters.steps'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNil(defaultScheduler) && defaultScheduler !== scheduler) {
|
|
||||||
settings.push(t('parameters.scheduler'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNil(defaultWidth) && defaultWidth !== width) {
|
|
||||||
settings.push(t('parameters.width'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNil(defaultHeight) && defaultHeight !== height) {
|
|
||||||
settings.push(t('parameters.height'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNil(defaultGuidance) && defaultGuidance !== guidance) {
|
|
||||||
settings.push(t('parameters.guidance'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return settings;
|
|
||||||
}, [
|
|
||||||
hasDefaultSettings,
|
|
||||||
vae,
|
|
||||||
vaePrecision,
|
|
||||||
cfg,
|
|
||||||
cfgRescale,
|
|
||||||
steps,
|
|
||||||
scheduler,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
guidance,
|
|
||||||
t,
|
|
||||||
defaultSettings,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const tooltip = useMemo(() => {
|
|
||||||
if (!model) {
|
|
||||||
return t('modelManager.noModelSelected');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasDefaultSettings) {
|
|
||||||
return t('modelManager.noDefaultSettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outOfSyncSettings.length === 0) {
|
|
||||||
return t('modelManager.usingDefaultSettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex direction="column" gap={2}>
|
|
||||||
<Text>{t('modelManager.defaultSettingsOutOfSync')}</Text>
|
|
||||||
<UnorderedList>
|
|
||||||
{outOfSyncSettings.map((setting) => (
|
|
||||||
<ListItem key={setting}>{setting}</ListItem>
|
|
||||||
))}
|
|
||||||
</UnorderedList>
|
|
||||||
<Text>{t('modelManager.restoreDefaultSettings')}</Text>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}, [model, hasDefaultSettings, outOfSyncSettings, t]);
|
|
||||||
|
|
||||||
const handleClickDefaultSettings = useCallback(() => {
|
const handleClickDefaultSettings = useCallback(() => {
|
||||||
dispatch(setDefaultSettings());
|
dispatch(setDefaultSettings());
|
||||||
@@ -163,13 +18,12 @@ export const UseDefaultSettingsButton = () => {
|
|||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<PiSparkleFill />}
|
icon={<PiSparkleFill />}
|
||||||
tooltip={tooltip}
|
tooltip={t('modelManager.useDefaultSettings')}
|
||||||
aria-label={t('modelManager.useDefaultSettings')}
|
aria-label={t('modelManager.useDefaultSettings')}
|
||||||
isDisabled={!model || !hasDefaultSettings || outOfSyncSettings.length === 0}
|
isDisabled={!model}
|
||||||
onClick={handleClickDefaultSettings}
|
onClick={handleClickDefaultSettings}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
colorScheme="warning"
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,12 +31,10 @@ import {
|
|||||||
selectSystemShouldConfirmOnDelete,
|
selectSystemShouldConfirmOnDelete,
|
||||||
selectSystemShouldConfirmOnNewSession,
|
selectSystemShouldConfirmOnNewSession,
|
||||||
selectSystemShouldEnableInformationalPopovers,
|
selectSystemShouldEnableInformationalPopovers,
|
||||||
selectSystemShouldEnableModelDescriptions,
|
|
||||||
selectSystemShouldUseNSFWChecker,
|
selectSystemShouldUseNSFWChecker,
|
||||||
selectSystemShouldUseWatermarker,
|
selectSystemShouldUseWatermarker,
|
||||||
setShouldConfirmOnDelete,
|
setShouldConfirmOnDelete,
|
||||||
setShouldEnableInformationalPopovers,
|
setShouldEnableInformationalPopovers,
|
||||||
setShouldEnableModelDescriptions,
|
|
||||||
shouldAntialiasProgressImageChanged,
|
shouldAntialiasProgressImageChanged,
|
||||||
shouldConfirmOnNewSessionToggled,
|
shouldConfirmOnNewSessionToggled,
|
||||||
shouldUseNSFWCheckerChanged,
|
shouldUseNSFWCheckerChanged,
|
||||||
@@ -101,7 +99,6 @@ const SettingsModal = ({ config = defaultConfig, children }: SettingsModalProps)
|
|||||||
const shouldUseNSFWChecker = useAppSelector(selectSystemShouldUseNSFWChecker);
|
const shouldUseNSFWChecker = useAppSelector(selectSystemShouldUseNSFWChecker);
|
||||||
const shouldUseWatermarker = useAppSelector(selectSystemShouldUseWatermarker);
|
const shouldUseWatermarker = useAppSelector(selectSystemShouldUseWatermarker);
|
||||||
const shouldEnableInformationalPopovers = useAppSelector(selectSystemShouldEnableInformationalPopovers);
|
const shouldEnableInformationalPopovers = useAppSelector(selectSystemShouldEnableInformationalPopovers);
|
||||||
const shouldEnableModelDescriptions = useAppSelector(selectSystemShouldEnableModelDescriptions);
|
|
||||||
const shouldConfirmOnNewSession = useAppSelector(selectSystemShouldConfirmOnNewSession);
|
const shouldConfirmOnNewSession = useAppSelector(selectSystemShouldConfirmOnNewSession);
|
||||||
const onToggleConfirmOnNewSession = useCallback(() => {
|
const onToggleConfirmOnNewSession = useCallback(() => {
|
||||||
dispatch(shouldConfirmOnNewSessionToggled());
|
dispatch(shouldConfirmOnNewSessionToggled());
|
||||||
@@ -157,12 +154,6 @@ const SettingsModal = ({ config = defaultConfig, children }: SettingsModalProps)
|
|||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
const handleChangeShouldEnableModelDescriptions = useCallback(
|
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
dispatch(setShouldEnableModelDescriptions(e.target.checked));
|
|
||||||
},
|
|
||||||
[dispatch]
|
|
||||||
);
|
|
||||||
const handleChangeShouldUseCpuNoise = useCallback(
|
const handleChangeShouldUseCpuNoise = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
dispatch(shouldUseCpuNoiseChanged(e.target.checked));
|
dispatch(shouldUseCpuNoiseChanged(e.target.checked));
|
||||||
@@ -235,13 +226,6 @@ const SettingsModal = ({ config = defaultConfig, children }: SettingsModalProps)
|
|||||||
onChange={handleChangeShouldEnableInformationalPopovers}
|
onChange={handleChangeShouldEnableInformationalPopovers}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
|
||||||
<FormLabel>{t('settings.enableModelDescriptions')}</FormLabel>
|
|
||||||
<Switch
|
|
||||||
isChecked={shouldEnableModelDescriptions}
|
|
||||||
onChange={handleChangeShouldEnableModelDescriptions}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</StickyScrollable>
|
</StickyScrollable>
|
||||||
|
|
||||||
{Boolean(config?.shouldShowDeveloperSettings) && (
|
{Boolean(config?.shouldShowDeveloperSettings) && (
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ const initialSystemState: SystemState = {
|
|||||||
shouldUseNSFWChecker: false,
|
shouldUseNSFWChecker: false,
|
||||||
shouldUseWatermarker: false,
|
shouldUseWatermarker: false,
|
||||||
shouldEnableInformationalPopovers: true,
|
shouldEnableInformationalPopovers: true,
|
||||||
shouldEnableModelDescriptions: true,
|
|
||||||
logIsEnabled: true,
|
logIsEnabled: true,
|
||||||
logLevel: 'debug',
|
logLevel: 'debug',
|
||||||
logNamespaces: [...zLogNamespace.options],
|
logNamespaces: [...zLogNamespace.options],
|
||||||
@@ -58,9 +57,6 @@ export const systemSlice = createSlice({
|
|||||||
setShouldEnableInformationalPopovers(state, action: PayloadAction<boolean>) {
|
setShouldEnableInformationalPopovers(state, action: PayloadAction<boolean>) {
|
||||||
state.shouldEnableInformationalPopovers = action.payload;
|
state.shouldEnableInformationalPopovers = action.payload;
|
||||||
},
|
},
|
||||||
setShouldEnableModelDescriptions(state, action: PayloadAction<boolean>) {
|
|
||||||
state.shouldEnableModelDescriptions = action.payload;
|
|
||||||
},
|
|
||||||
shouldConfirmOnNewSessionToggled(state) {
|
shouldConfirmOnNewSessionToggled(state) {
|
||||||
state.shouldConfirmOnNewSession = !state.shouldConfirmOnNewSession;
|
state.shouldConfirmOnNewSession = !state.shouldConfirmOnNewSession;
|
||||||
},
|
},
|
||||||
@@ -77,7 +73,6 @@ export const {
|
|||||||
shouldUseNSFWCheckerChanged,
|
shouldUseNSFWCheckerChanged,
|
||||||
shouldUseWatermarkerChanged,
|
shouldUseWatermarkerChanged,
|
||||||
setShouldEnableInformationalPopovers,
|
setShouldEnableInformationalPopovers,
|
||||||
setShouldEnableModelDescriptions,
|
|
||||||
shouldConfirmOnNewSessionToggled,
|
shouldConfirmOnNewSessionToggled,
|
||||||
} = systemSlice.actions;
|
} = systemSlice.actions;
|
||||||
|
|
||||||
@@ -113,7 +108,4 @@ export const selectSystemShouldAntialiasProgressImage = createSystemSelector(
|
|||||||
export const selectSystemShouldEnableInformationalPopovers = createSystemSelector(
|
export const selectSystemShouldEnableInformationalPopovers = createSystemSelector(
|
||||||
(system) => system.shouldEnableInformationalPopovers
|
(system) => system.shouldEnableInformationalPopovers
|
||||||
);
|
);
|
||||||
export const selectSystemShouldEnableModelDescriptions = createSystemSelector(
|
|
||||||
(system) => system.shouldEnableModelDescriptions
|
|
||||||
);
|
|
||||||
export const selectSystemShouldConfirmOnNewSession = createSystemSelector((system) => system.shouldConfirmOnNewSession);
|
export const selectSystemShouldConfirmOnNewSession = createSystemSelector((system) => system.shouldConfirmOnNewSession);
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export interface SystemState {
|
|||||||
shouldUseNSFWChecker: boolean;
|
shouldUseNSFWChecker: boolean;
|
||||||
shouldUseWatermarker: boolean;
|
shouldUseWatermarker: boolean;
|
||||||
shouldEnableInformationalPopovers: boolean;
|
shouldEnableInformationalPopovers: boolean;
|
||||||
shouldEnableModelDescriptions: boolean;
|
|
||||||
logIsEnabled: boolean;
|
logIsEnabled: boolean;
|
||||||
logLevel: LogLevel;
|
logLevel: LogLevel;
|
||||||
logNamespaces: LogNamespace[];
|
logNamespaces: LogNamespace[];
|
||||||
|
|||||||
@@ -1714,12 +1714,6 @@ export type components = {
|
|||||||
* @default null
|
* @default null
|
||||||
*/
|
*/
|
||||||
image?: components["schemas"]["ImageField"];
|
image?: components["schemas"]["ImageField"];
|
||||||
/**
|
|
||||||
* Invert
|
|
||||||
* @description Whether to invert the mask.
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
invert?: boolean;
|
|
||||||
/**
|
/**
|
||||||
* type
|
* type
|
||||||
* @default apply_tensor_mask_to_image
|
* @default apply_tensor_mask_to_image
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "5.3.0"
|
__version__ = "5.3.0rc1"
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ dependencies = [
|
|||||||
"pypatchmatch",
|
"pypatchmatch",
|
||||||
'pyperclip',
|
'pyperclip',
|
||||||
"pyreadline3",
|
"pyreadline3",
|
||||||
"python-multipart",
|
"python-multipart==0.0.12",
|
||||||
"requests~=2.28.2",
|
"requests~=2.28.2",
|
||||||
"rich~=13.3",
|
"rich~=13.3",
|
||||||
"scikit-image~=0.21.0",
|
"scikit-image~=0.21.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user