mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat: add resolution presets and imageConfig support for Gemini 3 models
Add combined resolution preset selector for external models that maps aspect ratio + image size to fixed dimensions. Gemini 3 Pro and 3.1 Flash now send imageConfig (aspectRatio + imageSize) via generationConfig instead of text-based aspect ratio hints used by Gemini 2.5 Flash. Backend: ExternalResolutionPreset model, resolution_presets capability field, image_size on ExternalGenerationRequest, and Gemini provider imageConfig logic. Frontend: ExternalSettingsAccordion with combo resolution select, dimension slider disabling for fixed-size models, and panel schema constraint wiring for Steps/Guidance/Seed controls.
This commit is contained in:
@@ -39,6 +39,7 @@ class BaseExternalImageGenerationInvocation(BaseInvocation, WithMetadata, WithBo
|
||||
num_images: int = InputField(default=1, gt=0, description="Number of images to generate")
|
||||
width: int = InputField(default=1024, gt=0, description=FieldDescriptions.width)
|
||||
height: int = InputField(default=1024, gt=0, description=FieldDescriptions.height)
|
||||
image_size: str | None = InputField(default=None, description="Image size preset (e.g. 1K, 2K, 4K)")
|
||||
steps: int | None = InputField(default=None, gt=0, description=FieldDescriptions.steps)
|
||||
guidance: float | None = InputField(default=None, ge=0, description="Guidance strength")
|
||||
init_image: ImageField | None = InputField(default=None, description="Init image for img2img/inpaint")
|
||||
@@ -91,6 +92,7 @@ class BaseExternalImageGenerationInvocation(BaseInvocation, WithMetadata, WithBo
|
||||
num_images=self.num_images,
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
image_size=self.image_size,
|
||||
steps=self.steps,
|
||||
guidance=self.guidance,
|
||||
init_image=init_image,
|
||||
|
||||
@@ -25,6 +25,7 @@ class ExternalGenerationRequest:
|
||||
num_images: int
|
||||
width: int
|
||||
height: int
|
||||
image_size: str | None
|
||||
steps: int | None
|
||||
guidance: float | None
|
||||
init_image: PILImageType | None
|
||||
|
||||
@@ -164,6 +164,7 @@ class ExternalGenerationService(ExternalGenerationServiceBase):
|
||||
num_images=request.num_images,
|
||||
width=request.width,
|
||||
height=request.height,
|
||||
image_size=request.image_size,
|
||||
steps=request.steps,
|
||||
guidance=request.guidance,
|
||||
init_image=request.init_image,
|
||||
@@ -234,6 +235,7 @@ class ExternalGenerationService(ExternalGenerationServiceBase):
|
||||
num_images=request.num_images,
|
||||
width=width,
|
||||
height=height,
|
||||
image_size=request.image_size,
|
||||
steps=request.steps,
|
||||
guidance=request.guidance,
|
||||
init_image=_resize_image(request.init_image, width, height, "RGB"),
|
||||
|
||||
@@ -73,6 +73,15 @@ class GeminiProvider(ExternalProvider):
|
||||
request.height,
|
||||
request.model.capabilities.allowed_aspect_ratios,
|
||||
)
|
||||
uses_image_config = request.model.capabilities.resolution_presets is not None
|
||||
if uses_image_config:
|
||||
image_config: dict[str, str] = {}
|
||||
if aspect_ratio is not None:
|
||||
image_config["aspectRatio"] = aspect_ratio
|
||||
if request.image_size is not None:
|
||||
image_config["imageSize"] = request.image_size
|
||||
if image_config:
|
||||
generation_config["imageConfig"] = image_config
|
||||
system_instruction = self._SYSTEM_INSTRUCTION
|
||||
if request.init_image is not None:
|
||||
system_instruction = (
|
||||
@@ -80,7 +89,7 @@ class GeminiProvider(ExternalProvider):
|
||||
"Treat the prompt as an edit instruction and modify the image accordingly. "
|
||||
"Do not return the original image unchanged."
|
||||
)
|
||||
if aspect_ratio is not None:
|
||||
if not uses_image_config and aspect_ratio is not None:
|
||||
system_instruction = f"{system_instruction} Use an aspect ratio of {aspect_ratio}."
|
||||
|
||||
payload: dict[str, object] = {
|
||||
|
||||
@@ -19,6 +19,16 @@ class ExternalImageSize(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class ExternalResolutionPreset(BaseModel):
|
||||
label: str = Field(min_length=1, description="Display label, e.g. '1:1 (1K)'")
|
||||
aspect_ratio: str = Field(min_length=1, description="Aspect ratio string, e.g. '1:1'")
|
||||
image_size: str = Field(min_length=1, description="Image size preset, e.g. '1K'")
|
||||
width: int = Field(gt=0)
|
||||
height: int = Field(gt=0)
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class ExternalModelCapabilities(BaseModel):
|
||||
modes: list[ExternalGenerationMode] = Field(default_factory=lambda: ["txt2img"])
|
||||
supports_reference_images: bool = Field(default=False)
|
||||
@@ -30,6 +40,7 @@ class ExternalModelCapabilities(BaseModel):
|
||||
max_image_size: ExternalImageSize | None = Field(default=None)
|
||||
allowed_aspect_ratios: list[str] | None = Field(default=None)
|
||||
aspect_ratio_sizes: dict[str, ExternalImageSize] | None = Field(default=None)
|
||||
resolution_presets: list[ExternalResolutionPreset] | None = Field(default=None)
|
||||
max_reference_images: int | None = Field(default=None, gt=0)
|
||||
mask_format: ExternalMaskFormat = Field(default="none")
|
||||
input_image_required_for: list[ExternalGenerationMode] | None = Field(default=None)
|
||||
|
||||
@@ -7,6 +7,7 @@ from invokeai.backend.model_manager.configs.external_api import (
|
||||
ExternalImageSize,
|
||||
ExternalModelCapabilities,
|
||||
ExternalModelPanelSchema,
|
||||
ExternalResolutionPreset,
|
||||
)
|
||||
from invokeai.backend.model_manager.taxonomy import BaseModelType, ModelFormat, ModelType
|
||||
|
||||
@@ -890,6 +891,45 @@ GEMINI_3_IMAGE_ALLOWED_ASPECT_RATIOS = [
|
||||
]
|
||||
GEMINI_3_IMAGE_MAX_SIZE = ExternalImageSize(width=4096, height=4096)
|
||||
|
||||
|
||||
def _gemini_3_resolution_presets(
|
||||
image_sizes: list[str],
|
||||
aspect_ratios: list[str] | None = None,
|
||||
) -> list[ExternalResolutionPreset]:
|
||||
"""Build resolution presets for Gemini 3 models.
|
||||
|
||||
Each preset combines an aspect ratio with an image size preset (512/1K/2K/4K).
|
||||
Pixel dimensions are approximations based on the preset name (longest side).
|
||||
"""
|
||||
if aspect_ratios is None:
|
||||
aspect_ratios = GEMINI_3_IMAGE_ALLOWED_ASPECT_RATIOS
|
||||
base_pixels = {"512": 512, "1K": 1024, "2K": 2048, "4K": 4096}
|
||||
presets: list[ExternalResolutionPreset] = []
|
||||
for image_size in image_sizes:
|
||||
base = base_pixels[image_size]
|
||||
for ratio_str in aspect_ratios:
|
||||
w_part, h_part = (int(x) for x in ratio_str.split(":"))
|
||||
if w_part >= h_part:
|
||||
w = base
|
||||
h = max(1, round(base * h_part / w_part))
|
||||
else:
|
||||
h = base
|
||||
w = max(1, round(base * w_part / h_part))
|
||||
presets.append(
|
||||
ExternalResolutionPreset(
|
||||
label=f"{ratio_str} ({image_size}) — {w}\u00d7{h}",
|
||||
aspect_ratio=ratio_str,
|
||||
image_size=image_size,
|
||||
width=w,
|
||||
height=h,
|
||||
)
|
||||
)
|
||||
return presets
|
||||
|
||||
|
||||
GEMINI_3_PRO_RESOLUTION_PRESETS = _gemini_3_resolution_presets(["1K", "2K", "4K"])
|
||||
GEMINI_3_1_FLASH_RESOLUTION_PRESETS = _gemini_3_resolution_presets(["512", "1K", "2K", "4K"])
|
||||
|
||||
gemini_flash_image = StarterModel(
|
||||
name="Gemini 2.5 Flash Image",
|
||||
base=BaseModelType.External,
|
||||
@@ -936,7 +976,7 @@ gemini_pro_image_preview = StarterModel(
|
||||
name="Gemini 3 Pro Image Preview",
|
||||
base=BaseModelType.External,
|
||||
source="external://gemini/gemini-3-pro-image-preview",
|
||||
description="Google Gemini 3 Pro image generation preview model (external API). Supports up to 14 reference images, including up to 6 object references and up to 5 character references. Supports 512/1K/2K/4K resolution presets. Requires a configured Gemini API key and may incur provider usage costs.",
|
||||
description="Google Gemini 3 Pro image generation preview model (external API). Supports up to 14 reference images, including up to 6 object references and up to 5 character references. Supports 1K/2K/4K resolution presets. Requires a configured Gemini API key and may incur provider usage costs.",
|
||||
type=ModelType.ExternalImageGenerator,
|
||||
format=ModelFormat.ExternalApi,
|
||||
capabilities=ExternalModelCapabilities(
|
||||
@@ -949,6 +989,7 @@ gemini_pro_image_preview = StarterModel(
|
||||
max_images_per_request=1,
|
||||
max_image_size=GEMINI_3_IMAGE_MAX_SIZE,
|
||||
allowed_aspect_ratios=GEMINI_3_IMAGE_ALLOWED_ASPECT_RATIOS,
|
||||
resolution_presets=GEMINI_3_PRO_RESOLUTION_PRESETS,
|
||||
),
|
||||
default_settings=ExternalApiModelDefaultSettings(width=1024, height=1024, num_images=1),
|
||||
panel_schema=ExternalModelPanelSchema(prompts=[{"name": "reference_images"}], image=[{"name": "dimensions"}]),
|
||||
@@ -970,6 +1011,7 @@ gemini_3_1_flash_image_preview = StarterModel(
|
||||
max_images_per_request=1,
|
||||
max_image_size=GEMINI_3_IMAGE_MAX_SIZE,
|
||||
allowed_aspect_ratios=GEMINI_3_IMAGE_ALLOWED_ASPECT_RATIOS,
|
||||
resolution_presets=GEMINI_3_1_FLASH_RESOLUTION_PRESETS,
|
||||
),
|
||||
default_settings=ExternalApiModelDefaultSettings(width=1024, height=1024, num_images=1),
|
||||
panel_schema=ExternalModelPanelSchema(prompts=[{"name": "reference_images"}], image=[{"name": "dimensions"}]),
|
||||
|
||||
@@ -1601,6 +1601,7 @@
|
||||
"boxBlur": "Box Blur",
|
||||
"staged": "Staged",
|
||||
"resolution": "Resolution",
|
||||
"imageSize": "Image Size",
|
||||
"modelDisabledForTrial": "Generating with {{modelName}} is not available on trial accounts. Visit your <LinkComponent>account settings</LinkComponent> to upgrade."
|
||||
},
|
||||
"dynamicPrompts": {
|
||||
|
||||
@@ -42,7 +42,7 @@ import type {
|
||||
ParameterT5EncoderModel,
|
||||
ParameterVAEModel,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
import { hasExternalPanelControl } from 'features/parameters/util/externalPanelSchema';
|
||||
import { getExternalPanelControl, hasExternalPanelControl } from 'features/parameters/util/externalPanelSchema';
|
||||
import { getGridSize, getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import { modelConfigsAdapterSelectors, selectModelConfigsQuery } from 'services/api/endpoints/models';
|
||||
import type { AnyModelConfigWithExternal } from 'services/api/types';
|
||||
@@ -366,21 +366,30 @@ const slice = createSlice({
|
||||
aspectRatioLockToggled: (state) => {
|
||||
state.dimensions.aspectRatio.isLocked = !state.dimensions.aspectRatio.isLocked;
|
||||
},
|
||||
aspectRatioIdChanged: (state, action: PayloadAction<{ id: AspectRatioID }>) => {
|
||||
const { id } = action.payload;
|
||||
aspectRatioIdChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ id: AspectRatioID; fixedSize?: { width: number; height: number } }>
|
||||
) => {
|
||||
const { id, fixedSize } = action.payload;
|
||||
state.dimensions.aspectRatio.id = id;
|
||||
if (id === 'Free') {
|
||||
state.dimensions.aspectRatio.isLocked = false;
|
||||
} else {
|
||||
state.dimensions.aspectRatio.isLocked = true;
|
||||
state.dimensions.aspectRatio.value = ASPECT_RATIO_MAP[id].ratio;
|
||||
const { width, height } = calculateNewSize(
|
||||
state.dimensions.aspectRatio.value,
|
||||
state.dimensions.width * state.dimensions.height,
|
||||
state.model?.base as BaseModelType | undefined
|
||||
);
|
||||
state.dimensions.width = width;
|
||||
state.dimensions.height = height;
|
||||
if (fixedSize) {
|
||||
state.dimensions.aspectRatio.value = fixedSize.width / fixedSize.height;
|
||||
state.dimensions.width = fixedSize.width;
|
||||
state.dimensions.height = fixedSize.height;
|
||||
} else {
|
||||
state.dimensions.aspectRatio.value = ASPECT_RATIO_MAP[id].ratio;
|
||||
const { width, height } = calculateNewSize(
|
||||
state.dimensions.aspectRatio.value,
|
||||
state.dimensions.width * state.dimensions.height,
|
||||
state.model?.base as BaseModelType | undefined
|
||||
);
|
||||
state.dimensions.width = width;
|
||||
state.dimensions.height = height;
|
||||
}
|
||||
}
|
||||
},
|
||||
dimensionsSwapped: (state) => {
|
||||
@@ -436,6 +445,21 @@ const slice = createSlice({
|
||||
state.dimensions.height = bboxDims.height;
|
||||
}
|
||||
},
|
||||
imageSizeChanged: (state, action: PayloadAction<string | null>) => {
|
||||
state.imageSize = action.payload;
|
||||
},
|
||||
resolutionPresetSelected: (
|
||||
state,
|
||||
action: PayloadAction<{ imageSize: string; aspectRatio: string; width: number; height: number }>
|
||||
) => {
|
||||
const { imageSize, aspectRatio, width, height } = action.payload;
|
||||
state.imageSize = imageSize;
|
||||
state.dimensions.width = width;
|
||||
state.dimensions.height = height;
|
||||
state.dimensions.aspectRatio.id = aspectRatio as AspectRatioID;
|
||||
state.dimensions.aspectRatio.value = width / height;
|
||||
state.dimensions.aspectRatio.isLocked = true;
|
||||
},
|
||||
paramsReset: (state) => resetState(state),
|
||||
},
|
||||
extraReducers(builder) {
|
||||
@@ -567,6 +591,7 @@ export const {
|
||||
sizeOptimized,
|
||||
syncedToOptimalDimension,
|
||||
|
||||
resolutionPresetSelected,
|
||||
paramsReset,
|
||||
} = slice.actions;
|
||||
|
||||
@@ -737,6 +762,24 @@ export const selectModelSupportsDimensions = createSelector(selectModel, selectM
|
||||
}
|
||||
return true;
|
||||
});
|
||||
export const selectStepsControl = createSelector(selectModelConfig, (modelConfig) => {
|
||||
if (modelConfig && isExternalApiModelConfig(modelConfig)) {
|
||||
return getExternalPanelControl(modelConfig, 'generation', 'steps');
|
||||
}
|
||||
return null;
|
||||
});
|
||||
export const selectGuidanceControl = createSelector(selectModelConfig, (modelConfig) => {
|
||||
if (modelConfig && isExternalApiModelConfig(modelConfig)) {
|
||||
return getExternalPanelControl(modelConfig, 'generation', 'guidance');
|
||||
}
|
||||
return null;
|
||||
});
|
||||
export const selectSeedControl = createSelector(selectModelConfig, (modelConfig) => {
|
||||
if (modelConfig && isExternalApiModelConfig(modelConfig)) {
|
||||
return getExternalPanelControl(modelConfig, 'image', 'seed');
|
||||
}
|
||||
return null;
|
||||
});
|
||||
export const selectScheduler = createParamsSelector((params) => params.scheduler);
|
||||
export const selectFluxScheduler = createParamsSelector((params) => params.fluxScheduler);
|
||||
export const selectFluxDypePreset = createParamsSelector((params) => params.fluxDypePreset);
|
||||
@@ -786,6 +829,24 @@ export const selectAllowedAspectRatioIDs = createSelector(selectModelConfig, (mo
|
||||
const allowed = modelConfig.capabilities.allowed_aspect_ratios;
|
||||
return allowed?.length ? allowed : null;
|
||||
});
|
||||
export const selectAspectRatioSizes = createSelector(selectModelConfig, (modelConfig) => {
|
||||
if (!modelConfig || !isExternalApiModelConfig(modelConfig)) {
|
||||
return null;
|
||||
}
|
||||
return modelConfig.capabilities.aspect_ratio_sizes ?? null;
|
||||
});
|
||||
export const selectResolutionPresets = createSelector(selectModelConfig, (modelConfig) => {
|
||||
if (!modelConfig || !isExternalApiModelConfig(modelConfig)) {
|
||||
return null;
|
||||
}
|
||||
return modelConfig.capabilities.resolution_presets ?? null;
|
||||
});
|
||||
export const selectHasFixedDimensionSizes = createSelector(
|
||||
selectAspectRatioSizes,
|
||||
selectResolutionPresets,
|
||||
(sizes, presets) => sizes !== null || (presets !== null && presets.length > 0)
|
||||
);
|
||||
export const selectImageSize = createParamsSelector((params) => params.imageSize);
|
||||
|
||||
export const selectMainModelConfig = createSelector(selectModelConfig, (modelConfig) => {
|
||||
if (!modelConfig) {
|
||||
|
||||
@@ -757,6 +757,7 @@ export const zParamsState = z.object({
|
||||
zImageSeedVarianceEnabled: z.boolean(),
|
||||
zImageSeedVarianceStrength: z.number().min(0).max(2),
|
||||
zImageSeedVarianceRandomizePercent: z.number().min(1).max(100),
|
||||
imageSize: z.string().nullable().default(null),
|
||||
dimensions: zDimensionsState,
|
||||
});
|
||||
export type ParamsState = z.infer<typeof zParamsState>;
|
||||
@@ -820,6 +821,7 @@ export const getInitialParamsState = (): ParamsState => ({
|
||||
zImageSeedVarianceEnabled: false,
|
||||
zImageSeedVarianceStrength: 0.1,
|
||||
zImageSeedVarianceRandomizePercent: 50,
|
||||
imageSize: null,
|
||||
dimensions: {
|
||||
width: 512,
|
||||
height: 512,
|
||||
|
||||
@@ -74,6 +74,7 @@ export const buildExternalGraph = async (arg: GraphBuilderArg): Promise<GraphBui
|
||||
negative_prompt: supportsNegativePrompt ? prompts.negative : null,
|
||||
steps: supportsSteps ? params.steps : null,
|
||||
guidance: supportsGuidance ? params.guidance : null,
|
||||
image_size: params.imageSize ?? null,
|
||||
num_images: 1,
|
||||
};
|
||||
g.addNode(externalNode as AnyInvocation);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { selectGuidance, setGuidance } from 'features/controlLayers/store/paramsSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { selectGuidance, selectGuidanceControl, setGuidance } from 'features/controlLayers/store/paramsSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const CONSTRAINTS = {
|
||||
@@ -23,10 +23,22 @@ export const MARKS = [
|
||||
|
||||
const ParamGuidance = () => {
|
||||
const guidance = useAppSelector(selectGuidance);
|
||||
const externalControl = useAppSelector(selectGuidanceControl);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const onChange = useCallback((v: number) => dispatch(setGuidance(v)), [dispatch]);
|
||||
|
||||
const sliderMin = externalControl?.slider_min ?? CONSTRAINTS.sliderMin;
|
||||
const sliderMax = externalControl?.slider_max ?? CONSTRAINTS.sliderMax;
|
||||
const numberInputMin = externalControl?.number_input_min ?? CONSTRAINTS.numberInputMin;
|
||||
const numberInputMax = externalControl?.number_input_max ?? CONSTRAINTS.numberInputMax;
|
||||
const fineStep = externalControl?.fine_step ?? CONSTRAINTS.fineStep;
|
||||
const coarseStep = externalControl?.coarse_step ?? CONSTRAINTS.coarseStep;
|
||||
const marks = useMemo(
|
||||
() => externalControl?.marks ?? [sliderMin, Math.floor(sliderMax - (sliderMax - sliderMin) / 2), sliderMax],
|
||||
[externalControl?.marks, sliderMin, sliderMax]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="paramGuidance">
|
||||
@@ -35,20 +47,20 @@ const ParamGuidance = () => {
|
||||
<CompositeSlider
|
||||
value={guidance}
|
||||
defaultValue={CONSTRAINTS.initial}
|
||||
min={CONSTRAINTS.sliderMin}
|
||||
max={CONSTRAINTS.sliderMax}
|
||||
step={CONSTRAINTS.coarseStep}
|
||||
fineStep={CONSTRAINTS.fineStep}
|
||||
min={sliderMin}
|
||||
max={sliderMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
marks={MARKS}
|
||||
marks={marks}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={guidance}
|
||||
defaultValue={CONSTRAINTS.initial}
|
||||
min={CONSTRAINTS.numberInputMin}
|
||||
max={CONSTRAINTS.numberInputMax}
|
||||
step={CONSTRAINTS.coarseStep}
|
||||
fineStep={CONSTRAINTS.fineStep}
|
||||
min={numberInputMin}
|
||||
max={numberInputMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { selectSteps, setSteps } from 'features/controlLayers/store/paramsSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { selectSteps, selectStepsControl, setSteps } from 'features/controlLayers/store/paramsSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const CONSTRAINTS = {
|
||||
@@ -19,6 +19,7 @@ export const MARKS = [CONSTRAINTS.sliderMin, Math.floor(CONSTRAINTS.sliderMax /
|
||||
|
||||
const ParamSteps = () => {
|
||||
const steps = useAppSelector(selectSteps);
|
||||
const externalControl = useAppSelector(selectStepsControl);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const onChange = useCallback(
|
||||
@@ -28,6 +29,17 @@ const ParamSteps = () => {
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const sliderMin = externalControl?.slider_min ?? CONSTRAINTS.sliderMin;
|
||||
const sliderMax = externalControl?.slider_max ?? CONSTRAINTS.sliderMax;
|
||||
const numberInputMin = externalControl?.number_input_min ?? CONSTRAINTS.numberInputMin;
|
||||
const numberInputMax = externalControl?.number_input_max ?? CONSTRAINTS.numberInputMax;
|
||||
const fineStep = externalControl?.fine_step ?? CONSTRAINTS.fineStep;
|
||||
const coarseStep = externalControl?.coarse_step ?? CONSTRAINTS.coarseStep;
|
||||
const marks = useMemo(
|
||||
() => externalControl?.marks ?? [sliderMin, Math.floor(sliderMax / 2), sliderMax],
|
||||
[externalControl?.marks, sliderMin, sliderMax]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="paramSteps">
|
||||
@@ -36,20 +48,20 @@ const ParamSteps = () => {
|
||||
<CompositeSlider
|
||||
value={steps}
|
||||
defaultValue={CONSTRAINTS.initial}
|
||||
min={CONSTRAINTS.sliderMin}
|
||||
max={CONSTRAINTS.sliderMax}
|
||||
step={CONSTRAINTS.coarseStep}
|
||||
fineStep={CONSTRAINTS.fineStep}
|
||||
min={sliderMin}
|
||||
max={sliderMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
marks={MARKS}
|
||||
marks={marks}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={steps}
|
||||
defaultValue={CONSTRAINTS.initial}
|
||||
min={CONSTRAINTS.numberInputMin}
|
||||
max={CONSTRAINTS.numberInputMax}
|
||||
step={CONSTRAINTS.coarseStep}
|
||||
fineStep={CONSTRAINTS.fineStep}
|
||||
min={numberInputMin}
|
||||
max={numberInputMax}
|
||||
step={coarseStep}
|
||||
fineStep={fineStep}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
aspectRatioIdChanged,
|
||||
selectAllowedAspectRatioIDs,
|
||||
selectAspectRatioID,
|
||||
selectAspectRatioSizes,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import { isAspectRatioID, zAspectRatioID } from 'features/controlLayers/store/types';
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
@@ -17,6 +18,7 @@ export const DimensionsAspectRatioSelect = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const id = useAppSelector(selectAspectRatioID);
|
||||
const allowedAspectRatios = useAppSelector(selectAllowedAspectRatioIDs);
|
||||
const aspectRatioSizes = useAppSelector(selectAspectRatioSizes);
|
||||
const options = allowedAspectRatios ?? zAspectRatioID.options;
|
||||
|
||||
const onChange = useCallback<ChangeEventHandler<HTMLSelectElement>>(
|
||||
@@ -24,9 +26,10 @@ export const DimensionsAspectRatioSelect = memo(() => {
|
||||
if (!isAspectRatioID(e.target.value)) {
|
||||
return;
|
||||
}
|
||||
dispatch(aspectRatioIdChanged({ id: e.target.value }));
|
||||
const fixedSize = aspectRatioSizes?.[e.target.value] ?? undefined;
|
||||
dispatch(aspectRatioIdChanged({ id: e.target.value, fixedSize }));
|
||||
},
|
||||
[dispatch]
|
||||
[dispatch, aspectRatioSizes]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { heightChanged, selectHeight } from 'features/controlLayers/store/paramsSlice';
|
||||
import { heightChanged, selectHasFixedDimensionSizes, selectHeight } from 'features/controlLayers/store/paramsSlice';
|
||||
import { selectGridSize, selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -22,6 +22,7 @@ export const DimensionsHeight = memo(() => {
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const height = useAppSelector(selectHeight);
|
||||
const gridSize = useAppSelector(selectGridSize);
|
||||
const hasFixedSizes = useAppSelector(selectHasFixedDimensionSizes);
|
||||
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
@@ -33,7 +34,7 @@ export const DimensionsHeight = memo(() => {
|
||||
const marks = useMemo(() => [CONSTRAINTS.sliderMin, optimalDimension, CONSTRAINTS.sliderMax], [optimalDimension]);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<FormControl isDisabled={hasFixedSizes}>
|
||||
<InformationalPopover feature="paramHeight">
|
||||
<FormLabel>{t('parameters.height')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { aspectRatioLockToggled, selectAspectRatioIsLocked } from 'features/controlLayers/store/paramsSlice';
|
||||
import {
|
||||
aspectRatioLockToggled,
|
||||
selectAspectRatioIsLocked,
|
||||
selectHasFixedDimensionSizes,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi';
|
||||
@@ -9,6 +13,7 @@ export const DimensionsLockAspectRatioButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isLocked = useAppSelector(selectAspectRatioIsLocked);
|
||||
const hasFixedSizes = useAppSelector(selectHasFixedDimensionSizes);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(aspectRatioLockToggled());
|
||||
@@ -22,6 +27,7 @@ export const DimensionsLockAspectRatioButton = memo(() => {
|
||||
variant={isLocked ? 'outline' : 'ghost'}
|
||||
size="sm"
|
||||
icon={isLocked ? <PiLockSimpleFill /> : <PiLockSimpleOpenBold />}
|
||||
isDisabled={hasFixedSizes}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectHeight, selectWidth, sizeOptimized } from 'features/controlLayers/store/paramsSlice';
|
||||
import {
|
||||
selectHasFixedDimensionSizes,
|
||||
selectHeight,
|
||||
selectWidth,
|
||||
sizeOptimized,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { getIsSizeTooLarge, getIsSizeTooSmall } from 'features/parameters/util/optimalDimension';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
@@ -13,6 +18,7 @@ export const DimensionsSetOptimalSizeButton = memo(() => {
|
||||
const width = useAppSelector(selectWidth);
|
||||
const height = useAppSelector(selectHeight);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const hasFixedSizes = useAppSelector(selectHasFixedDimensionSizes);
|
||||
const isSizeTooSmall = useMemo(
|
||||
() => getIsSizeTooSmall(width, height, optimalDimension),
|
||||
[height, width, optimalDimension]
|
||||
@@ -43,6 +49,7 @@ export const DimensionsSetOptimalSizeButton = memo(() => {
|
||||
size="sm"
|
||||
icon={<PiSparkleFill />}
|
||||
colorScheme={isSizeTooSmall || isSizeTooLarge ? 'warning' : 'base'}
|
||||
isDisabled={hasFixedSizes}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { dimensionsSwapped } from 'features/controlLayers/store/paramsSlice';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { dimensionsSwapped, selectHasFixedDimensionSizes } from 'features/controlLayers/store/paramsSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowsDownUpBold } from 'react-icons/pi';
|
||||
@@ -8,6 +8,7 @@ import { PiArrowsDownUpBold } from 'react-icons/pi';
|
||||
export const DimensionsSwapButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const hasFixedSizes = useAppSelector(selectHasFixedDimensionSizes);
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(dimensionsSwapped());
|
||||
}, [dispatch]);
|
||||
@@ -19,6 +20,7 @@ export const DimensionsSwapButton = memo(() => {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
icon={<PiArrowsDownUpBold />}
|
||||
isDisabled={hasFixedSizes}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { selectWidth, widthChanged } from 'features/controlLayers/store/paramsSlice';
|
||||
import { selectHasFixedDimensionSizes, selectWidth, widthChanged } from 'features/controlLayers/store/paramsSlice';
|
||||
import { selectGridSize, selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -22,6 +22,7 @@ export const DimensionsWidth = memo(() => {
|
||||
const width = useAppSelector(selectWidth);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const gridSize = useAppSelector(selectGridSize);
|
||||
const hasFixedSizes = useAppSelector(selectHasFixedDimensionSizes);
|
||||
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
@@ -33,7 +34,7 @@ export const DimensionsWidth = memo(() => {
|
||||
const marks = useMemo(() => [CONSTRAINTS.sliderMin, optimalDimension, CONSTRAINTS.sliderMax], [optimalDimension]);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<FormControl isDisabled={hasFixedSizes}>
|
||||
<InformationalPopover feature="paramWidth">
|
||||
<FormLabel>{t('parameters.width')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { FormControl, FormLabel, Select } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
resolutionPresetSelected,
|
||||
selectAspectRatioID,
|
||||
selectImageSize,
|
||||
selectResolutionPresets,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCaretDownBold } from 'react-icons/pi';
|
||||
|
||||
const makeKey = (aspectRatio: string, imageSize: string) => `${aspectRatio}|${imageSize}`;
|
||||
|
||||
export const ExternalModelImageSizeSelect = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const presets = useAppSelector(selectResolutionPresets);
|
||||
const currentAspectRatio = useAppSelector(selectAspectRatioID);
|
||||
const currentImageSize = useAppSelector(selectImageSize);
|
||||
|
||||
const presetMap = useMemo(() => {
|
||||
if (!presets) {
|
||||
return null;
|
||||
}
|
||||
const map = new Map<string, (typeof presets)[number]>();
|
||||
for (const preset of presets) {
|
||||
map.set(makeKey(preset.aspect_ratio, preset.image_size), preset);
|
||||
}
|
||||
return map;
|
||||
}, [presets]);
|
||||
|
||||
const selectedKey = useMemo(() => {
|
||||
if (!presets || presets.length === 0) {
|
||||
return '';
|
||||
}
|
||||
if (currentImageSize && currentAspectRatio) {
|
||||
const key = makeKey(currentAspectRatio, currentImageSize);
|
||||
if (presetMap?.has(key)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
// Fallback to first preset
|
||||
return makeKey(presets[0]!.aspect_ratio, presets[0]!.image_size);
|
||||
}, [presets, presetMap, currentAspectRatio, currentImageSize]);
|
||||
|
||||
const onChange = useCallback<ChangeEventHandler<HTMLSelectElement>>(
|
||||
(e) => {
|
||||
const preset = presetMap?.get(e.target.value);
|
||||
if (!preset) {
|
||||
return;
|
||||
}
|
||||
dispatch(
|
||||
resolutionPresetSelected({
|
||||
imageSize: preset.image_size,
|
||||
aspectRatio: preset.aspect_ratio,
|
||||
width: preset.width,
|
||||
height: preset.height,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, presetMap]
|
||||
);
|
||||
|
||||
if (!presets || presets.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<FormLabel>{t('parameters.resolution')}</FormLabel>
|
||||
<Select
|
||||
size="sm"
|
||||
value={selectedKey}
|
||||
onChange={onChange}
|
||||
cursor="pointer"
|
||||
iconSize="0.75rem"
|
||||
icon={<PiCaretDownBold />}
|
||||
>
|
||||
{presets.map((preset) => {
|
||||
const key = makeKey(preset.aspect_ratio, preset.image_size);
|
||||
return (
|
||||
<option key={key} value={key}>
|
||||
{preset.label}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
});
|
||||
|
||||
ExternalModelImageSizeSelect.displayName = 'ExternalModelImageSizeSelect';
|
||||
@@ -0,0 +1,68 @@
|
||||
import { FormControl, FormLabel, Select } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
aspectRatioIdChanged,
|
||||
selectAspectRatioID,
|
||||
selectAspectRatioSizes,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import { isAspectRatioID } from 'features/controlLayers/store/types';
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCaretDownBold } from 'react-icons/pi';
|
||||
|
||||
export const ExternalModelResolutionSelect = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const aspectRatioID = useAppSelector(selectAspectRatioID);
|
||||
const aspectRatioSizes = useAppSelector(selectAspectRatioSizes);
|
||||
|
||||
const options = useMemo(() => {
|
||||
if (!aspectRatioSizes) {
|
||||
return [];
|
||||
}
|
||||
return Object.entries(aspectRatioSizes).map(([ratio, size]) => ({
|
||||
ratio,
|
||||
label: `${ratio} (${size.width}×${size.height})`,
|
||||
size,
|
||||
}));
|
||||
}, [aspectRatioSizes]);
|
||||
|
||||
const onChange = useCallback<ChangeEventHandler<HTMLSelectElement>>(
|
||||
(e) => {
|
||||
const ratio = e.target.value;
|
||||
if (!isAspectRatioID(ratio)) {
|
||||
return;
|
||||
}
|
||||
const fixedSize = aspectRatioSizes?.[ratio] ?? undefined;
|
||||
dispatch(aspectRatioIdChanged({ id: ratio, fixedSize }));
|
||||
},
|
||||
[dispatch, aspectRatioSizes]
|
||||
);
|
||||
|
||||
if (!aspectRatioSizes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<FormLabel>{t('parameters.resolution')}</FormLabel>
|
||||
<Select
|
||||
size="sm"
|
||||
value={aspectRatioID}
|
||||
onChange={onChange}
|
||||
cursor="pointer"
|
||||
iconSize="0.75rem"
|
||||
icon={<PiCaretDownBold />}
|
||||
>
|
||||
{options.map(({ ratio, label }) => (
|
||||
<option key={ratio} value={ratio}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
});
|
||||
|
||||
ExternalModelResolutionSelect.displayName = 'ExternalModelResolutionSelect';
|
||||
@@ -2,13 +2,19 @@ import { CompositeNumberInput, FormControl, FormLabel } from '@invoke-ai/ui-libr
|
||||
import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { selectSeed, selectShouldRandomizeSeed, setSeed } from 'features/controlLayers/store/paramsSlice';
|
||||
import {
|
||||
selectSeed,
|
||||
selectSeedControl,
|
||||
selectShouldRandomizeSeed,
|
||||
setSeed,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const ParamSeedNumberInput = memo(() => {
|
||||
const seed = useAppSelector(selectSeed);
|
||||
const shouldRandomizeSeed = useAppSelector(selectShouldRandomizeSeed);
|
||||
const externalControl = useAppSelector(selectSeedControl);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -22,9 +28,10 @@ export const ParamSeedNumberInput = memo(() => {
|
||||
<FormLabel>{t('parameters.seed')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<CompositeNumberInput
|
||||
step={1}
|
||||
min={NUMPY_RAND_MIN}
|
||||
max={NUMPY_RAND_MAX}
|
||||
step={externalControl?.coarse_step ?? 1}
|
||||
fineStep={externalControl?.fine_step ?? undefined}
|
||||
min={externalControl?.number_input_min ?? NUMPY_RAND_MIN}
|
||||
max={externalControl?.number_input_max ?? NUMPY_RAND_MAX}
|
||||
onChange={handleChangeSeed}
|
||||
value={seed}
|
||||
flexGrow={1}
|
||||
|
||||
@@ -25,7 +25,7 @@ const buildExternalPanelSchemaFromCapabilities = (
|
||||
const getExternalPanelSchema = (modelConfig: ExternalApiModelConfig): ExternalModelPanelSchema =>
|
||||
modelConfig.panel_schema ?? buildExternalPanelSchemaFromCapabilities(modelConfig.capabilities);
|
||||
|
||||
const getExternalPanelControl = (
|
||||
export const getExternalPanelControl = (
|
||||
modelConfig: ExternalApiModelConfig,
|
||||
panel: ExternalPanelName,
|
||||
controlName: ExternalPanelControlName
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { FormLabelProps } from '@invoke-ai/ui-library';
|
||||
import { Flex, FormControlGroup, StandaloneAccordion } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectIsExternal } from 'features/controlLayers/store/paramsSlice';
|
||||
import { ExternalModelImageSizeSelect } from 'features/parameters/components/Dimensions/ExternalModelImageSizeSelect';
|
||||
import { ExternalModelResolutionSelect } from 'features/parameters/components/Dimensions/ExternalModelResolutionSelect';
|
||||
import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const formLabelProps: FormLabelProps = {
|
||||
minW: '4rem',
|
||||
};
|
||||
|
||||
export const ExternalSettingsAccordion = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const isExternal = useAppSelector(selectIsExternal);
|
||||
const { isOpen, onToggle } = useStandaloneAccordionToggle({
|
||||
id: 'external-settings',
|
||||
defaultIsOpen: true,
|
||||
});
|
||||
|
||||
if (!isExternal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StandaloneAccordion
|
||||
label={t('accordions.advanced.title')}
|
||||
badges={['EXTERNAL']}
|
||||
isOpen={isOpen}
|
||||
onToggle={onToggle}
|
||||
>
|
||||
<Flex gap={4} p={4} flexDir="column" data-testid="external-settings-accordion">
|
||||
<FormControlGroup formLabelProps={formLabelProps}>
|
||||
<ExternalModelResolutionSelect />
|
||||
<ExternalModelImageSizeSelect />
|
||||
</FormControlGroup>
|
||||
</Flex>
|
||||
</StandaloneAccordion>
|
||||
);
|
||||
});
|
||||
|
||||
ExternalSettingsAccordion.displayName = 'ExternalSettingsAccordion';
|
||||
@@ -6,6 +6,7 @@ import { selectIsCogView4, selectIsExternal, selectIsSDXL } from 'features/contr
|
||||
import { Prompts } from 'features/parameters/components/Prompts/Prompts';
|
||||
import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
|
||||
import { CompositingSettingsAccordion } from 'features/settingsAccordions/components/CompositingSettingsAccordion/CompositingSettingsAccordion';
|
||||
import { ExternalSettingsAccordion } from 'features/settingsAccordions/components/ExternalSettingsAccordion/ExternalSettingsAccordion';
|
||||
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
|
||||
import { CanvasTabImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/CanvasTabImageSettingsAccordion';
|
||||
import { RefinerSettingsAccordion } from 'features/settingsAccordions/components/RefinerSettingsAccordion/RefinerSettingsAccordion';
|
||||
@@ -47,6 +48,7 @@ export const ParametersPanelCanvas = memo(() => {
|
||||
{!isExternal && <CompositingSettingsAccordion />}
|
||||
{isSDXL && <RefinerSettingsAccordion />}
|
||||
{!isCogview4 && !isExternal && <AdvancedSettingsAccordion />}
|
||||
{isExternal && <ExternalSettingsAccordion />}
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
</Box>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/con
|
||||
import { selectIsCogView4, selectIsExternal, selectIsSDXL } from 'features/controlLayers/store/paramsSlice';
|
||||
import { Prompts } from 'features/parameters/components/Prompts/Prompts';
|
||||
import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
|
||||
import { ExternalSettingsAccordion } from 'features/settingsAccordions/components/ExternalSettingsAccordion/ExternalSettingsAccordion';
|
||||
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
|
||||
import { GenerateTabImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/GenerateTabImageSettingsAccordion';
|
||||
import { RefinerSettingsAccordion } from 'features/settingsAccordions/components/RefinerSettingsAccordion/RefinerSettingsAccordion';
|
||||
@@ -45,6 +46,7 @@ export const ParametersPanelGenerate = memo(() => {
|
||||
<GenerationSettingsAccordion />
|
||||
{isSDXL && <RefinerSettingsAccordion />}
|
||||
{!isCogview4 && !isExternal && <AdvancedSettingsAccordion />}
|
||||
{isExternal && <ExternalSettingsAccordion />}
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
</Box>
|
||||
|
||||
@@ -131,6 +131,14 @@ export type ExternalImageSize = {
|
||||
height: number;
|
||||
};
|
||||
|
||||
type ExternalResolutionPreset = {
|
||||
label: string;
|
||||
aspect_ratio: string;
|
||||
image_size: string;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
export type ExternalModelCapabilities = {
|
||||
modes: ('txt2img' | 'img2img' | 'inpaint')[];
|
||||
supports_reference_images?: boolean;
|
||||
@@ -141,6 +149,8 @@ export type ExternalModelCapabilities = {
|
||||
max_images_per_request?: number | null;
|
||||
max_image_size?: ExternalImageSize | null;
|
||||
allowed_aspect_ratios?: string[] | null;
|
||||
aspect_ratio_sizes?: Record<string, ExternalImageSize> | null;
|
||||
resolution_presets?: ExternalResolutionPreset[] | null;
|
||||
max_reference_images?: number | null;
|
||||
mask_format?: 'alpha' | 'binary' | 'none';
|
||||
input_image_required_for?: ('txt2img' | 'img2img' | 'inpaint')[] | null;
|
||||
|
||||
@@ -74,6 +74,7 @@ def _build_request(
|
||||
num_images=num_images,
|
||||
width=width,
|
||||
height=height,
|
||||
image_size=None,
|
||||
steps=10,
|
||||
guidance=guidance,
|
||||
init_image=init_image,
|
||||
|
||||
@@ -64,6 +64,7 @@ def _build_request(
|
||||
num_images=1,
|
||||
width=256,
|
||||
height=256,
|
||||
image_size=None,
|
||||
steps=20,
|
||||
guidance=5.5,
|
||||
init_image=init_image,
|
||||
|
||||
Reference in New Issue
Block a user