diff --git a/docs/src/content/docs/features/External Models/fal.mdx b/docs/src/content/docs/features/External Models/fal.mdx new file mode 100644 index 00000000000..135b4da5339 --- /dev/null +++ b/docs/src/content/docs/features/External Models/fal.mdx @@ -0,0 +1,80 @@ +--- +title: fal.ai +--- + +Invoke can use fal.ai-hosted image models from the Canvas Image Editor. Requests +run on fal.ai, and fal.ai bills them to your account. + +## Setup + +Configure fal.ai in **Models → Add Model → External Providers**. Paste your +fal.ai API key and save. Invoke stores it in `api_keys.yaml`; the key is not +written to `invokeai.yaml` or shown after saving. + +Manual configuration uses: + +```yaml +# api_keys.yaml +external_fal_api_key: "your-fal-api-key" + +# Optional queue API override, mainly for a compatible proxy +external_fal_base_url: "https://queue.fal.run" +``` + +Restart Invoke after manual configuration. + +## Models + +Invoke exposes two fal.ai model paths: + +- **Native Canvas models** — image endpoints can be searched in the fal.ai + catalog and installed into Invoke's External Models list. Their endpoint + schema determines supported modes and common fields. +- **Generic media endpoints** — every catalog endpoint can be called from the + `fal.ai Generic Media` node with its raw JSON schema. This covers text/video + generation, image-to-video, video-to-video, upscaling, audio, speech, 3D, + and new endpoints that fal.ai adds later. + +The provider keeps these curated image presets for quick setup: + +- `fal-ai/flux/schnell` — text to image +- `fal-ai/flux/dev` — text to image +- `fal-ai/flux-pro/kontext` — image to image and semantic edits +- `fal-ai/flux-lora-fill` — masked inpainting + +Use **Refresh** in the fal.ai catalog to load current endpoint metadata. No +model weights are downloaded; installed entries are lightweight external +references. + +## Generic media workflows + +Add the `fal.ai Generic Media` node in Workflow Editor. Set `model_id` to any +fal.ai endpoint and paste the endpoint's JSON input into `input_json`. Local +images, masks, and videos can be connected to the node and referenced with +`${image_url}`, `${mask_url}`, and `${video_url}` placeholders. The node +returns raw JSON, so endpoint-specific outputs remain available without +waiting for a new Invoke release. + +## Canvas + +1. Open **Canvas**. +2. Select one of the fal.ai models in the model picker. +3. Use normal generation for FLUX Schnell or FLUX Dev. +4. Use **img2img** with an existing canvas image for Kontext. +5. Draw an inpaint mask and choose **inpaint** with FLUX Fill. +6. Press **Invoke**. The image is uploaded to fal.ai, processed remotely, and +the result is imported into the Invoke gallery. + +Canvas settings are constrained by each model's capabilities. For example, +Kontext requires an input image, while Fill requires both an input image and a +mask. The provider converts Invoke's white-preserve/black-edit canvas mask to +the white-edit format expected by fal.ai. + +## Costs and limits + +Every generation, upload, and result download uses remote services. Check the +current model pricing and limits on its fal.ai model page before invoking. +Automated tests do not submit inference requests. + +- https://fal.ai/models +- https://fal.ai/docs/model-apis diff --git a/docs/src/content/docs/features/External Models/index.mdx b/docs/src/content/docs/features/External Models/index.mdx index 358b68fe68e..99895fb096c 100644 --- a/docs/src/content/docs/features/External Models/index.mdx +++ b/docs/src/content/docs/features/External Models/index.mdx @@ -12,7 +12,7 @@ External models appear in the model picker alongside locally installed models. G ## Supported Providers -- [Google Gemini](/features/external-models/gemini/) — Gemini 2.5 Flash Image, Gemini 3 Pro Image Preview, Gemini 3.1 Flash Image Preview +- [fal.ai](/features/external-models/fal/) — FLUX.1 [schnell], FLUX.1 [dev], FLUX.1 Kontext [pro], FLUX.1 Fill - [OpenAI](/features/external-models/openai/) — GPT Image 1 / 1.5 / 1-mini, DALL·E 3 - [BytePlus Seedream](/features/external-models/seedream/) — Seedream 5.0, 5.0 Lite, 4.5, 4.0 - [Alibaba Cloud DashScope](/features/external-models/alibabacloud/) — Qwen Image 2.0 / 2.0 Pro / Max / Edit Max, Wan 2.6 T2I @@ -23,10 +23,12 @@ External provider credentials are stored in a dedicated `api_keys.yaml` file alo ```yaml # api_keys.yaml +external_fal_api_key: "your-fal-api-key" external_gemini_api_key: "your-gemini-api-key" external_openai_api_key: "your-openai-api-key" # Optional: override the provider base URL (e.g. for a compatible proxy or regional endpoint) +external_fal_base_url: "https://queue.fal.run" external_gemini_base_url: "https://generativelanguage.googleapis.com" external_openai_base_url: "https://api.openai.com" ``` @@ -46,7 +48,7 @@ Once installed, external models show up everywhere a model can be selected. Choo Each external model declares its own **capabilities** — for example: -- Which generation modes it supports (`txt2img`, `img2img`). Inpainting is not currently supported by any external provider. +- Which generation modes it supports (`txt2img`, `img2img`, `inpaint`). Support differs by provider and model. - Whether it accepts reference images, and how many. - Which aspect ratios and resolutions it allows. - Whether it supports a negative prompt, seed, or batch size > 1. diff --git a/docs/src/generated/settings.json b/docs/src/generated/settings.json index 5a73c0483af..47787ef7455 100644 --- a/docs/src/generated/settings.json +++ b/docs/src/generated/settings.json @@ -853,6 +853,28 @@ "type": "", "validation": {} }, + { + "category": "EXTERNAL PROVIDERS", + "default": null, + "description": "API key for fal.ai image generation.", + "env_var": "INVOKEAI_EXTERNAL_FAL_API_KEY", + "literal_values": [], + "name": "external_fal_api_key", + "required": false, + "type": "typing.Optional[str]", + "validation": {} + }, + { + "category": "EXTERNAL PROVIDERS", + "default": null, + "description": "Base URL override for fal.ai queue API.", + "env_var": "INVOKEAI_EXTERNAL_FAL_BASE_URL", + "literal_values": [], + "name": "external_fal_base_url", + "required": false, + "type": "typing.Optional[str]", + "validation": {} + }, { "category": "EXTERNAL PROVIDERS", "default": null, diff --git a/invokeai/app/api/dependencies.py b/invokeai/app/api/dependencies.py index b3ba3be75cf..8c531b21377 100644 --- a/invokeai/app/api/dependencies.py +++ b/invokeai/app/api/dependencies.py @@ -20,6 +20,7 @@ from invokeai.app.services.external_generation.external_generation_default import ExternalGenerationService from invokeai.app.services.external_generation.providers import ( AlibabaCloudProvider, + FalProvider, GeminiProvider, OpenAIProvider, SeedreamProvider, @@ -188,6 +189,7 @@ def initialize( external_generation = ExternalGenerationService( providers={ AlibabaCloudProvider.provider_id: AlibabaCloudProvider(app_config=configuration, logger=logger), + FalProvider.provider_id: FalProvider(app_config=configuration, logger=logger), GeminiProvider.provider_id: GeminiProvider(app_config=configuration, logger=logger), OpenAIProvider.provider_id: OpenAIProvider(app_config=configuration, logger=logger), SeedreamProvider.provider_id: SeedreamProvider(app_config=configuration, logger=logger), diff --git a/invokeai/app/api/routers/app_info.py b/invokeai/app/api/routers/app_info.py index 3564e296b1d..826015ee186 100644 --- a/invokeai/app/api/routers/app_info.py +++ b/invokeai/app/api/routers/app_info.py @@ -1,4 +1,5 @@ import locale +import os import re from enum import Enum from importlib.metadata import distributions @@ -8,7 +9,7 @@ import torch import yaml -from fastapi import Body, HTTPException, Path +from fastapi import Body, HTTPException, Path, Query from fastapi.routing import APIRouter from pydantic import BaseModel, Field, field_validator, model_validator @@ -24,9 +25,21 @@ load_external_api_keys, ) from invokeai.app.services.external_generation.external_generation_common import ExternalProviderStatus +from invokeai.app.services.external_generation.providers.fal_catalog import ( + FalCatalogClient, + FalEndpointKind, + FalEndpointSchema, + classify_endpoint, +) from invokeai.app.services.invocation_cache.invocation_cache_common import InvocationCacheStatus -from invokeai.app.services.model_records.model_records_base import UnknownModelException +from invokeai.app.services.model_install.model_install_common import ModelInstallJob +from invokeai.app.services.model_records.model_records_base import ModelRecordChanges, UnknownModelException from invokeai.backend.image_util.infill_methods.patchmatch import PatchMatch +from invokeai.backend.model_manager.configs.external_api import ( + ExternalApiModelConfig, + ExternalModelCapabilities, + ExternalModelPanelSchema, +) from invokeai.backend.model_manager.taxonomy import BaseModelType, ModelType from invokeai.backend.util.devices import TorchDevice from invokeai.backend.util.logging import logging @@ -100,7 +113,41 @@ class ExternalProviderConfigModel(BaseModel): base_url: str | None = Field(default=None, description="Optional base URL override") +class FalCatalogModelResponse(BaseModel): + endpoint_id: str + display_name: str + description: str + category: str + kind: FalEndpointKind + model_url: str | None + thumbnail_url: str | None + tags: list[str] + installed: bool + + +class FalCatalogResponse(BaseModel): + models: list[FalCatalogModelResponse] + next_cursor: str | None + has_more: bool + + +class FalEndpointSchemaResponse(BaseModel): + endpoint_id: str + kind: FalEndpointKind + output_kind: FalEndpointKind + category: str + input_schema: dict[str, Any] + output_schema: dict[str, Any] + common_fields: dict[str, str] + public_properties: list[str] + + +class FalModelInstallRequest(BaseModel): + endpoint_id: str = Field(min_length=1, max_length=512, description="fal.ai endpoint identifier") + + EXTERNAL_PROVIDER_FIELDS: dict[str, tuple[str, str]] = { + "fal": ("external_fal_api_key", "external_fal_base_url"), "alibabacloud": ("external_alibabacloud_api_key", "external_alibabacloud_base_url"), "gemini": ("external_gemini_api_key", "external_gemini_base_url"), "openai": ("external_openai_api_key", "external_openai_base_url"), @@ -421,9 +468,12 @@ def _apply_external_provider_update(updates: dict[str, str | None]) -> None: def _build_external_provider_config(provider_id: str, config: InvokeAIAppConfig) -> ExternalProviderConfigModel: api_key_field, base_url_field = _get_external_provider_fields(provider_id) + api_key_configured = bool(getattr(config, api_key_field)) + if provider_id == "fal" and not api_key_configured: + api_key_configured = bool(os.getenv("FAL_KEY") or os.getenv("FAL_API_KEY")) return ExternalProviderConfigModel( provider_id=provider_id, - api_key_configured=bool(getattr(config, api_key_field)), + api_key_configured=api_key_configured, base_url=getattr(config, base_url_field), ) @@ -510,3 +560,204 @@ def disable_invocation_cache(current_admin: AdminUserOrDefault) -> None: def get_invocation_cache_status(current_admin: AdminUserOrDefault) -> InvocationCacheStatus: """Clears the invocation cache""" return ApiDependencies.invoker.services.invocation_cache.get_status() + + +@app_router.get( + "/external_providers/fal/models", + operation_id="list_fal_models", + status_code=200, + response_model=FalCatalogResponse, +) +def list_fal_models( + _: AdminUserOrDefault, + limit: int = Query(default=50, ge=1, le=100), + cursor: str | None = Query(default=None, max_length=4096), + search: str | None = Query(default=None, max_length=200), +) -> FalCatalogResponse: + client = _get_fal_catalog_client() + try: + page = client.list_models(limit=limit, cursor=cursor, search=search) + except Exception as exc: + _raise_fal_catalog_http_error(exc) + installed = _get_installed_fal_models() + return FalCatalogResponse( + models=[ + FalCatalogModelResponse( + endpoint_id=model.endpoint_id, + display_name=model.display_name, + description=model.description, + category=model.category, + kind=classify_endpoint(model.category, {}, endpoint_id=model.endpoint_id), + model_url=model.model_url, + thumbnail_url=model.thumbnail_url, + tags=list(model.tags), + installed=model.endpoint_id in installed, + ) + for model in page.models + ], + next_cursor=page.next_cursor, + has_more=page.has_more, + ) + + +@app_router.get( + "/external_providers/fal/models/{endpoint_id:path}/schema", + operation_id="get_fal_model_schema", + status_code=200, + response_model=FalEndpointSchemaResponse, +) +def get_fal_model_schema( + _: AdminUserOrDefault, + endpoint_id: str = Path(description="fal.ai endpoint identifier"), +) -> FalEndpointSchemaResponse: + client = _get_fal_catalog_client() + try: + schema = client.get_schema(endpoint_id) + except Exception as exc: + _raise_fal_catalog_http_error(exc) + return _fal_schema_to_response(schema) + + +@app_router.post( + "/external_providers/fal/models/install", + operation_id="install_fal_model", + status_code=201, + response_model=ModelInstallJob, +) +def install_fal_model( + _: AdminUserOrDefault, + request: FalModelInstallRequest, +) -> ModelInstallJob: + client = _get_fal_catalog_client() + try: + schema = client.get_schema(request.endpoint_id) + except Exception as exc: + _raise_fal_catalog_http_error(exc) + + if schema.kind not in _FAL_NATIVE_IMAGE_KINDS: + raise HTTPException( + status_code=422, + detail=( + f"fal.ai endpoint '{request.endpoint_id}' is {schema.kind.value}; " + "use the fal.ai generic media invocation for this endpoint" + ), + ) + + config = ModelRecordChanges( + name=f"fal.ai {request.endpoint_id}", + description=f"Dynamic fal.ai endpoint ({schema.category}).", + provider_id="fal", + provider_model_id=request.endpoint_id, + source_url=f"https://fal.ai/models/{request.endpoint_id}", + capabilities=_fal_capabilities_from_schema(schema), + panel_schema=_fal_panel_schema_from_schema(schema), + ) + try: + return ApiDependencies.invoker.services.model_manager.install.heuristic_import( + source=f"external://fal/{request.endpoint_id}", + config=config, + ) + except Exception as exc: + raise HTTPException(status_code=409, detail=f"Unable to install fal.ai model: {exc}") from exc + + +_FAL_NATIVE_IMAGE_KINDS = { + FalEndpointKind.TEXT_TO_IMAGE, + FalEndpointKind.IMAGE_TO_IMAGE, + FalEndpointKind.INPAINT, + FalEndpointKind.UPSCALE, +} + + +def _fal_panel_schema_from_schema(schema: FalEndpointSchema) -> ExternalModelPanelSchema: + prompts = [{"name": "reference_images"}] if "reference_images" in schema.common_fields else [] + image_controls: list[dict[str, str]] = [] + if any(name in schema.common_fields for name in ("width", "height", "aspect_ratio", "image_size")): + image_controls.append({"name": "dimensions"}) + if "seed" in schema.common_fields: + image_controls.append({"name": "seed"}) + return ExternalModelPanelSchema(prompts=prompts, image=image_controls) + + +def _fal_capabilities_from_schema(schema: FalEndpointSchema) -> ExternalModelCapabilities: + if schema.kind is FalEndpointKind.TEXT_TO_IMAGE: + modes = ["txt2img"] + elif schema.kind is FalEndpointKind.INPAINT: + modes = ["inpaint"] + else: + modes = ["img2img"] + + properties = schema.input_schema.get("properties", {}) + if not isinstance(properties, dict): + properties = {} + ratios = properties.get(schema.common_fields.get("aspect_ratio", ""), {}) + allowed_ratios = ratios.get("enum") if isinstance(ratios, dict) else None + if not isinstance(allowed_ratios, list) or not all(isinstance(value, str) for value in allowed_ratios): + allowed_ratios = None + + num_images_name = schema.common_fields.get("num_images", "") + num_images_schema = properties.get(num_images_name, {}) + maximum = num_images_schema.get("maximum") if isinstance(num_images_schema, dict) else None + max_images = maximum if isinstance(maximum, int) and maximum > 0 else None + reference_name = schema.common_fields.get("reference_images", "") + reference_schema = properties.get(reference_name, {}) + max_references = reference_schema.get("maxItems") if isinstance(reference_schema, dict) else None + max_references = max_references if isinstance(max_references, int) and max_references > 0 else None + + return ExternalModelCapabilities( + modes=modes, # type: ignore[arg-type] + supports_reference_images="reference_images" in schema.common_fields, + max_reference_images=max_references, + supports_negative_prompt="negative_prompt" in schema.common_fields, + supports_seed="seed" in schema.common_fields, + max_images_per_request=max_images, + allowed_aspect_ratios=allowed_ratios, + mask_format="binary" if "mask_image" in schema.common_fields else "none", + input_image_required_for=[modes[0]] if modes[0] != "txt2img" else None, # type: ignore[list-item] + ) + + +def _get_fal_catalog_client() -> FalCatalogClient: + config = get_config() + api_key = config.external_fal_api_key or os.getenv("FAL_KEY") or os.getenv("FAL_API_KEY") + if not api_key: + raise HTTPException(status_code=409, detail="fal.ai API key is not configured") + return FalCatalogClient(api_key) + + +def _get_installed_fal_models() -> set[str]: + models = ApiDependencies.invoker.services.model_manager.store.search_by_attr( + base_model=BaseModelType.External, + model_type=ModelType.ExternalImageGenerator, + ) + return { + model.provider_model_id + for model in models + if isinstance(model, ExternalApiModelConfig) and model.provider_id == "fal" and model.provider_model_id + } + + +def _fal_schema_to_response(schema: FalEndpointSchema) -> FalEndpointSchemaResponse: + return FalEndpointSchemaResponse( + endpoint_id=schema.endpoint_id, + kind=schema.kind, + output_kind=schema.output_kind, + category=schema.category, + input_schema=schema.input_schema, + output_schema=schema.output_schema, + common_fields=schema.common_fields, + public_properties=list(schema.public_properties), + ) + + +def _raise_fal_catalog_http_error(exc: Exception) -> None: + from invokeai.app.services.external_generation.errors import ( + ExternalProviderRateLimitError, + ExternalProviderRequestError, + ) + + if isinstance(exc, ExternalProviderRateLimitError): + raise HTTPException(status_code=429, detail=str(exc)) from exc + if isinstance(exc, ExternalProviderRequestError): + raise HTTPException(status_code=502, detail=str(exc)) from exc + raise HTTPException(status_code=502, detail="fal.ai catalog request failed") from exc diff --git a/invokeai/app/invocations/external_image_generation.py b/invokeai/app/invocations/external_image_generation.py index a6c6822d9dd..6c7f482abf3 100644 --- a/invokeai/app/invocations/external_image_generation.py +++ b/invokeai/app/invocations/external_image_generation.py @@ -349,3 +349,32 @@ class AlibabaCloudImageGenerationInvocation(BaseExternalImageGenerationInvocatio ui_model_format=[ModelFormat.ExternalApi], ui_model_provider_id=["alibabacloud"], ) + + +@invocation( + "fal_image_generation", + title="fal.ai Image Generation", + tags=["external", "generation", "fal", "fal.ai"], + category="image", + version="1.0.0", +) +class FalImageGenerationInvocation(BaseExternalImageGenerationInvocation): + """Generate or edit images using a fal.ai-hosted model.""" + + provider_id = "fal" + + advanced_options: dict[str, Any] = InputField( + default={}, + description="Additional JSON fields declared by the selected fal.ai endpoint", + ) + + model: ModelIdentifierField = InputField( + description=FieldDescriptions.main_model, + ui_model_base=[BaseModelType.External], + ui_model_type=[ModelType.ExternalImageGenerator], + ui_model_format=[ModelFormat.ExternalApi], + ui_model_provider_id=["fal"], + ) + + def _build_provider_options(self) -> dict[str, Any] | None: + return {"advanced": self.advanced_options} if self.advanced_options else None diff --git a/invokeai/app/invocations/external_media_generation.py b/invokeai/app/invocations/external_media_generation.py new file mode 100644 index 00000000000..b6ef315308d --- /dev/null +++ b/invokeai/app/invocations/external_media_generation.py @@ -0,0 +1,59 @@ +import json +from typing import Any + +from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation +from invokeai.app.invocations.fields import ImageField, InputField, VideoField +from invokeai.app.invocations.primitives import StringOutput +from invokeai.app.services.shared.invocation_context import InvocationContext + + +@invocation( + "fal_generic_media_native", + title="fal.ai Generic Media", + tags=["external", "generation", "fal", "fal.ai", "generic"], + category="media", + version="1.0.0", +) +class FalGenericMediaInvocation(BaseInvocation): + """Submit arbitrary JSON to any fal.ai endpoint and return its raw JSON result.""" + + model_id: str = InputField(description="fal.ai endpoint ID, for example fal-ai/kling-video/v3/pro/text-to-video") + input_json: str = InputField(default="{}", description="JSON object sent to the fal.ai endpoint") + image: ImageField | None = InputField( + default=None, description="Optional local image for ${image_url} placeholders" + ) + mask: ImageField | None = InputField(default=None, description="Optional local mask for ${mask_url} placeholders") + reference_images: list[ImageField] = InputField( + default=[], description="Optional local images for ${image_urls} or ${reference_image_urls} placeholders" + ) + video: VideoField | None = InputField( + default=None, description="Optional local video for ${video_url} placeholders" + ) + + def invoke(self, context: InvocationContext) -> StringOutput: + try: + payload = json.loads(self.input_json) + except json.JSONDecodeError as exc: + raise ValueError(f"fal.ai input_json is not valid JSON: {exc.msg}") from exc + if not isinstance(payload, dict): + raise ValueError("fal.ai input_json must be a JSON object") + + media_kwargs: dict[str, Any] = {} + if self.image is not None: + media_kwargs["image"] = context.images.get_pil(self.image.image_name, mode="RGB") + if self.mask is not None: + media_kwargs["mask_image"] = context.images.get_pil(self.mask.image_name, mode="L") + if self.reference_images: + media_kwargs["reference_images"] = [ + context.images.get_pil(field.image_name, mode="RGB") for field in self.reference_images + ] + if self.video is not None: + media_kwargs["video_path"] = context.videos.get_path(self.video.video_name) + + result: dict[str, Any] = context._services.external_generation.generate_generic( + provider_id="fal", + model_id=self.model_id, + payload=payload, + **media_kwargs, + ) + return StringOutput(value=json.dumps(result, ensure_ascii=False, sort_keys=True)) diff --git a/invokeai/app/services/config/config_default.py b/invokeai/app/services/config/config_default.py index 7174742a66d..6e5f8e9f053 100644 --- a/invokeai/app/services/config/config_default.py +++ b/invokeai/app/services/config/config_default.py @@ -45,6 +45,8 @@ "external_openai_base_url", "external_seedream_api_key", "external_seedream_base_url", + "external_fal_api_key", + "external_fal_base_url", ) @@ -135,6 +137,8 @@ class InvokeAIAppConfig(BaseSettings): allow_unknown_models: Allow installation of models that we are unable to identify. If enabled, models will be marked as `unknown` in the database, and will not have any metadata associated with them. If disabled, unknown models will be rejected during installation. multiuser: Enable multiuser support. When disabled, the application runs in single-user mode using a default system account with administrator privileges. When enabled, requires user authentication and authorization. strict_password_checking: Enforce strict password requirements. When True, passwords must contain uppercase, lowercase, and numbers. When False (default), any password is accepted but its strength (weak/moderate/strong) is reported to the user. + external_fal_api_key: API key for fal.ai image generation. + external_fal_base_url: Base URL override for fal.ai queue API. external_alibabacloud_api_key: API key for Alibaba Cloud DashScope image generation. external_alibabacloud_base_url: Base URL override for Alibaba Cloud DashScope image generation. external_gemini_api_key: API key for Gemini image generation. @@ -256,6 +260,10 @@ class InvokeAIAppConfig(BaseSettings): strict_password_checking: bool = Field(default=False, description="Enforce strict password requirements. When True, passwords must contain uppercase, lowercase, and numbers. When False (default), any password is accepted but its strength (weak/moderate/strong) is reported to the user.") # EXTERNAL PROVIDERS + external_fal_api_key: Optional[str] = Field(default=None, description="API key for fal.ai image generation.") + external_fal_base_url: Optional[str] = Field( + default=None, description="Base URL override for fal.ai queue API.", + ) external_alibabacloud_api_key: Optional[str] = Field(default=None, description="API key for Alibaba Cloud DashScope image generation.") external_alibabacloud_base_url: Optional[str] = Field( default=None, description="Base URL override for Alibaba Cloud DashScope image generation." diff --git a/invokeai/app/services/external_generation/external_generation_base.py b/invokeai/app/services/external_generation/external_generation_base.py index 2145ff5ca42..c9eb0e31ea7 100644 --- a/invokeai/app/services/external_generation/external_generation_base.py +++ b/invokeai/app/services/external_generation/external_generation_base.py @@ -2,6 +2,8 @@ from abc import ABC, abstractmethod from logging import Logger +from pathlib import Path +from typing import Any from invokeai.app.services.config import InvokeAIAppConfig from invokeai.app.services.external_generation.external_generation_common import ( @@ -26,6 +28,19 @@ def is_configured(self) -> bool: def generate(self, request: ExternalGenerationRequest) -> ExternalGenerationResult: raise NotImplementedError + def generate_generic( + self, + model_id: str, + payload: dict[str, object], + *, + image: Any = None, + mask_image: Any = None, + reference_images: list[Any] | None = None, + video_path: Path | None = None, + ) -> dict[str, object]: + """Submit provider-specific JSON for endpoints outside normalized image generation.""" + raise NotImplementedError(f"Provider '{self.provider_id}' does not support generic media generation") + def get_status(self) -> ExternalProviderStatus: return ExternalProviderStatus(provider_id=self.provider_id, configured=self.is_configured()) @@ -38,3 +53,17 @@ def generate(self, request: ExternalGenerationRequest) -> ExternalGenerationResu @abstractmethod def get_provider_statuses(self) -> dict[str, ExternalProviderStatus]: raise NotImplementedError + + @abstractmethod + def generate_generic( + self, + provider_id: str, + model_id: str, + payload: dict[str, object], + *, + image: Any = None, + mask_image: Any = None, + reference_images: list[Any] | None = None, + video_path: Path | None = None, + ) -> dict[str, object]: + raise NotImplementedError diff --git a/invokeai/app/services/external_generation/external_generation_default.py b/invokeai/app/services/external_generation/external_generation_default.py index d6a266753b3..501da41cc6b 100644 --- a/invokeai/app/services/external_generation/external_generation_default.py +++ b/invokeai/app/services/external_generation/external_generation_default.py @@ -3,7 +3,8 @@ import dataclasses import time from logging import Logger -from typing import TYPE_CHECKING +from pathlib import Path +from typing import TYPE_CHECKING, Any from PIL import Image from PIL.Image import Image as PILImageType @@ -13,6 +14,7 @@ ExternalProviderNotConfiguredError, ExternalProviderNotFoundError, ExternalProviderRateLimitError, + ExternalProviderRequestError, ) from invokeai.app.services.external_generation.external_generation_base import ( ExternalGenerationServiceBase, @@ -91,6 +93,34 @@ def _generate_with_retry( def get_provider_statuses(self) -> dict[str, ExternalProviderStatus]: return {provider_id: provider.get_status() for provider_id, provider in self._providers.items()} + def generate_generic( + self, + provider_id: str, + model_id: str, + payload: dict[str, object], + *, + image: Any = None, + mask_image: Any = None, + reference_images: list[Any] | None = None, + video_path: Path | None = None, + ) -> dict[str, object]: + provider = self._providers.get(provider_id) + if provider is None: + raise ExternalProviderRequestError(f"No external provider registered for '{provider_id}'") + if not provider.is_configured(): + raise ExternalProviderRequestError(f"Provider '{provider_id}' is missing credentials") + try: + return provider.generate_generic( + model_id, + payload, + image=image, + mask_image=mask_image, + reference_images=reference_images, + video_path=video_path, + ) + except NotImplementedError as exc: + raise ExternalProviderRequestError(str(exc)) from exc + def _validate_request(self, request: ExternalGenerationRequest) -> None: capabilities = request.model.capabilities diff --git a/invokeai/app/services/external_generation/providers/__init__.py b/invokeai/app/services/external_generation/providers/__init__.py index 9926302addf..cfc2afdbdac 100644 --- a/invokeai/app/services/external_generation/providers/__init__.py +++ b/invokeai/app/services/external_generation/providers/__init__.py @@ -1,6 +1,7 @@ from invokeai.app.services.external_generation.providers.alibabacloud import AlibabaCloudProvider +from invokeai.app.services.external_generation.providers.fal import FalProvider from invokeai.app.services.external_generation.providers.gemini import GeminiProvider from invokeai.app.services.external_generation.providers.openai import OpenAIProvider from invokeai.app.services.external_generation.providers.seedream import SeedreamProvider -__all__ = ["AlibabaCloudProvider", "GeminiProvider", "OpenAIProvider", "SeedreamProvider"] +__all__ = ["AlibabaCloudProvider", "FalProvider", "GeminiProvider", "OpenAIProvider", "SeedreamProvider"] diff --git a/invokeai/app/services/external_generation/providers/fal.py b/invokeai/app/services/external_generation/providers/fal.py new file mode 100644 index 00000000000..8c602b6ef5c --- /dev/null +++ b/invokeai/app/services/external_generation/providers/fal.py @@ -0,0 +1,587 @@ +from __future__ import annotations + +import base64 +import io +import os +import time +from logging import Logger +from pathlib import Path +from typing import Any + +import requests +from PIL import Image, ImageOps +from PIL.Image import Image as PILImageType + +from invokeai.app.services.config import InvokeAIAppConfig +from invokeai.app.services.external_generation.errors import ( + ExternalProviderRateLimitError, + ExternalProviderRequestError, +) +from invokeai.app.services.external_generation.external_generation_base import ExternalProvider +from invokeai.app.services.external_generation.external_generation_common import ( + ExternalGeneratedImage, + ExternalGenerationRequest, + ExternalGenerationResult, +) +from invokeai.app.services.external_generation.providers.fal_catalog import FalCatalogClient, FalEndpointSchema + +_DEFAULT_QUEUE_URL = "https://queue.fal.run" +_UPLOAD_URL = "https://rest.fal.ai/storage/upload/initiate?storage_type=fal-cdn-v3" +_POLL_INTERVAL = 1.0 +_POLL_TIMEOUT = 300.0 +_REQUEST_TIMEOUT = 60 +_DOWNLOAD_TIMEOUT = 60 +_DOWNLOAD_MAX_BYTES = 32 * 1024 * 1024 +_RETRY_STATUS_CODES = {500, 502, 503, 504} + +_IMAGE_SIZE_BY_RATIO = { + "1:1": "square_hd", + "4:3": "landscape_4_3", + "3:4": "portrait_4_3", + "16:9": "landscape_16_9", + "9:16": "portrait_16_9", +} + +_FLUX_FILL_MODELS = {"fal-ai/flux-lora-fill"} +_FLUX_KONTEXT_MODELS = {"fal-ai/flux-pro/kontext", "fal-ai/flux-pro/kontext/max"} +_FLUX_TEXT_MODELS = {"fal-ai/flux/schnell", "fal-ai/flux/dev"} + + +class FalProvider(ExternalProvider): + """InvokeAI adapter for fal.ai's queue and CDN APIs.""" + + provider_id = "fal" + + def __init__(self, app_config: InvokeAIAppConfig, logger: Logger) -> None: + super().__init__(app_config, logger) + self._schema_cache: dict[str, FalEndpointSchema] = {} + + def is_configured(self) -> bool: + return bool(self._api_key()) + + def generate(self, request: ExternalGenerationRequest) -> ExternalGenerationResult: + api_key = self._api_key() + if not api_key: + raise ExternalProviderRequestError("fal.ai API key is not configured") + + headers = {"Authorization": f"Key {api_key}", "Content-Type": "application/json"} + model_id = request.model.provider_model_id + _validate_endpoint_id(model_id) + image_url = None + mask_url = None + reference_urls: list[str] = [] + if request.init_image is not None: + image_url = self._upload_image(request.init_image, "image.png", headers) + for index, reference in enumerate(request.reference_images, start=1): + reference_urls.append(self._upload_image(reference.image, f"reference-{index}.png", headers)) + if request.mask_image is not None: + mask = ImageOps.invert(request.mask_image.convert("L")) + mask_url = self._upload_image(mask, "mask.png", headers) + + schema = self._get_schema(model_id) + payload = ( + build_schema_payload( + request, + schema, + image_url=image_url, + mask_url=mask_url, + reference_urls=reference_urls, + ) + if schema is not None + else self._build_payload(request, image_url=image_url, mask_url=mask_url) + ) + result_payload, request_id = self._submit_queue(model_id, payload, headers) + return self._parse_result(result_payload, request, request_id=request_id) + + def generate_generic( + self, + model_id: str, + payload: dict[str, Any], + *, + image: PILImageType | None = None, + mask_image: PILImageType | None = None, + reference_images: list[PILImageType] | None = None, + video_path: Path | None = None, + ) -> dict[str, Any]: + """Submit arbitrary JSON, expanding optional local media placeholders.""" + api_key = self._api_key() + if not api_key: + raise ExternalProviderRequestError("fal.ai API key is not configured") + _validate_endpoint_id(model_id) + headers = {"Authorization": f"Key {api_key}", "Content-Type": "application/json"} + media: dict[str, Any] = {} + if image is not None: + media["image_url"] = self._upload_image(image, "image.png", headers) + if mask_image is not None: + media["mask_url"] = self._upload_image(mask_image.convert("L"), "mask.png", headers) + if reference_images: + media["reference_image_urls"] = [ + self._upload_image(image, f"reference-{index}.png", headers) + for index, image in enumerate(reference_images, start=1) + ] + media["image_urls"] = media["reference_image_urls"] + if video_path is not None: + media["video_url"] = self._upload_file( + video_path, + video_path.name or "video.mp4", + "video/mp4", + headers, + ) + expanded_payload = _expand_media_placeholders(payload, media) + result, _ = self._submit_queue(model_id, expanded_payload, headers) + return result + + def _submit_queue( + self, model_id: str, payload: dict[str, Any], headers: dict[str, str] + ) -> tuple[dict[str, Any], str | None]: + queue_url = f"{self._queue_base_url}/{model_id}" + submit_response = self._request( + "POST", + queue_url, + headers=headers, + json=payload, + timeout=_REQUEST_TIMEOUT, + ) + self._raise_for_response(submit_response, "fal.ai request") + submitted = self._parse_json(submit_response, "fal.ai queue response") + + request_id = submitted.get("request_id") + if not isinstance(request_id, str) or not request_id: + return submitted, None + return self._wait_for_result(model_id, request_id, headers), request_id + + def _get_schema(self, model_id: str) -> FalEndpointSchema | None: + if model_id in _FLUX_FILL_MODELS | _FLUX_KONTEXT_MODELS | _FLUX_TEXT_MODELS: + return None + cached = self._schema_cache.get(model_id) + if cached is not None: + return cached + api_key = self._api_key() + if not api_key: + return None + schema = FalCatalogClient(api_key).get_schema(model_id) + self._schema_cache[model_id] = schema + return schema + + def _api_key(self) -> str | None: + return self._app_config.external_fal_api_key or os.getenv("FAL_KEY") or os.getenv("FAL_API_KEY") + + @property + def _queue_base_url(self) -> str: + return (self._app_config.external_fal_base_url or _DEFAULT_QUEUE_URL).rstrip("/") + + def _build_payload( + self, + request: ExternalGenerationRequest, + *, + image_url: str | None, + mask_url: str | None, + ) -> dict[str, Any]: + model_id = request.model.provider_model_id + ratio = _select_aspect_ratio(request.width, request.height, request.model.capabilities.allowed_aspect_ratios) + payload: dict[str, Any] = {"prompt": request.prompt} + + if request.seed is not None and request.model.capabilities.supports_seed: + payload["seed"] = request.seed + if request.num_images > 1 or request.model.capabilities.max_images_per_request is not None: + payload["num_images"] = request.num_images + + if model_id in _FLUX_FILL_MODELS or request.mode == "inpaint": + if image_url is None or mask_url is None: + raise ExternalProviderRequestError("fal.ai inpainting requires both image and mask inputs") + payload.update( + { + "image_size": _image_size_for_ratio(ratio), + "image_url": image_url, + "mask_url": mask_url, + "paste_back": True, + "resize_to_original": True, + } + ) + return payload + + if model_id in _FLUX_KONTEXT_MODELS or request.mode == "img2img": + if image_url is None: + raise ExternalProviderRequestError("fal.ai image editing requires an input image") + payload["image_url"] = image_url + payload["aspect_ratio"] = ratio + return payload + + if model_id in _FLUX_TEXT_MODELS or request.mode == "txt2img": + payload["image_size"] = _image_size_for_ratio(ratio) + return payload + + # Unknown models are schema-driven. This branch is retained for callers that use the provider + # directly with a model whose schema cannot be fetched yet. + if image_url is not None: + payload["image_url"] = image_url + if mask_url is not None: + payload["mask_url"] = mask_url + payload["image_size"] = _image_size_for_ratio(ratio) + return payload + + def _upload_file(self, path: Path, filename: str, content_type: str, headers: dict[str, str]) -> str: + if not path.is_file(): + raise ExternalProviderRequestError(f"fal.ai input file does not exist: {path}") + try: + file_size = path.stat().st_size + if file_size > _DOWNLOAD_MAX_BYTES: + raise ExternalProviderRequestError("fal.ai input file exceeds the safety limit") + data = path.read_bytes() + except OSError as exc: + raise ExternalProviderRequestError(f"fal.ai input file could not be read: {exc}") from exc + if len(data) > _DOWNLOAD_MAX_BYTES: + raise ExternalProviderRequestError("fal.ai input file exceeds the safety limit") + + upload_headers = {"Authorization": headers["Authorization"], "Content-Type": "application/json"} + response = self._request( + "POST", + _UPLOAD_URL, + headers=upload_headers, + json={"file_name": filename, "content_type": content_type}, + timeout=_REQUEST_TIMEOUT, + ) + self._raise_for_response(response, "fal.ai upload initiation") + upload = self._parse_json(response, "fal.ai upload initiation response") + file_url = upload.get("file_url") + upload_url = upload.get("upload_url") + if not isinstance(file_url, str) or not file_url or not isinstance(upload_url, str) or not upload_url: + raise ExternalProviderRequestError("fal.ai upload response missing file_url or upload_url") + put_response = self._request( + "PUT", + upload_url, + headers={"Content-Type": content_type}, + data=data, + timeout=_REQUEST_TIMEOUT, + ) + self._raise_for_response(put_response, "fal.ai file upload") + return file_url + + def _upload_image(self, image: PILImageType, filename: str, headers: dict[str, str]) -> str: + upload_headers = {"Authorization": headers["Authorization"], "Content-Type": "application/json"} + response = self._request( + "POST", + _UPLOAD_URL, + headers=upload_headers, + json={"file_name": filename, "content_type": "image/png"}, + timeout=_REQUEST_TIMEOUT, + ) + self._raise_for_response(response, "fal.ai upload initiation") + upload = self._parse_json(response, "fal.ai upload initiation response") + file_url = upload.get("file_url") + upload_url = upload.get("upload_url") + if not isinstance(file_url, str) or not file_url or not isinstance(upload_url, str) or not upload_url: + raise ExternalProviderRequestError("fal.ai upload response missing file_url or upload_url") + + image_bytes = _encode_png(image) + put_response = self._request( + "PUT", + upload_url, + headers={"Content-Type": "image/png"}, + data=image_bytes, + timeout=_REQUEST_TIMEOUT, + ) + self._raise_for_response(put_response, "fal.ai file upload") + return file_url + + def _wait_for_result(self, model_id: str, request_id: str, headers: dict[str, str]) -> dict[str, Any]: + status_url = f"{self._queue_base_url}/{model_id}/requests/{request_id}/status" + result_url = f"{self._queue_base_url}/{model_id}/requests/{request_id}" + deadline = time.monotonic() + _POLL_TIMEOUT + while True: + if time.monotonic() >= deadline: + raise ExternalProviderRequestError(f"fal.ai request {request_id} timed out") + + try: + status_response = self._request("GET", status_url, headers=headers, timeout=_REQUEST_TIMEOUT) + except ExternalProviderRequestError: + # The job already exists. Retrying polling avoids submitting a second billable job. + time.sleep(_POLL_INTERVAL) + continue + if status_response.status_code == 429 or status_response.status_code in _RETRY_STATUS_CODES: + time.sleep(_retry_delay(status_response)) + continue + self._raise_for_response(status_response, "fal.ai status request") + status_payload = self._parse_json(status_response, "fal.ai status response") + status = status_payload.get("status") + if status == "COMPLETED": + try: + result_response = self._request("GET", result_url, headers=headers, timeout=_REQUEST_TIMEOUT) + except ExternalProviderRequestError: + time.sleep(_POLL_INTERVAL) + continue + if result_response.status_code == 429 or result_response.status_code in _RETRY_STATUS_CODES: + time.sleep(_retry_delay(result_response)) + continue + self._raise_for_response(result_response, "fal.ai result request") + return self._parse_json(result_response, "fal.ai result response") + if status in {"FAILED", "CANCELED", "CANCELLED"}: + detail = status_payload.get("error") or status_payload.get("message") or status + raise ExternalProviderRequestError(f"fal.ai request {request_id} failed: {detail}") + if status not in {"IN_QUEUE", "IN_PROGRESS"}: + raise ExternalProviderRequestError(f"fal.ai returned unknown request status: {status}") + time.sleep(_POLL_INTERVAL) + + def _parse_result( + self, + payload: dict[str, Any], + request: ExternalGenerationRequest, + *, + request_id: str | None, + ) -> ExternalGenerationResult: + image_items = _extract_image_items(payload) + seed_value = payload.get("seed") + seed = seed_value if isinstance(seed_value, int) else request.seed + images: list[ExternalGeneratedImage] = [] + for item in image_items: + url = item if isinstance(item, str) else item.get("url") or item.get("image_url") + if isinstance(url, str) and url: + images.append(ExternalGeneratedImage(image=self._download_image(url), seed=seed)) + + if not images: + raise ExternalProviderRequestError("fal.ai response contained no images") + + return ExternalGenerationResult( + images=images, + seed_used=seed, + provider_request_id=request_id, + provider_metadata={"model": request.model.provider_model_id}, + ) + + def _download_image(self, url: str) -> PILImageType: + if url.startswith("data:image/"): + try: + encoded = url.split(",", 1)[1] + return Image.open(io.BytesIO(base64.b64decode(encoded))).convert("RGB") + except (IndexError, ValueError, OSError) as exc: + raise ExternalProviderRequestError("fal.ai returned an invalid image data URI") from exc + if not url.startswith("https://"): + raise ExternalProviderRequestError("fal.ai returned a non-HTTPS image URL") + + response = self._request("GET", url, headers={}, timeout=_DOWNLOAD_TIMEOUT, stream=True) + self._raise_for_response(response, "fal.ai image download") + content_length = response.headers.get("Content-Length") + if content_length: + try: + if int(content_length) > _DOWNLOAD_MAX_BYTES: + raise ExternalProviderRequestError("fal.ai image exceeds the download size limit") + except ValueError: + pass + + buffer = bytearray() + for chunk in response.iter_content(chunk_size=64 * 1024): + if chunk: + buffer.extend(chunk) + if len(buffer) > _DOWNLOAD_MAX_BYTES: + raise ExternalProviderRequestError("fal.ai image exceeds the download size limit") + try: + return Image.open(io.BytesIO(bytes(buffer))).convert("RGB") + except OSError as exc: + raise ExternalProviderRequestError("fal.ai returned invalid image data") from exc + + @staticmethod + def _has_images(payload: dict[str, Any]) -> bool: + return bool(_extract_image_items(payload)) + + @staticmethod + def _parse_json(response: requests.Response, label: str) -> dict[str, Any]: + try: + payload = response.json() + except ValueError as exc: + raise ExternalProviderRequestError(f"{label} was not valid JSON") from exc + if not isinstance(payload, dict): + raise ExternalProviderRequestError(f"{label} was not a JSON object") + return payload + + @staticmethod + def _request(method: str, url: str, **kwargs: Any) -> requests.Response: + try: + if method == "POST": + return requests.post(url, **kwargs) + if method == "PUT": + return requests.put(url, **kwargs) + return requests.get(url, **kwargs) + except requests.RequestException as exc: + raise ExternalProviderRequestError(f"fal.ai network request failed: {exc}") from exc + + @staticmethod + def _raise_for_response(response: requests.Response, operation: str) -> None: + if response.ok: + return + if response.status_code == 429: + retry_after = _parse_retry_after(response.headers.get("Retry-After")) + detail = f" Retry after {retry_after:.0f}s." if retry_after is not None else "" + raise ExternalProviderRateLimitError(f"fal.ai rate limit exceeded.{detail}", retry_after=retry_after) + if response.status_code in _RETRY_STATUS_CODES: + raise ExternalProviderRequestError(f"{operation} failed with status {response.status_code}; retry later") + raise ExternalProviderRequestError(f"{operation} failed with status {response.status_code}: {response.text}") + + +def _validate_endpoint_id(model_id: str) -> None: + if not model_id or model_id.startswith("/") or ".." in model_id.split("/"): + raise ExternalProviderRequestError("fal.ai endpoint ID is invalid") + + +def _expand_media_placeholders(value: Any, media: dict[str, Any]) -> Any: + if isinstance(value, dict): + return {key: _expand_media_placeholders(item, media) for key, item in value.items()} + if isinstance(value, list): + return [_expand_media_placeholders(item, media) for item in value] + if not isinstance(value, str): + return value + for name, replacement in media.items(): + if value == "${" + name + "}": + return replacement + for name, replacement in media.items(): + if isinstance(replacement, str): + value = value.replace("${" + name + "}", replacement) + return value + + +def _extract_image_items(payload: dict[str, Any]) -> list[Any]: + for key in ("images", "data"): + value = payload.get(key) + if isinstance(value, list) and value: + return value + for key in ("image", "image_url"): + value = payload.get(key) + if isinstance(value, (str, dict)): + return [value] + image_urls = payload.get("image_urls") + if isinstance(image_urls, list): + return image_urls + + # Segmentation and preprocessing endpoints use named mask/depth/image fields instead of `images`. + items: list[Any] = [] + for key, value in payload.items(): + key_lower = str(key).lower() + if any(term in key_lower for term in ("image", "mask", "depth", "normal", "matte")): + items.extend(_extract_media_items(value)) + return items + + +def _extract_media_items(value: Any) -> list[Any]: + if isinstance(value, str): + return [value] + if isinstance(value, dict): + if isinstance(value.get("url"), str) or isinstance(value.get("image_url"), str): + return [value] + items: list[Any] = [] + for nested in value.values(): + items.extend(_extract_media_items(nested)) + return items + if isinstance(value, list): + items: list[Any] = [] + for nested in value: + items.extend(_extract_media_items(nested)) + return items + return [] + + +def _encode_png(image: PILImageType) -> bytes: + buffer = io.BytesIO() + image.save(buffer, format="PNG") + return buffer.getvalue() + + +def _select_aspect_ratio(width: int, height: int, allowed: list[str] | None) -> str: + if width <= 0 or height <= 0: + return "1:1" + ratio = width / height + candidates = allowed or list(_IMAGE_SIZE_BY_RATIO) + parsed = [(value, _parse_ratio(value)) for value in candidates] + valid = [(value, value_ratio) for value, value_ratio in parsed if value_ratio is not None] + if not valid: + return "1:1" + return min(valid, key=lambda pair: abs(pair[1] - ratio))[0] + + +def _image_size_for_ratio(ratio: str) -> str: + return _IMAGE_SIZE_BY_RATIO.get(ratio, "square_hd") + + +def _parse_ratio(value: str) -> float | None: + try: + left, right = value.split(":", 1) + denominator = float(right) + if denominator == 0: + return None + return float(left) / denominator + except (ValueError, AttributeError): + return None + + +def _parse_retry_after(value: str | None) -> float | None: + if not value: + return None + try: + return float(value) + except ValueError: + return None + + +def _retry_delay(response: requests.Response) -> float: + retry_after = _parse_retry_after(response.headers.get("Retry-After")) + return min(retry_after if retry_after is not None else _POLL_INTERVAL, 60.0) + + +def build_schema_payload( + request: ExternalGenerationRequest, + schema: FalEndpointSchema, + *, + image_url: str | None, + mask_url: str | None, + reference_urls: list[str] | None = None, +) -> dict[str, Any]: + """Build payload for one endpoint using only fields declared by its OpenAPI schema.""" + properties = schema.input_schema.get("properties", {}) + if not isinstance(properties, dict): + properties = {} + advanced = request.provider_options.get("advanced", {}) if request.provider_options else {} + payload = ( + {name: value for name, value in advanced.items() if name in schema.public_properties and name in properties} + if isinstance(advanced, dict) + else {} + ) + + ratio = _select_aspect_ratio(request.width, request.height, request.model.capabilities.allowed_aspect_ratios) + common_values: dict[str, Any] = { + "prompt": request.prompt, + "seed": request.seed, + "num_images": request.num_images if request.num_images > 1 else None, + "width": request.width, + "height": request.height, + "aspect_ratio": ratio, + "image_size": request.image_size or _image_size_for_ratio(ratio), + "init_image": image_url, + "mask_image": mask_url, + "reference_images": ([image_url] if image_url else []) + (reference_urls or []), + } + for common_name, value in common_values.items(): + field_name = schema.common_fields.get(common_name) + if not field_name or value is None: + continue + property_schema = properties.get(field_name) + if not isinstance(property_schema, dict): + continue + if common_name == "image_size" and not _value_is_allowed(value, property_schema): + continue + if common_name == "aspect_ratio" and not _value_is_allowed(value, property_schema): + continue + if common_name == "reference_images": + if not value: + continue + if property_schema.get("type") != "array": + value = value[0] + elif common_name in {"init_image", "mask_image"} and property_schema.get("type") == "array": + value = [value] + payload[field_name] = value + return payload + + +def _value_is_allowed(value: Any, property_schema: dict[str, Any]) -> bool: + allowed = property_schema.get("enum") + return not isinstance(allowed, list) or value in allowed + + +__all__ = ["FalProvider", "build_schema_payload"] diff --git a/invokeai/app/services/external_generation/providers/fal_catalog.py b/invokeai/app/services/external_generation/providers/fal_catalog.py new file mode 100644 index 00000000000..171a72d1f29 --- /dev/null +++ b/invokeai/app/services/external_generation/providers/fal_catalog.py @@ -0,0 +1,369 @@ +from __future__ import annotations + +import copy +from dataclasses import dataclass +from enum import StrEnum +from typing import Any + +import requests + +from invokeai.app.services.external_generation.errors import ( + ExternalProviderRateLimitError, + ExternalProviderRequestError, +) + +_DEFAULT_CATALOG_URL = "https://api.fal.ai" +_DEFAULT_SCHEMA_URL = "https://fal.ai/api/openapi/queue/openapi.json" +_CATALOG_TIMEOUT = 30 +_MAX_PAGE_SIZE = 100 + + +class FalEndpointKind(StrEnum): + TEXT_TO_IMAGE = "text-to-image" + IMAGE_TO_IMAGE = "image-to-image" + INPAINT = "inpaint" + UPSCALE = "upscale" + TEXT_TO_VIDEO = "text-to-video" + IMAGE_TO_VIDEO = "image-to-video" + VIDEO_TO_VIDEO = "video-to-video" + AUDIO = "audio" + GENERIC = "generic" + + +@dataclass(frozen=True) +class FalCatalogModel: + endpoint_id: str + display_name: str + description: str + category: str + model_url: str | None + thumbnail_url: str | None + tags: tuple[str, ...] + + +@dataclass(frozen=True) +class FalCatalogPage: + models: list[FalCatalogModel] + next_cursor: str | None + has_more: bool + + +@dataclass(frozen=True) +class FalEndpointSchema: + endpoint_id: str + kind: FalEndpointKind + output_kind: FalEndpointKind + category: str + input_schema: dict[str, Any] + output_schema: dict[str, Any] + common_fields: dict[str, str] + public_properties: tuple[str, ...] + + +class FalCatalogClient: + """Read fal.ai's model catalog and per-endpoint OpenAPI schemas.""" + + def __init__( + self, + api_key: str, + *, + catalog_url: str = _DEFAULT_CATALOG_URL, + schema_url: str = _DEFAULT_SCHEMA_URL, + ) -> None: + self._api_key = api_key + self._catalog_url = catalog_url.rstrip("/") + self._schema_url = schema_url + + def list_models( + self, + *, + limit: int = 50, + cursor: str | None = None, + search: str | None = None, + ) -> FalCatalogPage: + page_size = min(max(limit, 1), _MAX_PAGE_SIZE) + if search: + return self._search_models(search, limit=page_size, cursor=cursor) + return self._list_models_page(limit=page_size, cursor=cursor) + + def _list_models_page(self, *, limit: int, cursor: str | None) -> FalCatalogPage: + params: dict[str, Any] = {"limit": limit} + if cursor: + params["cursor"] = cursor + + response = self._get(f"{self._catalog_url}/v1/models", params=params) + payload = self._parse_object(response, "fal.ai catalog response") + raw_models = payload.get("models") + if not isinstance(raw_models, list): + raise ExternalProviderRequestError("fal.ai catalog response missing models") + + models: list[FalCatalogModel] = [] + for raw_model in raw_models: + if not isinstance(raw_model, dict): + continue + endpoint_id = raw_model.get("endpoint_id") + metadata = raw_model.get("metadata") + if not isinstance(endpoint_id, str) or not endpoint_id or not isinstance(metadata, dict): + continue + models.append( + FalCatalogModel( + endpoint_id=endpoint_id, + display_name=_string_or_default(metadata.get("display_name"), endpoint_id.rsplit("/", 1)[-1]), + description=_string_or_default(metadata.get("description"), ""), + category=_string_or_default(metadata.get("category"), "generic"), + model_url=_optional_string(metadata.get("model_url")), + thumbnail_url=_optional_string(metadata.get("thumbnail_url")), + tags=tuple(value for value in metadata.get("tags", []) if isinstance(value, str)), + ) + ) + + next_cursor = payload.get("next_cursor") + return FalCatalogPage( + models=models, + next_cursor=next_cursor if isinstance(next_cursor, str) and next_cursor else None, + has_more=bool(payload.get("has_more")), + ) + + def _search_models(self, search: str, *, limit: int, cursor: str | None) -> FalCatalogPage: + needle = search.casefold().strip() + if not needle: + return self._list_models_page(limit=limit, cursor=cursor) + + matches: list[FalCatalogModel] = [] + next_cursor = cursor + has_more = True + while has_more and len(matches) < limit: + page = self._list_models_page(limit=_MAX_PAGE_SIZE, cursor=next_cursor) + for model in page.models: + haystack = " ".join( + [model.endpoint_id, model.display_name, model.description, model.category, *model.tags] + ).casefold() + if needle in haystack: + matches.append(model) + if len(matches) == limit: + break + has_more = page.has_more and page.next_cursor is not None + next_cursor = page.next_cursor + + return FalCatalogPage( + models=matches, + next_cursor=next_cursor if has_more else None, + has_more=has_more, + ) + + def get_schema(self, endpoint_id: str) -> FalEndpointSchema: + if not endpoint_id or endpoint_id.startswith("/") or ".." in endpoint_id.split("/"): + raise ExternalProviderRequestError("fal.ai catalog endpoint ID is invalid") + response = self._get(self._schema_url, params={"endpoint_id": endpoint_id}) + payload = self._parse_object(response, "fal.ai catalog schema response") + return normalize_openapi_schema(endpoint_id, payload) + + def _get(self, url: str, *, params: dict[str, Any]) -> requests.Response: + try: + response = requests.get( + url, + headers={"Authorization": f"Key {self._api_key}"}, + params=params, + timeout=_CATALOG_TIMEOUT, + ) + except requests.RequestException as exc: + raise ExternalProviderRequestError(f"fal.ai catalog network request failed: {exc}") from exc + if response.status_code == 429: + retry_after = _parse_retry_after(response.headers.get("Retry-After")) + raise ExternalProviderRateLimitError("fal.ai catalog rate limit exceeded", retry_after=retry_after) + if not response.ok: + raise ExternalProviderRequestError( + f"fal.ai catalog request failed with status {response.status_code}: {response.text}" + ) + return response + + @staticmethod + def _parse_object(response: requests.Response, label: str) -> dict[str, Any]: + try: + payload = response.json() + except ValueError as exc: + raise ExternalProviderRequestError(f"{label} was not valid JSON") from exc + if not isinstance(payload, dict): + raise ExternalProviderRequestError(f"{label} was not a JSON object") + return payload + + +def normalize_openapi_schema(endpoint_id: str, document: dict[str, Any]) -> FalEndpointSchema: + """Extract safe request/output metadata from one fal.ai queue OpenAPI document.""" + components = document.get("components", {}).get("schemas", {}) + if not isinstance(components, dict): + components = {} + + metadata = document.get("info", {}).get("x-fal-metadata", {}) + if not isinstance(metadata, dict): + metadata = {} + category = _string_or_default(metadata.get("category"), "generic") + + paths = document.get("paths") + if not isinstance(paths, dict): + raise ExternalProviderRequestError("fal.ai catalog schema response missing paths") + operation = next( + ( + item.get("post") + for path, item in paths.items() + if isinstance(path, str) + and "/requests/" not in path + and isinstance(item, dict) + and isinstance(item.get("post"), dict) + ), + None, + ) + if not isinstance(operation, dict): + raise ExternalProviderRequestError("fal.ai catalog schema response missing queue POST operation") + + request_schema = _resolve_schema( + operation.get("requestBody", {}).get("content", {}).get("application/json", {}).get("schema"), components + ) + if request_schema.get("type") != "object" or not isinstance(request_schema.get("properties"), dict): + raise ExternalProviderRequestError("fal.ai catalog endpoint has no object input schema") + + output_schema = _find_output_schema(components) + kind = classify_endpoint(category, request_schema, endpoint_id=endpoint_id) + output_kind = _classify_output_schema(output_schema, fallback=kind) + properties = request_schema["properties"] + public_properties = tuple( + name + for name, value in properties.items() + if isinstance(name, str) and isinstance(value, dict) and not value.get("writeOnly") + ) + + return FalEndpointSchema( + endpoint_id=endpoint_id, + kind=kind, + output_kind=output_kind, + category=category, + input_schema=request_schema, + output_schema=output_schema, + common_fields=_find_common_fields(properties), + public_properties=public_properties, + ) + + +def classify_endpoint(category: str, schema: dict[str, Any], *, endpoint_id: str = "") -> FalEndpointKind: + normalized = category.strip().lower().replace("_", "-") + endpoint = endpoint_id.lower() + if "upscal" in normalized or "upscal" in endpoint: + return FalEndpointKind.UPSCALE + if normalized in {"text-to-image", "text2image", "text-to-img"}: + return FalEndpointKind.TEXT_TO_IMAGE + if "segment" in normalized or "segment" in endpoint: + return FalEndpointKind.IMAGE_TO_IMAGE + if normalized in {"image-to-image", "image-editing", "image-edit", "inpainting", "inpaint"}: + properties = schema.get("properties", {}) + has_mask = isinstance(properties, dict) and any("mask" in str(name).lower() for name in properties) + return ( + FalEndpointKind.INPAINT + if "inpaint" in normalized or "inpaint" in endpoint or "fill" in endpoint or has_mask + else FalEndpointKind.IMAGE_TO_IMAGE + ) + if normalized in {"text-to-video", "text2video"}: + return FalEndpointKind.TEXT_TO_VIDEO + if normalized in {"image-to-video", "image2video"}: + return FalEndpointKind.IMAGE_TO_VIDEO + if normalized in {"video-to-video", "video2video", "video-editing", "video-edit"}: + return FalEndpointKind.VIDEO_TO_VIDEO + if normalized.startswith("audio") or normalized.endswith("-to-audio"): + return FalEndpointKind.AUDIO + properties = schema.get("properties", {}) + if isinstance(properties, dict) and any("video" in str(name).lower() for name in properties): + return FalEndpointKind.IMAGE_TO_VIDEO + return FalEndpointKind.GENERIC + + +def _find_output_schema(components: dict[str, Any]) -> dict[str, Any]: + for name, schema in components.items(): + if isinstance(name, str) and name.lower().endswith("output") and isinstance(schema, dict): + resolved = _resolve_schema(schema, components) + if resolved.get("type") == "object": + return resolved + return {} + + +def _classify_output_schema(schema: dict[str, Any], *, fallback: FalEndpointKind) -> FalEndpointKind: + properties = schema.get("properties", {}) + if not isinstance(properties, dict): + return fallback + names = " ".join(str(name).lower() for name in properties) + if "video" in names: + return FalEndpointKind.IMAGE_TO_VIDEO + if "audio" in names or "speech" in names: + return FalEndpointKind.AUDIO + if "image" in names: + return FalEndpointKind.IMAGE_TO_IMAGE + return fallback + + +def _find_common_fields(properties: dict[str, Any]) -> dict[str, str]: + aliases = { + "prompt": ("prompt", "text", "input_text"), + "negative_prompt": ("negative_prompt", "negative_prompt_text"), + "init_image": ( + "image_url", + "input_image_url", + "input_image", + "start_image_url", + "first_frame_url", + ), + "reference_images": ("reference_image_urls", "reference_urls", "image_urls"), + "mask_image": ("mask_url", "mask_image_url", "mask"), + "init_video": ("video_url", "video_urls", "input_video_url"), + "seed": ("seed",), + "num_images": ("num_images", "num_outputs", "num_inference_images"), + "width": ("width", "output_width"), + "height": ("height", "output_height"), + "aspect_ratio": ("aspect_ratio",), + "image_size": ("image_size", "resolution", "output_size"), + "duration": ("duration", "video_length", "num_frames"), + "fps": ("fps", "frame_rate"), + } + return { + common_name: next((candidate for candidate in candidates if candidate in properties), "") + for common_name, candidates in aliases.items() + if any(candidate in properties for candidate in candidates) + } + + +def _resolve_schema(schema: Any, components: dict[str, Any]) -> dict[str, Any]: + if not isinstance(schema, dict): + return {} + resolved = copy.deepcopy(schema) + reference = resolved.pop("$ref", None) + if isinstance(reference, str) and reference.startswith("#/components/schemas/"): + target = components.get(reference.removeprefix("#/components/schemas/")) + if isinstance(target, dict): + resolved = _resolve_schema(target, components) + resolved.update({key: value for key, value in schema.items() if key != "$ref"}) + return resolved + + +def _string_or_default(value: Any, default: str) -> str: + return value if isinstance(value, str) and value else default + + +def _optional_string(value: Any) -> str | None: + return value if isinstance(value, str) and value else None + + +def _parse_retry_after(value: str | None) -> float | None: + if not value: + return None + try: + return float(value) + except ValueError: + return None + + +__all__ = [ + "FalCatalogClient", + "FalCatalogModel", + "FalCatalogPage", + "FalEndpointKind", + "FalEndpointSchema", + "classify_endpoint", + "normalize_openapi_schema", +] diff --git a/invokeai/app/services/model_install/model_install_default.py b/invokeai/app/services/model_install/model_install_default.py index 5b7337d5870..45d8f05c20f 100644 --- a/invokeai/app/services/model_install/model_install_default.py +++ b/invokeai/app/services/model_install/model_install_default.py @@ -1043,10 +1043,12 @@ def _register_external_model(self, job: ModelInstallJob) -> None: key=key, name=name, description=job.config_in.description, + source_url=job.config_in.source_url, provider_id=provider_id, provider_model_id=provider_model_id, capabilities=capabilities, default_settings=default_settings, + panel_schema=job.config_in.panel_schema, source=str(job.source), source_type=MODEL_SOURCE_TO_TYPE_MAP[job.source.__class__], path="", diff --git a/invokeai/app/services/model_records/model_records_base.py b/invokeai/app/services/model_records/model_records_base.py index 41f098addcb..6c281646b9c 100644 --- a/invokeai/app/services/model_records/model_records_base.py +++ b/invokeai/app/services/model_records/model_records_base.py @@ -17,6 +17,7 @@ from invokeai.backend.model_manager.configs.external_api import ( ExternalApiModelDefaultSettings, ExternalModelCapabilities, + ExternalModelPanelSchema, ) from invokeai.backend.model_manager.configs.factory import AnyModelConfig from invokeai.backend.model_manager.configs.lora import LoraModelDefaultSettings @@ -128,6 +129,10 @@ def validate_source_url(cls, v: Any) -> Optional[str]: description="External model capabilities", default=None, ) + panel_schema: Optional[ExternalModelPanelSchema] = Field( + description="External model controls exposed in the frontend", + default=None, + ) cpu_only: Optional[bool] = Field(description="Whether this model should run on CPU only", default=None) # Checkpoint-specific changes diff --git a/invokeai/backend/model_manager/starter_models.py b/invokeai/backend/model_manager/starter_models.py index eb8812f02f5..f82de10877b 100644 --- a/invokeai/backend/model_manager/starter_models.py +++ b/invokeai/backend/model_manager/starter_models.py @@ -2278,9 +2278,94 @@ def _gemini_3_resolution_presets( ) # endregion +FAL_IMAGE_ASPECT_RATIOS = ["1:1", "4:3", "3:4", "16:9", "9:16"] +FAL_IMAGE_ASPECT_RATIO_SIZES = { + "1:1": ExternalImageSize(width=1024, height=1024), + "4:3": ExternalImageSize(width=1152, height=864), + "3:4": ExternalImageSize(width=864, height=1152), + "16:9": ExternalImageSize(width=1280, height=720), + "9:16": ExternalImageSize(width=720, height=1280), +} + +fal_flux_schnell = StarterModel( + name="FLUX.1 [schnell] (fal.ai)", + base=BaseModelType.External, + source="external://fal/fal-ai/flux/schnell", + description="fal.ai-hosted FLUX.1 [schnell] image generation. Requires a fal.ai API key; usage may incur provider-side costs.", + type=ModelType.ExternalImageGenerator, + format=ModelFormat.ExternalApi, + capabilities=ExternalModelCapabilities( + modes=["txt2img"], + supports_negative_prompt=False, + supports_seed=True, + max_images_per_request=4, + allowed_aspect_ratios=FAL_IMAGE_ASPECT_RATIOS, + aspect_ratio_sizes=FAL_IMAGE_ASPECT_RATIO_SIZES, + ), + default_settings=ExternalApiModelDefaultSettings(width=1024, height=1024, num_images=1), +) +fal_flux_dev = StarterModel( + name="FLUX.1 [dev] (fal.ai)", + base=BaseModelType.External, + source="external://fal/fal-ai/flux/dev", + description="fal.ai-hosted FLUX.1 [dev] image generation. Requires a fal.ai API key; usage may incur provider-side costs.", + type=ModelType.ExternalImageGenerator, + format=ModelFormat.ExternalApi, + capabilities=ExternalModelCapabilities( + modes=["txt2img"], + supports_negative_prompt=False, + supports_seed=True, + max_images_per_request=4, + allowed_aspect_ratios=FAL_IMAGE_ASPECT_RATIOS, + aspect_ratio_sizes=FAL_IMAGE_ASPECT_RATIO_SIZES, + ), + default_settings=ExternalApiModelDefaultSettings(width=1024, height=1024, num_images=1), +) +fal_flux_kontext = StarterModel( + name="FLUX.1 Kontext [pro] (fal.ai)", + base=BaseModelType.External, + source="external://fal/fal-ai/flux-pro/kontext", + description="fal.ai-hosted FLUX.1 Kontext [pro] image editing. Requires a fal.ai API key; usage may incur provider-side costs.", + type=ModelType.ExternalImageGenerator, + format=ModelFormat.ExternalApi, + capabilities=ExternalModelCapabilities( + modes=["img2img"], + supports_negative_prompt=False, + supports_seed=True, + max_images_per_request=4, + allowed_aspect_ratios=FAL_IMAGE_ASPECT_RATIOS, + aspect_ratio_sizes=FAL_IMAGE_ASPECT_RATIO_SIZES, + input_image_required_for=["img2img"], + ), + default_settings=ExternalApiModelDefaultSettings(width=1024, height=1024, num_images=1), +) +fal_flux_fill = StarterModel( + name="FLUX.1 Fill (fal.ai)", + base=BaseModelType.External, + source="external://fal/fal-ai/flux-lora-fill", + description="fal.ai-hosted FLUX Fill image inpainting. Requires a fal.ai API key; usage may incur provider-side costs.", + type=ModelType.ExternalImageGenerator, + format=ModelFormat.ExternalApi, + capabilities=ExternalModelCapabilities( + modes=["inpaint"], + supports_negative_prompt=False, + supports_seed=True, + max_images_per_request=4, + allowed_aspect_ratios=FAL_IMAGE_ASPECT_RATIOS, + aspect_ratio_sizes=FAL_IMAGE_ASPECT_RATIO_SIZES, + mask_format="binary", + input_image_required_for=["inpaint"], + ), + default_settings=ExternalApiModelDefaultSettings(width=1024, height=1024, num_images=1), +) + # List of starter models, displayed on the frontend. # The order/sort of this list is not changed by the frontend - set it how you want it here. STARTER_MODELS: list[StarterModel] = [ + fal_flux_schnell, + fal_flux_dev, + fal_flux_kontext, + fal_flux_fill, flux_kontext_quantized, flux_schnell_quantized, flux_dev_quantized, diff --git a/invokeai/frontend/web/openapi.json b/invokeai/frontend/web/openapi.json index ccd0f318694..c20a77d6aa8 100644 --- a/invokeai/frontend/web/openapi.json +++ b/invokeai/frontend/web/openapi.json @@ -9248,6 +9248,179 @@ ] } }, + "/api/v1/app/external_providers/fal/models": { + "get": { + "tags": ["app"], + "summary": "List Fal Models", + "operationId": "list_fal_models", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 50, + "title": "Limit" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "maxLength": 4096 + }, + { + "type": "null" + } + ], + "title": "Cursor" + } + }, + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "maxLength": 200 + }, + { + "type": "null" + } + ], + "title": "Search" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FalCatalogResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/app/external_providers/fal/models/{endpoint_id}/schema": { + "get": { + "tags": ["app"], + "summary": "Get Fal Model Schema", + "operationId": "get_fal_model_schema", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "endpoint_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "description": "fal.ai endpoint identifier", + "title": "Endpoint Id" + }, + "description": "fal.ai endpoint identifier" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FalEndpointSchemaResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/app/external_providers/fal/models/install": { + "post": { + "tags": ["app"], + "summary": "Install Fal Model", + "operationId": "install_fal_model", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FalModelInstallRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelInstallJob" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBearer": [] + } + ] + } + }, "/api/v1/queue/{queue_id}/enqueue_batch": { "post": { "tags": ["queue"], @@ -28532,17 +28705,582 @@ "title": "FaceOffOutput", "type": "object" }, - "FieldKind": { - "description": "The kind of field.\n- `Input`: An input field on a node.\n- `Output`: An output field on a node.\n- `Internal`: A field which is treated as an input, but cannot be used in node definitions. Metadata is\none example. It is provided to nodes via the WithMetadata class, and we want to reserve the field name\n\"metadata\" for this on all nodes. `FieldKind` is used to short-circuit the field name validation logic,\nallowing \"metadata\" for that field.\n- `NodeAttribute`: The field is a node attribute. These are fields which are not inputs or outputs,\nbut which are used to store information about the node. For example, the `id` and `type` fields are node\nattributes.\n\nThe presence of this in `json_schema_extra[\"field_kind\"]` is used when initializing node schemas on app\nstartup, and when generating the OpenAPI schema for the workflow editor.", - "enum": ["input", "output", "internal", "node_attribute"], - "title": "FieldKind", - "type": "string" + "FalCatalogModelResponse": { + "properties": { + "endpoint_id": { + "type": "string", + "title": "Endpoint Id" + }, + "display_name": { + "type": "string", + "title": "Display Name" + }, + "description": { + "type": "string", + "title": "Description" + }, + "category": { + "type": "string", + "title": "Category" + }, + "kind": { + "$ref": "#/components/schemas/FalEndpointKind" + }, + "model_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model Url" + }, + "thumbnail_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Thumbnail Url" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Tags" + }, + "installed": { + "type": "boolean", + "title": "Installed" + } + }, + "type": "object", + "required": [ + "endpoint_id", + "display_name", + "description", + "category", + "kind", + "model_url", + "thumbnail_url", + "tags", + "installed" + ], + "title": "FalCatalogModelResponse" }, - "FloatBatchInvocation": { - "category": "batch", + "FalCatalogResponse": { + "properties": { + "models": { + "items": { + "$ref": "#/components/schemas/FalCatalogModelResponse" + }, + "type": "array", + "title": "Models" + }, + "next_cursor": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Next Cursor" + }, + "has_more": { + "type": "boolean", + "title": "Has More" + } + }, + "type": "object", + "required": ["models", "next_cursor", "has_more"], + "title": "FalCatalogResponse" + }, + "FalEndpointKind": { + "type": "string", + "enum": [ + "text-to-image", + "image-to-image", + "inpaint", + "upscale", + "text-to-video", + "image-to-video", + "video-to-video", + "audio", + "generic" + ], + "title": "FalEndpointKind" + }, + "FalEndpointSchemaResponse": { + "properties": { + "endpoint_id": { + "type": "string", + "title": "Endpoint Id" + }, + "kind": { + "$ref": "#/components/schemas/FalEndpointKind" + }, + "output_kind": { + "$ref": "#/components/schemas/FalEndpointKind" + }, + "category": { + "type": "string", + "title": "Category" + }, + "input_schema": { + "additionalProperties": true, + "type": "object", + "title": "Input Schema" + }, + "output_schema": { + "additionalProperties": true, + "type": "object", + "title": "Output Schema" + }, + "common_fields": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "title": "Common Fields" + }, + "public_properties": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Public Properties" + } + }, + "type": "object", + "required": [ + "endpoint_id", + "kind", + "output_kind", + "category", + "input_schema", + "output_schema", + "common_fields", + "public_properties" + ], + "title": "FalEndpointSchemaResponse" + }, + "FalGenericMediaInvocation": { + "category": "media", "class": "invocation", - "classification": "special", - "description": "Create a batched generation, where the workflow is executed once for each float in the batch.", + "classification": "stable", + "description": "Submit arbitrary JSON to any fal.ai endpoint and return its raw JSON result.", + "node_pack": "invokeai", + "properties": { + "id": { + "description": "The id of this instance of an invocation. Must be unique among all instances of invocations.", + "field_kind": "node_attribute", + "title": "Id", + "type": "string" + }, + "is_intermediate": { + "default": false, + "description": "Whether or not this is an intermediate invocation.", + "field_kind": "node_attribute", + "input": "direct", + "orig_required": true, + "title": "Is Intermediate", + "type": "boolean", + "ui_hidden": false, + "ui_type": "IsIntermediate" + }, + "use_cache": { + "default": true, + "description": "Whether or not to use the cache", + "field_kind": "node_attribute", + "title": "Use Cache", + "type": "boolean" + }, + "model_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "fal.ai endpoint ID, for example fal-ai/kling-video/v3/pro/text-to-video", + "field_kind": "input", + "input": "any", + "orig_required": true, + "title": "Model Id" + }, + "input_json": { + "default": "{}", + "description": "JSON object sent to the fal.ai endpoint", + "field_kind": "input", + "input": "any", + "orig_default": "{}", + "orig_required": false, + "title": "Input Json", + "type": "string" + }, + "image": { + "anyOf": [ + { + "$ref": "#/components/schemas/ImageField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Optional local image for ${image_url} placeholders", + "field_kind": "input", + "input": "any", + "orig_default": null, + "orig_required": false + }, + "mask": { + "anyOf": [ + { + "$ref": "#/components/schemas/ImageField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Optional local mask for ${mask_url} placeholders", + "field_kind": "input", + "input": "any", + "orig_default": null, + "orig_required": false + }, + "reference_images": { + "default": [], + "description": "Optional local images for ${image_urls} or ${reference_image_urls} placeholders", + "field_kind": "input", + "input": "any", + "items": { + "$ref": "#/components/schemas/ImageField" + }, + "orig_default": [], + "orig_required": false, + "title": "Reference Images", + "type": "array" + }, + "video": { + "anyOf": [ + { + "$ref": "#/components/schemas/VideoField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Optional local video for ${video_url} placeholders", + "field_kind": "input", + "input": "any", + "orig_default": null, + "orig_required": false + }, + "type": { + "const": "fal_generic_media_native", + "default": "fal_generic_media_native", + "field_kind": "node_attribute", + "title": "type", + "type": "string" + } + }, + "required": ["type", "id"], + "tags": ["external", "generation", "fal", "fal.ai", "generic"], + "title": "fal.ai Generic Media", + "type": "object", + "version": "1.0.0", + "output": { + "$ref": "#/components/schemas/StringOutput" + } + }, + "FalImageGenerationInvocation": { + "category": "image", + "class": "invocation", + "classification": "stable", + "description": "Generate or edit images using a fal.ai-hosted model.", + "node_pack": "invokeai", + "properties": { + "board": { + "anyOf": [ + { + "$ref": "#/components/schemas/BoardField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "The board to save the image to", + "field_kind": "internal", + "input": "direct", + "orig_required": false, + "ui_hidden": false + }, + "metadata": { + "anyOf": [ + { + "$ref": "#/components/schemas/MetadataField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Optional metadata to be saved with the image", + "field_kind": "internal", + "input": "connection", + "orig_required": false, + "ui_hidden": false + }, + "id": { + "description": "The id of this instance of an invocation. Must be unique among all instances of invocations.", + "field_kind": "node_attribute", + "title": "Id", + "type": "string" + }, + "is_intermediate": { + "default": false, + "description": "Whether or not this is an intermediate invocation.", + "field_kind": "node_attribute", + "input": "direct", + "orig_required": true, + "title": "Is Intermediate", + "type": "boolean", + "ui_hidden": false, + "ui_type": "IsIntermediate" + }, + "use_cache": { + "default": true, + "description": "Whether or not to use the cache", + "field_kind": "node_attribute", + "title": "Use Cache", + "type": "boolean" + }, + "model": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelIdentifierField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Main model (UNet, VAE, CLIP) to load", + "field_kind": "input", + "input": "any", + "orig_required": true, + "ui_model_base": ["external"], + "ui_model_format": ["external_api"], + "ui_model_provider_id": ["fal"], + "ui_model_type": ["external_image_generator"] + }, + "mode": { + "default": "txt2img", + "description": "Generation mode. Not all modes are supported by every model; unsupported modes raise at runtime.", + "enum": ["txt2img", "img2img", "inpaint"], + "field_kind": "input", + "input": "any", + "orig_default": "txt2img", + "orig_required": false, + "title": "Mode", + "type": "string" + }, + "prompt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Prompt", + "field_kind": "input", + "input": "any", + "orig_required": true, + "title": "Prompt" + }, + "seed": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Seed for random number generation", + "field_kind": "input", + "input": "any", + "orig_default": null, + "orig_required": false, + "title": "Seed" + }, + "num_images": { + "default": 1, + "description": "Number of images to generate", + "exclusiveMinimum": 0, + "field_kind": "input", + "input": "any", + "orig_default": 1, + "orig_required": false, + "title": "Num Images", + "type": "integer" + }, + "width": { + "default": 1024, + "description": "Width of output (px)", + "exclusiveMinimum": 0, + "field_kind": "input", + "input": "any", + "orig_default": 1024, + "orig_required": false, + "title": "Width", + "type": "integer" + }, + "height": { + "default": 1024, + "description": "Height of output (px)", + "exclusiveMinimum": 0, + "field_kind": "input", + "input": "any", + "orig_default": 1024, + "orig_required": false, + "title": "Height", + "type": "integer" + }, + "image_size": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Image size preset (e.g. 1K, 2K, 4K)", + "field_kind": "input", + "input": "any", + "orig_default": null, + "orig_required": false, + "title": "Image Size" + }, + "init_image": { + "anyOf": [ + { + "$ref": "#/components/schemas/ImageField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Init image for img2img/inpaint", + "field_kind": "input", + "input": "any", + "orig_default": null, + "orig_required": false + }, + "mask_image": { + "anyOf": [ + { + "$ref": "#/components/schemas/ImageField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Mask image for inpaint", + "field_kind": "input", + "input": "any", + "orig_default": null, + "orig_required": false + }, + "reference_images": { + "default": [], + "description": "Reference images", + "field_kind": "input", + "input": "any", + "items": { + "$ref": "#/components/schemas/ImageField" + }, + "orig_default": [], + "orig_required": false, + "title": "Reference Images", + "type": "array" + }, + "advanced_options": { + "additionalProperties": true, + "default": {}, + "description": "Additional JSON fields declared by the selected fal.ai endpoint", + "field_kind": "input", + "input": "any", + "orig_default": {}, + "orig_required": false, + "title": "Advanced Options", + "type": "object" + }, + "type": { + "const": "fal_image_generation", + "default": "fal_image_generation", + "field_kind": "node_attribute", + "title": "type", + "type": "string" + } + }, + "required": ["type", "id"], + "tags": ["external", "generation", "fal", "fal.ai"], + "title": "fal.ai Image Generation", + "type": "object", + "version": "1.0.0", + "output": { + "$ref": "#/components/schemas/ImageCollectionOutput" + } + }, + "FalModelInstallRequest": { + "properties": { + "endpoint_id": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "title": "Endpoint Id", + "description": "fal.ai endpoint identifier" + } + }, + "type": "object", + "required": ["endpoint_id"], + "title": "FalModelInstallRequest" + }, + "FieldKind": { + "description": "The kind of field.\n- `Input`: An input field on a node.\n- `Output`: An output field on a node.\n- `Internal`: A field which is treated as an input, but cannot be used in node definitions. Metadata is\none example. It is provided to nodes via the WithMetadata class, and we want to reserve the field name\n\"metadata\" for this on all nodes. `FieldKind` is used to short-circuit the field name validation logic,\nallowing \"metadata\" for that field.\n- `NodeAttribute`: The field is a node attribute. These are fields which are not inputs or outputs,\nbut which are used to store information about the node. For example, the `id` and `type` fields are node\nattributes.\n\nThe presence of this in `json_schema_extra[\"field_kind\"]` is used when initializing node schemas on app\nstartup, and when generating the OpenAPI schema for the workflow editor.", + "enum": ["input", "output", "internal", "node_attribute"], + "title": "FieldKind", + "type": "string" + }, + "FloatBatchInvocation": { + "category": "batch", + "class": "invocation", + "classification": "special", + "description": "Create a batched generation, where the workflow is executed once for each float in the batch.", "node_pack": "invokeai", "properties": { "id": { @@ -35215,6 +35953,12 @@ { "$ref": "#/components/schemas/FaceOffInvocation" }, + { + "$ref": "#/components/schemas/FalGenericMediaInvocation" + }, + { + "$ref": "#/components/schemas/FalImageGenerationInvocation" + }, { "$ref": "#/components/schemas/FloatBatchInvocation" }, @@ -43753,6 +44497,12 @@ { "$ref": "#/components/schemas/FaceOffInvocation" }, + { + "$ref": "#/components/schemas/FalGenericMediaInvocation" + }, + { + "$ref": "#/components/schemas/FalImageGenerationInvocation" + }, { "$ref": "#/components/schemas/FloatBatchInvocation" }, @@ -45104,6 +45854,12 @@ { "$ref": "#/components/schemas/FaceOffInvocation" }, + { + "$ref": "#/components/schemas/FalGenericMediaInvocation" + }, + { + "$ref": "#/components/schemas/FalImageGenerationInvocation" + }, { "$ref": "#/components/schemas/FloatBatchInvocation" }, @@ -46061,6 +46817,12 @@ "face_off": { "$ref": "#/components/schemas/FaceOffOutput" }, + "fal_generic_media_native": { + "$ref": "#/components/schemas/StringOutput" + }, + "fal_image_generation": { + "$ref": "#/components/schemas/ImageCollectionOutput" + }, "float": { "$ref": "#/components/schemas/FloatOutput" }, @@ -46857,6 +47619,8 @@ "face_identifier", "face_mask_detection", "face_off", + "fal_generic_media_native", + "fal_image_generation", "float", "float_batch", "float_collection", @@ -47372,6 +48136,12 @@ { "$ref": "#/components/schemas/FaceOffInvocation" }, + { + "$ref": "#/components/schemas/FalGenericMediaInvocation" + }, + { + "$ref": "#/components/schemas/FalImageGenerationInvocation" + }, { "$ref": "#/components/schemas/FloatBatchInvocation" }, @@ -48432,6 +49202,12 @@ { "$ref": "#/components/schemas/FaceOffInvocation" }, + { + "$ref": "#/components/schemas/FalGenericMediaInvocation" + }, + { + "$ref": "#/components/schemas/FalImageGenerationInvocation" + }, { "$ref": "#/components/schemas/FloatBatchInvocation" }, @@ -49784,6 +50560,30 @@ "description": "Enforce strict password requirements. When True, passwords must contain uppercase, lowercase, and numbers. When False (default), any password is accepted but its strength (weak/moderate/strong) is reported to the user.", "default": false }, + "external_fal_api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "External Fal Api Key", + "description": "API key for fal.ai image generation." + }, + "external_fal_base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "External Fal Base Url", + "description": "Base URL override for fal.ai queue API." + }, "external_alibabacloud_api_key": { "anyOf": [ { @@ -49884,7 +50684,7 @@ "additionalProperties": false, "type": "object", "title": "InvokeAIAppConfig", - "description": "Invoke's global app configuration.\n\nTypically, you won't need to interact with this class directly. Instead, use the `get_config` function from `invokeai.app.services.config` to get a singleton config object.\n\nAttributes:\n host: IP address to bind to. Use `0.0.0.0` to serve to your local network.\n port: Port to bind to.\n allow_origins: Allowed CORS origins.\n allow_credentials: Allow CORS credentials.\n allow_methods: Methods allowed for CORS.\n allow_headers: Headers allowed for CORS.\n ssl_certfile: SSL certificate file for HTTPS. See https://www.uvicorn.dev/settings/#https.\n ssl_keyfile: SSL key file for HTTPS. See https://www.uvicorn.dev/settings/#https.\n log_tokenization: Enable logging of parsed prompt tokens.\n patchmatch: Enable patchmatch inpaint code.\n models_dir: Path to the models directory.\n convert_cache_dir: Path to the converted models cache directory (DEPRECATED, but do not delete because it is needed for migration from previous versions).\n download_cache_dir: Path to the directory that contains dynamically downloaded models.\n legacy_conf_dir: Path to directory of legacy checkpoint config files.\n db_dir: Path to InvokeAI databases directory.\n outputs_dir: Path to directory for outputs.\n image_subfolder_strategy: Strategy for organizing images into subfolders. 'flat' stores all images in a single folder. 'date' organizes by YYYY/MM/DD. 'type' organizes by image category. 'hash' uses first 2 characters of UUID for filesystem performance.
Valid values: `flat`, `date`, `type`, `hash`\n custom_nodes_dir: Path to directory for custom nodes.\n style_presets_dir: Path to directory for style presets.\n workflow_thumbnails_dir: Path to directory for workflow thumbnails.\n log_handlers: Log handler. Valid options are \"console\", \"file=\", \"syslog=path|address:host:port\", \"http=\".\n log_format: Log format. Use \"plain\" for text-only, \"color\" for colorized output, \"legacy\" for 2.3-style logging and \"syslog\" for syslog-style.
Valid values: `plain`, `color`, `syslog`, `legacy`\n log_level: Emit logging messages at this level or higher.
Valid values: `debug`, `info`, `warning`, `error`, `critical`\n log_sql: Log SQL queries. `log_level` must be `debug` for this to do anything. Extremely verbose.\n log_level_network: Log level for network-related messages. 'info' and 'debug' are very verbose.
Valid values: `debug`, `info`, `warning`, `error`, `critical`\n use_memory_db: Use in-memory database. Useful for development.\n dev_reload: Automatically reload when Python sources are changed. Does not reload node definitions.\n profile_graphs: Enable graph profiling using `cProfile`.\n profile_prefix: An optional prefix for profile output files.\n profiles_dir: Path to profiles output directory.\n max_cache_ram_gb: The maximum amount of CPU RAM to use for model caching in GB. If unset, the limit will be configured based on the available RAM. In most cases, it is recommended to leave this unset.\n max_cache_vram_gb: The amount of VRAM to use for model caching in GB. If unset, the limit will be configured based on the available VRAM and the device_working_mem_gb. In most cases, it is recommended to leave this unset.\n log_memory_usage: If True, a memory snapshot will be captured before and after every model cache operation, and the result will be logged (at debug level). There is a time cost to capturing the memory snapshots, so it is recommended to only enable this feature if you are actively inspecting the model cache's behaviour.\n model_cache_keep_alive_min: How long to keep models in cache after last use, in minutes. A value of 0 (the default) means models are kept in cache indefinitely. If no model generations occur within the timeout period, the model cache is cleared using the same logic as the 'Clear Model Cache' button.\n device_working_mem_gb: The amount of working memory to keep available on the compute device (in GB). Has no effect if running on CPU. If you are experiencing OOM errors, try increasing this value.\n enable_partial_loading: Enable partial loading of models. This enables models to run with reduced VRAM requirements (at the cost of slower speed) by streaming the model from RAM to VRAM as its used. In some edge cases, partial loading can cause models to run more slowly if they were previously being fully loaded into VRAM.\n keep_ram_copy_of_weights: Whether to keep a full RAM copy of a model's weights when the model is loaded in VRAM. Keeping a RAM copy increases average RAM usage, but speeds up model switching and LoRA patching (assuming there is sufficient RAM). Set this to False if RAM pressure is consistently high.\n ram: DEPRECATED: This setting is no longer used. It has been replaced by `max_cache_ram_gb`, but most users will not need to use this config since automatic cache size limits should work well in most cases. This config setting will be removed once the new model cache behavior is stable.\n vram: DEPRECATED: This setting is no longer used. It has been replaced by `max_cache_vram_gb`, but most users will not need to use this config since automatic cache size limits should work well in most cases. This config setting will be removed once the new model cache behavior is stable.\n lazy_offload: DEPRECATED: This setting is no longer used. Lazy-offloading is enabled by default. This config setting will be removed once the new model cache behavior is stable.\n pytorch_cuda_alloc_conf: Configure the Torch CUDA memory allocator. This will impact peak reserved VRAM usage and performance. Setting to \"backend:cudaMallocAsync\" works well on many systems. The optimal configuration is highly dependent on the system configuration (device type, VRAM, CUDA driver version, etc.), so must be tuned experimentally.\n device: Preferred execution device. `auto` will choose the device depending on the hardware platform and the installed torch capabilities.
Valid values: `auto`, `cpu`, `cuda`, `mps`, `xpu`, `cuda:N`, `xpu:N` (where N is a device number)\n precision: Floating point precision. `float16` will consume half the memory of `float32` but produce slightly lower-quality images. The `auto` setting will guess the proper precision based on your video card and operating system.
Valid values: `auto`, `float16`, `bfloat16`, `float32`\n sequential_guidance: Whether to calculate guidance in serial instead of in parallel, lowering memory requirements.\n wan_memory_optimization: Enable experimental Wan memory optimizations at the cost of slower generation.\n pid_memory_optimization: Enable experimental PiD decode memory optimizations. Roughly halves the peak activation memory of a PiD decode; in exchange the decoded image changes slightly, because neither the chunked pixel pathway nor the float32 sampler intermediates are bit-exact with the default path.\n attention_type: Attention type.
Valid values: `auto`, `normal`, `xformers`, `sliced`, `torch-sdp`\n attention_slice_size: Slice size, valid when attention_type==\"sliced\".
Valid values: `auto`, `balanced`, `max`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`\n force_tiled_decode: Whether to enable tiled VAE decode (reduces memory consumption with some performance penalty).\n pil_compress_level: The compress_level setting of PIL.Image.save(), used for PNG encoding. All settings are lossless. 0 = no compression, 1 = fastest with slightly larger filesize, 9 = slowest with smallest filesize. 1 is typically the best setting.\n max_queue_size: Maximum number of items in the session queue.\n session_queue_mode: Session queue mode. Use 'FIFO' for traditional first-in-first-out, or 'round_robin' to serve each user's jobs in turn. In single-user mode, FIFO is always used regardless of this setting.
Valid values: `FIFO`, `round_robin`\n clear_queue_on_startup: Empties session queue on startup. If true, disables `max_queue_history`.\n max_queue_history: Keep the last N completed, failed, and canceled queue items. Older items are deleted on startup. Set to 0 to prune all terminal items. Ignored if `clear_queue_on_startup` is true.\n allow_nodes: List of nodes to allow. Omit to allow all.\n deny_nodes: List of nodes to deny. Omit to deny none.\n node_cache_size: How many cached nodes to keep in memory.\n hashing_algorithm: Model hashing algorthim for model installs. 'blake3_multi' is best for SSDs. 'blake3_single' is best for spinning disk HDDs. 'random' disables hashing, instead assigning a UUID to models. Useful when using a memory db to reduce model installation time, or if you don't care about storing stable hashes for models. Alternatively, any other hashlib algorithm is accepted, though these are not nearly as performant as blake3.
Valid values: `blake3_multi`, `blake3_single`, `random`, `md5`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`, `blake2b`, `blake2s`, `sha3_224`, `sha3_256`, `sha3_384`, `sha3_512`, `shake_128`, `shake_256`\n remote_api_tokens: List of regular expression and token pairs used when downloading models from URLs. The download URL is tested against the regex, and if it matches, the token is provided in as a Bearer token.\n scan_models_on_startup: Scan the models directory on startup, registering orphaned models. This is typically only used in conjunction with `use_memory_db` for testing purposes.\n allow_private_download_urls: Allow the download queue to fetch from loopback, link-local and private-network addresses. Disabled by default so that a download URL cannot be used to reach services that are only reachable from the server. Enable this only if you install models from a mirror on your own network.\n download_proxy: Optional HTTP proxy for model downloads. The proxy must enforce the public-address policy because proxy-side DNS cannot be checked by InvokeAI.\n unsafe_disable_picklescan: UNSAFE. Disable the picklescan security check during model installation. Recommended only for development and testing purposes. This will allow arbitrary code execution during model installation, so should never be used in production.\n allow_unknown_models: Allow installation of models that we are unable to identify. If enabled, models will be marked as `unknown` in the database, and will not have any metadata associated with them. If disabled, unknown models will be rejected during installation.\n multiuser: Enable multiuser support. When disabled, the application runs in single-user mode using a default system account with administrator privileges. When enabled, requires user authentication and authorization.\n strict_password_checking: Enforce strict password requirements. When True, passwords must contain uppercase, lowercase, and numbers. When False (default), any password is accepted but its strength (weak/moderate/strong) is reported to the user.\n external_alibabacloud_api_key: API key for Alibaba Cloud DashScope image generation.\n external_alibabacloud_base_url: Base URL override for Alibaba Cloud DashScope image generation.\n external_gemini_api_key: API key for Gemini image generation.\n external_openai_api_key: API key for OpenAI image generation.\n external_gemini_base_url: Base URL override for Gemini image generation.\n external_openai_base_url: Base URL override for OpenAI image generation.\n external_seedream_api_key: API key for Seedream image generation.\n external_seedream_base_url: Base URL override for Seedream image generation.\n base_url: Public base path when running behind a reverse proxy under a sub-path, e.g. `/invoke`. Set only when the proxy PRESERVES the sub-path (the backend receives `/invoke/api/...`). Leave unset when the proxy strips the sub-path or when serving at the domain root.\n forwarded_allow_ips: Comma-separated list of IPs (or `*`) allowed to set X-Forwarded-* headers. Set to the reverse proxy's IP. Only used when `base_url` is set.\n http_compression_level: Compression level for gzipped HTTP API responses. 0 disables response compression entirely, 1 is fastest, 9 (the default) is smallest. Compression runs on the event loop and blocks the whole server while it works, and level 9 costs about 5.5x the time of level 1 for 0.4 percentage points of extra compression, so lowering this makes the app noticeably more responsive on large libraries. Set to 0 when a reverse proxy already compresses responses." + "description": "Invoke's global app configuration.\n\nTypically, you won't need to interact with this class directly. Instead, use the `get_config` function from `invokeai.app.services.config` to get a singleton config object.\n\nAttributes:\n host: IP address to bind to. Use `0.0.0.0` to serve to your local network.\n port: Port to bind to.\n allow_origins: Allowed CORS origins.\n allow_credentials: Allow CORS credentials.\n allow_methods: Methods allowed for CORS.\n allow_headers: Headers allowed for CORS.\n ssl_certfile: SSL certificate file for HTTPS. See https://www.uvicorn.dev/settings/#https.\n ssl_keyfile: SSL key file for HTTPS. See https://www.uvicorn.dev/settings/#https.\n log_tokenization: Enable logging of parsed prompt tokens.\n patchmatch: Enable patchmatch inpaint code.\n models_dir: Path to the models directory.\n convert_cache_dir: Path to the converted models cache directory (DEPRECATED, but do not delete because it is needed for migration from previous versions).\n download_cache_dir: Path to the directory that contains dynamically downloaded models.\n legacy_conf_dir: Path to directory of legacy checkpoint config files.\n db_dir: Path to InvokeAI databases directory.\n outputs_dir: Path to directory for outputs.\n image_subfolder_strategy: Strategy for organizing images into subfolders. 'flat' stores all images in a single folder. 'date' organizes by YYYY/MM/DD. 'type' organizes by image category. 'hash' uses first 2 characters of UUID for filesystem performance.
Valid values: `flat`, `date`, `type`, `hash`\n custom_nodes_dir: Path to directory for custom nodes.\n style_presets_dir: Path to directory for style presets.\n workflow_thumbnails_dir: Path to directory for workflow thumbnails.\n log_handlers: Log handler. Valid options are \"console\", \"file=\", \"syslog=path|address:host:port\", \"http=\".\n log_format: Log format. Use \"plain\" for text-only, \"color\" for colorized output, \"legacy\" for 2.3-style logging and \"syslog\" for syslog-style.
Valid values: `plain`, `color`, `syslog`, `legacy`\n log_level: Emit logging messages at this level or higher.
Valid values: `debug`, `info`, `warning`, `error`, `critical`\n log_sql: Log SQL queries. `log_level` must be `debug` for this to do anything. Extremely verbose.\n log_level_network: Log level for network-related messages. 'info' and 'debug' are very verbose.
Valid values: `debug`, `info`, `warning`, `error`, `critical`\n use_memory_db: Use in-memory database. Useful for development.\n dev_reload: Automatically reload when Python sources are changed. Does not reload node definitions.\n profile_graphs: Enable graph profiling using `cProfile`.\n profile_prefix: An optional prefix for profile output files.\n profiles_dir: Path to profiles output directory.\n max_cache_ram_gb: The maximum amount of CPU RAM to use for model caching in GB. If unset, the limit will be configured based on the available RAM. In most cases, it is recommended to leave this unset.\n max_cache_vram_gb: The amount of VRAM to use for model caching in GB. If unset, the limit will be configured based on the available VRAM and the device_working_mem_gb. In most cases, it is recommended to leave this unset.\n log_memory_usage: If True, a memory snapshot will be captured before and after every model cache operation, and the result will be logged (at debug level). There is a time cost to capturing the memory snapshots, so it is recommended to only enable this feature if you are actively inspecting the model cache's behaviour.\n model_cache_keep_alive_min: How long to keep models in cache after last use, in minutes. A value of 0 (the default) means models are kept in cache indefinitely. If no model generations occur within the timeout period, the model cache is cleared using the same logic as the 'Clear Model Cache' button.\n device_working_mem_gb: The amount of working memory to keep available on the compute device (in GB). Has no effect if running on CPU. If you are experiencing OOM errors, try increasing this value.\n enable_partial_loading: Enable partial loading of models. This enables models to run with reduced VRAM requirements (at the cost of slower speed) by streaming the model from RAM to VRAM as its used. In some edge cases, partial loading can cause models to run more slowly if they were previously being fully loaded into VRAM.\n keep_ram_copy_of_weights: Whether to keep a full RAM copy of a model's weights when the model is loaded in VRAM. Keeping a RAM copy increases average RAM usage, but speeds up model switching and LoRA patching (assuming there is sufficient RAM). Set this to False if RAM pressure is consistently high.\n ram: DEPRECATED: This setting is no longer used. It has been replaced by `max_cache_ram_gb`, but most users will not need to use this config since automatic cache size limits should work well in most cases. This config setting will be removed once the new model cache behavior is stable.\n vram: DEPRECATED: This setting is no longer used. It has been replaced by `max_cache_vram_gb`, but most users will not need to use this config since automatic cache size limits should work well in most cases. This config setting will be removed once the new model cache behavior is stable.\n lazy_offload: DEPRECATED: This setting is no longer used. Lazy-offloading is enabled by default. This config setting will be removed once the new model cache behavior is stable.\n pytorch_cuda_alloc_conf: Configure the Torch CUDA memory allocator. This will impact peak reserved VRAM usage and performance. Setting to \"backend:cudaMallocAsync\" works well on many systems. The optimal configuration is highly dependent on the system configuration (device type, VRAM, CUDA driver version, etc.), so must be tuned experimentally.\n device: Preferred execution device. `auto` will choose the device depending on the hardware platform and the installed torch capabilities.
Valid values: `auto`, `cpu`, `cuda`, `mps`, `xpu`, `cuda:N`, `xpu:N` (where N is a device number)\n precision: Floating point precision. `float16` will consume half the memory of `float32` but produce slightly lower-quality images. The `auto` setting will guess the proper precision based on your video card and operating system.
Valid values: `auto`, `float16`, `bfloat16`, `float32`\n sequential_guidance: Whether to calculate guidance in serial instead of in parallel, lowering memory requirements.\n wan_memory_optimization: Enable experimental Wan memory optimizations at the cost of slower generation.\n pid_memory_optimization: Enable experimental PiD decode memory optimizations. Roughly halves the peak activation memory of a PiD decode; in exchange the decoded image changes slightly, because neither the chunked pixel pathway nor the float32 sampler intermediates are bit-exact with the default path.\n attention_type: Attention type.
Valid values: `auto`, `normal`, `xformers`, `sliced`, `torch-sdp`\n attention_slice_size: Slice size, valid when attention_type==\"sliced\".
Valid values: `auto`, `balanced`, `max`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`\n force_tiled_decode: Whether to enable tiled VAE decode (reduces memory consumption with some performance penalty).\n pil_compress_level: The compress_level setting of PIL.Image.save(), used for PNG encoding. All settings are lossless. 0 = no compression, 1 = fastest with slightly larger filesize, 9 = slowest with smallest filesize. 1 is typically the best setting.\n max_queue_size: Maximum number of items in the session queue.\n session_queue_mode: Session queue mode. Use 'FIFO' for traditional first-in-first-out, or 'round_robin' to serve each user's jobs in turn. In single-user mode, FIFO is always used regardless of this setting.
Valid values: `FIFO`, `round_robin`\n clear_queue_on_startup: Empties session queue on startup. If true, disables `max_queue_history`.\n max_queue_history: Keep the last N completed, failed, and canceled queue items. Older items are deleted on startup. Set to 0 to prune all terminal items. Ignored if `clear_queue_on_startup` is true.\n allow_nodes: List of nodes to allow. Omit to allow all.\n deny_nodes: List of nodes to deny. Omit to deny none.\n node_cache_size: How many cached nodes to keep in memory.\n hashing_algorithm: Model hashing algorthim for model installs. 'blake3_multi' is best for SSDs. 'blake3_single' is best for spinning disk HDDs. 'random' disables hashing, instead assigning a UUID to models. Useful when using a memory db to reduce model installation time, or if you don't care about storing stable hashes for models. Alternatively, any other hashlib algorithm is accepted, though these are not nearly as performant as blake3.
Valid values: `blake3_multi`, `blake3_single`, `random`, `md5`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`, `blake2b`, `blake2s`, `sha3_224`, `sha3_256`, `sha3_384`, `sha3_512`, `shake_128`, `shake_256`\n remote_api_tokens: List of regular expression and token pairs used when downloading models from URLs. The download URL is tested against the regex, and if it matches, the token is provided in as a Bearer token.\n scan_models_on_startup: Scan the models directory on startup, registering orphaned models. This is typically only used in conjunction with `use_memory_db` for testing purposes.\n allow_private_download_urls: Allow the download queue to fetch from loopback, link-local and private-network addresses. Disabled by default so that a download URL cannot be used to reach services that are only reachable from the server. Enable this only if you install models from a mirror on your own network.\n download_proxy: Optional HTTP proxy for model downloads. The proxy must enforce the public-address policy because proxy-side DNS cannot be checked by InvokeAI.\n unsafe_disable_picklescan: UNSAFE. Disable the picklescan security check during model installation. Recommended only for development and testing purposes. This will allow arbitrary code execution during model installation, so should never be used in production.\n allow_unknown_models: Allow installation of models that we are unable to identify. If enabled, models will be marked as `unknown` in the database, and will not have any metadata associated with them. If disabled, unknown models will be rejected during installation.\n multiuser: Enable multiuser support. When disabled, the application runs in single-user mode using a default system account with administrator privileges. When enabled, requires user authentication and authorization.\n strict_password_checking: Enforce strict password requirements. When True, passwords must contain uppercase, lowercase, and numbers. When False (default), any password is accepted but its strength (weak/moderate/strong) is reported to the user.\n external_fal_api_key: API key for fal.ai image generation.\n external_fal_base_url: Base URL override for fal.ai queue API.\n external_alibabacloud_api_key: API key for Alibaba Cloud DashScope image generation.\n external_alibabacloud_base_url: Base URL override for Alibaba Cloud DashScope image generation.\n external_gemini_api_key: API key for Gemini image generation.\n external_openai_api_key: API key for OpenAI image generation.\n external_gemini_base_url: Base URL override for Gemini image generation.\n external_openai_base_url: Base URL override for OpenAI image generation.\n external_seedream_api_key: API key for Seedream image generation.\n external_seedream_base_url: Base URL override for Seedream image generation.\n base_url: Public base path when running behind a reverse proxy under a sub-path, e.g. `/invoke`. Set only when the proxy PRESERVES the sub-path (the backend receives `/invoke/api/...`). Leave unset when the proxy strips the sub-path or when serving at the domain root.\n forwarded_allow_ips: Comma-separated list of IPs (or `*`) allowed to set X-Forwarded-* headers. Set to the reverse proxy's IP. Only used when `base_url` is set.\n http_compression_level: Compression level for gzipped HTTP API responses. 0 disables response compression entirely, 1 is fastest, 9 (the default) is smallest. Compression runs on the event loop and blocks the whole server while it works, and level 9 costs about 5.5x the time of level 1 for 0.4 percentage points of extra compression, so lowering this makes the app noticeably more responsive on large libraries. Set to 0 when a reverse proxy already compresses responses." }, "InvokeAIAppConfigWithSetFields": { "properties": { @@ -69999,6 +70799,17 @@ ], "description": "External model capabilities" }, + "panel_schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ExternalModelPanelSchema" + }, + { + "type": "null" + } + ], + "description": "External model controls exposed in the frontend" + }, "cpu_only": { "anyOf": [ { diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index c2a4853cc20..d8f59e773b4 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1227,6 +1227,15 @@ "externalResetHelper": "Clear API key and base URL.", "externalProviderSaveFailed": "Failed to save external provider configuration.", "externalProviderResetFailed": "Failed to reset external provider configuration.", + "externalFalCatalogTitle": "fal.ai model catalog", + "externalFalCatalogDescription": "Search fal.ai endpoints. Image endpoints can be installed into Canvas; other media uses the generic workflow node.", + "externalFalCatalogRefresh": "Refresh", + "externalFalCatalogSearch": "Search endpoint or model", + "externalFalCatalogLoadFailed": "Unable to load fal.ai catalog.", + "externalFalCatalogLoading": "Loading fal.ai catalog...", + "externalFalCatalogInstalled": "Installed", + "externalFalCatalogInstall": "Install for Canvas", + "externalFalCatalogGeneric": "Generic workflow", "height": "Height", "huggingFace": "HuggingFace", "huggingFacePlaceholder": "owner/model-name", diff --git a/invokeai/frontend/web/public/locales/ru.json b/invokeai/frontend/web/public/locales/ru.json index d23150a7164..4055c9623b8 100644 --- a/invokeai/frontend/web/public/locales/ru.json +++ b/invokeai/frontend/web/public/locales/ru.json @@ -856,6 +856,15 @@ "externalResetHelper": "Очистить API-ключ и основной URL.", "externalProviderSaveFailed": "Не удалось сохранить конфигурацию внешнего провайдера.", "externalProviderResetFailed": "Не удалось сбросить конфигурацию внешнего провайдера.", + "externalFalCatalogTitle": "Каталог fal.ai", + "externalFalCatalogDescription": "Поиск endpoint fal.ai. Image endpoint можно установить в Canvas; остальные media-функции доступны через generic workflow node.", + "externalFalCatalogRefresh": "Обновить", + "externalFalCatalogSearch": "Поиск endpoint или модели", + "externalFalCatalogLoadFailed": "Не удалось загрузить каталог fal.ai.", + "externalFalCatalogLoading": "Загрузка каталога fal.ai...", + "externalFalCatalogInstalled": "Установлено", + "externalFalCatalogInstall": "Установить в Canvas", + "externalFalCatalogGeneric": "Generic workflow", "hfTokenLabel": "Токен HuggingFace (требуется для некоторых моделей)", "hfTokenHelperText": "Для использования некоторых моделей требуется токен HF. Нажмите здесь, чтобы создать или ввести ваш токен.", "hfTokenInvalid": "Недействительный или отсутствующий HF токен", diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/ExternalProvidersForm.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/ExternalProvidersForm.tsx index b4bbe8d6335..037d74f1c99 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/ExternalProvidersForm.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/ExternalProvidersForm.tsx @@ -21,22 +21,28 @@ import type { ChangeEvent } from 'react'; import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import type { IconType } from 'react-icons'; -import { PiCheckBold, PiWarningBold } from 'react-icons/pi'; +import { PiCheckBold, PiCloudLightningBold, PiWarningBold } from 'react-icons/pi'; import { SiAlibabacloud, SiBytedance, SiGooglegemini, SiOpenai } from 'react-icons/si'; import { useGetExternalProviderConfigsQuery, + useInstallFalModelMutation, + useLazyGetFalModelsQuery, useResetExternalProviderConfigMutation, useSetExternalProviderConfigMutation, } from 'services/api/endpoints/appInfo'; import { useGetStarterModelsQuery } from 'services/api/endpoints/models'; -import type { ExternalProviderConfig, StarterModel } from 'services/api/types'; +import type { ExternalProviderConfig, FalCatalogModel, StarterModel } from 'services/api/types'; -const PROVIDER_SORT_ORDER = ['gemini', 'openai', 'seedream', 'alibabacloud']; +import { isFalNativeCanvasModel } from './falCatalog'; + +const PROVIDER_SORT_ORDER = ['fal', 'gemini', 'openai', 'seedream', 'alibabacloud']; function resolveProviderIcon(providerId: string): IconType | null { const provider = providerId.toLowerCase(); switch (provider) { + case 'fal': + return PiCloudLightningBold; case 'openai': return SiOpenai; case 'gemini': @@ -345,8 +351,123 @@ const ProviderCard = memo(({ provider, onInstallModels, iconResolver }: Provider + {provider.provider_id === 'fal' && } ); }); ProviderCard.displayName = 'ProviderCard'; + +type FalModelCatalogProps = { + isConfigured: boolean; +}; + +const FalModelCatalog = memo(({ isConfigured }: FalModelCatalogProps) => { + const { t } = useTranslation(); + const toast = useToast(); + const [search, setSearch] = useState(''); + const [fetchModels, { isFetching, isError }] = useLazyGetFalModelsQuery(); + const [models, setModels] = useState([]); + const [nextCursor, setNextCursor] = useState(null); + const [installModel, { isLoading: isInstalling }] = useInstallFalModelMutation(); + + const loadModels = useCallback( + (append: boolean, cursor?: string | null) => { + if (!isConfigured) { + return Promise.resolve(); + } + return fetchModels({ limit: 30, cursor: cursor ?? undefined, search: search.trim() || undefined }) + .unwrap() + .then((result) => { + setModels((current) => (append ? [...current, ...result.models] : result.models)); + setNextCursor(result.next_cursor); + }) + .catch(() => undefined); + }, + [fetchModels, isConfigured, search] + ); + + useEffect(() => { + setModels([]); + setNextCursor(null); + const timer = window.setTimeout(() => void loadModels(false), 300); + return () => window.clearTimeout(timer); + }, [loadModels]); + + const handleInstall = useCallback( + (model: FalCatalogModel) => { + installModel({ endpoint_id: model.endpoint_id }) + .unwrap() + .catch(() => { + toast({ + id: `FAL_MODEL_INSTALL_FAILED_${model.endpoint_id}`, + title: t('modelManager.externalProviderSaveFailed'), + status: 'error', + }); + }); + }, + [installModel, t, toast] + ); + + if (!isConfigured) { + return null; + } + + return ( + + + + {t('modelManager.externalFalCatalogTitle')} + {t('modelManager.externalFalCatalogDescription')} + + + + setSearch(event.target.value)} + /> + {isError && {t('modelManager.externalFalCatalogLoadFailed')}} + {isFetching && models.length === 0 && {t('modelManager.externalFalCatalogLoading')}} + {models.map((model) => { + const isCanvasModel = isFalNativeCanvasModel(model); + return ( + + + + {model.display_name} + {model.kind} + + + {model.endpoint_id} + + + {isCanvasModel ? ( + + ) : ( + {t('modelManager.externalFalCatalogGeneric')} + )} + + ); + })} + {nextCursor && ( + + )} + + ); +}); + +FalModelCatalog.displayName = 'FalModelCatalog'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/falCatalog.test.ts b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/falCatalog.test.ts new file mode 100644 index 00000000000..53b53812a44 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/falCatalog.test.ts @@ -0,0 +1,31 @@ +import type { FalCatalogModel } from 'services/api/types'; +import { describe, expect, it } from 'vitest'; + +import { isFalNativeCanvasModel } from './falCatalog'; + +const model = (kind: FalCatalogModel['kind']): FalCatalogModel => ({ + endpoint_id: 'fal-ai/test', + display_name: 'Test model', + description: '', + category: kind, + kind, + model_url: null, + thumbnail_url: null, + tags: [], + installed: false, +}); + +describe('fal catalog model capabilities', () => { + it('marks image endpoints as native Canvas-compatible', () => { + expect(isFalNativeCanvasModel(model('text-to-image'))).toBe(true); + expect(isFalNativeCanvasModel(model('image-to-image'))).toBe(true); + expect(isFalNativeCanvasModel(model('inpaint'))).toBe(true); + expect(isFalNativeCanvasModel(model('upscale'))).toBe(true); + }); + + it('keeps video and generic endpoints out of image Canvas picker', () => { + expect(isFalNativeCanvasModel(model('text-to-video'))).toBe(false); + expect(isFalNativeCanvasModel(model('image-to-video'))).toBe(false); + expect(isFalNativeCanvasModel(model('generic'))).toBe(false); + }); +}); diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/falCatalog.ts b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/falCatalog.ts new file mode 100644 index 00000000000..90c5996656f --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/falCatalog.ts @@ -0,0 +1,5 @@ +import type { FalCatalogModel } from 'services/api/types'; + +const NATIVE_CANVAS_KINDS = new Set(['text-to-image', 'image-to-image', 'inpaint', 'upscale']); + +export const isFalNativeCanvasModel = (model: FalCatalogModel): boolean => NATIVE_CANVAS_KINDS.has(model.kind); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildExternalGraph.test.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildExternalGraph.test.ts index f1fa54b4e0b..2f143306055 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildExternalGraph.test.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildExternalGraph.test.ts @@ -182,6 +182,28 @@ describe('buildExternalGraph', () => { expect(externalNode?.type).toBe('gemini_image_generation'); }); + it('uses the fal.ai invocation for fal models', async () => { + mockModelConfig = createExternalModel({ + provider_id: 'fal', + provider_model_id: 'fal-ai/flux/schnell', + path: 'external://fal/fal-ai/flux/schnell', + source: 'external://fal/fal-ai/flux/schnell', + hash: 'external:fal:fal-ai/flux/schnell', + }); + + const { g } = await buildExternalGraph({ + generationMode: 'txt2img', + state: {} as RootState, + manager: null, + }); + + const graph = g.getGraph(); + const externalNode = Object.values(graph.nodes).find((node) => node.type === 'fal_image_generation'); + + expect(externalNode).toBeDefined(); + expect(externalNode?.type).toBe('fal_image_generation'); + }); + it('throws when mode is unsupported', async () => { const modelConfig = createExternalModel({ capabilities: { diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildExternalGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildExternalGraph.ts index 6e7f12cabe3..eabda714277 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildExternalGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildExternalGraph.ts @@ -23,6 +23,7 @@ import { import { assert } from 'tsafe'; const EXTERNAL_PROVIDER_NODE_TYPES = { + fal: 'fal_image_generation', alibabacloud: 'alibabacloud_image_generation', gemini: 'gemini_image_generation', openai: 'openai_image_generation', diff --git a/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts b/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts index 8a7c6b0448e..4e6708d4f43 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts @@ -116,6 +116,36 @@ export const appInfoApi = api.injectEndpoints({ }), invalidatesTags: ['AppConfig', 'FetchOnReconnect'], }), + getFalModels: build.query< + paths['/api/v1/app/external_providers/fal/models']['get']['responses']['200']['content']['application/json'], + paths['/api/v1/app/external_providers/fal/models']['get']['parameters']['query'] + >({ + query: (query) => ({ + url: buildAppInfoUrl('external_providers/fal/models', query ?? undefined), + method: 'GET', + }), + providesTags: ['FetchOnReconnect'], + }), + getFalModelSchema: build.query< + paths['/api/v1/app/external_providers/fal/models/{endpoint_id}/schema']['get']['responses']['200']['content']['application/json'], + string + >({ + query: (endpoint_id) => ({ + url: buildAppInfoUrl(`external_providers/fal/models/${endpoint_id}/schema`), + method: 'GET', + }), + }), + installFalModel: build.mutation< + paths['/api/v1/app/external_providers/fal/models/install']['post']['responses']['201']['content']['application/json'], + paths['/api/v1/app/external_providers/fal/models/install']['post']['requestBody']['content']['application/json'] + >({ + query: (body) => ({ + url: buildAppInfoUrl('external_providers/fal/models/install'), + method: 'POST', + body, + }), + invalidatesTags: ['FetchOnReconnect'], + }), getInvocationCacheStatus: build.query< paths['/api/v1/app/invocation_cache/status']['get']['responses']['200']['content']['application/json'], void @@ -162,6 +192,8 @@ export const { useGetGenerationDeviceOptionsQuery, useGetExternalProviderStatusesQuery, useGetExternalProviderConfigsQuery, + useLazyGetFalModelsQuery, + useInstallFalModelMutation, useSetExternalProviderConfigMutation, useResetExternalProviderConfigMutation, useUpdateRuntimeConfigMutation, diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index c19832d09a1..8cf1b4de9cf 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -2239,6 +2239,57 @@ export type paths = { patch?: never; trace?: never; }; + "/api/v1/app/external_providers/fal/models": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Fal Models */ + get: operations["list_fal_models"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/app/external_providers/fal/models/{endpoint_id}/schema": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Fal Model Schema */ + get: operations["get_fal_model_schema"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/app/external_providers/fal/models/install": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Install Fal Model */ + post: operations["install_fal_model"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/v1/queue/{queue_id}/enqueue_batch": { parameters: { query?: never; @@ -11337,6 +11388,244 @@ export type components = { */ y: number; }; + /** FalCatalogModelResponse */ + FalCatalogModelResponse: { + /** Endpoint Id */ + endpoint_id: string; + /** Display Name */ + display_name: string; + /** Description */ + description: string; + /** Category */ + category: string; + kind: components["schemas"]["FalEndpointKind"]; + /** Model Url */ + model_url: string | null; + /** Thumbnail Url */ + thumbnail_url: string | null; + /** Tags */ + tags: string[]; + /** Installed */ + installed: boolean; + }; + /** FalCatalogResponse */ + FalCatalogResponse: { + /** Models */ + models: components["schemas"]["FalCatalogModelResponse"][]; + /** Next Cursor */ + next_cursor: string | null; + /** Has More */ + has_more: boolean; + }; + /** + * FalEndpointKind + * @enum {string} + */ + FalEndpointKind: "text-to-image" | "image-to-image" | "inpaint" | "upscale" | "text-to-video" | "image-to-video" | "video-to-video" | "audio" | "generic"; + /** FalEndpointSchemaResponse */ + FalEndpointSchemaResponse: { + /** Endpoint Id */ + endpoint_id: string; + kind: components["schemas"]["FalEndpointKind"]; + output_kind: components["schemas"]["FalEndpointKind"]; + /** Category */ + category: string; + /** Input Schema */ + input_schema: { + [key: string]: unknown; + }; + /** Output Schema */ + output_schema: { + [key: string]: unknown; + }; + /** Common Fields */ + common_fields: { + [key: string]: string; + }; + /** Public Properties */ + public_properties: string[]; + }; + /** + * fal.ai Generic Media + * @description Submit arbitrary JSON to any fal.ai endpoint and return its raw JSON result. + */ + FalGenericMediaInvocation: { + /** + * Id + * @description The id of this instance of an invocation. Must be unique among all instances of invocations. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this is an intermediate invocation. + * @default false + */ + is_intermediate?: boolean; + /** + * Use Cache + * @description Whether or not to use the cache + * @default true + */ + use_cache?: boolean; + /** + * Model Id + * @description fal.ai endpoint ID, for example fal-ai/kling-video/v3/pro/text-to-video + * @default null + */ + model_id?: string | null; + /** + * Input Json + * @description JSON object sent to the fal.ai endpoint + * @default {} + */ + input_json?: string; + /** + * @description Optional local image for ${image_url} placeholders + * @default null + */ + image?: components["schemas"]["ImageField"] | null; + /** + * @description Optional local mask for ${mask_url} placeholders + * @default null + */ + mask?: components["schemas"]["ImageField"] | null; + /** + * Reference Images + * @description Optional local images for ${image_urls} or ${reference_image_urls} placeholders + * @default [] + */ + reference_images?: components["schemas"]["ImageField"][]; + /** + * @description Optional local video for ${video_url} placeholders + * @default null + */ + video?: components["schemas"]["VideoField"] | null; + /** + * type + * @default fal_generic_media_native + * @constant + */ + type: "fal_generic_media_native"; + }; + /** + * fal.ai Image Generation + * @description Generate or edit images using a fal.ai-hosted model. + */ + FalImageGenerationInvocation: { + /** + * @description The board to save the image to + * @default null + */ + board?: components["schemas"]["BoardField"] | null; + /** + * @description Optional metadata to be saved with the image + * @default null + */ + metadata?: components["schemas"]["MetadataField"] | null; + /** + * Id + * @description The id of this instance of an invocation. Must be unique among all instances of invocations. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this is an intermediate invocation. + * @default false + */ + is_intermediate?: boolean; + /** + * Use Cache + * @description Whether or not to use the cache + * @default true + */ + use_cache?: boolean; + /** + * @description Main model (UNet, VAE, CLIP) to load + * @default null + */ + model?: components["schemas"]["ModelIdentifierField"] | null; + /** + * Mode + * @description Generation mode. Not all modes are supported by every model; unsupported modes raise at runtime. + * @default txt2img + * @enum {string} + */ + mode?: "txt2img" | "img2img" | "inpaint"; + /** + * Prompt + * @description Prompt + * @default null + */ + prompt?: string | null; + /** + * Seed + * @description Seed for random number generation + * @default null + */ + seed?: number | null; + /** + * Num Images + * @description Number of images to generate + * @default 1 + */ + num_images?: number; + /** + * Width + * @description Width of output (px) + * @default 1024 + */ + width?: number; + /** + * Height + * @description Height of output (px) + * @default 1024 + */ + height?: number; + /** + * Image Size + * @description Image size preset (e.g. 1K, 2K, 4K) + * @default null + */ + image_size?: string | null; + /** + * @description Init image for img2img/inpaint + * @default null + */ + init_image?: components["schemas"]["ImageField"] | null; + /** + * @description Mask image for inpaint + * @default null + */ + mask_image?: components["schemas"]["ImageField"] | null; + /** + * Reference Images + * @description Reference images + * @default [] + */ + reference_images?: components["schemas"]["ImageField"][]; + /** + * Advanced Options + * @description Additional JSON fields declared by the selected fal.ai endpoint + * @default {} + */ + advanced_options?: { + [key: string]: unknown; + }; + /** + * type + * @default fal_image_generation + * @constant + */ + type: "fal_image_generation"; + }; + /** FalModelInstallRequest */ + FalModelInstallRequest: { + /** + * Endpoint Id + * @description fal.ai endpoint identifier + */ + endpoint_id: string; + }; /** * FieldKind * @description The kind of field. @@ -14653,7 +14942,7 @@ export type components = { * @description The nodes in this graph */ nodes?: { - [key: string]: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; + [key: string]: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FalGenericMediaInvocation"] | components["schemas"]["FalImageGenerationInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; }; /** * Edges @@ -18478,7 +18767,7 @@ export type components = { * Invocation * @description The ID of the invocation */ - invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; + invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FalGenericMediaInvocation"] | components["schemas"]["FalImageGenerationInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; /** * Invocation Source Id * @description The ID of the prepared invocation's source node @@ -18542,7 +18831,7 @@ export type components = { * Invocation * @description The ID of the invocation */ - invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; + invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FalGenericMediaInvocation"] | components["schemas"]["FalImageGenerationInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; /** * Invocation Source Id * @description The ID of the prepared invocation's source node @@ -18630,6 +18919,8 @@ export type components = { face_identifier: components["schemas"]["ImageOutput"]; face_mask_detection: components["schemas"]["FaceMaskOutput"]; face_off: components["schemas"]["FaceOffOutput"]; + fal_generic_media_native: components["schemas"]["StringOutput"]; + fal_image_generation: components["schemas"]["ImageCollectionOutput"]; float: components["schemas"]["FloatOutput"]; float_batch: components["schemas"]["FloatOutput"]; float_collection: components["schemas"]["FloatCollectionOutput"]; @@ -18926,7 +19217,7 @@ export type components = { * Invocation * @description The ID of the invocation */ - invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; + invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FalGenericMediaInvocation"] | components["schemas"]["FalImageGenerationInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; /** * Invocation Source Id * @description The ID of the prepared invocation's source node @@ -19007,7 +19298,7 @@ export type components = { * Invocation * @description The ID of the invocation */ - invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; + invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FalGenericMediaInvocation"] | components["schemas"]["FalImageGenerationInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; /** * Invocation Source Id * @description The ID of the prepared invocation's source node @@ -19087,6 +19378,8 @@ export type components = { * allow_unknown_models: Allow installation of models that we are unable to identify. If enabled, models will be marked as `unknown` in the database, and will not have any metadata associated with them. If disabled, unknown models will be rejected during installation. * multiuser: Enable multiuser support. When disabled, the application runs in single-user mode using a default system account with administrator privileges. When enabled, requires user authentication and authorization. * strict_password_checking: Enforce strict password requirements. When True, passwords must contain uppercase, lowercase, and numbers. When False (default), any password is accepted but its strength (weak/moderate/strong) is reported to the user. + * external_fal_api_key: API key for fal.ai image generation. + * external_fal_base_url: Base URL override for fal.ai queue API. * external_alibabacloud_api_key: API key for Alibaba Cloud DashScope image generation. * external_alibabacloud_base_url: Base URL override for Alibaba Cloud DashScope image generation. * external_gemini_api_key: API key for Gemini image generation. @@ -19548,6 +19841,16 @@ export type components = { * @default false */ strict_password_checking?: boolean; + /** + * External Fal Api Key + * @description API key for fal.ai image generation. + */ + external_fal_api_key?: string | null; + /** + * External Fal Base Url + * @description Base URL override for fal.ai queue API. + */ + external_fal_base_url?: string | null; /** * External Alibabacloud Api Key * @description API key for Alibaba Cloud DashScope image generation. @@ -29243,6 +29546,8 @@ export type components = { provider_model_id?: string | null; /** @description External model capabilities */ capabilities?: components["schemas"]["ExternalModelCapabilities"] | null; + /** @description External model controls exposed in the frontend */ + panel_schema?: components["schemas"]["ExternalModelPanelSchema"] | null; /** * Cpu Only * @description Whether this model should run on CPU only @@ -47064,6 +47369,104 @@ export interface operations { }; }; }; + list_fal_models: { + parameters: { + query?: { + limit?: number; + cursor?: string | null; + search?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FalCatalogResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_fal_model_schema: { + parameters: { + query?: never; + header?: never; + path: { + /** @description fal.ai endpoint identifier */ + endpoint_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FalEndpointSchemaResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + install_fal_model: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FalModelInstallRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ModelInstallJob"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; enqueue_batch: { parameters: { query?: never; diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index 93589541742..165a0543011 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -58,6 +58,7 @@ export type ExternalProviderConfigUpdate = { api_key?: string; base_url?: string | null; }; +export type FalCatalogModel = S['FalCatalogModelResponse']; export type UpdateModelBody = paths['/api/v2/models/i/{key}']['patch']['requestBody']['content']['application/json']; const zResourceOrigin = z.enum(['internal', 'external']); diff --git a/tests/app/invocations/test_external_image_generation.py b/tests/app/invocations/test_external_image_generation.py index 4247a366871..8c4141d2675 100644 --- a/tests/app/invocations/test_external_image_generation.py +++ b/tests/app/invocations/test_external_image_generation.py @@ -4,7 +4,10 @@ import pytest from PIL import Image -from invokeai.app.invocations.external_image_generation import OpenAIImageGenerationInvocation +from invokeai.app.invocations.external_image_generation import ( + FalImageGenerationInvocation, + OpenAIImageGenerationInvocation, +) from invokeai.app.invocations.fields import ImageField from invokeai.app.invocations.model import ModelIdentifierField from invokeai.app.services.external_generation.external_generation_common import ( @@ -86,6 +89,44 @@ def test_provider_specific_external_invocation_rejects_wrong_provider() -> None: invocation.invoke(context) +def test_fal_invocation_forwards_advanced_schema_options() -> None: + model_config = _build_model().model_copy(update={"provider_id": "fal", "provider_model_id": "fal-ai/custom"}) + model_field = ModelIdentifierField.from_config(model_config) + generated_image = Image.new("RGB", (16, 16), color="black") + context = _build_context(model_config, generated_image) + + invocation = FalImageGenerationInvocation( + id="fal_node", + model=model_field, + mode="txt2img", + prompt="A prompt", + advanced_options={"style": "cinematic", "num_inference_steps": 4}, + ) + + invocation.invoke(context) + + request = context._services.external_generation.generate.call_args[0][0] + assert request.provider_options == {"advanced": {"style": "cinematic", "num_inference_steps": 4}} + + +def test_fal_invocation_requires_fal_model() -> None: + model_config = _build_model().model_copy(update={"provider_id": "fal", "provider_model_id": "fal-ai/flux/schnell"}) + model_field = ModelIdentifierField.from_config(model_config) + generated_image = Image.new("RGB", (16, 16), color="black") + context = _build_context(model_config, generated_image) + + invocation = FalImageGenerationInvocation( + id="fal_node", + model=model_field, + mode="txt2img", + prompt="A prompt", + ) + + invocation.invoke(context) + + assert context._services.external_generation.generate.call_args[0][0].model.provider_id == "fal" + + def test_external_graph_execution_state_runs_node() -> None: model_config = _build_model() model_field = ModelIdentifierField.from_config(model_config) diff --git a/tests/app/invocations/test_fal_generic_media.py b/tests/app/invocations/test_fal_generic_media.py new file mode 100644 index 00000000000..5176f836fec --- /dev/null +++ b/tests/app/invocations/test_fal_generic_media.py @@ -0,0 +1,62 @@ +from pathlib import Path +from unittest.mock import MagicMock + +import pytest +from PIL import Image + +from invokeai.app.invocations.external_media_generation import FalGenericMediaInvocation +from invokeai.app.invocations.fields import ImageField, VideoField + + +def test_fal_generic_media_invocation_uploads_local_media_fields() -> None: + context = MagicMock() + context.images.get_pil.return_value = Image.new("RGB", (2, 2)) + context.videos.get_path.return_value = Path("/tmp/input.mp4") + context._services.external_generation.generate_generic.return_value = {} + invocation = FalGenericMediaInvocation( + id="fal_generic", + model_id="fal-ai/video", + input_json='{"start_image_url": "${image_url}", "video_url": "${video_url}"}', + image=ImageField(image_name="input.png"), + mask=ImageField(image_name="mask.png"), + reference_images=[ImageField(image_name="ref.png")], + video=VideoField(video_name="input.mp4"), + ) + + invocation.invoke(context) + + call = context._services.external_generation.generate_generic.call_args + assert call.kwargs["image"].size == (2, 2) + assert call.kwargs["mask_image"].size == (2, 2) + assert len(call.kwargs["reference_images"]) == 1 + assert call.kwargs["video_path"] == Path("/tmp/input.mp4") + + +def test_fal_generic_media_invocation_submits_declared_json_and_returns_raw_result() -> None: + context = MagicMock() + context._services.external_generation.generate_generic.return_value = { + "video": {"url": "https://cdn.test/video.mp4"}, + "seed": 7, + } + invocation = FalGenericMediaInvocation( + id="fal_generic", + model_id="fal-ai/video", + input_json='{"prompt": "A moving test", "duration": "5"}', + ) + + output = invocation.invoke(context) + + context._services.external_generation.generate_generic.assert_called_once_with( + provider_id="fal", + model_id="fal-ai/video", + payload={"prompt": "A moving test", "duration": "5"}, + ) + assert output.value == '{"seed": 7, "video": {"url": "https://cdn.test/video.mp4"}}' + + +def test_fal_generic_media_invocation_rejects_non_object_json() -> None: + context = MagicMock() + invocation = FalGenericMediaInvocation(id="fal_generic", model_id="fal-ai/video", input_json="[]") + + with pytest.raises(ValueError, match="JSON object"): + invocation.invoke(context) diff --git a/tests/app/routers/test_app_info.py b/tests/app/routers/test_app_info.py index 4ca610a0a43..01a8fc9e2fc 100644 --- a/tests/app/routers/test_app_info.py +++ b/tests/app/routers/test_app_info.py @@ -62,6 +62,8 @@ def test_get_external_provider_statuses(monkeypatch: Any, mock_invoker: Invoker, def test_external_provider_config_update_and_reset(monkeypatch: Any, mock_invoker: Invoker, client: TestClient) -> None: + monkeypatch.delenv("FAL_KEY", raising=False) + monkeypatch.delenv("FAL_API_KEY", raising=False) mock_store = Mock() mock_store.search_by_attr.return_value = [] mock_install = Mock() @@ -72,7 +74,7 @@ def test_external_provider_config_update_and_reset(monkeypatch: Any, mock_invoke monkeypatch.setattr("invokeai.app.api.routers.app_info.ApiDependencies", MockApiDependencies(mock_invoker)) monkeypatch.setattr("invokeai.app.api.auth_dependencies.ApiDependencies", MockApiDependencies(mock_invoker)) - for provider_id in ("gemini", "openai"): + for provider_id in ("fal", "gemini", "openai"): response = client.delete(f"/api/v1/app/external_providers/config/{provider_id}") assert response.status_code == 200 @@ -82,6 +84,9 @@ def test_external_provider_config_update_and_reset(monkeypatch: Any, mock_invoke openai_config = _get_provider_config(payload, "openai") assert openai_config["api_key_configured"] is False assert openai_config["base_url"] is None + fal_config = _get_provider_config(payload, "fal") + assert fal_config["api_key_configured"] is False + assert fal_config["base_url"] is None response = client.post( "/api/v1/app/external_providers/config/openai", @@ -99,6 +104,15 @@ def test_external_provider_config_update_and_reset(monkeypatch: Any, mock_invoke assert openai_config["api_key_configured"] is True assert openai_config["base_url"] == "https://api.openai.test" + response = client.post( + "/api/v1/app/external_providers/config/fal", + json={"api_key": "fal-key", "base_url": "https://queue.fal.test"}, + ) + assert response.status_code == 200 + fal_config = response.json() + assert fal_config["api_key_configured"] is True + assert fal_config["base_url"] == "https://queue.fal.test" + config_path = get_config().config_file_path api_keys_path = get_config().api_keys_file_path file_config = load_and_migrate_config(config_path) @@ -116,6 +130,12 @@ def test_external_provider_config_update_and_reset(monkeypatch: Any, mock_invoke assert payload["api_key_configured"] is False assert payload["base_url"] is None + response = client.delete("/api/v1/app/external_providers/config/fal") + assert response.status_code == 200 + payload = response.json() + assert payload["api_key_configured"] is False + assert payload["base_url"] is None + file_config = load_and_migrate_config(config_path) api_keys = load_external_api_keys(api_keys_path) assert file_config.external_openai_api_key is None @@ -123,6 +143,22 @@ def test_external_provider_config_update_and_reset(monkeypatch: Any, mock_invoke assert "external_openai_api_key" not in config_path.read_text() assert "external_openai_api_key" not in api_keys assert "external_openai_base_url" not in api_keys + assert "external_fal_api_key" not in api_keys + assert "external_fal_base_url" not in api_keys + + +def test_fal_external_provider_config_reports_environment_key( + monkeypatch: Any, mock_invoker: Invoker, client: TestClient +) -> None: + monkeypatch.setenv("FAL_KEY", "environment-key") + monkeypatch.setattr("invokeai.app.api.routers.app_info.ApiDependencies", MockApiDependencies(mock_invoker)) + monkeypatch.setattr("invokeai.app.api.auth_dependencies.ApiDependencies", MockApiDependencies(mock_invoker)) + + response = client.get("/api/v1/app/external_providers/config") + + assert response.status_code == 200 + fal_config = _get_provider_config(response.json(), "fal") + assert fal_config["api_key_configured"] is True def test_reset_external_provider_config_removes_provider_models( @@ -461,5 +497,170 @@ def test_reset_external_provider_config_rejects_non_admin_users( assert response.json()["detail"] == "Admin privileges required" +def test_list_fal_catalog_models_without_exposing_credentials( + monkeypatch: Any, mock_invoker: Invoker, client: TestClient +) -> None: + monkeypatch.setenv("FAL_KEY", "secret-key") + mock_store = Mock() + mock_store.search_by_attr.return_value = [] + mock_invoker.services.model_manager = Mock(store=mock_store) + monkeypatch.setattr("invokeai.app.api.routers.app_info.ApiDependencies", MockApiDependencies(mock_invoker)) + monkeypatch.setattr("invokeai.app.api.auth_dependencies.ApiDependencies", MockApiDependencies(mock_invoker)) + + class FakeCatalogClient: + def __init__(self, api_key: str) -> None: + assert api_key == "secret-key" + + def list_models(self, **kwargs: Any) -> Any: + from invokeai.app.services.external_generation.providers.fal_catalog import FalCatalogModel, FalCatalogPage + + return FalCatalogPage( + models=[ + FalCatalogModel( + endpoint_id="fal-ai/test", + display_name="Test model", + description="Safe description", + category="text-to-image", + model_url="https://fal.ai/models/fal-ai/test", + thumbnail_url="https://cdn.test/thumb.jpg", + tags=("image",), + ) + ], + next_cursor="next", + has_more=True, + ) + + monkeypatch.setattr(app_info, "FalCatalogClient", FakeCatalogClient) + response = client.get("/api/v1/app/external_providers/fal/models?limit=10&search=test") + + assert response.status_code == 200 + assert response.json() == { + "models": [ + { + "endpoint_id": "fal-ai/test", + "display_name": "Test model", + "description": "Safe description", + "category": "text-to-image", + "kind": "text-to-image", + "model_url": "https://fal.ai/models/fal-ai/test", + "thumbnail_url": "https://cdn.test/thumb.jpg", + "tags": ["image"], + "installed": False, + } + ], + "next_cursor": "next", + "has_more": True, + } + assert "secret-key" not in response.text + + +def test_get_fal_endpoint_schema_requires_configured_key( + monkeypatch: Any, mock_invoker: Invoker, client: TestClient +) -> None: + monkeypatch.delenv("FAL_KEY", raising=False) + monkeypatch.delenv("FAL_API_KEY", raising=False) + monkeypatch.setattr("invokeai.app.api.routers.app_info.ApiDependencies", MockApiDependencies(mock_invoker)) + monkeypatch.setattr("invokeai.app.api.auth_dependencies.ApiDependencies", MockApiDependencies(mock_invoker)) + + response = client.get("/api/v1/app/external_providers/fal/models/fal-ai/test/schema") + + assert response.status_code == 409 + assert response.json()["detail"] == "fal.ai API key is not configured" + + +def test_install_fal_image_model_from_catalog_schema( + monkeypatch: Any, mock_invoker: Invoker, client: TestClient +) -> None: + monkeypatch.setenv("FAL_KEY", "secret-key") + schema = _fake_fal_schema("fal-ai/test", "text-to-image") + + class FakeCatalogClient: + def __init__(self, api_key: str) -> None: + assert api_key == "secret-key" + + def get_schema(self, endpoint_id: str) -> Any: + assert endpoint_id == "fal-ai/test" + return schema + + install = Mock() + mock_invoker.services.model_manager = Mock(install=install, store=Mock()) + monkeypatch.setattr(app_info, "FalCatalogClient", FakeCatalogClient) + monkeypatch.setattr("invokeai.app.api.routers.app_info.ApiDependencies", MockApiDependencies(mock_invoker)) + monkeypatch.setattr("invokeai.app.api.auth_dependencies.ApiDependencies", MockApiDependencies(mock_invoker)) + + from invokeai.app.services.model_install.model_install_common import ExternalModelSource, ModelInstallJob + + install.heuristic_import.return_value = ModelInstallJob( + id=1, + source=ExternalModelSource(provider_id="fal", provider_model_id="fal-ai/test"), + local_path=".", + ) + response = client.post("/api/v1/app/external_providers/fal/models/install", json={"endpoint_id": "fal-ai/test"}) + + assert response.status_code == 201 + install.heuristic_import.assert_called_once() + call = install.heuristic_import.call_args.kwargs + assert call["source"] == "external://fal/fal-ai/test" + assert call["config"].provider_id == "fal" + assert call["config"].provider_model_id == "fal-ai/test" + assert call["config"].source_url == "https://fal.ai/models/fal-ai/test" + assert call["config"].capabilities.modes == ["txt2img"] + assert call["config"].panel_schema is not None + assert any(control.name == "dimensions" for control in call["config"].panel_schema.image) + + +def test_install_fal_video_model_requires_generic_media_node( + monkeypatch: Any, mock_invoker: Invoker, client: TestClient +) -> None: + monkeypatch.setenv("FAL_KEY", "secret-key") + schema = _fake_fal_schema("fal-ai/video", "image-to-video") + + class FakeCatalogClient: + def __init__(self, api_key: str) -> None: + del api_key + + def get_schema(self, endpoint_id: str) -> Any: + del endpoint_id + return schema + + monkeypatch.setattr(app_info, "FalCatalogClient", FakeCatalogClient) + monkeypatch.setattr("invokeai.app.api.routers.app_info.ApiDependencies", MockApiDependencies(mock_invoker)) + monkeypatch.setattr("invokeai.app.api.auth_dependencies.ApiDependencies", MockApiDependencies(mock_invoker)) + + response = client.post("/api/v1/app/external_providers/fal/models/install", json={"endpoint_id": "fal-ai/video"}) + + assert response.status_code == 422 + assert "generic media" in response.json()["detail"] + + +def _fake_fal_schema(endpoint_id: str, category: str) -> Any: + from invokeai.app.services.external_generation.providers.fal_catalog import ( + FalEndpointKind, + FalEndpointSchema, + ) + + kind = { + "text-to-image": FalEndpointKind.TEXT_TO_IMAGE, + "image-to-video": FalEndpointKind.IMAGE_TO_VIDEO, + }[category] + return FalEndpointSchema( + endpoint_id=endpoint_id, + kind=kind, + output_kind=kind, + category=category, + input_schema={ + "type": "object", + "properties": { + "prompt": {"type": "string"}, + "width": {"type": "integer"}, + "height": {"type": "integer"}, + }, + }, + output_schema={}, + common_fields={"prompt": "prompt", "width": "width", "height": "height"}, + public_properties=("prompt", "width", "height"), + ) + + def _get_provider_config(payload: list[dict[str, Any]], provider_id: str) -> dict[str, Any]: return next(item for item in payload if item["provider_id"] == provider_id) diff --git a/tests/app/services/external_generation/test_fal_catalog.py b/tests/app/services/external_generation/test_fal_catalog.py new file mode 100644 index 00000000000..00a9898ca7b --- /dev/null +++ b/tests/app/services/external_generation/test_fal_catalog.py @@ -0,0 +1,187 @@ +from typing import Any + +import pytest + +from invokeai.app.services.external_generation.errors import ExternalProviderRequestError +from invokeai.app.services.external_generation.providers.fal_catalog import ( + FalCatalogClient, + FalEndpointKind, + classify_endpoint, + normalize_openapi_schema, +) + + +class DummyResponse: + def __init__(self, *, json_data: dict[str, Any], status_code: int = 200, text: str = "") -> None: + self.status_code = status_code + self.ok = status_code < 400 + self._json_data = json_data + self.text = text + self.headers: dict[str, str] = {} + + def json(self) -> dict[str, Any]: + return self._json_data + + +def _schema() -> dict[str, Any]: + return { + "openapi": "3.0.4", + "info": { + "title": "Queue OpenAPI for fal-ai/test", + "x-fal-metadata": { + "endpointId": "fal-ai/test", + "category": "image-to-video", + "about": "test endpoint", + }, + }, + "paths": { + "/fal-ai/test": { + "post": { + "requestBody": { + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TestInput"}}} + }, + "responses": { + "200": { + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TestOutput"}}} + } + }, + } + } + }, + "components": { + "schemas": { + "TestInput": { + "type": "object", + "required": ["prompt", "start_image_url"], + "properties": { + "prompt": {"type": "string", "description": "Motion prompt"}, + "start_image_url": {"type": "string", "format": "uri"}, + "duration": {"type": "string", "enum": ["5", "10"], "default": "5"}, + "secret": {"type": "string", "writeOnly": True}, + }, + }, + "TestOutput": { + "type": "object", + "properties": {"video": {"type": "object", "properties": {"url": {"type": "string"}}}}, + }, + } + }, + } + + +def test_normalize_openapi_schema_resolves_refs_and_common_aliases() -> None: + normalized = normalize_openapi_schema("fal-ai/test", _schema()) + + assert normalized.endpoint_id == "fal-ai/test" + assert normalized.kind is FalEndpointKind.IMAGE_TO_VIDEO + assert normalized.input_schema["required"] == ["prompt", "start_image_url"] + assert normalized.input_schema["properties"]["duration"]["enum"] == ["5", "10"] + assert normalized.common_fields == {"prompt": "prompt", "init_image": "start_image_url", "duration": "duration"} + assert normalized.output_kind is FalEndpointKind.IMAGE_TO_VIDEO + assert "secret" not in normalized.public_properties + + +def test_classify_endpoint_keeps_unknown_categories_generic() -> None: + assert classify_endpoint("speech-to-text", {}) is FalEndpointKind.GENERIC + assert classify_endpoint("text-to-image", {}) is FalEndpointKind.TEXT_TO_IMAGE + assert classify_endpoint("upscaling", {}) is FalEndpointKind.UPSCALE + assert ( + classify_endpoint("image-to-image", {"properties": {"image_url": {}, "mask_url": {}}}) + is FalEndpointKind.INPAINT + ) + + +def test_classify_endpoint_keeps_segmentation_out_of_inpaint_mode() -> None: + assert ( + classify_endpoint( + "image-to-image", + {"properties": {"image_url": {}, "min_mask_region_area": {}}}, + endpoint_id="fal-ai/sam2/auto-segment", + ) + is FalEndpointKind.IMAGE_TO_IMAGE + ) + + +def test_catalog_client_lists_pages_and_fetches_schema(monkeypatch: pytest.MonkeyPatch) -> None: + calls: list[tuple[str, dict[str, Any]]] = [] + + def fake_get(url: str, **kwargs: Any) -> DummyResponse: + calls.append((url, kwargs)) + if url.endswith("/v1/models"): + return DummyResponse( + json_data={ + "models": [ + { + "endpoint_id": "fal-ai/test", + "metadata": { + "display_name": "Test model", + "category": "image-to-video", + "description": "A test model", + "tags": ["video"], + "thumbnail_url": "https://cdn.test/thumb.jpg", + }, + } + ], + "next_cursor": "next", + "has_more": True, + } + ) + return DummyResponse(json_data=_schema()) + + monkeypatch.setattr("requests.get", fake_get) + client = FalCatalogClient("test-key") + + page = client.list_models(limit=10, cursor="old") + schema = client.get_schema("fal-ai/test") + + assert page.next_cursor == "next" + assert page.has_more is True + assert page.models[0].endpoint_id == "fal-ai/test" + assert page.models[0].display_name == "Test model" + assert schema.kind is FalEndpointKind.IMAGE_TO_VIDEO + assert calls[0][0] == "https://api.fal.ai/v1/models" + assert calls[0][1]["params"] == {"limit": 10, "cursor": "old"} + assert calls[1][0] == "https://fal.ai/api/openapi/queue/openapi.json" + assert calls[1][1]["params"] == {"endpoint_id": "fal-ai/test"} + + +def test_catalog_client_filters_search_across_api_pages(monkeypatch: pytest.MonkeyPatch) -> None: + calls: list[dict[str, Any]] = [] + + def fake_get(url: str, **kwargs: Any) -> DummyResponse: + del url + calls.append(kwargs["params"]) + if len(calls) == 1: + return DummyResponse( + json_data={ + "models": [ + {"endpoint_id": "fal-ai/other", "metadata": {"display_name": "Other"}}, + ], + "next_cursor": "next", + "has_more": True, + } + ) + return DummyResponse( + json_data={ + "models": [ + {"endpoint_id": "fal-ai/flux/schnell", "metadata": {"display_name": "Flux Schnell"}}, + ], + "next_cursor": None, + "has_more": False, + } + ) + + monkeypatch.setattr("requests.get", fake_get) + page = FalCatalogClient("test-key").list_models(limit=1, search="flux") + + assert [model.endpoint_id for model in page.models] == ["fal-ai/flux/schnell"] + assert calls == [{"limit": 100}, {"limit": 100, "cursor": "next"}] + assert page.has_more is False + + +def test_catalog_client_rejects_non_object_schema(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr("requests.get", lambda *args, **kwargs: DummyResponse(json_data={"models": []})) + client = FalCatalogClient("test-key") + + with pytest.raises(ExternalProviderRequestError, match="catalog"): + client.get_schema("fal-ai/bad") diff --git a/tests/app/services/external_generation/test_fal_provider.py b/tests/app/services/external_generation/test_fal_provider.py new file mode 100644 index 00000000000..c861659dd39 --- /dev/null +++ b/tests/app/services/external_generation/test_fal_provider.py @@ -0,0 +1,526 @@ +import dataclasses +import io +import logging +from collections.abc import Iterator +from typing import Any + +import pytest +from PIL import Image + +from invokeai.app.services.config.config_default import InvokeAIAppConfig +from invokeai.app.services.external_generation.errors import ExternalProviderRequestError +from invokeai.app.services.external_generation.external_generation_common import ( + ExternalGenerationRequest, + ExternalReferenceImage, +) +from invokeai.app.services.external_generation.providers.fal import FalProvider, build_schema_payload +from invokeai.app.services.external_generation.providers.fal_catalog import FalEndpointKind, FalEndpointSchema +from invokeai.backend.model_manager.configs.external_api import ( + ExternalApiModelConfig, + ExternalImageSize, + ExternalModelCapabilities, +) + + +class DummyResponse: + def __init__( + self, + *, + ok: bool = True, + status_code: int = 200, + json_data: dict[str, Any] | None = None, + content: bytes = b"", + text: str = "", + headers: dict[str, str] | None = None, + ) -> None: + self.ok = ok + self.status_code = status_code + self._json_data = json_data or {} + self.content = content + self.text = text + self.headers = headers or {} + + def json(self) -> dict[str, Any]: + return self._json_data + + def iter_content(self, chunk_size: int) -> Iterator[bytes]: + del chunk_size + yield self.content + + def __enter__(self) -> "DummyResponse": + return self + + def __exit__(self, *args: object) -> None: + return None + + +def _png_bytes(color: str = "red") -> bytes: + image = Image.new("RGB", (2, 2), color=color) + buffer = io.BytesIO() + image.save(buffer, format="PNG") + return buffer.getvalue() + + +def _model(model_id: str, *, modes: list[str]) -> ExternalApiModelConfig: + return ExternalApiModelConfig( + key="fal-test", + name="fal test", + provider_id="fal", + provider_model_id=model_id, + capabilities=ExternalModelCapabilities( + modes=modes, # type: ignore[arg-type] + supports_seed=True, + max_images_per_request=4, + allowed_aspect_ratios=["1:1", "4:3", "3:4", "16:9", "9:16"], + aspect_ratio_sizes={ + "1:1": ExternalImageSize(width=1024, height=1024), + "4:3": ExternalImageSize(width=1152, height=864), + "3:4": ExternalImageSize(width=864, height=1152), + "16:9": ExternalImageSize(width=1280, height=720), + "9:16": ExternalImageSize(width=720, height=1280), + }, + ), + ) + + +def _request( + model: ExternalApiModelConfig, + *, + mode: str = "txt2img", + init_image: Image.Image | None = None, + mask_image: Image.Image | None = None, + provider_options: dict[str, Any] | None = None, +) -> ExternalGenerationRequest: + return ExternalGenerationRequest( + model=model, + mode=mode, # type: ignore[arg-type] + prompt="A test prompt", + seed=123, + num_images=2, + width=1024, + height=1024, + image_size=None, + init_image=init_image, + mask_image=mask_image, + reference_images=[], + metadata=None, + provider_options=provider_options, + ) + + +def test_build_schema_payload_allows_declared_advanced_fields_and_overrides_common_values() -> None: + schema = FalEndpointSchema( + endpoint_id="fal-ai/custom", + kind=FalEndpointKind.TEXT_TO_IMAGE, + output_kind=FalEndpointKind.IMAGE_TO_IMAGE, + category="text-to-image", + input_schema={ + "type": "object", + "properties": { + "prompt": {"type": "string"}, + "aspect_ratio": {"type": "string", "enum": ["1:1", "16:9"]}, + "seed": {"type": "integer"}, + "style": {"type": "string"}, + "write_only_secret": {"type": "string", "writeOnly": True}, + }, + }, + output_schema={}, + common_fields={"prompt": "prompt", "aspect_ratio": "aspect_ratio", "seed": "seed"}, + public_properties=("prompt", "aspect_ratio", "seed", "style"), + ) + request = _request( + _model("fal-ai/custom", modes=["txt2img"]), + provider_options={"advanced": {"style": "cinematic", "seed": 999, "write_only_secret": "nope"}}, + ) + + payload = build_schema_payload(request, schema, image_url=None, mask_url=None) + + assert payload == {"style": "cinematic", "prompt": "A test prompt", "aspect_ratio": "1:1", "seed": 123} + + +def test_fal_provider_reports_configuration_from_api_key(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("FAL_KEY", raising=False) + monkeypatch.delenv("FAL_API_KEY", raising=False) + configured = FalProvider(InvokeAIAppConfig(external_fal_api_key="test-key"), logging.getLogger("test")) + unconfigured = FalProvider(InvokeAIAppConfig(), logging.getLogger("test")) + + assert configured.is_configured() is True + assert unconfigured.is_configured() is False + + +def test_fal_provider_accepts_official_environment_key_names(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("FAL_KEY", raising=False) + monkeypatch.setenv("FAL_API_KEY", "legacy-key") + assert FalProvider(InvokeAIAppConfig(), logging.getLogger("test")).is_configured() is True + + monkeypatch.setenv("FAL_KEY", "official-key") + assert FalProvider(InvokeAIAppConfig(), logging.getLogger("test")).is_configured() is True + + +def test_fal_provider_submits_queue_polls_and_downloads_images(monkeypatch: pytest.MonkeyPatch) -> None: + config = InvokeAIAppConfig(external_fal_api_key="fal-key", external_fal_base_url="https://queue.test") + provider = FalProvider(config, logging.getLogger("test")) + request = _request(_model("fal-ai/flux/schnell", modes=["txt2img"])) + captured: dict[str, Any] = {} + output_bytes = _png_bytes("green") + + def fake_post(url: str, headers: dict[str, str], json: dict[str, Any], timeout: int) -> DummyResponse: + captured["post_url"] = url + captured["post_headers"] = headers + captured["post_json"] = json + captured["post_timeout"] = timeout + return DummyResponse(json_data={"request_id": "request-1"}) + + def fake_get(url: str, headers: dict[str, str], timeout: int, stream: bool = False) -> DummyResponse: + del headers, timeout + if url.endswith("/status"): + return DummyResponse(json_data={"status": "COMPLETED"}) + if "/requests/request-1" in url: + return DummyResponse(json_data={"images": [{"url": "https://cdn.test/result.png"}], "seed": 777}) + assert stream is True + return DummyResponse(content=output_bytes) + + monkeypatch.setattr("requests.post", fake_post) + monkeypatch.setattr("requests.get", fake_get) + + result = provider.generate(request) + + assert captured["post_url"] == "https://queue.test/fal-ai/flux/schnell" + assert captured["post_headers"] == {"Authorization": "Key fal-key", "Content-Type": "application/json"} + assert captured["post_json"] == { + "prompt": request.prompt, + "image_size": "square_hd", + "num_images": 2, + "seed": 123, + } + assert result.provider_request_id == "request-1" + assert result.seed_used == 777 + assert result.images[0].image.size == (2, 2) + assert result.images[0].seed == 777 + + +def test_fal_provider_uploads_kontext_input_and_uses_image_url(monkeypatch: pytest.MonkeyPatch) -> None: + config = InvokeAIAppConfig(external_fal_api_key="fal-key", external_fal_base_url="https://queue.test") + provider = FalProvider(config, logging.getLogger("test")) + request = _request( + _model("fal-ai/flux-pro/kontext", modes=["img2img"]), + mode="img2img", + init_image=Image.new("RGB", (2, 2), color="blue"), + ) + post_calls: list[dict[str, Any]] = [] + put_calls: list[dict[str, Any]] = [] + + def fake_post(url: str, headers: dict[str, str], json: dict[str, Any], timeout: int) -> DummyResponse: + post_calls.append({"url": url, "headers": headers, "json": json, "timeout": timeout}) + if "/storage/upload/initiate" in url: + return DummyResponse( + json_data={ + "file_url": "https://cdn.test/input.png", + "upload_url": "https://upload.test/input", + } + ) + return DummyResponse(json_data={"request_id": "request-2"}) + + def fake_put(url: str, data: bytes, headers: dict[str, str], timeout: int) -> DummyResponse: + put_calls.append({"url": url, "data": data, "headers": headers, "timeout": timeout}) + return DummyResponse() + + def fake_get(url: str, headers: dict[str, str], timeout: int, stream: bool = False) -> DummyResponse: + del headers, timeout + if url.endswith("/status"): + return DummyResponse(json_data={"status": "COMPLETED"}) + if "/requests/request-2" in url: + return DummyResponse(json_data={"images": [{"url": "https://cdn.test/result.png"}], "seed": 123}) + assert stream is True + return DummyResponse(content=_png_bytes("green")) + + monkeypatch.setattr("requests.post", fake_post) + monkeypatch.setattr("requests.put", fake_put) + monkeypatch.setattr("requests.get", fake_get) + + provider.generate(request) + + assert post_calls[0]["url"] == "https://rest.fal.ai/storage/upload/initiate?storage_type=fal-cdn-v3" + assert post_calls[0]["json"] == {"file_name": "image.png", "content_type": "image/png"} + assert put_calls[0]["url"] == "https://upload.test/input" + assert put_calls[0]["headers"] == {"Content-Type": "image/png"} + assert post_calls[1]["url"] == "https://queue.test/fal-ai/flux-pro/kontext" + assert post_calls[1]["json"] == { + "prompt": request.prompt, + "image_url": "https://cdn.test/input.png", + "aspect_ratio": "1:1", + "num_images": 2, + "seed": 123, + } + + +def test_fal_provider_inverts_invoke_mask_for_flux_fill(monkeypatch: pytest.MonkeyPatch) -> None: + config = InvokeAIAppConfig(external_fal_api_key="fal-key", external_fal_base_url="https://queue.test") + provider = FalProvider(config, logging.getLogger("test")) + mask = Image.new("L", (2, 1)) + mask.putdata([0, 255]) + request = _request( + _model("fal-ai/flux-lora-fill", modes=["inpaint"]), + mode="inpaint", + init_image=Image.new("RGB", (2, 1), color="blue"), + mask_image=mask, + ) + uploads: dict[str, bytes] = {} + queue_payload: dict[str, Any] = {} + upload_index = 0 + + def fake_post(url: str, headers: dict[str, str], json: dict[str, Any], timeout: int) -> DummyResponse: + nonlocal upload_index + del headers, timeout + if "/storage/upload/initiate" in url: + upload_index += 1 + name = "image" if upload_index == 1 else "mask" + return DummyResponse( + json_data={ + "file_url": f"https://cdn.test/{name}.png", + "upload_url": f"https://upload.test/{name}", + } + ) + queue_payload.update(json) + return DummyResponse(json_data={"request_id": "request-3"}) + + def fake_put(url: str, data: bytes, headers: dict[str, str], timeout: int) -> DummyResponse: + del headers, timeout + uploads[url.rsplit("/", 1)[-1]] = data + return DummyResponse() + + def fake_get(url: str, headers: dict[str, str], timeout: int, stream: bool = False) -> DummyResponse: + del headers, timeout + if url.endswith("/status"): + return DummyResponse(json_data={"status": "COMPLETED"}) + if "/requests/request-3" in url: + return DummyResponse(json_data={"images": [{"url": "https://cdn.test/result.png"}], "seed": 123}) + assert stream is True + return DummyResponse(content=_png_bytes("green")) + + monkeypatch.setattr("requests.post", fake_post) + monkeypatch.setattr("requests.put", fake_put) + monkeypatch.setattr("requests.get", fake_get) + + provider.generate(request) + + uploaded_mask = Image.open(io.BytesIO(uploads["mask"])).convert("L") + assert list(uploaded_mask.getdata()) == [255, 0] + assert queue_payload == { + "prompt": request.prompt, + "image_size": "square_hd", + "num_images": 2, + "seed": 123, + "image_url": "https://cdn.test/image.png", + "mask_url": "https://cdn.test/mask.png", + "paste_back": True, + "resize_to_original": True, + } + + +def test_fal_provider_retries_poll_rate_limit_without_resubmitting(monkeypatch: pytest.MonkeyPatch) -> None: + config = InvokeAIAppConfig(external_fal_api_key="fal-key", external_fal_base_url="https://queue.test") + provider = FalProvider(config, logging.getLogger("test")) + request = _request(_model("fal-ai/flux/schnell", modes=["txt2img"])) + submit_count = 0 + status_count = 0 + + def fake_post(*args: Any, **kwargs: Any) -> DummyResponse: + nonlocal submit_count + del args, kwargs + submit_count += 1 + return DummyResponse(json_data={"request_id": "request-4"}) + + def fake_get(url: str, headers: dict[str, str], timeout: int, stream: bool = False) -> DummyResponse: + nonlocal status_count + del headers, timeout + if url.endswith("/status"): + status_count += 1 + if status_count == 1: + return DummyResponse(ok=False, status_code=429, headers={"Retry-After": "1"}) + return DummyResponse(json_data={"status": "COMPLETED"}) + if "/requests/request-4" in url: + return DummyResponse(json_data={"images": [{"url": "https://cdn.test/result.png"}], "seed": 123}) + assert stream is True + return DummyResponse(content=_png_bytes("green")) + + monkeypatch.setattr("requests.post", fake_post) + monkeypatch.setattr("requests.get", fake_get) + monkeypatch.setattr("invokeai.app.services.external_generation.providers.fal.time.sleep", lambda _: None) + + provider.generate(request) + + assert submit_count == 1 + assert status_count == 2 + + +def test_fal_provider_parses_single_image_output_shape(monkeypatch: pytest.MonkeyPatch) -> None: + config = InvokeAIAppConfig(external_fal_api_key="fal-key") + provider = FalProvider(config, logging.getLogger("test")) + request = _request(_model("fal-ai/upscale", modes=["img2img"]), mode="img2img") + monkeypatch.setattr(provider, "_download_image", lambda url: Image.new("RGB", (3, 3), color="green")) + + result = provider._parse_result( + {"image": {"url": "https://cdn.test/upscaled.png"}, "seed": 22}, + request, + request_id="upscale-1", + ) + + assert result.provider_request_id == "upscale-1" + assert result.images[0].image.size == (3, 3) + assert result.seed_used == 22 + + +def test_fal_provider_parses_segmentation_mask_output(monkeypatch: pytest.MonkeyPatch) -> None: + config = InvokeAIAppConfig(external_fal_api_key="fal-key") + provider = FalProvider(config, logging.getLogger("test")) + request = _request(_model("fal-ai/sam2/auto-segment", modes=["img2img"]), mode="img2img") + monkeypatch.setattr(provider, "_download_image", lambda url: Image.new("RGB", (3, 3), color="green")) + + result = provider._parse_result( + { + "combined_mask": {"url": "https://cdn.test/combined-mask.png"}, + "individual_masks": [{"url": "https://cdn.test/mask-1.png"}], + }, + request, + request_id="segment-1", + ) + + assert len(result.images) == 2 + + +def test_fal_provider_generic_media_uploads_local_media_and_expands_placeholders( + monkeypatch: pytest.MonkeyPatch, tmp_path: Any +) -> None: + config = InvokeAIAppConfig(external_fal_api_key="fal-key") + provider = FalProvider(config, logging.getLogger("test")) + video_path = tmp_path / "input.mp4" + video_path.write_bytes(b"video") + captured: dict[str, Any] = {} + + monkeypatch.setattr(provider, "_upload_image", lambda image, filename, headers: "https://cdn.test/image.png") + monkeypatch.setattr( + provider, + "_upload_file", + lambda path, filename, content_type, headers: "https://cdn.test/video.mp4", + ) + + def fake_submit( + model_id: str, payload: dict[str, Any], headers: dict[str, str] + ) -> tuple[dict[str, Any], str | None]: + del model_id, headers + captured.update(payload) + return payload, "request-5" + + monkeypatch.setattr(provider, "_submit_queue", fake_submit) + + result = provider.generate_generic( + "fal-ai/video", + { + "start_image_url": "${image_url}", + "video_url": "${video_url}", + "reference_image_urls": "${reference_image_urls}", + }, + image=Image.new("RGB", (2, 2)), + reference_images=[Image.new("RGB", (2, 2))], + video_path=video_path, + ) + + assert captured == { + "start_image_url": "https://cdn.test/image.png", + "video_url": "https://cdn.test/video.mp4", + "reference_image_urls": ["https://cdn.test/image.png"], + } + assert result == captured + + +def test_fal_provider_schema_payload_maps_init_and_reference_images() -> None: + schema = FalEndpointSchema( + endpoint_id="fal-ai/edit", + kind=FalEndpointKind.IMAGE_TO_IMAGE, + output_kind=FalEndpointKind.IMAGE_TO_IMAGE, + category="image-to-image", + input_schema={ + "type": "object", + "properties": { + "prompt": {"type": "string"}, + "image_urls": {"type": "array", "items": {"type": "string"}}, + }, + }, + output_schema={}, + common_fields={"prompt": "prompt", "reference_images": "image_urls"}, + public_properties=("prompt", "image_urls"), + ) + request = _request(_model("fal-ai/edit", modes=["img2img"]), mode="img2img") + request = dataclasses.replace( + request, + reference_images=[ + ExternalReferenceImage(image=Image.new("RGB", (2, 2))), + ExternalReferenceImage(image=Image.new("RGB", (2, 2))), + ], + ) + + payload = build_schema_payload( + request, + schema, + image_url="https://cdn.test/init.png", + mask_url=None, + reference_urls=["https://cdn.test/ref-1.png", "https://cdn.test/ref-2.png"], + ) + + assert payload["image_urls"] == [ + "https://cdn.test/init.png", + "https://cdn.test/ref-1.png", + "https://cdn.test/ref-2.png", + ] + + +def test_fal_provider_generic_media_returns_raw_video_result_without_downloading( + monkeypatch: pytest.MonkeyPatch, +) -> None: + config = InvokeAIAppConfig(external_fal_api_key="fal-key", external_fal_base_url="https://queue.test") + provider = FalProvider(config, logging.getLogger("test")) + post_payload: dict[str, Any] = {} + + def fake_post(url: str, headers: dict[str, str], json: dict[str, Any], timeout: int) -> DummyResponse: + del headers, timeout + assert url == "https://queue.test/fal-ai/video" + post_payload.update(json) + return DummyResponse(json_data={"request_id": "video-request"}) + + def fake_get(url: str, headers: dict[str, str], timeout: int, stream: bool = False) -> DummyResponse: + del headers, timeout, stream + if url.endswith("/status"): + return DummyResponse(json_data={"status": "COMPLETED"}) + return DummyResponse(json_data={"video": {"url": "https://cdn.test/video.mp4"}, "seed": 99}) + + monkeypatch.setattr("requests.post", fake_post) + monkeypatch.setattr("requests.get", fake_get) + + result = provider.generate_generic("fal-ai/video", {"prompt": "A moving test"}) + + assert post_payload == {"prompt": "A moving test"} + assert result == {"video": {"url": "https://cdn.test/video.mp4"}, "seed": 99} + + +def test_fal_provider_rejects_path_traversal_endpoint_id() -> None: + config = InvokeAIAppConfig(external_fal_api_key="fal-key") + provider = FalProvider(config, logging.getLogger("test")) + + with pytest.raises(ExternalProviderRequestError, match="endpoint ID is invalid"): + provider.generate_generic("fal-ai/../secret", {}) + + +def test_fal_provider_reports_queue_error(monkeypatch: pytest.MonkeyPatch) -> None: + config = InvokeAIAppConfig(external_fal_api_key="fal-key") + provider = FalProvider(config, logging.getLogger("test")) + request = _request(_model("fal-ai/flux/schnell", modes=["txt2img"])) + + def fake_post(*args: Any, **kwargs: Any) -> DummyResponse: + del args, kwargs + return DummyResponse(ok=False, status_code=400, text="invalid model input") + + monkeypatch.setattr("requests.post", fake_post) + + with pytest.raises(ExternalProviderRequestError, match="fal.ai request failed"): + provider.generate(request) diff --git a/tests/app/services/model_install/test_model_install.py b/tests/app/services/model_install/test_model_install.py index 2b9d04647bc..5150bbe6eca 100644 --- a/tests/app/services/model_install/test_model_install.py +++ b/tests/app/services/model_install/test_model_install.py @@ -39,7 +39,7 @@ ) from invokeai.app.services.model_install.model_install_default import TMPDIR_PREFIX from invokeai.app.services.model_records import ModelRecordChanges, UnknownModelException -from invokeai.backend.model_manager.configs.external_api import ExternalApiModelConfig +from invokeai.backend.model_manager.configs.external_api import ExternalApiModelConfig, ExternalModelPanelSchema from invokeai.backend.model_manager.taxonomy import ( BaseModelType, ModelFormat, @@ -237,6 +237,34 @@ def test_external_install(mm2_installer: ModelInstallServiceBase) -> None: assert job.config_out.source_type == ModelSourceType.External +def test_external_install_preserves_panel_schema(mm2_installer: ModelInstallServiceBase) -> None: + panel_schema = ExternalModelPanelSchema(image=[{"name": "dimensions"}]) + job = mm2_installer.heuristic_import( + "external://fal/fal-ai/test", + config=ModelRecordChanges(panel_schema=panel_schema), + ) + + mm2_installer.wait_for_installs() + + assert job.status == InstallStatus.COMPLETED + assert job.config_out is not None + assert isinstance(job.config_out, ExternalApiModelConfig) + assert job.config_out.panel_schema == panel_schema + + +def test_external_install_preserves_source_url(mm2_installer: ModelInstallServiceBase) -> None: + job = mm2_installer.heuristic_import( + "external://fal/fal-ai/test", + config=ModelRecordChanges(source_url="https://fal.ai/models/fal-ai/test"), + ) + + mm2_installer.wait_for_installs() + + assert job.status == InstallStatus.COMPLETED + assert job.config_out is not None + assert job.config_out.source_url == "https://fal.ai/models/fal-ai/test" + + def test_external_install_is_idempotent(mm2_installer: ModelInstallServiceBase) -> None: first_job = mm2_installer.heuristic_import( "external://openai/gpt-image-1", diff --git a/tests/backend/model_manager/test_starter_models.py b/tests/backend/model_manager/test_starter_models.py index cdcddd495ec..6aa6678edfc 100644 --- a/tests/backend/model_manager/test_starter_models.py +++ b/tests/backend/model_manager/test_starter_models.py @@ -6,6 +6,7 @@ standalone dependencies so installing it also pulls the pieces needed to run it. """ +from invokeai.backend.model_manager.configs.external_api import ExternalApiModelDefaultSettings from invokeai.backend.model_manager.starter_models import ( STARTER_BUNDLES, STARTER_MODELS, @@ -24,6 +25,24 @@ def _krea2_bundle_by_source() -> dict[str, StarterModel]: return {model.source: model for model in bundle.models} +def test_fal_external_models_are_registered_with_canvas_capabilities() -> None: + models = {model.source: model for model in STARTER_MODELS if model.source.startswith("external://fal/")} + + assert set(models) == { + "external://fal/fal-ai/flux/schnell", + "external://fal/fal-ai/flux/dev", + "external://fal/fal-ai/flux-pro/kontext", + "external://fal/fal-ai/flux-lora-fill", + } + assert models["external://fal/fal-ai/flux/schnell"].capabilities.modes == ["txt2img"] + assert models["external://fal/fal-ai/flux-pro/kontext"].capabilities.modes == ["img2img"] + assert models["external://fal/fal-ai/flux-pro/kontext"].capabilities.input_image_required_for == ["img2img"] + fill = models["external://fal/fal-ai/flux-lora-fill"] + assert fill.capabilities.modes == ["inpaint"] + assert fill.capabilities.mask_format == "binary" + assert fill.default_settings == ExternalApiModelDefaultSettings(width=1024, height=1024, num_images=1) + + def test_krea2_bundle_is_registered() -> None: assert BaseModelType.Krea2 in STARTER_BUNDLES assert STARTER_BUNDLES[BaseModelType.Krea2].name == "Krea-2"