Merge branch 'main' into feat/ui/allow-number-to-string

This commit is contained in:
Jonathan 2023-09-22 21:02:28 -05:00 committed by GitHub
commit 4c9344b0ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 1288 additions and 760 deletions

View File

@ -88,6 +88,9 @@ class FieldDescriptions:
num_1 = "The first number"
num_2 = "The second number"
mask = "The mask to use for the operation"
board = "The board to save the image to"
image = "The image to process"
tile_size = "Tile size"
class Input(str, Enum):
@ -173,6 +176,7 @@ class UIType(str, Enum):
WorkflowField = "WorkflowField"
IsIntermediate = "IsIntermediate"
MetadataField = "MetadataField"
BoardField = "BoardField"
# endregion
@ -656,6 +660,8 @@ def invocation(
:param Optional[str] title: Adds a title to the invocation. Use if the auto-generated title isn't quite right. Defaults to None.
:param Optional[list[str]] tags: Adds tags to the invocation. Invocations may be searched for by their tags. Defaults to None.
:param Optional[str] category: Adds a category to the invocation. Used to group the invocations in the UI. Defaults to None.
:param Optional[str] version: Adds a version to the invocation. Must be a valid semver string. Defaults to None.
:param Optional[bool] use_cache: Whether or not to use the invocation cache. Defaults to True. The user may override this in the workflow editor.
"""
def wrapper(cls: Type[GenericBaseInvocation]) -> Type[GenericBaseInvocation]:

View File

@ -559,3 +559,33 @@ class SamDetectorReproducibleColors(SamDetector):
img[:, :] = ann_color
final_img.paste(Image.fromarray(img, mode="RGB"), (0, 0), Image.fromarray(np.uint8(m * 255)))
return np.array(final_img, dtype=np.uint8)
@invocation(
"color_map_image_processor",
title="Color Map Processor",
tags=["controlnet"],
category="controlnet",
version="1.0.0",
)
class ColorMapImageProcessorInvocation(ImageProcessorInvocation):
"""Generates a color map from the provided image"""
color_map_tile_size: int = InputField(default=64, ge=0, description=FieldDescriptions.tile_size)
def run_processor(self, image: Image.Image):
image = image.convert("RGB")
image = np.array(image, dtype=np.uint8)
height, width = image.shape[:2]
width_tile_size = min(self.color_map_tile_size, width)
height_tile_size = min(self.color_map_tile_size, height)
color_map = cv2.resize(
image,
(width // width_tile_size, height // height_tile_size),
interpolation=cv2.INTER_CUBIC,
)
color_map = cv2.resize(color_map, (width, height), interpolation=cv2.INTER_NEAREST)
color_map = Image.fromarray(color_map)
return color_map

View File

@ -8,12 +8,12 @@ import numpy
from PIL import Image, ImageChops, ImageFilter, ImageOps
from invokeai.app.invocations.metadata import CoreMetadata
from invokeai.app.invocations.primitives import ColorField, ImageField, ImageOutput
from invokeai.app.invocations.primitives import BoardField, ColorField, ImageField, ImageOutput
from invokeai.backend.image_util.invisible_watermark import InvisibleWatermark
from invokeai.backend.image_util.safety_checker import SafetyChecker
from ..models.image import ImageCategory, ResourceOrigin
from .baseinvocation import BaseInvocation, FieldDescriptions, InputField, InvocationContext, invocation
from .baseinvocation import BaseInvocation, FieldDescriptions, Input, InputField, InvocationContext, invocation
@invocation("show_image", title="Show Image", tags=["image"], category="image", version="1.0.0")
@ -972,13 +972,14 @@ class ImageChannelMultiplyInvocation(BaseInvocation):
title="Save Image",
tags=["primitives", "image"],
category="primitives",
version="1.0.0",
version="1.0.1",
use_cache=False,
)
class SaveImageInvocation(BaseInvocation):
"""Saves an image. Unlike an image primitive, this invocation stores a copy of the image."""
image: ImageField = InputField(description="The image to load")
image: ImageField = InputField(description=FieldDescriptions.image)
board: Optional[BoardField] = InputField(default=None, description=FieldDescriptions.board, input=Input.Direct)
metadata: CoreMetadata = InputField(
default=None,
description=FieldDescriptions.core_metadata,
@ -992,6 +993,7 @@ class SaveImageInvocation(BaseInvocation):
image=image,
image_origin=ResourceOrigin.INTERNAL,
image_category=ImageCategory.GENERAL,
board_id=self.board.board_id if self.board else None,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,

View File

@ -226,6 +226,12 @@ class ImageField(BaseModel):
image_name: str = Field(description="The name of the image")
class BoardField(BaseModel):
"""A board primitive field"""
board_id: str = Field(description="The id of the board")
@invocation_output("image_output")
class ImageOutput(BaseInvocationOutput):
"""Base class for nodes that output a single image"""

View File

@ -8,97 +8,97 @@ from invokeai.app.services.invoker import Invoker
class MemoryInvocationCache(InvocationCacheBase):
__cache: dict[Union[int, str], tuple[BaseInvocationOutput, str]]
__max_cache_size: int
__disabled: bool
__hits: int
__misses: int
__cache_ids: Queue
__invoker: Invoker
_cache: dict[Union[int, str], tuple[BaseInvocationOutput, str]]
_max_cache_size: int
_disabled: bool
_hits: int
_misses: int
_cache_ids: Queue
_invoker: Invoker
def __init__(self, max_cache_size: int = 0) -> None:
self.__cache = dict()
self.__max_cache_size = max_cache_size
self.__disabled = False
self.__hits = 0
self.__misses = 0
self.__cache_ids = Queue()
self._cache = dict()
self._max_cache_size = max_cache_size
self._disabled = False
self._hits = 0
self._misses = 0
self._cache_ids = Queue()
def start(self, invoker: Invoker) -> None:
self.__invoker = invoker
if self.__max_cache_size == 0:
self._invoker = invoker
if self._max_cache_size == 0:
return
self.__invoker.services.images.on_deleted(self._delete_by_match)
self.__invoker.services.latents.on_deleted(self._delete_by_match)
self._invoker.services.images.on_deleted(self._delete_by_match)
self._invoker.services.latents.on_deleted(self._delete_by_match)
def get(self, key: Union[int, str]) -> Optional[BaseInvocationOutput]:
if self.__max_cache_size == 0 or self.__disabled:
if self._max_cache_size == 0 or self._disabled:
return
item = self.__cache.get(key, None)
item = self._cache.get(key, None)
if item is not None:
self.__hits += 1
self._hits += 1
return item[0]
self.__misses += 1
self._misses += 1
def save(self, key: Union[int, str], invocation_output: BaseInvocationOutput) -> None:
if self.__max_cache_size == 0 or self.__disabled:
if self._max_cache_size == 0 or self._disabled:
return
if key not in self.__cache:
self.__cache[key] = (invocation_output, invocation_output.json())
self.__cache_ids.put(key)
if self.__cache_ids.qsize() > self.__max_cache_size:
if key not in self._cache:
self._cache[key] = (invocation_output, invocation_output.json())
self._cache_ids.put(key)
if self._cache_ids.qsize() > self._max_cache_size:
try:
self.__cache.pop(self.__cache_ids.get())
self._cache.pop(self._cache_ids.get())
except KeyError:
# this means the cache_ids are somehow out of sync w/ the cache
pass
def delete(self, key: Union[int, str]) -> None:
if self.__max_cache_size == 0 or self.__disabled:
if self._max_cache_size == 0:
return
if key in self.__cache:
del self.__cache[key]
if key in self._cache:
del self._cache[key]
def clear(self, *args, **kwargs) -> None:
if self.__max_cache_size == 0 or self.__disabled:
if self._max_cache_size == 0:
return
self.__cache.clear()
self.__cache_ids = Queue()
self.__misses = 0
self.__hits = 0
self._cache.clear()
self._cache_ids = Queue()
self._misses = 0
self._hits = 0
def create_key(self, invocation: BaseInvocation) -> int:
return hash(invocation.json(exclude={"id"}))
def disable(self) -> None:
if self.__max_cache_size == 0:
if self._max_cache_size == 0:
return
self.__disabled = True
self._disabled = True
def enable(self) -> None:
if self.__max_cache_size == 0:
if self._max_cache_size == 0:
return
self.__disabled = False
self._disabled = False
def get_status(self) -> InvocationCacheStatus:
return InvocationCacheStatus(
hits=self.__hits,
misses=self.__misses,
enabled=not self.__disabled and self.__max_cache_size > 0,
size=len(self.__cache),
max_size=self.__max_cache_size,
hits=self._hits,
misses=self._misses,
enabled=not self._disabled and self._max_cache_size > 0,
size=len(self._cache),
max_size=self._max_cache_size,
)
def _delete_by_match(self, to_match: str) -> None:
if self.__max_cache_size == 0 or self.__disabled:
if self._max_cache_size == 0:
return
keys_to_delete = set()
for key, value_tuple in self.__cache.items():
for key, value_tuple in self._cache.items():
if to_match in value_tuple[1]:
keys_to_delete.add(key)
@ -108,4 +108,4 @@ class MemoryInvocationCache(InvocationCacheBase):
for key in keys_to_delete:
self.delete(key)
self.__invoker.services.logger.debug(f"Deleted {len(keys_to_delete)} cached invocation outputs for {to_match}")
self._invoker.services.logger.debug(f"Deleted {len(keys_to_delete)} cached invocation outputs for {to_match}")

View File

@ -81,6 +81,7 @@
"load": "Load",
"loading": "Loading",
"loadingInvokeAI": "Loading Invoke AI",
"learnMore": "Learn More",
"modelManager": "Model Manager",
"nodeEditor": "Node Editor",
"nodes": "Workflow Editor",
@ -135,6 +136,8 @@
"bgth": "bg_th",
"canny": "Canny",
"cannyDescription": "Canny edge detection",
"colorMap": "Color",
"colorMapDescription": "Generates a color map from the image",
"coarse": "Coarse",
"contentShuffle": "Content Shuffle",
"contentShuffleDescription": "Shuffles the content in an image",
@ -158,6 +161,7 @@
"hideAdvanced": "Hide Advanced",
"highThreshold": "High Threshold",
"imageResolution": "Image Resolution",
"colorMapTileSize": "Tile Size",
"importImageFromCanvas": "Import Image From Canvas",
"importMaskFromCanvas": "Import Mask From Canvas",
"incompatibleBaseModel": "Incompatible base model:",
@ -701,6 +705,8 @@
"addNodeToolTip": "Add Node (Shift+A, Space)",
"animatedEdges": "Animated Edges",
"animatedEdgesHelp": "Animate selected edges and edges connected to selected nodes",
"boardField": "Board",
"boardFieldDescription": "A gallery board",
"boolean": "Booleans",
"booleanCollection": "Boolean Collection",
"booleanCollectionDescription": "A collection of booleans.",
@ -888,7 +894,7 @@
"zoomOutNodes": "Zoom Out"
},
"parameters": {
"aspectRatio": "Ratio",
"aspectRatio": "Aspect Ratio",
"boundingBoxHeader": "Bounding Box",
"boundingBoxHeight": "Bounding Box Height",
"boundingBoxWidth": "Bounding Box Width",
@ -1020,8 +1026,8 @@
"label": "Seed Behaviour",
"perIterationLabel": "Seed per Iteration",
"perIterationDesc": "Use a different seed for each iteration",
"perPromptLabel": "Seed per Prompt",
"perPromptDesc": "Use a different seed for each prompt"
"perPromptLabel": "Seed per Image",
"perPromptDesc": "Use a different seed for each image"
}
},
"sdxl": {
@ -1173,131 +1179,205 @@
"popovers": {
"clipSkip": {
"heading": "CLIP Skip",
"paragraph": "Choose how many layers of the CLIP model to skip. Certain models are better suited to be used with CLIP Skip."
},
"compositingBlur": {
"heading": "Blur",
"paragraph": "The blur radius of the mask."
},
"compositingBlurMethod": {
"heading": "Blur Method",
"paragraph": "The method of blur applied to the masked area."
},
"compositingCoherencePass": {
"heading": "Coherence Pass",
"paragraph": "Composite the Inpainted/Outpainted images."
},
"compositingCoherenceMode": {
"heading": "Mode",
"paragraph": "The mode of the Coherence Pass."
},
"compositingCoherenceSteps": {
"heading": "Steps",
"paragraph": "Number of steps in the Coherence Pass. Similar to Denoising Steps."
},
"compositingStrength": {
"heading": "Strength",
"paragraph": "Amount of noise added for the Coherence Pass. Similar to Denoising Strength."
},
"compositingMaskAdjustments": {
"heading": "Mask Adjustments",
"paragraph": "Adjust the mask."
},
"controlNetBeginEnd": {
"heading": "Begin / End Step Percentage",
"paragraph": "Which parts of the denoising process will have the ControlNet applied. ControlNets applied at the start of the process guide composition, and ControlNets applied at the end guide details."
},
"controlNetControlMode": {
"heading": "Control Mode",
"paragraph": "Lends more weight to either the prompt or ControlNet."
},
"controlNetResizeMode": {
"heading": "Resize Mode",
"paragraph": "How the ControlNet image will be fit to the image generation Ratio"
},
"controlNetToggle": {
"heading": "Enable ControlNet",
"paragraph": "ControlNets provide guidance to the generation process, helping create images with controlled composition, structure, or style, depending on the model selected."
},
"controlNetWeight": {
"heading": "Weight",
"paragraph": "How strongly the ControlNet will impact the generated image."
},
"dynamicPromptsToggle": {
"heading": "Enable Dynamic Prompts",
"paragraph": "Dynamic prompts allow multiple options within a prompt. Dynamic prompts can be used by: {option1|option2|option3}. Combinations of prompts will be randomly generated until the “Images” number has been reached."
},
"dynamicPromptsCombinatorial": {
"heading": "Combinatorial Generation",
"paragraph": "Generate an image for every possible combination of Dynamic Prompts until the Max Prompts is reached."
},
"infillMethod": {
"heading": "Infill Method",
"paragraph": "Method to infill the selected area."
},
"lora": {
"heading": "LoRA Weight",
"paragraph": "Weight of the LoRA. Higher weight will lead to larger impacts on the final image."
},
"noiseEnable": {
"heading": "Enable Noise Settings",
"paragraph": "Advanced control over noise generation."
},
"noiseUseCPU": {
"heading": "Use CPU Noise",
"paragraph": "Uses the CPU to generate random noise."
},
"paramCFGScale": {
"heading": "CFG Scale",
"paragraph": "Controls how much your prompt influences the generation process."
},
"paramDenoisingStrength": {
"heading": "Denoising Strength",
"paragraph": "How much noise is added to the input image. 0 will result in an identical image, while 1 will result in a completely new image."
},
"paramIterations": {
"heading": "Iterations",
"paragraph": "The number of images to generate. If Dynamic Prompts is enabled, each of the prompts will be generated this many times."
},
"paramModel": {
"heading": "Model",
"paragraph": "Model used for the denoising steps. Different models are trained to specialize in producing different aesthetic results and content."
"paragraphs": [
"Choose how many layers of the CLIP model to skip.",
"Some models work better with certain CLIP Skip settings.",
"A higher value typically results in a less detailed image."
]
},
"paramNegativeConditioning": {
"heading": "Negative Prompt",
"paragraph": "The generation process avoids the concepts in the negative prompt. Use this to exclude qualities or objects from the output. Supports Compel syntax and embeddings."
"paragraphs": [
"The generation process avoids the concepts in the negative prompt. Use this to exclude qualities or objects from the output.",
"Supports Compel syntax and embeddings."
]
},
"paramPositiveConditioning": {
"heading": "Positive Prompt",
"paragraph": "Guides the generation process. You may use any words or phrases. Supports Compel and Dynamic Prompts syntaxes and embeddings."
},
"paramRatio": {
"heading": "Ratio",
"paragraph": "The ratio of the dimensions of the image generated. An image size (in number of pixels) equivalent to 512x512 is recommended for SD1.5 models and a size equivalent to 1024x1024 is recommended for SDXL models."
"paragraphs": [
"Guides the generation process. You may use any words or phrases.",
"Compel and Dynamic Prompts syntaxes and embeddings."
]
},
"paramScheduler": {
"heading": "Scheduler",
"paragraph": "Scheduler defines how to iteratively add noise to an image or how to update a sample based on a model's output."
"paragraphs": [
"Scheduler defines how to iteratively add noise to an image or how to update a sample based on a model's output."
]
},
"compositingBlur": {
"heading": "Blur",
"paragraphs": ["The blur radius of the mask."]
},
"compositingBlurMethod": {
"heading": "Blur Method",
"paragraphs": ["The method of blur applied to the masked area."]
},
"compositingCoherencePass": {
"heading": "Coherence Pass",
"paragraphs": [
"A second round of denoising helps to composite the Inpainted/Outpainted image."
]
},
"compositingCoherenceMode": {
"heading": "Mode",
"paragraphs": ["The mode of the Coherence Pass."]
},
"compositingCoherenceSteps": {
"heading": "Steps",
"paragraphs": [
"Number of denoising steps used in the Coherence Pass.",
"Same as the main Steps parameter."
]
},
"compositingStrength": {
"heading": "Strength",
"paragraphs": [
"Denoising strength for the Coherence Pass.",
"Same as the Image to Image Denoising Strength parameter."
]
},
"compositingMaskAdjustments": {
"heading": "Mask Adjustments",
"paragraphs": ["Adjust the mask."]
},
"controlNetBeginEnd": {
"heading": "Begin / End Step Percentage",
"paragraphs": [
"Which steps of the denoising process will have the ControlNet applied.",
"ControlNets applied at the beginning of the process guide composition, and ControlNets applied at the end guide details."
]
},
"controlNetControlMode": {
"heading": "Control Mode",
"paragraphs": [
"Lends more weight to either the prompt or ControlNet."
]
},
"controlNetResizeMode": {
"heading": "Resize Mode",
"paragraphs": [
"How the ControlNet image will be fit to the image output size."
]
},
"controlNet": {
"heading": "ControlNet",
"paragraphs": [
"ControlNets provide guidance to the generation process, helping create images with controlled composition, structure, or style, depending on the model selected."
]
},
"controlNetWeight": {
"heading": "Weight",
"paragraphs": [
"How strongly the ControlNet will impact the generated image."
]
},
"dynamicPrompts": {
"heading": "Dynamic Prompts",
"paragraphs": [
"Dynamic Prompts parses a single prompt into many.",
"The basic syntax is \"a {red|green|blue} ball\". This will produce three prompts: \"a red ball\", \"a green ball\" and \"a blue ball\".",
"You can use the syntax as many times as you like in a single prompt, but be sure to keep the number of prompts generated in check with the Max Prompts setting."
]
},
"dynamicPromptsMaxPrompts": {
"heading": "Max Prompts",
"paragraphs": [
"Limits the number of prompts that can be generated by Dynamic Prompts."
]
},
"dynamicPromptsSeedBehaviour": {
"heading": "Seed Behaviour",
"paragraphs": [
"Controls how the seed is used when generating prompts.",
"Per Iteration will use a unique seed for each iteration. Use this to explore prompt variations on a single seed.",
"For example, if you have 5 prompts, each image will use the same seed.",
"Per Image will use a unique seed for each image. This provides more variation."
]
},
"infillMethod": {
"heading": "Infill Method",
"paragraphs": ["Method to infill the selected area."]
},
"lora": {
"heading": "LoRA Weight",
"paragraphs": [
"Higher LoRA weight will lead to larger impacts on the final image."
]
},
"noiseUseCPU": {
"heading": "Use CPU Noise",
"paragraphs": [
"Controls whether noise is generated on the CPU or GPU.",
"With CPU Noise enabled, a particular seed will produce the same image on any machine.",
"There is no performance impact to enabling CPU Noise."
]
},
"paramCFGScale": {
"heading": "CFG Scale",
"paragraphs": [
"Controls how much your prompt influences the generation process."
]
},
"paramDenoisingStrength": {
"heading": "Denoising Strength",
"paragraphs": [
"How much noise is added to the input image.",
"0 will result in an identical image, while 1 will result in a completely new image."
]
},
"paramIterations": {
"heading": "Iterations",
"paragraphs": [
"The number of images to generate.",
"If Dynamic Prompts is enabled, each of the prompts will be generated this many times."
]
},
"paramModel": {
"heading": "Model",
"paragraphs": [
"Model used for the denoising steps.",
"Different models are typically trained to specialize in producing particular aesthetic results and content."
]
},
"paramRatio": {
"heading": "Aspect Ratio",
"paragraphs": [
"The aspect ratio of the dimensions of the image generated.",
"An image size (in number of pixels) equivalent to 512x512 is recommended for SD1.5 models and a size equivalent to 1024x1024 is recommended for SDXL models."
]
},
"paramSeed": {
"heading": "Seed",
"paragraph": "Controls the starting noise used for generation. Disable “Random Seed” to produce identical results with the same generation settings."
"paragraphs": [
"Controls the starting noise used for generation.",
"Disable “Random Seed” to produce identical results with the same generation settings."
]
},
"paramSteps": {
"heading": "Steps",
"paragraph": "Number of steps that will be performed in each generation. Higher step counts will typically create better images but will require more generation time."
"paragraphs": [
"Number of steps that will be performed in each generation.",
"Higher step counts will typically create better images but will require more generation time."
]
},
"paramVAE": {
"heading": "VAE",
"paragraph": "Model used for translating AI output into the final image."
"paragraphs": [
"Model used for translating AI output into the final image."
]
},
"paramVAEPrecision": {
"heading": "VAE Precision",
"paragraph": "The precision used during VAE encoding and decoding. Fp16/Half precision is more efficient, at the expense of minor image variations."
"paragraphs": [
"The precision used during VAE encoding and decoding. FP16/half precision is more efficient, at the expense of minor image variations."
]
},
"scaleBeforeProcessing": {
"heading": "Scale Before Processing",
"paragraph": "Scales the selected area to the size best suited for the model before the image generation process."
"paragraphs": [
"Scales the selected area to the size best suited for the model before the image generation process."
]
}
},
"ui": {

View File

@ -17,7 +17,8 @@ import {
} from 'services/events/actions';
import { startAppListening } from '../..';
const nodeDenylist = ['load_image'];
// These nodes output an image, but do not actually *save* an image, so we don't want to handle the gallery logic on them
const nodeDenylist = ['load_image', 'image'];
export const addInvocationCompleteEventListener = () => {
startAppListening({
@ -37,6 +38,7 @@ export const addInvocationCompleteEventListener = () => {
const { image_name } = result.image;
const { canvas, gallery } = getState();
// This populates the `getImageDTO` cache
const imageDTO = await dispatch(
imagesApi.endpoints.getImageDTO.initiate(image_name)
).unwrap();
@ -52,54 +54,36 @@ export const addInvocationCompleteEventListener = () => {
if (!imageDTO.is_intermediate) {
/**
* Cache updates for when an image result is received
* - *add* to getImageDTO
* - IF `autoAddBoardId` is set:
* - THEN add it to the board_id/images
* - ELSE (`autoAddBoardId` is not set):
* - THEN add it to the no_board/images
* - add it to the no_board/images
*/
const { autoAddBoardId } = gallery;
if (autoAddBoardId && autoAddBoardId !== 'none') {
dispatch(
imagesApi.endpoints.addImageToBoard.initiate({
board_id: autoAddBoardId,
imageDTO,
})
);
} else {
dispatch(
imagesApi.util.updateQueryData(
'listImages',
{
board_id: 'none',
categories: IMAGE_CATEGORIES,
},
(draft) => {
imagesAdapter.addOne(draft, imageDTO);
}
)
);
}
dispatch(
imagesApi.util.updateQueryData(
'listImages',
{
board_id: imageDTO.board_id ?? 'none',
categories: IMAGE_CATEGORIES,
},
(draft) => {
imagesAdapter.addOne(draft, imageDTO);
}
)
);
dispatch(
imagesApi.util.invalidateTags([
{ type: 'BoardImagesTotal', id: autoAddBoardId },
{ type: 'BoardAssetsTotal', id: autoAddBoardId },
{ type: 'BoardImagesTotal', id: imageDTO.board_id },
{ type: 'BoardAssetsTotal', id: imageDTO.board_id },
])
);
const { selectedBoardId, shouldAutoSwitch } = gallery;
const { shouldAutoSwitch } = gallery;
// If auto-switch is enabled, select the new image
if (shouldAutoSwitch) {
// if auto-add is enabled, switch the board as the image comes in
if (autoAddBoardId && autoAddBoardId !== selectedBoardId) {
dispatch(boardIdSelected(autoAddBoardId));
dispatch(galleryViewChanged('images'));
} else if (!autoAddBoardId) {
dispatch(galleryViewChanged('images'));
}
dispatch(galleryViewChanged('images'));
dispatch(boardIdSelected(imageDTO.board_id ?? 'none'));
dispatch(imageSelected(imageDTO));
}
}

View File

@ -18,11 +18,14 @@ export const addUpscaleRequestedListener = () => {
const log = logger('session');
const { image_name } = action.payload;
const { esrganModelName } = getState().postprocessing;
const state = getState();
const { esrganModelName } = state.postprocessing;
const { autoAddBoardId } = state.gallery;
const graph = buildAdHocUpscaleGraph({
image_name,
esrganModelName,
autoAddBoardId,
});
try {

View File

@ -1,124 +0,0 @@
import {
Box,
Button,
Divider,
Flex,
Heading,
Image,
Popover,
PopoverArrow,
PopoverBody,
PopoverCloseButton,
PopoverContent,
PopoverProps,
PopoverTrigger,
Portal,
Text,
} from '@chakra-ui/react';
import { ReactNode, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { useAppSelector } from '../../app/store/storeHooks';
const OPEN_DELAY = 1500;
type Props = Omit<PopoverProps, 'children'> & {
details: string;
children: ReactNode;
image?: string;
buttonLabel?: string;
buttonHref?: string;
placement?: PopoverProps['placement'];
};
const IAIInformationalPopover = ({
details,
image,
buttonLabel,
buttonHref,
children,
placement,
}: Props) => {
const shouldEnableInformationalPopovers = useAppSelector(
(state) => state.system.shouldEnableInformationalPopovers
);
const { t } = useTranslation();
const heading = t(`popovers.${details}.heading`);
const paragraph = t(`popovers.${details}.paragraph`);
if (!shouldEnableInformationalPopovers) {
return <>{children}</>;
}
return (
<Popover
placement={placement || 'top'}
closeOnBlur={false}
trigger="hover"
variant="informational"
openDelay={OPEN_DELAY}
>
<PopoverTrigger>
<Box w="full">{children}</Box>
</PopoverTrigger>
<Portal>
<PopoverContent>
<PopoverArrow />
<PopoverCloseButton />
<PopoverBody>
<Flex
sx={{
gap: 3,
flexDirection: 'column',
width: '100%',
alignItems: 'center',
}}
>
{image && (
<Image
sx={{
objectFit: 'contain',
maxW: '60%',
maxH: '60%',
backgroundColor: 'white',
}}
src={image}
alt="Optional Image"
/>
)}
<Flex
sx={{
gap: 3,
flexDirection: 'column',
width: '100%',
}}
>
{heading && (
<>
<Heading size="sm">{heading}</Heading>
<Divider />
</>
)}
<Text>{paragraph}</Text>
{buttonLabel && (
<Flex justifyContent="flex-end">
<Button
onClick={() => window.open(buttonHref)}
size="sm"
variant="invokeAIOutline"
>
{buttonLabel}
</Button>
</Flex>
)}
</Flex>
</Flex>
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
);
};
export default memo(IAIInformationalPopover);

View File

@ -0,0 +1,155 @@
import {
Box,
BoxProps,
Button,
Divider,
Flex,
Heading,
Image,
Popover,
PopoverBody,
PopoverCloseButton,
PopoverContent,
PopoverProps,
PopoverTrigger,
Portal,
Text,
forwardRef,
} from '@chakra-ui/react';
import { merge, omit } from 'lodash-es';
import { PropsWithChildren, memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaExternalLinkAlt } from 'react-icons/fa';
import { useAppSelector } from '../../../app/store/storeHooks';
import {
Feature,
OPEN_DELAY,
POPOVER_DATA,
POPPER_MODIFIERS,
} from './constants';
type Props = PropsWithChildren & {
feature: Feature;
wrapperProps?: BoxProps;
popoverProps?: PopoverProps;
};
const IAIInformationalPopover = forwardRef(
({ feature, children, wrapperProps, ...rest }: Props, ref) => {
const { t } = useTranslation();
const shouldEnableInformationalPopovers = useAppSelector(
(state) => state.system.shouldEnableInformationalPopovers
);
const data = useMemo(() => POPOVER_DATA[feature], [feature]);
const popoverProps = useMemo(
() => merge(omit(data, ['image', 'href', 'buttonLabel']), rest),
[data, rest]
);
const heading = useMemo<string | undefined>(
() => t(`popovers.${feature}.heading`),
[feature, t]
);
const paragraphs = useMemo<string[]>(
() =>
t(`popovers.${feature}.paragraphs`, {
returnObjects: true,
}) ?? [],
[feature, t]
);
const handleClick = useCallback(() => {
if (!data?.href) {
return;
}
window.open(data.href);
}, [data?.href]);
if (!shouldEnableInformationalPopovers) {
return (
<Box ref={ref} w="full" {...wrapperProps}>
{children}
</Box>
);
}
return (
<Popover
isLazy
closeOnBlur={false}
trigger="hover"
variant="informational"
openDelay={OPEN_DELAY}
modifiers={POPPER_MODIFIERS}
placement="top"
{...popoverProps}
>
<PopoverTrigger>
<Box ref={ref} w="full" {...wrapperProps}>
{children}
</Box>
</PopoverTrigger>
<Portal>
<PopoverContent w={96}>
<PopoverCloseButton />
<PopoverBody>
<Flex
sx={{
gap: 2,
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
{heading && (
<>
<Heading size="sm">{heading}</Heading>
<Divider />
</>
)}
{data?.image && (
<>
<Image
sx={{
objectFit: 'contain',
maxW: '60%',
maxH: '60%',
backgroundColor: 'white',
}}
src={data.image}
alt="Optional Image"
/>
<Divider />
</>
)}
{paragraphs.map((p) => (
<Text key={p}>{p}</Text>
))}
{data?.href && (
<>
<Divider />
<Button
pt={1}
onClick={handleClick}
leftIcon={<FaExternalLinkAlt />}
alignSelf="flex-end"
variant="link"
>
{t('common.learnMore') ?? heading}
</Button>
</>
)}
</Flex>
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
);
}
);
IAIInformationalPopover.displayName = 'IAIInformationalPopover';
export default memo(IAIInformationalPopover);

View File

@ -0,0 +1,98 @@
import { PopoverProps } from '@chakra-ui/react';
export type Feature =
| 'clipSkip'
| 'paramNegativeConditioning'
| 'paramPositiveConditioning'
| 'paramScheduler'
| 'compositingBlur'
| 'compositingBlurMethod'
| 'compositingCoherencePass'
| 'compositingCoherenceMode'
| 'compositingCoherenceSteps'
| 'compositingStrength'
| 'compositingMaskAdjustments'
| 'controlNetBeginEnd'
| 'controlNetControlMode'
| 'controlNetResizeMode'
| 'controlNet'
| 'controlNetWeight'
| 'dynamicPrompts'
| 'dynamicPromptsMaxPrompts'
| 'dynamicPromptsSeedBehaviour'
| 'infillMethod'
| 'lora'
| 'noiseUseCPU'
| 'paramCFGScale'
| 'paramDenoisingStrength'
| 'paramIterations'
| 'paramModel'
| 'paramRatio'
| 'paramSeed'
| 'paramSteps'
| 'paramVAE'
| 'paramVAEPrecision'
| 'scaleBeforeProcessing';
export type PopoverData = PopoverProps & {
image?: string;
href?: string;
buttonLabel?: string;
};
export const POPOVER_DATA: { [key in Feature]?: PopoverData } = {
paramNegativeConditioning: {
placement: 'right',
},
controlNet: {
href: 'https://support.invoke.ai/support/solutions/articles/151000105880',
},
lora: {
href: 'https://support.invoke.ai/support/solutions/articles/151000159072',
},
compositingCoherenceMode: {
href: 'https://support.invoke.ai/support/solutions/articles/151000158838',
},
infillMethod: {
href: 'https://support.invoke.ai/support/solutions/articles/151000158841',
},
scaleBeforeProcessing: {
href: 'https://support.invoke.ai/support/solutions/articles/151000158841',
},
paramIterations: {
href: 'https://support.invoke.ai/support/solutions/articles/151000159073',
},
paramPositiveConditioning: {
href: 'https://support.invoke.ai/support/solutions/articles/151000096606-tips-on-crafting-prompts',
placement: 'right',
},
paramScheduler: {
placement: 'right',
href: 'https://support.invoke.ai/support/solutions/articles/151000159073',
},
paramModel: {
placement: 'right',
href: 'https://support.invoke.ai/support/solutions/articles/151000096601-what-is-a-model-which-should-i-use-',
},
paramRatio: {
gutter: 16,
},
controlNetControlMode: {
placement: 'right',
},
controlNetResizeMode: {
placement: 'right',
},
paramVAE: {
placement: 'right',
},
paramVAEPrecision: {
placement: 'right',
},
} as const;
export const OPEN_DELAY = 1000; // in milliseconds
export const POPPER_MODIFIERS: PopoverProps['modifiers'] = [
{ name: 'preventOverflow', options: { padding: 10 } },
];

View File

@ -44,23 +44,19 @@ const IAIMantineMultiSelect = forwardRef((props: IAIMultiSelectProps, ref) => {
return (
<Tooltip label={tooltip} placement="top" hasArrow isOpen={true}>
<MultiSelect
label={
label ? (
<FormControl ref={ref} isDisabled={disabled}>
<FormLabel>{label}</FormLabel>
</FormControl>
) : undefined
}
ref={inputRef}
disabled={disabled}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
searchable={searchable}
maxDropdownHeight={300}
styles={styles}
{...rest}
/>
<FormControl ref={ref} isDisabled={disabled}>
{label && <FormLabel>{label}</FormLabel>}
<MultiSelect
ref={inputRef}
disabled={disabled}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
searchable={searchable}
maxDropdownHeight={300}
styles={styles}
{...rest}
/>
</FormControl>
</Tooltip>
);
});

View File

@ -70,26 +70,23 @@ const IAIMantineSearchableSelect = forwardRef((props: IAISelectProps, ref) => {
return (
<Tooltip label={tooltip} placement="top" hasArrow>
<Select
ref={inputRef}
label={
label ? (
<FormControl ref={ref} isDisabled={disabled}>
<FormLabel>{label}</FormLabel>
</FormControl>
) : undefined
}
disabled={disabled}
searchValue={searchValue}
onSearchChange={setSearchValue}
onChange={handleChange}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
searchable={searchable}
maxDropdownHeight={300}
styles={styles}
{...rest}
/>
<FormControl ref={ref} isDisabled={disabled}>
{label && <FormLabel>{label}</FormLabel>}
<Select
ref={inputRef}
withinPortal
disabled={disabled}
searchValue={searchValue}
onSearchChange={setSearchValue}
onChange={handleChange}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
searchable={searchable}
maxDropdownHeight={300}
styles={styles}
{...rest}
/>
</FormControl>
</Tooltip>
);
});

View File

@ -22,19 +22,10 @@ const IAIMantineSelect = forwardRef((props: IAISelectProps, ref) => {
return (
<Tooltip label={tooltip} placement="top" hasArrow>
<Select
label={
label ? (
<FormControl ref={ref} isRequired={required} isDisabled={disabled}>
<FormLabel>{label}</FormLabel>
</FormControl>
) : undefined
}
disabled={disabled}
ref={inputRef}
styles={styles}
{...rest}
/>
<FormControl ref={ref} isRequired={required} isDisabled={disabled}>
<FormLabel>{label}</FormLabel>
<Select disabled={disabled} ref={inputRef} styles={styles} {...rest} />
</FormControl>
</Tooltip>
);
});

View File

@ -1,6 +1,7 @@
import { memo } from 'react';
import { ControlNetConfig } from '../store/controlNetSlice';
import CannyProcessor from './processors/CannyProcessor';
import ColorMapProcessor from './processors/ColorMapProcessor';
import ContentShuffleProcessor from './processors/ContentShuffleProcessor';
import HedProcessor from './processors/HedProcessor';
import LineartAnimeProcessor from './processors/LineartAnimeProcessor';
@ -30,6 +31,16 @@ const ControlNetProcessorComponent = (props: ControlNetProcessorProps) => {
);
}
if (processorNode.type === 'color_map_image_processor') {
return (
<ColorMapProcessor
controlNetId={controlNetId}
processorNode={processorNode}
isEnabled={isEnabled}
/>
);
}
if (processorNode.type === 'hed_image_processor') {
return (
<HedProcessor

View File

@ -10,7 +10,7 @@ import {
Tooltip,
} from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import {
ControlNetConfig,
controlNetBeginStepPctChanged,
@ -50,7 +50,7 @@ const ParamControlNetBeginEnd = (props: Props) => {
);
return (
<IAIInformationalPopover details="controlNetBeginEnd">
<IAIInformationalPopover feature="controlNetBeginEnd">
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.beginEndStepPercent')}</FormLabel>
<HStack w="100%" gap={2} alignItems="center">

View File

@ -1,5 +1,5 @@
import { useAppDispatch } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import {
ControlModes,
@ -35,7 +35,7 @@ export default function ParamControlNetControlMode(
);
return (
<IAIInformationalPopover details="controlNetControlMode">
<IAIInformationalPopover feature="controlNetControlMode">
<IAIMantineSelect
disabled={!isEnabled}
label={t('controlnet.controlMode')}

View File

@ -3,7 +3,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAISwitch from 'common/components/IAISwitch';
import { isControlNetEnabledToggled } from 'features/controlNet/store/controlNetSlice';
import { memo, useCallback } from 'react';
@ -28,7 +28,7 @@ const ParamControlNetFeatureToggle = () => {
return (
<Box width="100%">
<IAIInformationalPopover details="controlNetToggle">
<IAIInformationalPopover feature="controlNet">
<IAISwitch
label="Enable ControlNet"
isChecked={isEnabled}

View File

@ -1,5 +1,5 @@
import { useAppDispatch } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import {
ControlNetConfig,
@ -34,7 +34,7 @@ export default function ParamControlNetResizeMode(
);
return (
<IAIInformationalPopover details="controlNetResizeMode">
<IAIInformationalPopover feature="controlNetResizeMode">
<IAIMantineSelect
disabled={!isEnabled}
label={t('controlnet.resizeMode')}

View File

@ -1,5 +1,5 @@
import { useAppDispatch } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAISlider from 'common/components/IAISlider';
import {
ControlNetConfig,
@ -24,7 +24,7 @@ const ParamControlNetWeight = (props: ParamControlNetWeightProps) => {
);
return (
<IAIInformationalPopover details="controlNetWeight">
<IAIInformationalPopover feature="controlNetWeight">
<IAISlider
isDisabled={!isEnabled}
label={t('controlnet.weight')}

View File

@ -0,0 +1,59 @@
import IAISlider from 'common/components/IAISlider';
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
import { RequiredColorMapImageProcessorInvocation } from 'features/controlNet/store/types';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper';
const DEFAULTS = CONTROLNET_PROCESSORS.color_map_image_processor
.default as RequiredColorMapImageProcessorInvocation;
type ColorMapProcessorProps = {
controlNetId: string;
processorNode: RequiredColorMapImageProcessorInvocation;
isEnabled: boolean;
};
const ColorMapProcessor = (props: ColorMapProcessorProps) => {
const { controlNetId, processorNode, isEnabled } = props;
const { color_map_tile_size } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const handleColorMapTileSizeChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { color_map_tile_size: v });
},
[controlNetId, processorChanged]
);
const handleColorMapTileSizeReset = useCallback(() => {
processorChanged(controlNetId, {
color_map_tile_size: DEFAULTS.color_map_tile_size,
});
}, [controlNetId, processorChanged]);
return (
<ProcessorWrapper>
<IAISlider
isDisabled={!isEnabled}
label={t('controlnet.colorMapTileSize')}
value={color_map_tile_size}
onChange={handleColorMapTileSizeChanged}
handleReset={handleColorMapTileSizeReset}
withReset
min={1}
max={256}
step={1}
withInput
withSliderMarks
sliderNumberInputProps={{
max: 4096,
}}
/>
</ProcessorWrapper>
);
};
export default memo(ColorMapProcessor);

View File

@ -4,5 +4,9 @@ import { PropsWithChildren } from 'react';
type Props = PropsWithChildren;
export default function ProcessorWrapper(props: Props) {
return <Flex sx={{ flexDirection: 'column', gap: 2 }}>{props.children}</Flex>;
return (
<Flex sx={{ flexDirection: 'column', gap: 2, pb: 2 }}>
{props.children}
</Flex>
);
}

View File

@ -1,8 +1,8 @@
import i18n from 'i18next';
import {
ControlNetProcessorType,
RequiredControlNetProcessorNode,
} from './types';
import i18n from 'i18next';
type ControlNetProcessorsDict = Record<
ControlNetProcessorType,
@ -50,6 +50,20 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
high_threshold: 200,
},
},
color_map_image_processor: {
type: 'color_map_image_processor',
get label() {
return i18n.t('controlnet.colorMap');
},
get description() {
return i18n.t('controlnet.colorMapDescription');
},
default: {
id: 'color_map_image_processor',
type: 'color_map_image_processor',
color_map_tile_size: 64,
},
},
content_shuffle_image_processor: {
type: 'content_shuffle_image_processor',
get label() {

View File

@ -1,6 +1,7 @@
import { isObject } from 'lodash-es';
import {
CannyImageProcessorInvocation,
ColorMapImageProcessorInvocation,
ContentShuffleImageProcessorInvocation,
HedImageProcessorInvocation,
LineartAnimeImageProcessorInvocation,
@ -20,6 +21,7 @@ import { O } from 'ts-toolbelt';
*/
export type ControlNetProcessorNode =
| CannyImageProcessorInvocation
| ColorMapImageProcessorInvocation
| ContentShuffleImageProcessorInvocation
| HedImageProcessorInvocation
| LineartAnimeImageProcessorInvocation
@ -47,6 +49,14 @@ export type RequiredCannyImageProcessorInvocation = O.Required<
'type' | 'low_threshold' | 'high_threshold'
>;
/**
* The Color Map processor node, with parameters flagged as required
*/
export type RequiredColorMapImageProcessorInvocation = O.Required<
ColorMapImageProcessorInvocation,
'type' | 'color_map_tile_size'
>;
/**
* The ContentShuffle processor node, with parameters flagged as required
*/
@ -140,6 +150,7 @@ export type RequiredZoeDepthImageProcessorInvocation = O.Required<
*/
export type RequiredControlNetProcessorNode = O.Required<
| RequiredCannyImageProcessorInvocation
| RequiredColorMapImageProcessorInvocation
| RequiredContentShuffleImageProcessorInvocation
| RequiredHedImageProcessorInvocation
| RequiredLineartAnimeImageProcessorInvocation
@ -166,6 +177,22 @@ export const isCannyImageProcessorInvocation = (
return false;
};
/**
* Type guard for ColorMapImageProcessorInvocation
*/
export const isColorMapImageProcessorInvocation = (
obj: unknown
): obj is ColorMapImageProcessorInvocation => {
if (
isObject(obj) &&
'type' in obj &&
obj.type === 'color_map_image_processor'
) {
return true;
}
return false;
};
/**
* Type guard for ContentShuffleImageProcessorInvocation
*/

View File

@ -43,8 +43,8 @@ const ParamDynamicPromptsCollapse = () => {
activeLabel={activeLabel}
>
<Flex sx={{ gap: 2, flexDir: 'column' }}>
<ParamDynamicPromptsSeedBehaviour />
<ParamDynamicPromptsPreview />
<ParamDynamicPromptsSeedBehaviour />
<ParamDynamicPromptsMaxPrompts />
</Flex>
</IAICollapse>

View File

@ -4,9 +4,8 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISwitch from 'common/components/IAISwitch';
import { memo, useCallback } from 'react';
import { combinatorialToggled } from '../store/dynamicPromptsSlice';
import { useTranslation } from 'react-i18next';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import { combinatorialToggled } from '../store/dynamicPromptsSlice';
const selector = createSelector(
stateSelector,
@ -28,13 +27,11 @@ const ParamDynamicPromptsCombinatorial = () => {
}, [dispatch]);
return (
<IAIInformationalPopover details="dynamicPromptsCombinatorial">
<IAISwitch
label={t('dynamicPrompts.combinatorial')}
isChecked={combinatorial}
onChange={handleChange}
/>
</IAIInformationalPopover>
<IAISwitch
label={t('dynamicPrompts.combinatorial')}
isChecked={combinatorial}
onChange={handleChange}
/>
);
};

View File

@ -9,6 +9,7 @@ import {
maxPromptsReset,
} from '../store/dynamicPromptsSlice';
import { useTranslation } from 'react-i18next';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
const selector = createSelector(
stateSelector,
@ -46,19 +47,21 @@ const ParamDynamicPromptsMaxPrompts = () => {
}, [dispatch]);
return (
<IAISlider
label={t('dynamicPrompts.maxPrompts')}
isDisabled={isDisabled}
min={min}
max={sliderMax}
value={maxPrompts}
onChange={handleChange}
sliderNumberInputProps={{ max: inputMax }}
withSliderMarks
withInput
withReset
handleReset={handleReset}
/>
<IAIInformationalPopover feature="dynamicPromptsMaxPrompts">
<IAISlider
label={t('dynamicPrompts.maxPrompts')}
isDisabled={isDisabled}
min={min}
max={sliderMax}
value={maxPrompts}
onChange={handleChange}
sliderNumberInputProps={{ max: inputMax }}
withSliderMarks
withInput
withReset
handleReset={handleReset}
/>
</IAIInformationalPopover>
);
};

View File

@ -13,6 +13,7 @@ import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
import { memo } from 'react';
import { FaCircleExclamation } from 'react-icons/fa6';
@ -42,58 +43,73 @@ const ParamDynamicPromptsPreview = () => {
if (isError) {
return (
<Flex
w="full"
h="full"
layerStyle="second"
alignItems="center"
justifyContent="center"
p={8}
>
<IAINoContentFallback
icon={FaCircleExclamation}
label="Problem generating prompts"
/>
</Flex>
<IAIInformationalPopover feature="dynamicPrompts">
<Flex
w="full"
h="full"
layerStyle="second"
alignItems="center"
justifyContent="center"
p={8}
>
<IAINoContentFallback
icon={FaCircleExclamation}
label="Problem generating prompts"
/>
</Flex>
</IAIInformationalPopover>
);
}
return (
<FormControl isInvalid={Boolean(parsingError)}>
<FormLabel whiteSpace="nowrap" overflow="hidden" textOverflow="ellipsis">
Prompts Preview ({prompts.length}){parsingError && ` - ${parsingError}`}
</FormLabel>
<Flex h={64} pos="relative" layerStyle="third" borderRadius="base" p={2}>
<ScrollableContent>
<OrderedList stylePosition="inside" ms={0}>
{prompts.map((prompt, i) => (
<ListItem
fontSize="sm"
key={`${prompt}.${i}`}
sx={listItemStyles}
>
<Text as="span">{prompt}</Text>
</ListItem>
))}
</OrderedList>
</ScrollableContent>
{isLoading && (
<Flex
pos="absolute"
w="full"
h="full"
top={0}
insetInlineStart={0}
layerStyle="second"
opacity={0.7}
alignItems="center"
justifyContent="center"
>
<Spinner />
</Flex>
)}
</Flex>
</FormControl>
<IAIInformationalPopover feature="dynamicPrompts">
<FormControl isInvalid={Boolean(parsingError)}>
<FormLabel
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
>
Prompts Preview ({prompts.length})
{parsingError && ` - ${parsingError}`}
</FormLabel>
<Flex
h={64}
pos="relative"
layerStyle="third"
borderRadius="base"
p={2}
>
<ScrollableContent>
<OrderedList stylePosition="inside" ms={0}>
{prompts.map((prompt, i) => (
<ListItem
fontSize="sm"
key={`${prompt}.${i}`}
sx={listItemStyles}
>
<Text as="span">{prompt}</Text>
</ListItem>
))}
</OrderedList>
</ScrollableContent>
{isLoading && (
<Flex
pos="absolute"
w="full"
h="full"
top={0}
insetInlineStart={0}
layerStyle="second"
opacity={0.7}
alignItems="center"
justifyContent="center"
>
<Spinner />
</Flex>
)}
</Flex>
</FormControl>
</IAIInformationalPopover>
);
};

View File

@ -7,6 +7,7 @@ import {
seedBehaviourChanged,
} from '../store/dynamicPromptsSlice';
import IAIMantineSelectItemWithDescription from 'common/components/IAIMantineSelectItemWithDescription';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
type Item = {
label: string;
@ -47,13 +48,15 @@ const ParamDynamicPromptsSeedBehaviour = () => {
);
return (
<IAIMantineSelect
label={t('dynamicPrompts.seedBehaviour.label')}
value={seedBehaviour}
data={data}
itemComponent={IAIMantineSelectItemWithDescription}
onChange={handleChange}
/>
<IAIInformationalPopover feature="dynamicPromptsSeedBehaviour">
<IAIMantineSelect
label={t('dynamicPrompts.seedBehaviour.label')}
value={seedBehaviour}
data={data}
itemComponent={IAIMantineSelectItemWithDescription}
onChange={handleChange}
/>
</IAIInformationalPopover>
);
};

View File

@ -10,7 +10,7 @@ import {
loraWeightChanged,
loraWeightReset,
} from '../store/loraSlice';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
type Props = {
lora: LoRA;
@ -36,7 +36,7 @@ const ParamLora = (props: Props) => {
}, [dispatch, lora.id]);
return (
<IAIInformationalPopover details="lora">
<IAIInformationalPopover feature="lora">
<Flex sx={{ gap: 2.5, alignItems: 'flex-end' }}>
<IAISlider
label={lora.model_name}

View File

@ -25,8 +25,8 @@ const InvocationNodeFooter = ({ nodeId }: Props) => {
justifyContent: 'space-between',
}}
>
{hasImageOutput && <EmbedWorkflowCheckbox nodeId={nodeId} />}
<UseCacheCheckbox nodeId={nodeId} />
{hasImageOutput && <EmbedWorkflowCheckbox nodeId={nodeId} />}
{hasImageOutput && <SaveToGalleryCheckbox nodeId={nodeId} />}
</Flex>
);

View File

@ -16,6 +16,7 @@ import SchedulerInputField from './inputs/SchedulerInputField';
import StringInputField from './inputs/StringInputField';
import VaeModelInputField from './inputs/VaeModelInputField';
import IPAdapterModelInputField from './inputs/IPAdapterModelInputField';
import BoardInputField from './inputs/BoardInputField';
type InputFieldProps = {
nodeId: string;
@ -99,6 +100,16 @@ const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => {
);
}
if (field?.type === 'BoardField' && fieldTemplate?.type === 'BoardField') {
return (
<BoardInputField
nodeId={nodeId}
field={field}
fieldTemplate={fieldTemplate}
/>
);
}
if (
field?.type === 'MainModelField' &&
fieldTemplate?.type === 'MainModelField'

View File

@ -0,0 +1,64 @@
import { SelectItem } from '@mantine/core';
import { useAppDispatch } from 'app/store/storeHooks';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import { fieldBoardValueChanged } from 'features/nodes/store/nodesSlice';
import {
BoardInputFieldTemplate,
BoardInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { memo, useCallback } from 'react';
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
const BoardInputFieldComponent = (
props: FieldComponentProps<BoardInputFieldValue, BoardInputFieldTemplate>
) => {
const { nodeId, field } = props;
const dispatch = useAppDispatch();
const { data, hasBoards } = useListAllBoardsQuery(undefined, {
selectFromResult: ({ data }) => {
const boards: SelectItem[] = [
{
label: 'None',
value: 'none',
},
];
data?.forEach(({ board_id, board_name }) => {
boards.push({
label: board_name,
value: board_id,
});
});
return {
data: boards,
hasBoards: boards.length > 1,
};
},
});
const handleChange = useCallback(
(v: string | null) => {
dispatch(
fieldBoardValueChanged({
nodeId,
fieldName: field.name,
value: v && v !== 'none' ? { board_id: v } : undefined,
})
);
},
[dispatch, field.name, nodeId]
);
return (
<IAIMantineSearchableSelect
className="nowheel nodrag"
value={field.value?.board_id ?? 'none'}
data={data}
onChange={handleChange}
disabled={!hasBoards}
/>
);
};
export default memo(BoardInputFieldComponent);

View File

@ -65,11 +65,6 @@ const SchedulerInputField = (
return (
<IAIMantineSearchableSelect
className="nowheel nodrag"
sx={{
'.mantine-Select-dropdown': {
width: '14rem !important',
},
}}
value={field.value}
data={data}
onChange={handleChange}

View File

@ -143,7 +143,7 @@ export const useBuildNodeData = () => {
notes: '',
isOpen: true,
embedWorkflow: false,
isIntermediate: true,
isIntermediate: type === 'save_image' ? false : true,
inputs,
outputs,
useCache: template.useCache,

View File

@ -17,8 +17,12 @@ export const useHasImageOutput = (nodeId: string) => {
if (!isInvocationNode(node)) {
return false;
}
return some(node.data.outputs, (output) =>
IMAGE_FIELDS.includes(output.type)
return some(
node.data.outputs,
(output) =>
IMAGE_FIELDS.includes(output.type) &&
// the image primitive node does not actually save the image, do not show the image-saving checkboxes
node.data.type !== 'image'
);
},
defaultSelectorOptions

View File

@ -30,6 +30,7 @@ import {
import { v4 as uuidv4 } from 'uuid';
import { DRAG_HANDLE_CLASSNAME } from '../types/constants';
import {
BoardInputFieldValue,
BooleanInputFieldValue,
ColorInputFieldValue,
ControlNetModelInputFieldValue,
@ -494,6 +495,12 @@ const nodesSlice = createSlice({
) => {
fieldValueReducer(state, action);
},
fieldBoardValueChanged: (
state,
action: FieldValueAction<BoardInputFieldValue>
) => {
fieldValueReducer(state, action);
},
fieldImageValueChanged: (
state,
action: FieldValueAction<ImageInputFieldValue>
@ -871,7 +878,7 @@ const nodesSlice = createSlice({
builder.addCase(appSocketQueueItemStatusChanged, (state, action) => {
if (['in_progress'].includes(action.payload.data.status)) {
forEach(state.nodeExecutionStates, (nes) => {
nes.status = NodeStatus.IN_PROGRESS;
nes.status = NodeStatus.PENDING;
nes.error = null;
nes.progress = null;
nes.progressImage = null;
@ -897,6 +904,7 @@ export const {
imageCollectionFieldValueChanged,
fieldStringValueChanged,
fieldNumberValueChanged,
fieldBoardValueChanged,
fieldBooleanValueChanged,
fieldImageValueChanged,
fieldColorValueChanged,

View File

@ -1,4 +1,9 @@
import { FieldType, FieldUIConfig } from './types';
import {
FieldType,
FieldTypeMap,
FieldTypeMapWithNumber,
FieldUIConfig,
} from './types';
import { t } from 'i18next';
export const HANDLE_TOOLTIP_OPEN_DELAY = 500;
@ -28,7 +33,7 @@ export const COLLECTION_TYPES: FieldType[] = [
'ColorCollection',
];
export const POLYMORPHIC_TYPES = [
export const POLYMORPHIC_TYPES: FieldType[] = [
'IntegerPolymorphic',
'BooleanPolymorphic',
'FloatPolymorphic',
@ -40,7 +45,7 @@ export const POLYMORPHIC_TYPES = [
'ColorPolymorphic',
];
export const MODEL_TYPES = [
export const MODEL_TYPES: FieldType[] = [
'IPAdapterModelField',
'ControlNetModelField',
'LoRAModelField',
@ -54,7 +59,7 @@ export const MODEL_TYPES = [
'ClipField',
];
export const COLLECTION_MAP = {
export const COLLECTION_MAP: FieldTypeMapWithNumber = {
integer: 'IntegerCollection',
boolean: 'BooleanCollection',
number: 'FloatCollection',
@ -71,7 +76,7 @@ export const isCollectionItemType = (
): itemType is keyof typeof COLLECTION_MAP =>
Boolean(itemType && itemType in COLLECTION_MAP);
export const SINGLE_TO_POLYMORPHIC_MAP = {
export const SINGLE_TO_POLYMORPHIC_MAP: FieldTypeMapWithNumber = {
integer: 'IntegerPolymorphic',
boolean: 'BooleanPolymorphic',
number: 'FloatPolymorphic',
@ -84,7 +89,7 @@ export const SINGLE_TO_POLYMORPHIC_MAP = {
ColorField: 'ColorPolymorphic',
};
export const POLYMORPHIC_TO_SINGLE_MAP = {
export const POLYMORPHIC_TO_SINGLE_MAP: FieldTypeMap = {
IntegerPolymorphic: 'integer',
BooleanPolymorphic: 'boolean',
FloatPolymorphic: 'float',
@ -96,7 +101,7 @@ export const POLYMORPHIC_TO_SINGLE_MAP = {
ColorPolymorphic: 'ColorField',
};
export const TYPES_WITH_INPUT_COMPONENTS = [
export const TYPES_WITH_INPUT_COMPONENTS: FieldType[] = [
'string',
'StringPolymorphic',
'boolean',
@ -117,6 +122,7 @@ export const TYPES_WITH_INPUT_COMPONENTS = [
'SDXLMainModelField',
'Scheduler',
'IPAdapterModelField',
'BoardField',
];
export const isPolymorphicItemType = (
@ -240,6 +246,11 @@ export const FIELDS: Record<FieldType, FieldUIConfig> = {
description: t('nodes.imageFieldDescription'),
title: t('nodes.imageField'),
},
BoardField: {
color: 'purple.500',
description: t('nodes.imageFieldDescription'),
title: t('nodes.imageField'),
},
ImagePolymorphic: {
color: 'purple.500',
description: t('nodes.imagePolymorphicDescription'),

View File

@ -72,6 +72,7 @@ export type FieldUIConfig = {
// TODO: Get this from the OpenAPI schema? may be tricky...
export const zFieldType = z.enum([
'BoardField',
'boolean',
'BooleanCollection',
'BooleanPolymorphic',
@ -119,6 +120,10 @@ export const zFieldType = z.enum([
]);
export type FieldType = z.infer<typeof zFieldType>;
export type FieldTypeMap = { [key in FieldType]?: FieldType };
export type FieldTypeMapWithNumber = {
[key in FieldType | 'number']?: FieldType;
};
export const zReservedFieldType = z.enum([
'WorkflowField',
@ -187,6 +192,11 @@ export const zImageField = z.object({
});
export type ImageField = z.infer<typeof zImageField>;
export const zBoardField = z.object({
board_id: z.string().trim().min(1),
});
export type BoardField = z.infer<typeof zBoardField>;
export const zLatentsField = z.object({
latents_name: z.string().trim().min(1),
seed: z.number().int().optional(),
@ -494,6 +504,12 @@ export const zImageInputFieldValue = zInputFieldValueBase.extend({
});
export type ImageInputFieldValue = z.infer<typeof zImageInputFieldValue>;
export const zBoardInputFieldValue = zInputFieldValueBase.extend({
type: z.literal('BoardField'),
value: zBoardField.optional(),
});
export type BoardInputFieldValue = z.infer<typeof zBoardInputFieldValue>;
export const zImagePolymorphicInputFieldValue = zInputFieldValueBase.extend({
type: z.literal('ImagePolymorphic'),
value: zImageField.optional(),
@ -630,6 +646,7 @@ export type SchedulerInputFieldValue = z.infer<
>;
export const zInputFieldValue = z.discriminatedUnion('type', [
zBoardInputFieldValue,
zBooleanCollectionInputFieldValue,
zBooleanInputFieldValue,
zBooleanPolymorphicInputFieldValue,
@ -770,6 +787,11 @@ export type BooleanPolymorphicInputFieldTemplate = Omit<
type: 'BooleanPolymorphic';
};
export type BoardInputFieldTemplate = InputFieldTemplateBase & {
default: BoardField;
type: 'BoardField';
};
export type ImageInputFieldTemplate = InputFieldTemplateBase & {
default: ImageField;
type: 'ImageField';
@ -952,6 +974,7 @@ export type WorkflowInputFieldTemplate = InputFieldTemplateBase & {
* maximum length, pattern to match, etc).
*/
export type InputFieldTemplate =
| BoardInputFieldTemplate
| BooleanCollectionInputFieldTemplate
| BooleanPolymorphicInputFieldTemplate
| BooleanInputFieldTemplate

View File

@ -62,6 +62,8 @@ import {
ConditioningField,
IPAdapterInputFieldTemplate,
IPAdapterModelInputFieldTemplate,
BoardInputFieldTemplate,
InputFieldTemplate,
} from '../types/types';
import { ControlField } from 'services/api/types';
@ -450,6 +452,19 @@ const buildIPAdapterModelInputFieldTemplate = ({
return template;
};
const buildBoardInputFieldTemplate = ({
schemaObject,
baseField,
}: BuildInputFieldArg): BoardInputFieldTemplate => {
const template: BoardInputFieldTemplate = {
...baseField,
type: 'BoardField',
default: schemaObject.default ?? undefined,
};
return template;
};
const buildImageInputFieldTemplate = ({
schemaObject,
baseField,
@ -851,7 +866,10 @@ export const getFieldType = (
return;
};
const TEMPLATE_BUILDER_MAP = {
const TEMPLATE_BUILDER_MAP: {
[key in FieldType]?: (arg: BuildInputFieldArg) => InputFieldTemplate;
} = {
BoardField: buildBoardInputFieldTemplate,
boolean: buildBooleanInputFieldTemplate,
BooleanCollection: buildBooleanCollectionInputFieldTemplate,
BooleanPolymorphic: buildBooleanPolymorphicInputFieldTemplate,
@ -937,7 +955,13 @@ export const buildInputFieldTemplate = (
return;
}
return TEMPLATE_BUILDER_MAP[fieldType]({
const builder = TEMPLATE_BUILDER_MAP[fieldType];
if (!builder) {
return;
}
return builder({
schemaObject: fieldSchema,
baseField,
});

View File

@ -1,7 +1,10 @@
import { InputFieldTemplate, InputFieldValue } from '../types/types';
import { FieldType, InputFieldTemplate, InputFieldValue } from '../types/types';
const FIELD_VALUE_FALLBACK_MAP = {
const FIELD_VALUE_FALLBACK_MAP: {
[key in FieldType]: InputFieldValue['value'];
} = {
enum: '',
BoardField: undefined,
boolean: false,
BooleanCollection: [],
BooleanPolymorphic: false,

View File

@ -24,12 +24,14 @@ export const addSaveImageNode = (
const activeTabName = activeTabNameSelector(state);
const is_intermediate =
activeTabName === 'unifiedCanvas' ? !state.canvas.shouldAutoSave : false;
const { autoAddBoardId } = state.gallery;
const saveImageNode: SaveImageInvocation = {
id: SAVE_IMAGE,
type: 'save_image',
is_intermediate,
use_cache: false,
board: autoAddBoardId === 'none' ? undefined : { board_id: autoAddBoardId },
};
graph.nodes[SAVE_IMAGE] = saveImageNode;

View File

@ -6,15 +6,18 @@ import {
SaveImageInvocation,
} from 'services/api/types';
import { REALESRGAN as ESRGAN, SAVE_IMAGE } from './constants';
import { BoardId } from 'features/gallery/store/types';
type Arg = {
image_name: string;
esrganModelName: ESRGANModelName;
autoAddBoardId: BoardId;
};
export const buildAdHocUpscaleGraph = ({
image_name,
esrganModelName,
autoAddBoardId,
}: Arg): Graph => {
const realesrganNode: ESRGANInvocation = {
id: ESRGAN,
@ -28,6 +31,8 @@ export const buildAdHocUpscaleGraph = ({
id: SAVE_IMAGE,
type: 'save_image',
use_cache: false,
is_intermediate: false,
board: autoAddBoardId === 'none' ? undefined : { board_id: autoAddBoardId },
};
const graph: NonNullableGraph = {

View File

@ -1,6 +1,6 @@
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAISlider from 'common/components/IAISlider';
import { setClipSkip } from 'features/parameters/store/generationSlice';
import { clipSkipMap } from 'features/parameters/types/constants';
@ -47,7 +47,7 @@ export default function ParamClipSkip() {
}
return (
<IAIInformationalPopover details="clipSkip">
<IAIInformationalPopover feature="clipSkip" placement="top">
<IAISlider
label={t('parameters.clipSkip')}
aria-label={t('parameters.clipSkip')}

View File

@ -1,7 +1,8 @@
import { Box, Flex, Spacer, Text } from '@chakra-ui/react';
import { Flex, FormControl, FormLabel, Spacer } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { flipBoundingBoxAxes } from 'features/canvas/store/canvasSlice';
import { generationSelector } from 'features/parameters/store/generationSelectors';
@ -18,7 +19,6 @@ import ParamAspectRatio, {
} from '../../Core/ParamAspectRatio';
import ParamBoundingBoxHeight from './ParamBoundingBoxHeight';
import ParamBoundingBoxWidth from './ParamBoundingBoxWidth';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
const sizeOptsSelector = createSelector(
[generationSelector, canvasSelector],
@ -93,42 +93,29 @@ export default function ParamBoundingBoxSize() {
},
}}
>
<Flex alignItems="center" gap={2}>
<Box width="full">
<IAIInformationalPopover details="paramRatio">
<Text
sx={{
fontSize: 'sm',
width: 'full',
color: 'base.700',
_dark: {
color: 'base.300',
},
}}
>
{t('parameters.aspectRatio')}
</Text>
</IAIInformationalPopover>
</Box>
<Spacer />
<ParamAspectRatio />
<IAIIconButton
tooltip={t('ui.swapSizes')}
aria-label={t('ui.swapSizes')}
size="sm"
icon={<MdOutlineSwapVert />}
fontSize={20}
onClick={handleToggleSize}
/>
<IAIIconButton
tooltip={t('ui.lockRatio')}
aria-label={t('ui.lockRatio')}
size="sm"
icon={<FaLock />}
isChecked={shouldLockAspectRatio}
onClick={handleLockRatio}
/>
</Flex>
<IAIInformationalPopover feature="paramRatio">
<FormControl as={Flex} flexDir="row" alignItems="center" gap={2}>
<FormLabel>{t('parameters.aspectRatio')}</FormLabel>
<Spacer />
<ParamAspectRatio />
<IAIIconButton
tooltip={t('ui.swapSizes')}
aria-label={t('ui.swapSizes')}
size="sm"
icon={<MdOutlineSwapVert />}
fontSize={20}
onClick={handleToggleSize}
/>
<IAIIconButton
tooltip={t('ui.lockRatio')}
aria-label={t('ui.lockRatio')}
size="sm"
icon={<FaLock />}
isChecked={shouldLockAspectRatio}
onClick={handleLockRatio}
/>
</FormControl>
</IAIInformationalPopover>
<ParamBoundingBoxWidth />
<ParamBoundingBoxHeight />
</Flex>

View File

@ -1,6 +1,6 @@
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import { IAISelectDataType } from 'common/components/IAIMantineSearchableSelect';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { setCanvasCoherenceMode } from 'features/parameters/store/generationSlice';
@ -31,7 +31,7 @@ const ParamCanvasCoherenceMode = () => {
};
return (
<IAIInformationalPopover details="compositingCoherenceMode">
<IAIInformationalPopover feature="compositingCoherenceMode">
<IAIMantineSelect
label={t('parameters.coherenceMode')}
data={coherenceModeSelectData}

View File

@ -1,6 +1,6 @@
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAISlider from 'common/components/IAISlider';
import { setCanvasCoherenceSteps } from 'features/parameters/store/generationSlice';
import { memo } from 'react';
@ -14,7 +14,7 @@ const ParamCanvasCoherenceSteps = () => {
const { t } = useTranslation();
return (
<IAIInformationalPopover details="compositingCoherenceSteps">
<IAIInformationalPopover feature="compositingCoherenceSteps">
<IAISlider
label={t('parameters.coherenceSteps')}
min={1}

View File

@ -1,6 +1,6 @@
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAISlider from 'common/components/IAISlider';
import { setCanvasCoherenceStrength } from 'features/parameters/store/generationSlice';
import { memo } from 'react';
@ -14,7 +14,7 @@ const ParamCanvasCoherenceStrength = () => {
const { t } = useTranslation();
return (
<IAIInformationalPopover details="compositingStrength">
<IAIInformationalPopover feature="compositingStrength">
<IAISlider
label={t('parameters.coherenceStrength')}
min={0}

View File

@ -1,6 +1,6 @@
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAISlider from 'common/components/IAISlider';
import { setMaskBlur } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
@ -13,7 +13,7 @@ export default function ParamMaskBlur() {
const { t } = useTranslation();
return (
<IAIInformationalPopover details="compositingBlur">
<IAIInformationalPopover feature="compositingBlur">
<IAISlider
label={t('parameters.maskBlur')}
min={0}

View File

@ -2,7 +2,7 @@ import { SelectItem } from '@mantine/core';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { setMaskBlurMethod } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
@ -29,7 +29,7 @@ export default function ParamMaskBlurMethod() {
};
return (
<IAIInformationalPopover details="compositingBlurMethod">
<IAIInformationalPopover feature="compositingBlurMethod">
<IAIMantineSelect
value={maskBlurMethod}
onChange={handleMaskBlurMethodChange}

View File

@ -15,19 +15,13 @@ const ParamCompositingSettingsCollapse = () => {
return (
<IAICollapse label={t('parameters.compositingSettingsHeader')}>
<Flex sx={{ flexDirection: 'column', gap: 2 }}>
<SubParametersWrapper
label={t('parameters.coherencePassHeader')}
headerInfoPopover="compositingCoherencePass"
>
<SubParametersWrapper label={t('parameters.coherencePassHeader')}>
<ParamCanvasCoherenceMode />
<ParamCanvasCoherenceSteps />
<ParamCanvasCoherenceStrength />
</SubParametersWrapper>
<Divider />
<SubParametersWrapper
label={t('parameters.maskAdjustmentsHeader')}
headerInfoPopover="compositingMaskAdjustments"
>
<SubParametersWrapper label={t('parameters.maskAdjustmentsHeader')}>
<ParamMaskBlur />
<ParamMaskBlurMethod />
</SubParametersWrapper>

View File

@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { setInfillMethod } from 'features/parameters/store/generationSlice';
@ -40,7 +40,7 @@ const ParamInfillMethod = () => {
);
return (
<IAIInformationalPopover details="infillMethod">
<IAIInformationalPopover feature="infillMethod">
<IAIMantineSelect
disabled={infill_methods?.length === 0}
placeholder={isLoading ? 'Loading...' : undefined}

View File

@ -1,7 +1,7 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { setBoundingBoxScaleMethod } from 'features/canvas/store/canvasSlice';
@ -36,7 +36,7 @@ const ParamScaleBeforeProcessing = () => {
};
return (
<IAIInformationalPopover details="scaleBeforeProcessing">
<IAIInformationalPopover feature="scaleBeforeProcessing">
<IAIMantineSearchableSelect
label={t('parameters.scaleBeforeProcessing')}
data={BOUNDING_BOX_SCALES_DICT}

View File

@ -1,4 +1,4 @@
import { ButtonGroup, Flex } from '@chakra-ui/react';
import { ButtonGroup } from '@chakra-ui/react';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
@ -29,25 +29,23 @@ export default function ParamAspectRatio() {
const activeTabName = useAppSelector(activeTabNameSelector);
return (
<Flex gap={2} flexGrow={1}>
<ButtonGroup isAttached>
{aspectRatios.map((ratio) => (
<IAIButton
key={ratio.name}
size="sm"
isChecked={aspectRatio === ratio.value}
isDisabled={
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
}
onClick={() => {
dispatch(setAspectRatio(ratio.value));
dispatch(setShouldLockAspectRatio(false));
}}
>
{ratio.name}
</IAIButton>
))}
</ButtonGroup>
</Flex>
<ButtonGroup isAttached>
{aspectRatios.map((ratio) => (
<IAIButton
key={ratio.name}
size="sm"
isChecked={aspectRatio === ratio.value}
isDisabled={
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
}
onClick={() => {
dispatch(setAspectRatio(ratio.value));
dispatch(setShouldLockAspectRatio(false));
}}
>
{ratio.name}
</IAIButton>
))}
</ButtonGroup>
);
}

View File

@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISlider from 'common/components/IAISlider';
import { setCfgScale } from 'features/parameters/store/generationSlice';
@ -54,7 +54,7 @@ const ParamCFGScale = () => {
);
return shouldUseSliders ? (
<IAIInformationalPopover details="paramCFGScale">
<IAIInformationalPopover feature="paramCFGScale">
<IAISlider
label={t('parameters.cfgScale')}
step={shift ? 0.1 : 0.5}
@ -71,7 +71,7 @@ const ParamCFGScale = () => {
/>
</IAIInformationalPopover>
) : (
<IAIInformationalPopover details="paramCFGScale">
<IAIInformationalPopover feature="paramCFGScale">
<IAINumberInput
label={t('parameters.cfgScale')}
step={0.5}

View File

@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISlider from 'common/components/IAISlider';
import { setIterations } from 'features/parameters/store/generationSlice';
@ -61,7 +61,7 @@ const ParamIterations = ({ asSlider }: Props) => {
}, [dispatch, initial]);
return asSlider || shouldUseSliders ? (
<IAIInformationalPopover details="paramIterations">
<IAIInformationalPopover feature="paramIterations">
<IAISlider
label={t('parameters.iterations')}
step={step}
@ -77,7 +77,7 @@ const ParamIterations = ({ asSlider }: Props) => {
/>
</IAIInformationalPopover>
) : (
<IAIInformationalPopover details="paramIterations">
<IAIInformationalPopover feature="paramIterations">
<IAINumberInput
label={t('parameters.iterations')}
step={step}

View File

@ -1,7 +1,7 @@
import { Box, FormControl, useDisclosure } from '@chakra-ui/react';
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAITextarea from 'common/components/IAITextarea';
import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton';
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
@ -76,15 +76,15 @@ const ParamNegativeConditioning = () => {
const isEmbeddingEnabled = useFeatureStatus('embedding').isFeatureEnabled;
return (
<IAIInformationalPopover
placement="right"
details="paramNegativeConditioning"
>
<FormControl>
<ParamEmbeddingPopover
isOpen={isOpen}
onClose={onClose}
onSelect={handleSelectEmbedding}
<FormControl>
<ParamEmbeddingPopover
isOpen={isOpen}
onClose={onClose}
onSelect={handleSelectEmbedding}
>
<IAIInformationalPopover
feature="paramNegativeConditioning"
placement="right"
>
<IAITextarea
id="negativePrompt"
@ -98,20 +98,20 @@ const ParamNegativeConditioning = () => {
minH={16}
{...(isEmbeddingEnabled && { onKeyDown: handleKeyDown })}
/>
</ParamEmbeddingPopover>
{!isOpen && isEmbeddingEnabled && (
<Box
sx={{
position: 'absolute',
top: 0,
insetInlineEnd: 0,
}}
>
<AddEmbeddingButton onClick={onOpen} />
</Box>
)}
</FormControl>
</IAIInformationalPopover>
</IAIInformationalPopover>
</ParamEmbeddingPopover>
{!isOpen && isEmbeddingEnabled && (
<Box
sx={{
position: 'absolute',
top: 0,
insetInlineEnd: 0,
}}
>
<AddEmbeddingButton onClick={onOpen} />
</Box>
)}
</FormControl>
);
};

View File

@ -2,6 +2,7 @@ import { Box, FormControl, useDisclosure } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAITextarea from 'common/components/IAITextarea';
import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton';
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
@ -12,7 +13,6 @@ import { flushSync } from 'react-dom';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
import IAIInformationalPopover from '../../../../../common/components/IAIInformationalPopover';
const promptInputSelector = createSelector(
[stateSelector],
@ -104,15 +104,15 @@ const ParamPositiveConditioning = () => {
return (
<Box position="relative">
<IAIInformationalPopover
placement="right"
details="paramPositiveConditioning"
>
<FormControl>
<ParamEmbeddingPopover
isOpen={isOpen}
onClose={onClose}
onSelect={handleSelectEmbedding}
<FormControl>
<ParamEmbeddingPopover
isOpen={isOpen}
onClose={onClose}
onSelect={handleSelectEmbedding}
>
<IAIInformationalPopover
feature="paramPositiveConditioning"
placement="right"
>
<IAITextarea
id="prompt"
@ -125,9 +125,9 @@ const ParamPositiveConditioning = () => {
resize="vertical"
minH={32}
/>
</ParamEmbeddingPopover>
</FormControl>
</IAIInformationalPopover>
</IAIInformationalPopover>
</ParamEmbeddingPopover>
</FormControl>
{!isOpen && isEmbeddingEnabled && (
<Box
sx={{

View File

@ -1,7 +1,7 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import { generationSelector } from 'features/parameters/store/generationSelectors';
import { setScheduler } from 'features/parameters/store/generationSlice';
@ -52,7 +52,7 @@ const ParamScheduler = () => {
);
return (
<IAIInformationalPopover details="paramScheduler">
<IAIInformationalPopover feature="paramScheduler">
<IAIMantineSearchableSelect
label={t('parameters.scheduler')}
value={scheduler}

View File

@ -1,7 +1,9 @@
import { Box, Flex, Spacer, Text } from '@chakra-ui/react';
import { Flex, FormControl, FormLabel, Spacer } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIIconButton from 'common/components/IAIIconButton';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import { generationSelector } from 'features/parameters/store/generationSelectors';
import {
setAspectRatio,
@ -16,8 +18,6 @@ import { activeTabNameSelector } from '../../../../ui/store/uiSelectors';
import ParamAspectRatio, { mappedAspectRatios } from './ParamAspectRatio';
import ParamHeight from './ParamHeight';
import ParamWidth from './ParamWidth';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
const sizeOptsSelector = createSelector(
[generationSelector, activeTabNameSelector],
@ -83,47 +83,35 @@ export default function ParamSize() {
},
}}
>
<Flex alignItems="center" gap={2}>
<Box width="full">
<IAIInformationalPopover details="paramRatio">
<Text
sx={{
fontSize: 'sm',
color: 'base.700',
_dark: {
color: 'base.300',
},
}}
>
{t('parameters.aspectRatio')}
</Text>
</IAIInformationalPopover>
</Box>
<Spacer />
<ParamAspectRatio />
<IAIIconButton
tooltip={t('ui.swapSizes')}
aria-label={t('ui.swapSizes')}
size="sm"
icon={<MdOutlineSwapVert />}
fontSize={20}
isDisabled={
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
}
onClick={handleToggleSize}
/>
<IAIIconButton
tooltip={t('ui.lockRatio')}
aria-label={t('ui.lockRatio')}
size="sm"
icon={<FaLock />}
isChecked={shouldLockAspectRatio}
isDisabled={
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
}
onClick={handleLockRatio}
/>
</Flex>
<IAIInformationalPopover feature="paramRatio">
<FormControl as={Flex} flexDir="row" alignItems="center" gap={2}>
<FormLabel>{t('parameters.aspectRatio')}</FormLabel>
<Spacer />
<ParamAspectRatio />
<IAIIconButton
tooltip={t('ui.swapSizes')}
aria-label={t('ui.swapSizes')}
size="sm"
icon={<MdOutlineSwapVert />}
fontSize={20}
isDisabled={
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
}
onClick={handleToggleSize}
/>
<IAIIconButton
tooltip={t('ui.lockRatio')}
aria-label={t('ui.lockRatio')}
size="sm"
icon={<FaLock />}
isChecked={shouldLockAspectRatio}
isDisabled={
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
}
onClick={handleLockRatio}
/>
</FormControl>
</IAIInformationalPopover>
<Flex gap={2} alignItems="center">
<Flex gap={2} flexDirection="column" width="full">
<ParamWidth

View File

@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISlider from 'common/components/IAISlider';
@ -57,7 +57,7 @@ const ParamSteps = () => {
}, [dispatch]);
return shouldUseSliders ? (
<IAIInformationalPopover details="paramSteps">
<IAIInformationalPopover feature="paramSteps">
<IAISlider
label={t('parameters.steps')}
min={min}
@ -73,7 +73,7 @@ const ParamSteps = () => {
/>
</IAIInformationalPopover>
) : (
<IAIInformationalPopover details="paramSteps">
<IAIInformationalPopover feature="paramSteps">
<IAINumberInput
label={t('parameters.steps')}
min={min}

View File

@ -7,7 +7,7 @@ import { setImg2imgStrength } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import SubParametersWrapper from '../SubParametersWrapper';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
const selector = createSelector(
[stateSelector],
@ -46,8 +46,8 @@ const ImageToImageStrength = () => {
}, [dispatch, initial]);
return (
<SubParametersWrapper>
<IAIInformationalPopover details="paramDenoisingStrength">
<IAIInformationalPopover feature="paramDenoisingStrength">
<SubParametersWrapper>
<IAISlider
label={`${t('parameters.denoisingStrength')}`}
step={step}
@ -62,8 +62,8 @@ const ImageToImageStrength = () => {
withReset
sliderNumberInputProps={{ max: inputMax }}
/>
</IAIInformationalPopover>
</SubParametersWrapper>
</SubParametersWrapper>
</IAIInformationalPopover>
);
};

View File

@ -21,7 +21,7 @@ import {
useGetOnnxModelsQuery,
} from 'services/api/endpoints/models';
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
const selector = createSelector(
stateSelector,
@ -120,7 +120,7 @@ const ParamMainModelSelect = () => {
/>
) : (
<Flex w="100%" alignItems="center" gap={3}>
<IAIInformationalPopover details="paramModel" placement="bottom">
<IAIInformationalPopover feature="paramModel">
<IAIMantineSearchableSelect
tooltip={selectedModel?.description}
label={t('modelManager.model')}
@ -136,7 +136,7 @@ const ParamMainModelSelect = () => {
/>
</IAIInformationalPopover>
{isSyncModelEnabled && (
<Box mt={7}>
<Box mt={6}>
<SyncModelsButton iconMode />
</Box>
)}

View File

@ -1,5 +1,5 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAISwitch from 'common/components/IAISwitch';
import { shouldUseCpuNoiseChanged } from 'features/parameters/store/generationSlice';
import { ChangeEvent, useCallback } from 'react';
@ -20,7 +20,7 @@ export const ParamCpuNoiseToggle = () => {
);
return (
<IAIInformationalPopover details="noiseUseCPU">
<IAIInformationalPopover feature="noiseUseCPU">
<IAISwitch
label={t('parameters.useCpuNoise')}
isChecked={shouldUseCpuNoise}

View File

@ -3,11 +3,11 @@ import { memo } from 'react';
import ParamSeed from './ParamSeed';
import ParamSeedShuffle from './ParamSeedShuffle';
import ParamSeedRandomize from './ParamSeedRandomize';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
const ParamSeedFull = () => {
return (
<IAIInformationalPopover details="paramSeed">
<IAIInformationalPopover feature="paramSeed">
<Flex sx={{ gap: 3, alignItems: 'flex-end' }}>
<ParamSeed />
<ParamSeedShuffle />

View File

@ -1,40 +1,28 @@
import { Flex, Text } from '@chakra-ui/react';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import { Flex, Text, forwardRef } from '@chakra-ui/react';
import { ReactNode, memo } from 'react';
type SubParameterWrapperProps = {
children: ReactNode | ReactNode[];
children: ReactNode;
label?: string;
headerInfoPopover?: string;
};
const SubParametersWrapper = (props: SubParameterWrapperProps) => (
<Flex
sx={{
flexDir: 'column',
gap: 2,
bg: 'base.100',
px: 4,
pt: 2,
pb: 4,
borderRadius: 'base',
_dark: {
bg: 'base.750',
},
}}
>
{props.headerInfoPopover && props.label && (
<IAIInformationalPopover details={props.headerInfoPopover}>
<Text
fontSize="sm"
fontWeight="bold"
sx={{ color: 'base.600', _dark: { color: 'base.300' } }}
>
{props.label}
</Text>
</IAIInformationalPopover>
)}
{!props.headerInfoPopover && props.label && (
const SubParametersWrapper = forwardRef(
(props: SubParameterWrapperProps, ref) => (
<Flex
ref={ref}
sx={{
flexDir: 'column',
gap: 2,
bg: 'base.100',
px: 4,
pt: 2,
pb: 4,
borderRadius: 'base',
_dark: {
bg: 'base.750',
},
}}
>
<Text
fontSize="sm"
fontWeight="bold"
@ -42,9 +30,9 @@ const SubParametersWrapper = (props: SubParameterWrapperProps) => (
>
{props.label}
</Text>
)}
{props.children}
</Flex>
{props.children}
</Flex>
)
);
SubParametersWrapper.displayName = 'SubSettingsWrapper';

View File

@ -15,7 +15,7 @@ import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectI
import { vaeSelected } from 'features/parameters/store/generationSlice';
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
import { modelIdToVAEModelParam } from 'features/parameters/util/modelIdToVAEModelParam';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
const selector = createSelector(
stateSelector,
@ -94,7 +94,7 @@ const ParamVAEModelSelect = () => {
);
return (
<IAIInformationalPopover details="paramVAE">
<IAIInformationalPopover feature="paramVAE">
<IAIMantineSearchableSelect
itemComponent={IAIMantineSelectItemWithTooltip}
tooltip={selectedVaeModel?.description}

View File

@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { vaePrecisionChanged } from 'features/parameters/store/generationSlice';
import { PrecisionParam } from 'features/parameters/types/parameterSchemas';
@ -35,7 +35,7 @@ const ParamVAEModelSelect = () => {
);
return (
<IAIInformationalPopover details="paramVAEPrecision">
<IAIInformationalPopover feature="paramVAEPrecision">
<IAIMantineSelect
label="VAE Precision"
value={vaePrecision}

View File

@ -7,7 +7,7 @@ import SubParametersWrapper from 'features/parameters/components/Parameters/SubP
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { setSDXLImg2ImgDenoisingStrength } from '../store/sdxlSlice';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
const selector = createSelector(
[stateSelector],
@ -36,8 +36,8 @@ const ParamSDXLImg2ImgDenoisingStrength = () => {
}, [dispatch]);
return (
<SubParametersWrapper>
<IAIInformationalPopover details="paramDenoisingStrength">
<IAIInformationalPopover feature="paramDenoisingStrength">
<SubParametersWrapper>
<IAISlider
label={t('sdxl.denoisingStrength')}
step={0.01}
@ -51,8 +51,8 @@ const ParamSDXLImg2ImgDenoisingStrength = () => {
withSliderMarks
withReset
/>
</IAIInformationalPopover>
</SubParametersWrapper>
</SubParametersWrapper>
</IAIInformationalPopover>
);
};

File diff suppressed because one or more lines are too long

View File

@ -152,6 +152,8 @@ export type SaveImageInvocation = s['SaveImageInvocation'];
export type ControlNetInvocation = s['ControlNetInvocation'];
export type IPAdapterInvocation = s['IPAdapterInvocation'];
export type CannyImageProcessorInvocation = s['CannyImageProcessorInvocation'];
export type ColorMapImageProcessorInvocation =
s['ColorMapImageProcessorInvocation'];
export type ContentShuffleImageProcessorInvocation =
s['ContentShuffleImageProcessorInvocation'];
export type HedImageProcessorInvocation = s['HedImageProcessorInvocation'];

View File

@ -31,14 +31,14 @@ const invokeAIContent = defineStyle((props) => {
const informationalContent = defineStyle((props) => {
return {
[$arrowBg.variable]: mode('colors.base.100', 'colors.base.600')(props),
[$popperBg.variable]: mode('colors.base.100', 'colors.base.600')(props),
[$arrowBg.variable]: mode('colors.base.100', 'colors.base.700')(props),
[$popperBg.variable]: mode('colors.base.100', 'colors.base.700')(props),
[$arrowShadowColor.variable]: mode(
'colors.base.400',
'colors.base.400'
)(props),
p: 4,
bg: mode('base.100', 'base.600')(props),
bg: mode('base.100', 'base.700')(props),
border: 'none',
shadow: 'dark-lg',
};
@ -46,6 +46,7 @@ const informationalContent = defineStyle((props) => {
const invokeAI = definePartsStyle((props) => ({
content: invokeAIContent(props),
body: { padding: 0 },
}));
const informational = definePartsStyle((props) => ({

View File

@ -76,6 +76,7 @@ export const theme: ThemeOverride = {
direction: 'ltr',
fonts: {
body: `'Inter Variable', sans-serif`,
heading: `'Inter Variable', sans-serif`,
},
shadows: {
light: {